rework tabs
This commit is contained in:
parent
5baadc9258
commit
3dd8f17c71
|
@ -1,7 +1,7 @@
|
|||
__software__ = 'Barkshark Web'
|
||||
__shortname__ = 'bsweb'
|
||||
__author__ = 'Zoey Mae'
|
||||
__version__ = '0.3.8'
|
||||
__version__ = '0.4.0'
|
||||
|
||||
|
||||
import os, sys, gi, izzylib
|
||||
|
|
|
@ -6,50 +6,45 @@ from izzylib_http_async import Template
|
|||
from izzylib_sql import Database
|
||||
from urllib.parse import quote
|
||||
|
||||
from .web_context import WebContext
|
||||
from .window import Window
|
||||
|
||||
from .. import dbus, var
|
||||
from .. import __version__ as version, __software__ as swname
|
||||
from ..cookies import get_cookie_db
|
||||
from ..database import get_database
|
||||
from ..enums import LibraryPage
|
||||
from ..exceptions import AccountNotFoundError, NoAccountsError
|
||||
from ..functions import connect
|
||||
|
||||
|
||||
class Application(Gtk.Application):
|
||||
library_pages = (
|
||||
'bookmarks', 'downloads', 'history', 'passwords',
|
||||
'search', 'fediverse', 'extensions', 'preferences',
|
||||
'help'
|
||||
)
|
||||
|
||||
|
||||
def __init__(self, profile, *urls):
|
||||
super().__init__()
|
||||
|
||||
self.profile = profile
|
||||
self.path = None
|
||||
self.cfg = None
|
||||
self.template = None
|
||||
self.db = None
|
||||
self.cookies = None
|
||||
|
||||
self.setup_paths(profile)
|
||||
self.setup_database(profile)
|
||||
self.setup_misc(profile)
|
||||
|
||||
self.window = None
|
||||
self.http_client = HttpClient()
|
||||
self.clipboard = Gtk.Clipboard.get_default(Gdk.Display.get_default())
|
||||
self.startup_urls = urls
|
||||
self.accounts = []
|
||||
|
||||
self.connect('activate', self.handle_activate)
|
||||
self.connect('startup', self.handle_startup)
|
||||
self.path = None
|
||||
self.cfg = None
|
||||
self.template = None
|
||||
self.db = None
|
||||
self.cookies = None
|
||||
self.context = None
|
||||
self.window = None
|
||||
|
||||
self.setup(profile)
|
||||
|
||||
connect(self, 'activate', self.handle_activate, urls)
|
||||
connect(self, 'startup', self.handle_startup)
|
||||
|
||||
signal_handler(self.quit)
|
||||
|
||||
|
||||
def setup_paths(self, profile):
|
||||
def setup(self, profile):
|
||||
script = Path(__file__).parent.parent
|
||||
data = Path.app_data_dir('config', 'barkshark', 'web')
|
||||
profilepath = data.join(profile or 'DEFAULT')
|
||||
|
@ -86,8 +81,6 @@ class Application(Gtk.Application):
|
|||
logging.verbose('Create dir:', path)
|
||||
path.mkdir()
|
||||
|
||||
|
||||
def setup_database(self, profile):
|
||||
self.cfg = JsonConfig(self.path.config,
|
||||
name = self.profile,
|
||||
description = '',
|
||||
|
@ -107,10 +100,7 @@ class Application(Gtk.Application):
|
|||
|
||||
self.db = get_database(self)
|
||||
self.cookies = get_cookie_db(self)
|
||||
|
||||
|
||||
def setup_misc(self, profile):
|
||||
proto = 'pyweb'
|
||||
self.context = WebContext(self)
|
||||
|
||||
proxy_resolver = Gio.ProxyResolver.get_default()
|
||||
proxies = proxy_resolver.lookup('https://www.barkshark.xyz', None)
|
||||
|
@ -136,20 +126,6 @@ class Application(Gtk.Application):
|
|||
})
|
||||
|
||||
|
||||
def create_tabs(self):
|
||||
with self.db.session as s:
|
||||
for row in s.fetch('tabs', orderby='order'):
|
||||
self.window.new_tab(row=row)
|
||||
|
||||
for url in self.startup_urls:
|
||||
self.window.new_tab(url, switch=True)
|
||||
|
||||
if len(self.window.tabdata.keys()) < 1:
|
||||
self.window.new_tab(None, switch=False)
|
||||
|
||||
self.window.startup = False
|
||||
|
||||
|
||||
def get_account_by_handle(self, username, domain=None):
|
||||
if not len(self.accounts):
|
||||
raise NoAccountsError('No accounts')
|
||||
|
@ -204,14 +180,29 @@ class Application(Gtk.Application):
|
|||
super().quit()
|
||||
|
||||
|
||||
def handle_activate(self, *args):
|
||||
def handle_activate(self, urls):
|
||||
self.window = Window(self)
|
||||
self.dbus = dbus.Server(self.window)
|
||||
|
||||
self.add_window(self.window)
|
||||
self.create_tabs()
|
||||
self.http_client.set_useragent(list(self.window.tabdata.values())[0].webview.get_settings().get_user_agent())
|
||||
|
||||
with self.db.session as s:
|
||||
self.accounts = s.fetch('accounts').all()
|
||||
|
||||
for row in s.fetch('tabs', orderby='order'):
|
||||
self.window.tab_new(row=row, switch=row.active)
|
||||
|
||||
for url in urls:
|
||||
self.window.tab_new(url, switch=True)
|
||||
|
||||
if len(self.window.tabdata.keys()) < 1:
|
||||
self.window.tab_new(None, switch=False)
|
||||
|
||||
tab = list(self.window.tabdata.values())[0]
|
||||
self.http_client.set_useragent(tab.settings['user-agent'])
|
||||
|
||||
self.window.show()
|
||||
self.window.startup = False
|
||||
|
||||
|
||||
def handle_clipboard_clear_password(self, password):
|
||||
|
@ -230,30 +221,30 @@ class Application(Gtk.Application):
|
|||
self.keybinds = {
|
||||
'Close current tab': ('close_tab', ['<Ctrl>W']),
|
||||
'Focus url bar': ('focus_urlbar', ['<Ctrl>L']),
|
||||
'Force reload page': ('force_reload', ['<Ctrl><Shift>R', '<Shift>F5']),
|
||||
'Force refresh page': ('force_refresh', ['<Ctrl><Shift>R', '<Shift>F5']),
|
||||
'Add or edit Bookmark': ('new_bookmark', ['<Ctrl>B']),
|
||||
'Open new tab': ('new_tab', ['<Ctrl>T']),
|
||||
'Open file': ('open_file', ['<Ctrl>O']),
|
||||
'Open help page': ('open_help', ['<Ctrl>H', 'F1']),
|
||||
'Open library': ('open_library', ['<Ctrl>U']),
|
||||
'Open web inspector': ('open_inspector', ['<Ctrl><Shift>I', 'F12']),
|
||||
'Open page search bar': ('open_search', ['<Ctrl>F', 'F3']),
|
||||
'Print page': ('print', ['<Ctrl>P']),
|
||||
'Quit app': ('quit', ['<Ctrl>Q']),
|
||||
'Reload page': ('reload', ['<Ctrl>R', 'F5']),
|
||||
'Refresh page': ('refresh', ['<Ctrl>R', 'F5']),
|
||||
'Reopen last closed tab': ('reopen', ['<Ctrl><Shift>T']),
|
||||
'Save page as MHTML': ('save_page', ['<Ctrl>S']),
|
||||
'Toggle fullscreen state': ('toggle_fullscreen', ['<Alt>Return', 'F11']),
|
||||
'Open or close library': ('toggle_library', ['<Ctrl>U'])
|
||||
'Toggle search bar': ('toggle_search', ['<Ctrl>F', 'F3'])
|
||||
}
|
||||
|
||||
for action, keybind in self.keybinds.values():
|
||||
self.set_accels_for_action(f'app.accel::{action}', keybind)
|
||||
|
||||
for idx, page in enumerate(self.library_pages):
|
||||
self.set_accels_for_action(f'app.accel::library_page_{page}', [f'<Ctrl>{idx+1}'])
|
||||
for idx, page in enumerate(LibraryPage):
|
||||
if page == LibraryPage.HOME:
|
||||
continue
|
||||
|
||||
with self.db.session as s:
|
||||
self.accounts = s.fetch('accounts').all()
|
||||
self.set_accels_for_action(f'app.accel::library_page_{page.value}', [f'<Ctrl>{idx}'])
|
||||
|
||||
|
||||
def handle_template_context(self, context):
|
||||
|
@ -268,91 +259,62 @@ class Application(Gtk.Application):
|
|||
tab = self.window.active_tab
|
||||
|
||||
if action.startswith('library_page'):
|
||||
self.handle_library_page(action, tab)
|
||||
return
|
||||
self.window.library_open(action.replace('library_page_', ''))
|
||||
|
||||
try:
|
||||
func = getattr(self, f'handle_accel_{action}')
|
||||
elif action == 'close_tab':
|
||||
tab.close()
|
||||
|
||||
except AttributeError:
|
||||
elif action == 'focus_urlbar':
|
||||
tab['navbar-url'].grab_focus()
|
||||
|
||||
elif action == 'force_refresh':
|
||||
tab.page_action('refresh', ignore_cache=True)
|
||||
|
||||
elif action == 'new_bookmark':
|
||||
widget = self.window.statusbar['bookmark']
|
||||
|
||||
if widget.get_sensitive():
|
||||
widget.activate()
|
||||
|
||||
elif action == 'new_tab':
|
||||
self.window.tab_new(switch=True)
|
||||
|
||||
elif action == 'open_file':
|
||||
self.window.file_open()
|
||||
|
||||
elif action == 'help':
|
||||
self.window.library_open('help')
|
||||
|
||||
elif action == 'open_inspector':
|
||||
tab.inspector_toggle()
|
||||
|
||||
elif action == 'toggle_search':
|
||||
tab.search_action('toggle')
|
||||
|
||||
elif action == 'print':
|
||||
tab.page_action('print')
|
||||
|
||||
elif action == 'quit':
|
||||
self.quit()
|
||||
|
||||
elif action == 'refresh':
|
||||
tab.page_action('refresh')
|
||||
|
||||
elif action == 'reopen':
|
||||
if not len(self.window.closed):
|
||||
return
|
||||
|
||||
last_tab = list(self.window.closed.keys())[-1]
|
||||
self.window.tab_reopen(last_tab)
|
||||
|
||||
elif action == 'save_page':
|
||||
tab.page_action('save')
|
||||
|
||||
elif action == 'toggle_fullscreen':
|
||||
self.window.fullscreen_set()
|
||||
|
||||
elif action == 'open_library':
|
||||
self.window.library_open()
|
||||
|
||||
else:
|
||||
logging.error('Unhandled keyboard shortcut action:', action)
|
||||
return
|
||||
|
||||
func(action, tab)
|
||||
|
||||
|
||||
def handle_accel_close_tab(self, action, tab):
|
||||
tab.close_tab()
|
||||
|
||||
|
||||
def handle_accel_focus_urlbar(self, action, tab):
|
||||
self.window['navbar-url'].grab_focus()
|
||||
|
||||
|
||||
def handle_accel_force_reload(self, action, tab):
|
||||
tab.page_action('reload', ignore_cache=True)
|
||||
|
||||
|
||||
def handle_library_page(self, action, tab):
|
||||
page = action.replace('library_page_', '')
|
||||
|
||||
self.window.open_library(page)
|
||||
|
||||
|
||||
def handle_accel_new_bookmark(self, action, tab):
|
||||
widget = self.window['statusbar-bookmark']
|
||||
|
||||
if widget.get_sensitive():
|
||||
widget.activate()
|
||||
|
||||
|
||||
def handle_accel_new_tab(self, action, tab):
|
||||
self.window.new_tab(switch=True)
|
||||
|
||||
|
||||
def handle_accel_open_file(self, action, tab):
|
||||
self.window.open_file()
|
||||
|
||||
|
||||
def handle_accel_open_help(self, action, tab):
|
||||
self.window.open_library('help')
|
||||
|
||||
|
||||
def handle_accel_open_inspector(self, action, tab):
|
||||
tab.toggle_inspector()
|
||||
|
||||
|
||||
def handle_accel_open_search(self, action, tab):
|
||||
tab.search_toggle()
|
||||
|
||||
|
||||
def handle_accel_print(self, action, tab):
|
||||
tab.page_print()
|
||||
|
||||
|
||||
def handle_accel_quit(self, action, tab):
|
||||
self.quit()
|
||||
|
||||
|
||||
def handle_accel_reload(self, action, tab):
|
||||
tab.page_action('reload')
|
||||
|
||||
|
||||
def handle_accel_reopen(self, action, tab):
|
||||
if not len(self.window.closed.keys()):
|
||||
return
|
||||
|
||||
last_tab = list(self.window.closed.keys())[-1]
|
||||
self.window.reopen_tab(last_tab)
|
||||
|
||||
|
||||
def handle_accel_save_page(self, action, tab):
|
||||
tab.page_save()
|
||||
|
||||
|
||||
def handle_accel_toggle_fullscreen(self, action, tab):
|
||||
self.window.fullscreen_toggle()
|
||||
|
||||
|
||||
def handle_accel_toggle_library(self, action, tab):
|
||||
self.window.library_toggle()
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
from .. import var
|
||||
|
||||
|
||||
class MenuBar:
|
||||
def __init__(self, window):
|
||||
self.app = window.app
|
||||
self.window = window
|
||||
self.connect = window.Connect
|
||||
|
||||
self.setup_signals()
|
||||
|
||||
|
||||
def setup_signals(self):
|
||||
signals = {
|
||||
'file': {
|
||||
'new_tab': {'callback': self.window.new_tab, 'kwargs': {'switch': True}},
|
||||
'close_tab': {'callback': self.window.close_tab},
|
||||
'open': {'callback': self.window.open_file},
|
||||
'save': {'callback': self.handle_save_page},
|
||||
'print': {'callback': self.handle_print_page},
|
||||
'quit': {'callback': self.window.app.quit}
|
||||
},
|
||||
'edit': ['cut', 'copy', 'paste', 'delete', 'undo', 'redo', 'select', 'unselect', 'source', 'inspect'],
|
||||
'library': ['bookmarks', 'downloads', 'history', 'passwords', 'search', 'fediverse', 'extensions', 'preferences'],
|
||||
'help': {
|
||||
'usage': {'callback': print, 'args': ['UvU']},
|
||||
'about': {'callback': self.window.open_library, 'args': ['help']}
|
||||
}
|
||||
}
|
||||
|
||||
for cat, names in signals.items():
|
||||
if cat == 'edit':
|
||||
for name in names:
|
||||
if name == 'inspect':
|
||||
self.connect(f'menubar-edit-inspect', 'activate', self.handle_inspector_toggle)
|
||||
|
||||
elif name == 'source':
|
||||
self.connect(f'menubar-edit-source', 'activate', self.handle_page_source)
|
||||
|
||||
else:
|
||||
self.connect(f'menubar-edit-{name}', 'activate', self.handle_edit_action, name)
|
||||
|
||||
elif cat == 'library':
|
||||
for name in names:
|
||||
self.connect(f'menubar-library-{name}', 'activate', self.handle_library_page, name)
|
||||
|
||||
else:
|
||||
for name, data in names.items():
|
||||
widget_name = f'menubar-{cat}-{name}'
|
||||
self.connect(widget_name, 'activate', data['callback'], *data.get('args', []), **data.get('kwargs', {}))
|
||||
|
||||
|
||||
def handle_save_page(self):
|
||||
self.window.active_tab.page_save()
|
||||
|
||||
|
||||
def handle_print_page(self):
|
||||
self.window.active_tab.page_print()
|
||||
|
||||
|
||||
def handle_page_source(self):
|
||||
self.window.active_tab.page_view_source()
|
||||
|
||||
|
||||
def handle_inspector_toggle(self):
|
||||
self.window.active_tab.toggle_inspector()
|
||||
|
||||
|
||||
def handle_edit_action(self, action):
|
||||
tab = self.window.active_tab
|
||||
|
||||
if action == 'unselect':
|
||||
tab.run_js('document.getSelection().removeAllRanges()')
|
||||
|
||||
elif action == 'delete':
|
||||
tab.run_js('document.activeElement.setRangeText("")')
|
||||
|
||||
else:
|
||||
tab.editing_action(action)
|
||||
|
||||
|
||||
def handle_library_page(self, name):
|
||||
tab = self.window.active_tab
|
||||
url = var.local + name
|
||||
|
||||
if tab.url.proto == var.local_proto:
|
||||
return tab.load_url(url)
|
||||
|
||||
self.window.new_tab(url, switch=True)
|
||||
|
||||
|
||||
def handle_about_dialog(self):
|
||||
self.window['about-buttons']
|
||||
self.window['about-dialog'].show()
|
|
@ -1,16 +1,28 @@
|
|||
from .. import cache, var
|
||||
from ..database import default_permissions
|
||||
from ..exceptions import AccountNotFoundError, NoAccountsError
|
||||
from ..functions import SignalBlock, connect, get_buffer_text
|
||||
from ..objects.login_rows import SavedLoginRow
|
||||
from ..functions import ComponentBase, connect, get_buffer_text
|
||||
from ..objects import SavedLoginRow
|
||||
|
||||
|
||||
class StatusBar:
|
||||
SITEOPTIONS = [
|
||||
'instance',
|
||||
'adblock',
|
||||
'fullscreen',
|
||||
'autoplay',
|
||||
'dialog',
|
||||
'notification',
|
||||
'microphone',
|
||||
'location',
|
||||
'camera'
|
||||
]
|
||||
|
||||
|
||||
class StatusBar(ComponentBase):
|
||||
def __init__(self, window):
|
||||
self.app = window.app
|
||||
self.window = window
|
||||
self.db = window.app.db
|
||||
ComponentBase.__init__(self, 'statusbar')
|
||||
|
||||
self.window = window
|
||||
self.siteoptions_handler_ids = []
|
||||
self.bookmark_row = None
|
||||
self.theme_enabled = False
|
||||
|
@ -24,10 +36,6 @@ class StatusBar:
|
|||
self.setup()
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.window[f'statusbar-{key}']
|
||||
|
||||
|
||||
def bookmark_get_data(self):
|
||||
data = DotDict()
|
||||
|
||||
|
@ -207,15 +215,11 @@ class StatusBar:
|
|||
#self.window.active_tab.editing_action('copy')
|
||||
self.window.notification('Merp!', 'INFO', 0)
|
||||
|
||||
with self.app.cookies.session as s:
|
||||
for row in s.get_domain('cnn.com'):
|
||||
print(row.host, row.name)
|
||||
if self.window.themes.current:
|
||||
self.window.themes.unset()
|
||||
|
||||
#if self.window.themes.current_theme:
|
||||
#self.window.themes.UnloadTheme()
|
||||
|
||||
#else:
|
||||
#self.window.themes.LoadTheme('test')
|
||||
else:
|
||||
self.window.themes.set('test')
|
||||
|
||||
elif name == 'bookmark':
|
||||
if not tab.url:
|
||||
|
@ -254,7 +258,7 @@ class StatusBar:
|
|||
self['siteoptions-reset'].set_sensitive(True if row != None else False)
|
||||
self.siteoptions_row = row
|
||||
|
||||
with SignalBlock(self.siteoptions_handler_ids):
|
||||
with self.block_signals(*[f'siteoptions-{name}' for name in SITEOPTIONS]):
|
||||
for k, v in default_permissions.items():
|
||||
value = row.get(k, v)
|
||||
self[f'siteoptions-{k}'].set_state(value)
|
||||
|
@ -296,15 +300,14 @@ class StatusBar:
|
|||
self['bookmark-popover'].popdown()
|
||||
|
||||
|
||||
def handle_bookmark_category_button(self, entry, icon_pos, event, _):
|
||||
if icon_pos == Gtk.EntryIconPosition.SECONDARY:
|
||||
menu = self['bookmark-category-menu']
|
||||
menu.popup_at_widget(
|
||||
widget = self['bookmark-category'],
|
||||
widget_anchor = Gdk.Gravity.NORTH_EAST,
|
||||
menu_anchor = Gdk.Gravity.SOUTH_EAST,
|
||||
trigger_event = None
|
||||
)
|
||||
def handle_bookmark_category_button(self):
|
||||
menu = self['bookmark-category-menu']
|
||||
menu.popup_at_widget(
|
||||
widget = self['bookmark-category'],
|
||||
widget_anchor = Gdk.Gravity.NORTH_EAST,
|
||||
menu_anchor = Gdk.Gravity.SOUTH_EAST,
|
||||
trigger_event = None
|
||||
)
|
||||
|
||||
|
||||
def handle_bookmark_set_category(self, category):
|
||||
|
@ -360,15 +363,18 @@ class StatusBar:
|
|||
cache.posts.store(self.window.active_tab.url, post)
|
||||
|
||||
|
||||
def handle_siteoptions_switch(self, name, switch):
|
||||
tab = self.window.active_tab
|
||||
def handle_siteoptions_switch(self, name):
|
||||
active = self[f'siteoptions-{name}'].get_active()
|
||||
try:
|
||||
domain = self.window.active_tab.url.domain
|
||||
|
||||
if not tab.webview:
|
||||
except:
|
||||
return
|
||||
|
||||
with self.db.session as s:
|
||||
s.put_permission(tab.url.domain, name, switch.get_active())
|
||||
s.put_permission(domain, name, active)
|
||||
|
||||
logging.verbose(f'siteoptions: set {name}: {active}')
|
||||
self['siteoptions-reset'].set_sensitive(True)
|
||||
|
||||
|
||||
|
@ -437,81 +443,39 @@ class StatusBar:
|
|||
saved = self['logins-saved-list']
|
||||
)
|
||||
|
||||
signals = {
|
||||
'bookmark': [
|
||||
{'signal': 'toggled', 'callback': self.handle_status_button}
|
||||
],
|
||||
'bookmark-save': [
|
||||
{'signal': 'clicked', 'callback': self.handle_bookmark_button}
|
||||
],
|
||||
'bookmark-delete': [
|
||||
{'signal': 'clicked', 'callback': self.handle_bookmark_button}
|
||||
],
|
||||
'bookmark-close': [
|
||||
{'signal': 'clicked', 'callback': self.handle_bookmark_button}
|
||||
],
|
||||
'bookmark-category': [
|
||||
{'signal': 'icon-press', 'callback': self.handle_bookmark_category_button, 'kwargs': {'original_args': True}}
|
||||
],
|
||||
'favorite': [
|
||||
{'signal': 'clicked', 'callback': self.handle_fedi_button}
|
||||
],
|
||||
'boost': [
|
||||
{'signal': 'clicked', 'callback': self.handle_fedi_button}
|
||||
],
|
||||
'reply': [
|
||||
{'signal': 'clicked', 'callback': self.handle_fedi_button}
|
||||
],
|
||||
'debug': [
|
||||
{'signal': 'clicked', 'callback': self.handle_status_button}
|
||||
],
|
||||
'toot': [
|
||||
{'signal': 'toggled', 'callback': self.handle_status_button}
|
||||
],
|
||||
'siteoptions': [
|
||||
{'signal': 'toggled', 'callback': self.handle_status_button}
|
||||
],
|
||||
'logins': [
|
||||
{'signal': 'toggled', 'callback': self.handle_status_button}
|
||||
],
|
||||
'logins-close': [
|
||||
{'signal': 'clicked', 'callback': self.handle_logins_button}
|
||||
],
|
||||
'logins-unsaved-clear': [
|
||||
{'signal': 'clicked', 'callback': self.handle_logins_button}
|
||||
],
|
||||
'siteoptions-reset': [
|
||||
{'signal': 'clicked', 'callback': self.handle_siteoptions_button}
|
||||
],
|
||||
'siteoptions-close': [
|
||||
{'signal': 'clicked', 'callback': self.handle_siteoptions_button}
|
||||
],
|
||||
'toot-spoiler': [
|
||||
{'signal': 'changed', 'callback': self.toot_update_count},
|
||||
{'signal': 'activate', 'callback': self.handle_toot_spoiler_activate}
|
||||
],
|
||||
'toot-reset': [
|
||||
{'signal': 'clicked', 'callback': self.toot_clear}
|
||||
],
|
||||
'toot-send': [
|
||||
{'signal': 'clicked', 'callback': self.toot_send}
|
||||
],
|
||||
'toot-close': [
|
||||
{'signal': 'clicked', 'callback': self.toot_close}
|
||||
],
|
||||
'toot-content': [
|
||||
{'signal': 'key-press-event', 'callback': self.handle_toot_key_press, 'kwargs': {'original_args': True}}
|
||||
]
|
||||
}
|
||||
|
||||
for name, sigs in signals.items():
|
||||
for sig in sigs:
|
||||
full_name = f'statusbar-{name}'
|
||||
short_name = name.split('-')[-1]
|
||||
self.window.Connect(full_name, sig['signal'], sig['callback'], short_name, *sig.get('args', []), **sig.get('kwargs', {}))
|
||||
## Status bar
|
||||
self.connect('debug', 'clicked', self.handle_status_button, 'debug')
|
||||
self.connect('toot', 'toggled', self.handle_status_button, 'toot')
|
||||
self.connect('reply', 'clicked', self.handle_fedi_button, 'reply')
|
||||
self.connect('boost', 'clicked', self.handle_fedi_button, 'boost')
|
||||
self.connect('favorite', 'clicked', self.handle_fedi_button, 'favorite')
|
||||
self.connect('logins', 'toggled', self.handle_status_button, 'logins')
|
||||
self.connect('bookmark', 'toggled', self.handle_status_button, 'bookmark')
|
||||
self.connect('siteoptions', 'toggled', self.handle_status_button, 'siteoptions')
|
||||
|
||||
## Toot
|
||||
self.connect('toot-spoiler', 'changed', self.toot_update_count)
|
||||
self.connect('toot-spoiler', 'activate', self.handle_toot_spoiler_activate)
|
||||
self.connect('toot-reset', 'clicked', self.toot_clear)
|
||||
self.connect('toot-send', 'clicked', self.toot_send)
|
||||
self.connect('toot-close', 'clicked', self.toot_close)
|
||||
self.connect('toot-content', 'key-press-event', self.handle_toot_key_press)
|
||||
connect(self['toot-content'].get_buffer(), 'changed', self.toot_update_count)
|
||||
|
||||
## Logins
|
||||
self.connect('logins-close', 'clicked', self.handle_logins_button, 'close')
|
||||
self.connect('logins-unsaved-clear', 'clicked', self.handle_logins_button, 'clear')
|
||||
|
||||
## Bookmarks
|
||||
self.connect('bookmark-save', 'clicked', self.handle_bookmark_button, 'save')
|
||||
self.connect('bookmark-delete', 'clicked', self.handle_bookmark_button, 'delete')
|
||||
self.connect('bookmark-close', 'clicked', self.handle_bookmark_button, 'close')
|
||||
self.connect('bookmark-category', 'icon-press', self.handle_bookmark_category_button)
|
||||
|
||||
## Siteoptions
|
||||
self.connect('siteoptions-reset', 'clicked', self.handle_siteoptions_button, 'reset')
|
||||
self.connect('siteoptions-close', 'clicked', self.handle_siteoptions_button, 'close')
|
||||
|
||||
# extra work needs to be done to toggle webview settings
|
||||
self.permissions = DotDict({
|
||||
'instance': False,
|
||||
|
@ -530,8 +494,6 @@ class StatusBar:
|
|||
})
|
||||
|
||||
for name in self.permissions:
|
||||
widget = self[f'siteoptions-{name}']
|
||||
sigid = self.window.Connect(f'statusbar-siteoptions-{name}', 'state-set', self.handle_siteoptions_switch, name, widget)
|
||||
self.siteoptions_handler_ids.append([sigid, widget])
|
||||
self.connect(f'siteoptions-{name}', 'state-set', self.handle_siteoptions_switch, name)
|
||||
|
||||
self.bookmark_refresh_categories()
|
||||
|
|
|
@ -5,14 +5,13 @@ from ..widgets import FileChooser
|
|||
|
||||
|
||||
class WebContext(WebKit2.WebContext):
|
||||
def __init__(self, window):
|
||||
self.app = window.app
|
||||
self.window = window
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
## Setup storage
|
||||
self.storage = WebKit2.WebsiteDataManager(
|
||||
base_data_directory = self.app.path.storage,
|
||||
base_cache_directory = self.app.path.cache
|
||||
base_data_directory = app.path.storage,
|
||||
base_cache_directory = app.path.cache
|
||||
)
|
||||
|
||||
super().__init__(website_data_manager = self.storage)
|
||||
|
@ -22,14 +21,14 @@ class WebContext(WebKit2.WebContext):
|
|||
self.set_property('use-system-appearance-for-scrollbars', True)
|
||||
#self.set_web_extensions_initialization_user_data(GLib.Variant.new_string('heck'))
|
||||
self.set_spell_checking_enabled(True)
|
||||
self.set_favicon_database_directory(self.app.path.favicon)
|
||||
self.set_web_extensions_directory(self.app.path.script.join('bin'))
|
||||
self.set_favicon_database_directory(app.path.favicon)
|
||||
self.set_web_extensions_directory(app.path.script.join('bin'))
|
||||
self.set_cache_model(WebKit2.CacheModel(1))
|
||||
self.set_use_system_appearance_for_scrollbars(True)
|
||||
|
||||
## Setup Cookie storage
|
||||
self.cookiemanager = self.get_cookie_manager()
|
||||
self.cookiemanager.set_persistent_storage(self.app.path.cookies, WebKit2.CookiePersistentStorage(1))
|
||||
self.cookiemanager.set_persistent_storage(app.path.cookies, WebKit2.CookiePersistentStorage(1))
|
||||
self.cookiemanager.set_accept_policy(WebKit2.CookieAcceptPolicy(2))
|
||||
|
||||
## Register local uri schemes
|
||||
|
@ -53,6 +52,11 @@ class WebContext(WebKit2.WebContext):
|
|||
self.connect('user-message-received', self.extensions.handle_command)
|
||||
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
return self.app.window
|
||||
|
||||
|
||||
def handle_new_download(self, context, download):
|
||||
NewDownload(self, download)
|
||||
|
||||
|
|
563
barkshark_web/component/web_tab.py
Normal file
563
barkshark_web/component/web_tab.py
Normal file
|
@ -0,0 +1,563 @@
|
|||
import cairo
|
||||
import threading
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from izzylib.exceptions import DNSResolverError
|
||||
|
||||
from .web_tab_settings import WebSettings
|
||||
from .web_tab_webview_handler import WebviewHandler
|
||||
|
||||
from ..enums import EditAction, Javascript
|
||||
from ..objects import WebviewState
|
||||
from ..widgets import Box, FileChooser
|
||||
from ..functions import (
|
||||
BuilderBase,
|
||||
connect,
|
||||
run_in_gui_thread,
|
||||
set_image,
|
||||
surface_to_pixbuf
|
||||
)
|
||||
|
||||
|
||||
class WebTab(BuilderBase, Gtk.Box):
|
||||
def __init__(self, url=None):
|
||||
BuilderBase.__init__(self, self.app.path.resources.join('tab.ui'))
|
||||
Gtk.Box.__init__(self,
|
||||
orientation = Gtk.Orientation.VERTICAL,
|
||||
spacing = 5
|
||||
)
|
||||
|
||||
for widget in ['navbar', 'search']:
|
||||
self.add(self[widget])
|
||||
|
||||
self['navbar'].set_margin_top(5)
|
||||
|
||||
self.id = self.window.tab_new_id()
|
||||
self.settings = WebSettings(self)
|
||||
self.webview = None
|
||||
self.search = None
|
||||
|
||||
self._data = DotDict(
|
||||
history = {},
|
||||
state = {},
|
||||
post = None,
|
||||
search = False,
|
||||
favicon = False
|
||||
)
|
||||
|
||||
self.set_button_icons()
|
||||
self.setup_webview()
|
||||
|
||||
if url:
|
||||
self.load_url(url)
|
||||
|
||||
self.setup_signals()
|
||||
self.show_all()
|
||||
|
||||
|
||||
@classmethod
|
||||
def new_from_row(cls, row, load=False):
|
||||
state = WebviewState.new_from_row(row)
|
||||
tab = cls.new_from_state(state, row.active or load)
|
||||
|
||||
return tab
|
||||
|
||||
|
||||
@classmethod
|
||||
def new_from_state(cls, state, load=False):
|
||||
tab = cls()
|
||||
|
||||
tab.webview.restore_session_state(state.session)
|
||||
tab['label-favicon'].set_sensitive(True)
|
||||
|
||||
tab.id = state.tabid
|
||||
tab.set_title(state.title or '')
|
||||
tab.set_url(state.url or '')
|
||||
|
||||
tab._data.state = state
|
||||
|
||||
if load:
|
||||
tab.state_load()
|
||||
|
||||
return tab
|
||||
|
||||
|
||||
## Booleans
|
||||
@property
|
||||
def is_loaded(self):
|
||||
return not self._data.state
|
||||
|
||||
|
||||
## Widgets
|
||||
@property
|
||||
def label(self):
|
||||
return self['label']
|
||||
|
||||
|
||||
@property
|
||||
def menu(self):
|
||||
return self['menu']
|
||||
|
||||
|
||||
## Misc
|
||||
@property
|
||||
def context(self):
|
||||
return self.app.context
|
||||
|
||||
|
||||
@property
|
||||
def favicon(self):
|
||||
return self.webview.get_favicon()
|
||||
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._data.state
|
||||
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self.webview.get_title()
|
||||
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
try:
|
||||
return Url(self.webview.get_uri())
|
||||
|
||||
except:
|
||||
return
|
||||
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
return self.app.window
|
||||
|
||||
|
||||
def close(self):
|
||||
self.window.tab_close(self.id)
|
||||
|
||||
|
||||
def destroy(self):
|
||||
self.webview.destroy()
|
||||
Gtk.Box.destroy(self)
|
||||
BuilderBase.destroy(self)
|
||||
|
||||
|
||||
def edit_action(self, action):
|
||||
if not isinstance(action, EditAction):
|
||||
action_set = False
|
||||
|
||||
try:
|
||||
action = EditAction[action.upper()]
|
||||
|
||||
except KeyError:
|
||||
for value in EditAction:
|
||||
if not new_action and value.name.lower() == action:
|
||||
action = value
|
||||
action_set = True
|
||||
|
||||
if not new_action:
|
||||
raise KeyError(f'Invalid editing action: {action}')
|
||||
|
||||
self.webview.execute_editing_command(action.value)
|
||||
|
||||
|
||||
def get_state(self, force_new=False):
|
||||
if force_new or not self._data.state:
|
||||
return WebviewState.new_from_tab(self)
|
||||
|
||||
return self._data.state
|
||||
|
||||
|
||||
def load_url(self, url):
|
||||
threading.Thread(target=self.handle_load_url, args=[url]).start()
|
||||
|
||||
|
||||
def page_action(self, action, **kwargs):
|
||||
if action == 'refresh':
|
||||
if not self.is_loaded:
|
||||
self.state_load()
|
||||
|
||||
elif kwargs.get('ignore_cache'):
|
||||
logging.verbose('Refreshing without cache')
|
||||
self.webview.reload_bypass_cache()
|
||||
|
||||
else:
|
||||
self.webview.reload()
|
||||
|
||||
elif action == 'back':
|
||||
self.webview.go_back()
|
||||
|
||||
elif action == 'forward':
|
||||
self.webview.go_forward()
|
||||
|
||||
elif action == 'stop':
|
||||
self.webview.stop_loading()
|
||||
|
||||
elif action == 'home':
|
||||
with self.db.session as s:
|
||||
self.load_url(s.get_config('homepage'))
|
||||
|
||||
elif action == 'print':
|
||||
self.run_js(Javascript.PRINT)
|
||||
|
||||
elif action == 'source':
|
||||
self.run_js('document.documentElement.outerHTML', self.handle_page_view_source)
|
||||
|
||||
else:
|
||||
logging.warning('Invalid page action:', action)
|
||||
|
||||
|
||||
def run_js(self, js, callback=None, *args, **kwargs):
|
||||
if isinstance(js, Javascript):
|
||||
js = js.value
|
||||
|
||||
else:
|
||||
try:
|
||||
js = Javascript[js.upper()].value
|
||||
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not callback:
|
||||
self.webview.run_javascript(js, None)
|
||||
|
||||
else:
|
||||
js_callback = lambda webview, task: self.handle_run_js(webview, task, callback, *args, **kwargs)
|
||||
self.webview.run_javascript(js, None, js_callback)
|
||||
|
||||
|
||||
def search_action(self, action, **kwargs):
|
||||
search = self['search']
|
||||
text = self['search-text']
|
||||
no_search_msg = f'search action "{action}": no search started'
|
||||
|
||||
if action == 'open':
|
||||
search.show()
|
||||
|
||||
self.run_js(Javascript.SELECTION, self.handle_search_get_selection)
|
||||
text.grab_focus()
|
||||
|
||||
elif action == 'close':
|
||||
self.search_action('clear')
|
||||
search.hide()
|
||||
|
||||
elif action == 'clear':
|
||||
if self._data.search:
|
||||
self.search.search_finish()
|
||||
self._data.search = False
|
||||
|
||||
text.set_text('')
|
||||
|
||||
elif action == 'toggle':
|
||||
self.search_action('close' if search.get_visible() else 'open')
|
||||
|
||||
elif action == 'search':
|
||||
if self._data.search:
|
||||
return
|
||||
|
||||
if kwargs.get('text'):
|
||||
search_text = kwargs.get('text')
|
||||
|
||||
else:
|
||||
search_text = text.get_text()
|
||||
|
||||
options = WebKit2.FindOptions.WRAP_AROUND
|
||||
|
||||
if kwargs.get('insensitive'):
|
||||
options += WebKit2.FindOptions.CASE_INSENSITIVE
|
||||
|
||||
self.search.search(search_text, options, int(kwargs.get('limit', 1000)))
|
||||
self._data.search = True
|
||||
|
||||
elif action == 'next':
|
||||
if not self._data.search:
|
||||
return logging.verbose(no_search_msg)
|
||||
|
||||
self.search.search_next()
|
||||
|
||||
elif action == 'previous':
|
||||
if not self._data.search:
|
||||
return logging.verbose(no_search_msg)
|
||||
|
||||
self.search.search_previous()
|
||||
|
||||
else:
|
||||
logging.warning('Invalid search action:', action)
|
||||
|
||||
|
||||
def set_button_icons(self):
|
||||
self.set_icon_from_resource('navbar-prev-icon', 'previous.svg', 24)
|
||||
self.set_icon_from_resource('navbar-next-icon', 'next.svg', 24)
|
||||
self.set_icon_from_resource('navbar-stop-icon', 'stop.svg', 24)
|
||||
self.set_icon_from_resource('navbar-refresh-icon', 'refresh.svg', 24)
|
||||
self.set_icon_from_resource('navbar-home-icon', 'home.svg', 24)
|
||||
self.set_icon_from_resource('navbar-go-icon', 'go.svg', 24)
|
||||
|
||||
|
||||
def set_button_state(self):
|
||||
#self['navbar-url'].set_text(self.url)
|
||||
#self['navbar-stop'].set_sensitive(not self.idle)
|
||||
#self['navbar-refresh'].set_sensitive(self.idle)
|
||||
|
||||
for button, action in {'navbar-prev': self.webview.can_go_back(), 'navbar-next': self.webview.can_go_forward()}.items():
|
||||
self[button].set_sensitive(action)
|
||||
|
||||
if self.window.active_tab == self:
|
||||
self.window.set_button_state(self.id)
|
||||
|
||||
|
||||
def set_favicon(self, icon=None):
|
||||
icon = icon or self.favicon or 'image-x-generic'
|
||||
|
||||
for widget in ['label-favicon-icon', 'menu-favicon']:
|
||||
set_image(self[widget], icon, 16)
|
||||
|
||||
self._data.favicon = True
|
||||
|
||||
|
||||
def set_title(self, text=None):
|
||||
if not text:
|
||||
text = self.webview.get_title() or ''
|
||||
|
||||
for widget in ['label-title', 'menu-title']:
|
||||
self[widget].set_text(text)
|
||||
self[widget].set_tooltip_text(text)
|
||||
|
||||
|
||||
def set_url(self, url=None):
|
||||
self['navbar-url'].set_text(
|
||||
url or self.webview.get_uri()
|
||||
)
|
||||
|
||||
|
||||
def setup_signals(self):
|
||||
## Navigation bar
|
||||
self.connect('label-close', 'clicked', self.close)
|
||||
self.connect('label-favicon', 'clicked', self.state_unload)
|
||||
self.connect('navbar-prev', 'clicked', self.page_action, 'back')
|
||||
self.connect('navbar-next', 'clicked', self.page_action, 'forward')
|
||||
self.connect('navbar-stop', 'clicked', self.page_action, 'stop')
|
||||
self.connect('navbar-refresh', 'clicked', self.page_action, 'refresh')
|
||||
self.connect('navbar-home', 'clicked', self.page_action, 'home')
|
||||
self.connect('navbar-go', 'clicked', self.page_action, 'go')
|
||||
self.connect('navbar-url', 'key-press-event', self.handle_url_keys, original_args=True)
|
||||
self.connect('navbar-url', 'populate-popup', self.handle_url_popup, original_args=True)
|
||||
|
||||
## Search bar
|
||||
self.connect('search-previous', 'clicked', self.search_action, 'previous')
|
||||
self.connect('search-next', 'clicked', self.search_action, 'next')
|
||||
self.connect('search-find', 'clicked', self.search_action, 'search')
|
||||
self.connect('search-close', 'clicked', self.search_action, 'close')
|
||||
self.connect('search-text', 'activate', self.search_action, 'search')
|
||||
self.connect('search-text', 'icon-press', self.search_action, 'clear')
|
||||
self.connect('search-text', 'key-press-event', self.handle_search_keys, original_args=True)
|
||||
|
||||
# Misc
|
||||
GObject.Object.connect(self, 'focus', lambda *args: self.handle_get_focus())
|
||||
|
||||
|
||||
def setup_webview(self):
|
||||
if self.webview:
|
||||
self.webview.destroy()
|
||||
|
||||
self.webview = WebKit2.WebView(
|
||||
settings = self.settings,
|
||||
web_context = self.context,
|
||||
#user_content_manager = self.userscripts
|
||||
)
|
||||
|
||||
self.webview.set_property('expand', True)
|
||||
self.webview.get_style_context().add_class('webview')
|
||||
|
||||
self.settings.apply()
|
||||
|
||||
self.search = self.webview.get_find_controller()
|
||||
self.handler = WebviewHandler(self)
|
||||
self.webview.show()
|
||||
self.add(self.webview)
|
||||
|
||||
connect(self.search, 'failed-to-find-text', self.handle_search_failed)
|
||||
connect(self.search, 'found-text', self.handle_search_text_change)
|
||||
|
||||
|
||||
def state_load(self):
|
||||
if not self._data.state:
|
||||
return logging.warning('No state to restore')
|
||||
|
||||
self.webview.restore_session_state(self._data.state.session)
|
||||
self._data.state = None
|
||||
self['label-favicon'].set_sensitive(True)
|
||||
self.page_action('refresh')
|
||||
|
||||
|
||||
def state_unload(self):
|
||||
if not self._data.state:
|
||||
self._data.state = self.get_state(True)
|
||||
self.setup_webview()
|
||||
|
||||
self['label-favicon'].set_sensitive(False)
|
||||
|
||||
return self._data.state
|
||||
|
||||
|
||||
def handle_button(self, name, **kwargs):
|
||||
if name == 'go':
|
||||
self.load_url(self['navbar-url'].get_text())
|
||||
|
||||
|
||||
def handle_editing_action_check(self, webview, raw_result, data):
|
||||
result = self.webview.can_execute_editing_command_finish(raw_result)
|
||||
data['callback'](*data['args'], **data['kwargs'])
|
||||
|
||||
|
||||
def handle_get_focus(self):
|
||||
self.webview.grab_focus()
|
||||
self['navbar-url'].select_region(0,0)
|
||||
|
||||
|
||||
def handle_load_url(self, full_url):
|
||||
keyword = None
|
||||
address = None
|
||||
|
||||
if Path(full_url).exists():
|
||||
full_url = f'local://{full_url}'
|
||||
|
||||
full_url = Url(full_url)
|
||||
|
||||
if not full_url.domain and full_url.path and not full_url.proto:
|
||||
try:
|
||||
url = Url('https://' + full_url)
|
||||
except ValueError:
|
||||
self.window.notification(f'Not a valid url: {full_url}')
|
||||
return
|
||||
|
||||
else:
|
||||
url = Url(full_url)
|
||||
|
||||
# Can't register the ftp protocol, so use an alternative scheme name
|
||||
if full_url.proto == 'ftp':
|
||||
logging.verbose('ftp url. Redirecting to filetp instead')
|
||||
url = full_url.replace_property('proto', 'filetp')
|
||||
|
||||
# Can't register the file protocol either
|
||||
elif full_url.proto == 'file':
|
||||
logging.verbose('file url. Redirecting to local instead')
|
||||
url = full_url.replace_property('proto', 'local')
|
||||
|
||||
elif not full_url.proto:
|
||||
with self.db.session as s:
|
||||
## Check the first word is a search keyword
|
||||
try:
|
||||
keyword, data = full_url.split(' ', 1)
|
||||
|
||||
except ValueError:
|
||||
keyword, data = None, full_url
|
||||
|
||||
## Is the url.domain an actual domain?
|
||||
if not keyword:
|
||||
try:
|
||||
address = url.resolve_hostname(1)
|
||||
|
||||
logging.verbose('Url without protocol')
|
||||
|
||||
if address.is_type('private'):
|
||||
url = url.replace_properties(
|
||||
proto = 'http',
|
||||
port = 80 if url.port == 443 else url.port
|
||||
)
|
||||
|
||||
except DNSResolverError:
|
||||
pass
|
||||
|
||||
if not address:
|
||||
search = s.get_search(keyword, default=True)
|
||||
logging.verbose('Keyword search:', search.keyword)
|
||||
url = search.compile(full_url)
|
||||
|
||||
run_in_gui_thread(self.webview.load_uri, url)
|
||||
|
||||
|
||||
def handle_page_view_source(self, text):
|
||||
tab = self.window.tab_new(url=False, switch=True)
|
||||
tab.webview.load_plain_text(BeautifulSoup(text, features='lxml').prettify())
|
||||
tab.set_title(f'Source: {self.url}')
|
||||
|
||||
|
||||
def handle_run_js(self, webview, task, callback, *args, **kwargs):
|
||||
value = self.webview.run_javascript_finish(task).get_js_value().to_string()
|
||||
callback(value, *args, **kwargs)
|
||||
|
||||
|
||||
def handle_save_page_finish(self, webview, raw_result, path):
|
||||
result = webview.save_to_file_finish(raw_result)
|
||||
|
||||
if result:
|
||||
msg = 'Successfully saved web page'
|
||||
level = 'INFO'
|
||||
|
||||
else:
|
||||
msg = 'Failed to save webpage'
|
||||
level = 'ERROR'
|
||||
|
||||
self.window.notification(msg, level, system=False)
|
||||
|
||||
|
||||
def handle_search_get_selection(self, selection):
|
||||
if not selection:
|
||||
return
|
||||
|
||||
self.search_action('search', text=selection)
|
||||
|
||||
|
||||
def handle_search_failed(self):
|
||||
self.window.notification(f'Cannot find text on page: {self.search.get_search_text()}')
|
||||
|
||||
|
||||
def handle_search_keys(self, widget, event):
|
||||
if event.keyval == Gdk.KEY_Escape:
|
||||
self.search_action('close')
|
||||
return self.webview.grab_focus()
|
||||
|
||||
|
||||
def handle_search_text_change(self):
|
||||
self['search-text'].set_text(self.search.get_search_text())
|
||||
|
||||
|
||||
def handle_url_keys(self, widget, event):
|
||||
if event.keyval == Gdk.KEY_Escape:
|
||||
widget.select_region(0,0)
|
||||
widget.set_text(self.url or '')
|
||||
return self.webview.grab_focus()
|
||||
|
||||
elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter]:
|
||||
return self.handle_button('go')
|
||||
|
||||
with self.app.db.session as s:
|
||||
if not s.get_config('enable_autocomplete'):
|
||||
return
|
||||
|
||||
if not self.autocomplete_timer:
|
||||
self.autocomplete_timer = AutocompleteTimeout()
|
||||
|
||||
else:
|
||||
self.autocomplete_timer.refresh()
|
||||
|
||||
|
||||
def handle_url_popup(self, entry, menu):
|
||||
if not self.app.clipboard.wait_is_text_available():
|
||||
return
|
||||
|
||||
item = Gtk.MenuItem.new_with_label('Paste and go')
|
||||
item.connect('activate', self.handle_url_popup_go)
|
||||
item.show()
|
||||
|
||||
menu.insert(item, 3)
|
||||
|
||||
return menu
|
||||
|
||||
|
||||
def handle_url_popup_go(self, item):
|
||||
self['navbar-url'].set_text(self.app.clipboard.wait_for_text())
|
||||
self.handle_button('go')
|
|
@ -317,10 +317,17 @@ class WebSettings(WebKit2.Settings):
|
|||
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == 'user-agent':
|
||||
return self.get_user_agent()
|
||||
|
||||
return self.get_property(key)
|
||||
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == 'user-agent':
|
||||
self.set_user_agent(value)
|
||||
return
|
||||
|
||||
if key not in default_settings:
|
||||
logging.error('Invalid setting:', key)
|
||||
return
|
|
@ -7,53 +7,54 @@ from urllib.parse import urlparse, quote, unquote
|
|||
from ssl import SSLCertVerificationError
|
||||
|
||||
from .. import cache, var
|
||||
from ..functions import Thread, connect, run_in_gui_thread
|
||||
from ..enums import EditAction, Javascript, WebviewContextActions
|
||||
from ..functions import ComponentBase, Thread, connect, run_in_gui_thread
|
||||
from ..widgets import FileChooser
|
||||
|
||||
|
||||
context_menu_actions = {
|
||||
'audio_copy': WebKit2.ContextMenuAction.COPY_AUDIO_LINK_TO_CLIPBOARD,
|
||||
'audio_download': WebKit2.ContextMenuAction.DOWNLOAD_AUDIO_TO_DISK,
|
||||
'frame_open': WebKit2.ContextMenuAction.OPEN_FRAME_IN_NEW_WINDOW,
|
||||
'audio_tab': WebKit2.ContextMenuAction.OPEN_AUDIO_IN_NEW_WINDOW,
|
||||
'go_back': WebKit2.ContextMenuAction.GO_BACK,
|
||||
'go_forward': WebKit2.ContextMenuAction.GO_FORWARD,
|
||||
'go_reload': WebKit2.ContextMenuAction.RELOAD,
|
||||
'go_stop': WebKit2.ContextMenuAction.STOP,
|
||||
'image_copy': WebKit2.ContextMenuAction.COPY_IMAGE_URL_TO_CLIPBOARD,
|
||||
'image_copy-full': WebKit2.ContextMenuAction.COPY_IMAGE_TO_CLIPBOARD,
|
||||
'image_download': WebKit2.ContextMenuAction.DOWNLOAD_IMAGE_TO_DISK,
|
||||
'image_tab': WebKit2.ContextMenuAction.OPEN_IMAGE_IN_NEW_WINDOW,
|
||||
'inspect_element': WebKit2.ContextMenuAction.INSPECT_ELEMENT,
|
||||
'link_copy': WebKit2.ContextMenuAction.COPY_LINK_TO_CLIPBOARD,
|
||||
'link_download': WebKit2.ContextMenuAction.DOWNLOAD_LINK_TO_DISK,
|
||||
'link_open': WebKit2.ContextMenuAction.OPEN_LINK,
|
||||
'link_tab': WebKit2.ContextMenuAction.OPEN_LINK_IN_NEW_WINDOW,
|
||||
'media_control': WebKit2.ContextMenuAction.TOGGLE_MEDIA_CONTROLS,
|
||||
'media_mute': WebKit2.ContextMenuAction.MEDIA_MUTE,
|
||||
'media_loop': WebKit2.ContextMenuAction.TOGGLE_MEDIA_LOOP,
|
||||
'media_pause': WebKit2.ContextMenuAction.MEDIA_PAUSE,
|
||||
'spell_add': WebKit2.ContextMenuAction.LEARN_SPELLING,
|
||||
'media_play': WebKit2.ContextMenuAction.MEDIA_PLAY,
|
||||
'spell_guess': WebKit2.ContextMenuAction.SPELLING_GUESS,
|
||||
'spell_ignore-grammar': WebKit2.ContextMenuAction.IGNORE_GRAMMAR,
|
||||
'spell_ignore-spell': WebKit2.ContextMenuAction.IGNORE_SPELLING,
|
||||
'spell_none': WebKit2.ContextMenuAction.NO_GUESSES_FOUND,
|
||||
'text_bold': WebKit2.ContextMenuAction.BOLD,
|
||||
'text_copy': WebKit2.ContextMenuAction.COPY,
|
||||
'text_cut': WebKit2.ContextMenuAction.CUT,
|
||||
'text_delete': WebKit2.ContextMenuAction.DELETE,
|
||||
'text_emoji': WebKit2.ContextMenuAction.INSERT_EMOJI,
|
||||
'text_italic': WebKit2.ContextMenuAction.ITALIC,
|
||||
'text_paste': WebKit2.ContextMenuAction.PASTE,
|
||||
'text_paste-plain': WebKit2.ContextMenuAction.PASTE_AS_PLAIN_TEXT,
|
||||
'text_select': WebKit2.ContextMenuAction.SELECT_ALL,
|
||||
'text_underline': WebKit2.ContextMenuAction.UNDERLINE,
|
||||
'video_copy': WebKit2.ContextMenuAction.COPY_VIDEO_LINK_TO_CLIPBOARD,
|
||||
'video_download': WebKit2.ContextMenuAction.DOWNLOAD_VIDEO_TO_DISK,
|
||||
'video_fullscreen': WebKit2.ContextMenuAction.ENTER_VIDEO_FULLSCREEN,
|
||||
'video_tab': WebKit2.ContextMenuAction.OPEN_VIDEO_IN_NEW_WINDOW,
|
||||
}
|
||||
#context_menu_actions = {
|
||||
#'audio_copy': WebKit2.ContextMenuAction.COPY_AUDIO_LINK_TO_CLIPBOARD,
|
||||
#'audio_download': WebKit2.ContextMenuAction.DOWNLOAD_AUDIO_TO_DISK,
|
||||
#'frame_open': WebKit2.ContextMenuAction.OPEN_FRAME_IN_NEW_WINDOW,
|
||||
#'audio_tab': WebKit2.ContextMenuAction.OPEN_AUDIO_IN_NEW_WINDOW,
|
||||
#'go_back': WebKit2.ContextMenuAction.GO_BACK,
|
||||
#'go_forward': WebKit2.ContextMenuAction.GO_FORWARD,
|
||||
#'go_reload': WebKit2.ContextMenuAction.RELOAD,
|
||||
#'go_stop': WebKit2.ContextMenuAction.STOP,
|
||||
#'image_copy': WebKit2.ContextMenuAction.COPY_IMAGE_URL_TO_CLIPBOARD,
|
||||
#'image_copy-full': WebKit2.ContextMenuAction.COPY_IMAGE_TO_CLIPBOARD,
|
||||
#'image_download': WebKit2.ContextMenuAction.DOWNLOAD_IMAGE_TO_DISK,
|
||||
#'image_tab': WebKit2.ContextMenuAction.OPEN_IMAGE_IN_NEW_WINDOW,
|
||||
#'inspect_element': WebKit2.ContextMenuAction.INSPECT_ELEMENT,
|
||||
#'link_copy': WebKit2.ContextMenuAction.COPY_LINK_TO_CLIPBOARD,
|
||||
#'link_download': WebKit2.ContextMenuAction.DOWNLOAD_LINK_TO_DISK,
|
||||
#'link_open': WebKit2.ContextMenuAction.OPEN_LINK,
|
||||
#'link_tab': WebKit2.ContextMenuAction.OPEN_LINK_IN_NEW_WINDOW,
|
||||
#'media_control': WebKit2.ContextMenuAction.TOGGLE_MEDIA_CONTROLS,
|
||||
#'media_mute': WebKit2.ContextMenuAction.MEDIA_MUTE,
|
||||
#'media_loop': WebKit2.ContextMenuAction.TOGGLE_MEDIA_LOOP,
|
||||
#'media_pause': WebKit2.ContextMenuAction.MEDIA_PAUSE,
|
||||
#'spell_add': WebKit2.ContextMenuAction.LEARN_SPELLING,
|
||||
#'media_play': WebKit2.ContextMenuAction.MEDIA_PLAY,
|
||||
#'spell_guess': WebKit2.ContextMenuAction.SPELLING_GUESS,
|
||||
#'spell_ignore-grammar': WebKit2.ContextMenuAction.IGNORE_GRAMMAR,
|
||||
#'spell_ignore-spell': WebKit2.ContextMenuAction.IGNORE_SPELLING,
|
||||
#'spell_none': WebKit2.ContextMenuAction.NO_GUESSES_FOUND,
|
||||
#'text_bold': WebKit2.ContextMenuAction.BOLD,
|
||||
#'text_copy': WebKit2.ContextMenuAction.COPY,
|
||||
#'text_cut': WebKit2.ContextMenuAction.CUT,
|
||||
#'text_delete': WebKit2.ContextMenuAction.DELETE,
|
||||
#'text_emoji': WebKit2.ContextMenuAction.INSERT_EMOJI,
|
||||
#'text_italic': WebKit2.ContextMenuAction.ITALIC,
|
||||
#'text_paste': WebKit2.ContextMenuAction.PASTE,
|
||||
#'text_paste-plain': WebKit2.ContextMenuAction.PASTE_AS_PLAIN_TEXT,
|
||||
#'text_select': WebKit2.ContextMenuAction.SELECT_ALL,
|
||||
#'text_underline': WebKit2.ContextMenuAction.UNDERLINE,
|
||||
#'video_copy': WebKit2.ContextMenuAction.COPY_VIDEO_LINK_TO_CLIPBOARD,
|
||||
#'video_download': WebKit2.ContextMenuAction.DOWNLOAD_VIDEO_TO_DISK,
|
||||
#'video_fullscreen': WebKit2.ContextMenuAction.ENTER_VIDEO_FULLSCREEN,
|
||||
#'video_tab': WebKit2.ContextMenuAction.OPEN_VIDEO_IN_NEW_WINDOW,
|
||||
#}
|
||||
|
||||
cert_error_msg = {
|
||||
'unknown-ca': 'Unknown certificate authority for url: {url}',
|
||||
|
@ -61,121 +62,139 @@ cert_error_msg = {
|
|||
}
|
||||
|
||||
|
||||
class WebviewHandler:
|
||||
class WebviewHandler(ComponentBase):
|
||||
def __init__(self, tab):
|
||||
self.app = tab.window.app
|
||||
self.window = tab.window
|
||||
ComponentBase.__init__(self)
|
||||
|
||||
self.tab = tab
|
||||
self.webview = tab.webview
|
||||
self.inspector = tab.webview.get_inspector()
|
||||
self.inspector_open = False
|
||||
|
||||
signals = {
|
||||
'close': {'handler': self.tab.close_tab},
|
||||
'close': {'handler': self.tab.close},
|
||||
'context-menu': {'handler': self.handle_context_menu},
|
||||
'create': {'handler': self.handle_new_window},
|
||||
'decide-policy': {'handler': self.handle_decide_policy},
|
||||
'enter-fullscreen': {'handler': self.handle_fullscreen, 'args': ['enter']},
|
||||
'insecure-content-detected': {'handler': self.handle_insecure_content},
|
||||
#'insecure-content-detected': {'handler': self.handle_insecure_content},
|
||||
'leave-fullscreen': {'handler': self.handle_fullscreen, 'args': ['exit']},
|
||||
'load-changed': {'handler': self.handle_load_changed},
|
||||
'load-failed-with-tls-errors': {'handler': self.handle_tls_error},
|
||||
'mouse-target-changed': {'handler': self.handle_mouse_hover},
|
||||
'resource-load-started': {'handler': self.handle_resource_load},
|
||||
'script-dialog': {'handler': self.handle_script_dialog},
|
||||
'show-notification': {'handler': self.handle_notification},
|
||||
#'show-notification': {'handler': self.handle_notification},
|
||||
#'submit-form': {'handler': self.handle_submit_form},
|
||||
'permission-request': {'handler': self.handle_permission_request},
|
||||
'web-process-terminated': {'handler': self.handle_tab_crashed},
|
||||
'notify::estimated-load-progress': {'handler': self.handle_load_progress},
|
||||
'notify::favicon': {'handler': self.handle_set_favicon},
|
||||
'notify::title': {'handler': self.handle_set_title},
|
||||
'notify::uri': {'handler': self.handle_set_url}
|
||||
'notify::favicon': {'handler': self.tab.set_favicon, 'kwargs': {'original_args': False}},
|
||||
'notify::title': {'handler': self.tab.set_title, 'kwargs': {'original_args': False}},
|
||||
'notify::uri': {'handler': self.tab.set_url, 'kwargs': {'original_args': False}}
|
||||
}
|
||||
|
||||
for signal, data in signals.items():
|
||||
connect(self.webview, signal, data['handler'], *data.get('args', ''), **data.get('kwargs', {}), original_args=True)
|
||||
if data.get('kwargs', {}).get('original_args') == None:
|
||||
data['kwargs'] = {'original_args': True}
|
||||
|
||||
connect(self.webview, signal, data['handler'], *data.get('args', ''), **data['kwargs'])
|
||||
|
||||
connect(self.inspector, 'attach', self.handle_inspector, 'attach', original_args=True)
|
||||
connect(self.inspector, 'detach', self.handle_inspector, 'detach', original_args=True)
|
||||
connect(self.inspector, 'closed', self.handle_inspector, 'close', original_args=True)
|
||||
|
||||
|
||||
@property
|
||||
def inspector(self):
|
||||
return self.webview.get_inspector()
|
||||
|
||||
|
||||
@property
|
||||
def settings(self):
|
||||
return self.tab.settings
|
||||
|
||||
|
||||
@property
|
||||
def webview(self):
|
||||
return self.tab.webview
|
||||
|
||||
|
||||
def handle_context_menu(self, webview, context_menu, event, hit):
|
||||
'''
|
||||
context_is_editable
|
||||
context_is_image
|
||||
context_is_link
|
||||
context_is_media
|
||||
context_is_scrollbar
|
||||
context_is_selection
|
||||
'''
|
||||
url = webview.get_uri()
|
||||
link_url = hit.get_link_uri()
|
||||
link_text = hit.get_link_label()
|
||||
image_url = hit.get_image_uri()
|
||||
media_url = hit.get_media_uri()
|
||||
|
||||
menu = ContextMenuClass(context_menu, self.tab)
|
||||
url = DotDict(
|
||||
page = webview.get_uri(),
|
||||
link = hit.get_link_uri(),
|
||||
image = hit.get_image_uri(),
|
||||
media = hit.get_media_uri()
|
||||
)
|
||||
data = DotDict(
|
||||
link_text = hit.get_link_label(),
|
||||
selection = hit.context_is_selection(),
|
||||
editable = hit.context_is_editable(),
|
||||
link = hit.context_is_link(),
|
||||
media = hit.context_is_media(),
|
||||
image = hit.context_is_image(),
|
||||
scrollbar = hit.context_is_scrollbar()
|
||||
)
|
||||
|
||||
selection = hit.context_is_selection()
|
||||
editable = hit.context_is_editable()
|
||||
link = hit.context_is_link()
|
||||
media = hit.context_is_media()
|
||||
image = hit.context_is_image()
|
||||
for key, value in url.items():
|
||||
if not value:
|
||||
continue
|
||||
|
||||
if link:
|
||||
menu.new_action('link_open', 'Open Link', self.tab.load_url, link_url)
|
||||
menu.new_action('link_new', 'Open Link In New Tab', self.window.new_tab, link_url, switch=True)
|
||||
menu.new_action('link_bg', 'Open Link In Background Tab', self.window.new_tab, link_url)
|
||||
menu.new_stock('link_copy', 'Copy Link Url', 'link_copy')
|
||||
menu.new_stock('link_dl', 'Download Link', 'link_download')
|
||||
url[key] = Url(value)
|
||||
|
||||
|
||||
if data.link:
|
||||
menu.new_action('link_open', 'Open Link', self.tab.load_url, url.link)
|
||||
menu.new_action('link_new', 'Open Link In New Tab', self.window.tab_new, url.link, switch=True)
|
||||
menu.new_action('link_bg', 'Open Link In Background Tab', self.window.tab_new, url.link)
|
||||
menu.new_stock('link_copy', 'Copy Link Url', WebviewContextActions.LINK_COPY)
|
||||
menu.new_stock('link_dl', 'Download Link', WebviewContextActions.LINK_DOWNLOAD)
|
||||
menu.new_sep()
|
||||
|
||||
if media:
|
||||
menu.new_stock('media_play', 'Play', 'media_play')
|
||||
menu.new_stock('media_pause', 'Pause', 'media_pause')
|
||||
menu.new_stock('media_loop', 'Toggle Loop', 'media_loop')
|
||||
menu.new_stock('media_ctrl', 'Toggle Controls', 'media_control')
|
||||
menu.new_action('media_open', 'Open Media', self.tab.load_url, media_url)
|
||||
menu.new_action('media_new', 'Open Media In New Tab', self.window.new_tab, media_url, switch=True)
|
||||
menu.new_action('media_bg', 'Open Media In Background Tab', self.window.new_tab, media_url)
|
||||
menu.new_action('media_dl', 'Download Media', self.window.context.download_uri, media_url)
|
||||
if data.media:
|
||||
menu.new_stock('media_play', 'Play', WebviewContextActions.MEDIA_PLAY)
|
||||
menu.new_stock('media_pause', 'Pause', WebviewContextActions.MEDIA_PAUSE)
|
||||
menu.new_stock('media_loop', 'Toggle Loop', WebviewContextActions.MEDIA_LOOP)
|
||||
menu.new_stock('media_ctrl', 'Toggle Controls', WebviewContextActions.MEDIA_TOGGLE)
|
||||
menu.new_action('media_open', 'Open Media', self.tab.load_url, url.media)
|
||||
menu.new_action('media_new', 'Open Media In New Tab', self.window.tab_new, url.media, switch=True)
|
||||
menu.new_action('media_bg', 'Open Media In Background Tab', self.window.tab_new, url.media)
|
||||
menu.new_action('media_dl', 'Download Media', self.app.context.download_uri, url.media)
|
||||
menu.new_sep()
|
||||
|
||||
if image:
|
||||
menu.new_action('image_open', 'Open Image', self.tab.load_url, image_url)
|
||||
menu.new_action('image_new', 'Open Image In New Tab', self.window.new_tab, image_url, switch=True)
|
||||
menu.new_action('image_bg', 'Open Image In Background Tab', self.window.new_tab, image_url)
|
||||
menu.new_stock('image_dl', 'Download Image', 'image_download')
|
||||
menu.new_stock('image_url', 'Copy Image Url', 'image_copy')
|
||||
menu.new_stock('image_copy', 'Copy Image', 'image_copy-full')
|
||||
if data.image:
|
||||
menu.new_action('image_open', 'Open Image', self.tab.load_url, url.image)
|
||||
menu.new_action('image_new', 'Open Image In New Tab', self.window.tab_new, url.image, switch=True)
|
||||
menu.new_action('image_bg', 'Open Image In Background Tab', self.window.tab_new, url.image)
|
||||
menu.new_stock('image_dl', 'Download Image', WebviewContextActions.IMAGE_DOWNLOAD)
|
||||
menu.new_stock('image_url', 'Copy Image Url', WebviewContextActions.IMAGE_COPY)
|
||||
menu.new_stock('image_copy', 'Copy Image', WebviewContextActions.IMAGE_COPY_FULL)
|
||||
menu.new_sep()
|
||||
|
||||
if editable:
|
||||
if data.editable:
|
||||
menu.new_webview_action('edit_undo', 'Undo', 'Undo')
|
||||
menu.new_webview_action('edit_redo', 'Redo', 'Redo')
|
||||
menu.new_stock('edit_paste', 'Paste', 'text_paste')
|
||||
menu.new_stock('edit_plain', 'Paste Plain Text', 'text_paste-plain')
|
||||
menu.new_stock('edit_paste', 'Paste', WebviewContextActions.TEXT_PASTE)
|
||||
menu.new_stock('edit_plain', 'Paste Plain Text', WebviewContextActions.TEXT_PASTE_PLAIN)
|
||||
|
||||
if selection:
|
||||
menu.new_stock('edit_copy', 'Copy', 'text_copy')
|
||||
menu.new_stock('edit_cut', 'Cut', 'text_cut')
|
||||
if data.selection:
|
||||
menu.new_stock('edit_copy', 'Copy', WebviewContextActions.TEXT_COPY)
|
||||
|
||||
if selection and editable:
|
||||
menu.new_stock('edit_delete', 'Delete', 'text_delete')
|
||||
if data.selection and data.editable:
|
||||
menu.new_stock('edit_cut', 'Cut', WebviewContextActions.TEXT_CUT)
|
||||
menu.new_stock('edit_delete', 'Delete', WebviewContextActions.TEXT_DELETE)
|
||||
|
||||
menu.new_stock('edit_select', 'Select All', 'text_select')
|
||||
menu.new_stock('edit_select', 'Select All', WebviewContextActions.TEXT_SELECT)
|
||||
|
||||
if selection or editable:
|
||||
if data.selection or data.editable:
|
||||
menu.new_sep()
|
||||
|
||||
if selection:
|
||||
if data.selection:
|
||||
menu.new_action(
|
||||
'search',
|
||||
'Search or Go',
|
||||
self.tab.run_js,
|
||||
self.tab.js_functions.get_selection,
|
||||
Javascript.SELECTION,
|
||||
self.callback_new_tab
|
||||
)
|
||||
|
||||
|
@ -189,20 +208,20 @@ class WebviewHandler:
|
|||
row.keyword,
|
||||
row.name,
|
||||
self.tab.run_js,
|
||||
self.tab.js_functions.get_selection,
|
||||
Javascript.SELECTION,
|
||||
self.callback_search,
|
||||
row.keyword
|
||||
)
|
||||
|
||||
menu.new_sep()
|
||||
|
||||
if not url.startswith(var.local):
|
||||
menu.new_action('print', 'Print Page', self.tab.page_print)
|
||||
if url.page.proto != var.local:
|
||||
menu.new_action('print', 'Print Page', self.tab.page_action, 'print')
|
||||
|
||||
if any(map(url.startswith, ['https://', 'http://'])):
|
||||
menu.new_action('source', 'View Page Source', self.tab.page_view_source)
|
||||
if url.page.proto in ['http', 'https']:
|
||||
menu.new_action('source', 'View Page Source', self.tab.page_action, 'source')
|
||||
|
||||
menu.new_stock('inspect', 'Inspect Element', 'inspect_element')
|
||||
menu.new_stock('inspect', 'Inspect Element', WebviewContextActions.INSPECT)
|
||||
|
||||
return False
|
||||
|
||||
|
@ -212,10 +231,12 @@ class WebviewHandler:
|
|||
|
||||
if decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION:
|
||||
if url.proto == 'file':
|
||||
decision.ignore()
|
||||
webview.load_uri(url.replace_property('proto', 'local'))
|
||||
return True
|
||||
|
||||
elif url.proto == 'ftp':
|
||||
decision.ignore()
|
||||
webview.load_uri(url.replace_property('proto', 'filetp'))
|
||||
return True
|
||||
|
||||
|
@ -223,6 +244,7 @@ class WebviewHandler:
|
|||
if url.startswith('https://pyweb-oauth'):
|
||||
url = url.replace('https://pyweb-oauth', 'oauth://', 1)
|
||||
webview.load_uri(url)
|
||||
decision.ignore()
|
||||
return True
|
||||
|
||||
try:
|
||||
|
@ -241,7 +263,7 @@ class WebviewHandler:
|
|||
|
||||
def handle_fullscreen(self, webview, state):
|
||||
domain = self.tab.url.domain
|
||||
navbar = self.window['navbar']
|
||||
navbar = self.tab['navbar']
|
||||
statusbar = self.window['statusbar']
|
||||
tabs = self.window['tabs']
|
||||
|
||||
|
@ -293,13 +315,11 @@ class WebviewHandler:
|
|||
|
||||
|
||||
def handle_load_changed(self, webview, event):
|
||||
url = self.tab.url
|
||||
|
||||
if not url:
|
||||
if not (url := self.tab.url):
|
||||
return
|
||||
|
||||
## Only enable the page cache for http(s) urls
|
||||
self.tab.settings['enable-page-cache'] = url.proto in ['http', 'https']
|
||||
self.settings.set('enable-page-cache', url.proto in ['http', 'https'])
|
||||
|
||||
try:
|
||||
host = url.hostname()
|
||||
|
@ -310,23 +330,22 @@ class WebviewHandler:
|
|||
permissions = s.get_permission(host)
|
||||
|
||||
if event.value_nick == 'started':
|
||||
self.tab.settings['media-playback-requires-user-gesture'] = not permissions.autoplay
|
||||
self.tab._data.favicon = False
|
||||
self.settings.set('media-playback-requires-user-gesture', not permissions.autoplay)
|
||||
|
||||
if self.window.active_tab == self.tab:
|
||||
self.window['navbar-stop'].set_sensitive(True)
|
||||
self.window['navbar-refresh'].set_sensitive(False)
|
||||
|
||||
self.tab.current_url = url
|
||||
self.tab.search_close()
|
||||
self.tab.idle = False
|
||||
self.tab['navbar-stop'].set_sensitive(True)
|
||||
self.tab['navbar-refresh'].set_sensitive(False)
|
||||
self.tab.search_action('close')
|
||||
|
||||
elif event.value_nick == 'committed':
|
||||
self.tab.set_button_state()
|
||||
|
||||
elif event.value_nick == 'redirected':
|
||||
logging.debug(f'handle_load_changed: redirect {url}')
|
||||
pass
|
||||
|
||||
elif event.value_nick in ['finished', 'redirected']:
|
||||
self.tab.idle = True
|
||||
|
||||
if event.value_nick == 'finished' and isinstance(url, Url):
|
||||
elif event.value_nick == 'finished':
|
||||
if isinstance(url, Url):
|
||||
if url.proto == 'https':
|
||||
FediverseCheck(None, self.tab, permissions).start()
|
||||
|
||||
|
@ -335,7 +354,10 @@ class WebviewHandler:
|
|||
s.put_history_from_tab(self.tab)
|
||||
|
||||
if self.window.active_tab == self.tab:
|
||||
self.window.set_button_state(self.tab)
|
||||
self.window.set_button_state(self.tab.id)
|
||||
|
||||
if not self.tab._data.favicon:
|
||||
self.tab.set_favicon()
|
||||
|
||||
|
||||
def handle_load_failed(self, webview, event, uri, error):
|
||||
|
@ -343,9 +365,12 @@ class WebviewHandler:
|
|||
|
||||
|
||||
def handle_load_progress(self, webview, _):
|
||||
if self.window.active_tab == self.tab:
|
||||
progress = webview.get_estimated_load_progress()
|
||||
self.window['navbar-url'].set_progress_fraction(0 if progress == 1.0 else progress)
|
||||
progress = webview.get_estimated_load_progress()
|
||||
finished = True if progress == 1.0 else False
|
||||
|
||||
self.tab['navbar-url'].set_progress_fraction(0 if progress == 1.0 else progress)
|
||||
self.tab['navbar-stop'].set_sensitive(not finished)
|
||||
self.tab['navbar-refresh'].set_sensitive(finished)
|
||||
|
||||
|
||||
def handle_mouse_hover(self, webview, hit, _):
|
||||
|
@ -354,14 +379,29 @@ class WebviewHandler:
|
|||
|
||||
|
||||
def handle_resource_load(self, webview, resource, request):
|
||||
if logging.get_config('level') == logging.LogLevel.DEBUG:
|
||||
resource.connect('failed', self.handle_resource_failed)
|
||||
resource.connect('finished', self.handle_resource_finished)
|
||||
resource.connect('failed', self.handle_resource_failed)
|
||||
resource.connect('finished', self.handle_resource_finished)
|
||||
|
||||
|
||||
def handle_resource_failed(self, resource, error):
|
||||
try:
|
||||
url = Url(resource.get_uri())
|
||||
|
||||
except Exception as e:
|
||||
print(f'{class_name(e)}: {e}')
|
||||
return
|
||||
|
||||
if error.domain == 'WebKitPolicyError' and 'interrupted' in error.message.lower():
|
||||
if not url.mimetype:
|
||||
self.window.notification(f'Cannot open url: {url}')
|
||||
|
||||
elif not self.webview.can_show_mime_type(url.mimetype):
|
||||
self.app.context.download_uri(url)
|
||||
|
||||
return
|
||||
|
||||
if error.domain not in ['HandlerError', 'soup-http-error-quark']:
|
||||
if error.domain == 'WebKitNetworkError' and error.message.lower == 'load request cancelled':
|
||||
if error.domain == 'WebKitNetworkError' and error.message.lower() == 'load request cancelled':
|
||||
return
|
||||
|
||||
logging.debug(f'''Resource failed to load: {resource.get_uri()}
|
||||
|
@ -379,11 +419,11 @@ class WebviewHandler:
|
|||
url = action.get_request().get_uri()
|
||||
|
||||
if not user_action:
|
||||
logging.debug('handle_new_window: Not a user action')
|
||||
logging.debug('handle_new_window: Not a user action: "{url}"')
|
||||
return
|
||||
|
||||
if navtype in [WebKit2.NavigationType.FORM_SUBMITTED, WebKit2.NavigationType.LINK_CLICKED, WebKit2.NavigationType.OTHER]:
|
||||
self.window.new_tab(url, switch=True)
|
||||
self.window.tab_new(url, switch=True)
|
||||
|
||||
else:
|
||||
logging.debug('handle_new_window: unhandled nav type:', navtype)
|
||||
|
@ -414,7 +454,7 @@ class WebviewHandler:
|
|||
permrequest.deny()
|
||||
return
|
||||
|
||||
with self.app.db.session as s:
|
||||
with self.db.session as s:
|
||||
row = s.get_permission(domain)
|
||||
|
||||
if type(permrequest) == WebKit2.NotificationPermissionRequest and row.notification:
|
||||
|
@ -439,24 +479,14 @@ class WebviewHandler:
|
|||
permrequest.deny()
|
||||
|
||||
|
||||
def handle_save_page_finish(self, webview, raw_result, path):
|
||||
result = webview.save_to_file_finish(raw_result)
|
||||
|
||||
if result:
|
||||
msg = 'Successfully saved web page'
|
||||
level = 'INFO'
|
||||
|
||||
else:
|
||||
msg = 'Failed to save webpage'
|
||||
level = 'ERROR'
|
||||
|
||||
self.window.notification(msg, level, system=False)
|
||||
|
||||
|
||||
def handle_script_dialog(self, webview, dialog):
|
||||
domain = self.tab.url.domain
|
||||
try:
|
||||
domain = self.tab.url.domain
|
||||
|
||||
with self.app.db.session as s:
|
||||
except:
|
||||
return True
|
||||
|
||||
with self.db.session as s:
|
||||
row = s.get_permission(domain)
|
||||
|
||||
if not row.dialog:
|
||||
|
@ -492,7 +522,7 @@ class WebviewHandler:
|
|||
url = Url(url)
|
||||
err = error.first_value_nick
|
||||
|
||||
with self.app.db.session as s:
|
||||
with self.db.session as s:
|
||||
if url.address.is_type('private') and s.get_config('allow_local_unsigned'):
|
||||
self.tab.context.allow_tls_certificate_for_host(cert, url.domain)
|
||||
webview.load_uri(url)
|
||||
|
@ -504,14 +534,13 @@ class WebviewHandler:
|
|||
logging.debug('handlers.signal.TlsError: Unhandled error:', err)
|
||||
|
||||
context = {'error_message': msg.format(url=url), 'title': 'TLS Error'}
|
||||
webview.load_html(self.app.template.render('error.haml', context), url)
|
||||
webview.load_html(self.app.template.render('error.haml', context))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def handle_set_url(self, *_):
|
||||
if self.window.active_tab == self.tab:
|
||||
self.window.set_navbar_url(self.tab.url)
|
||||
def handle_set_url(self, webview, url):
|
||||
self.tab.set_url()
|
||||
|
||||
|
||||
def handle_set_favicon(self, *_):
|
||||
|
@ -577,10 +606,11 @@ class ContextMenuClass(DotDict):
|
|||
|
||||
|
||||
def new_stock(self, name, label, action):
|
||||
if action not in context_menu_actions:
|
||||
logging.error('Not a valid context menu action:', action)
|
||||
if not isinstance(action, WebviewContextActions):
|
||||
raise TypeError(f'Webview context action must be a WebviewContextActions, not {class_name(action)}')
|
||||
#action = WebviewContextActions[action.upper()]
|
||||
|
||||
item = WebKit2.ContextMenuItem.new_from_stock_action_with_label(context_menu_actions[action], label)
|
||||
item = WebKit2.ContextMenuItem.new_from_stock_action_with_label(action.value, label)
|
||||
|
||||
self[name] = item
|
||||
self.menu.append(item)
|
||||
|
@ -613,11 +643,11 @@ class ContextMenuClass(DotDict):
|
|||
|
||||
class FediverseCheck(Thread):
|
||||
def run_func(self, tab, permissions):
|
||||
acct = tab.app.get_default_account()
|
||||
url = tab.url
|
||||
post = {}
|
||||
|
||||
with tab.db.session as s:
|
||||
acct = s.get_account()
|
||||
history_row = s.get_history(url)
|
||||
is_post = history_row.post if history_row else None
|
||||
nodeinfo = None
|
||||
|
@ -658,12 +688,17 @@ class FediverseCheck(Thread):
|
|||
permissions = s.put_permission(permissions.domain, 'instance', is_instance)
|
||||
|
||||
if s.get_config('post_check') and permissions and permissions.instance and (is_post in [None, True]):
|
||||
if not (post := cache.posts.fetch(url)):
|
||||
if url.path.startswith('/web'):
|
||||
is_post = False
|
||||
|
||||
elif not (post := cache.posts.fetch(url)):
|
||||
post = acct.fetch_post(url)
|
||||
cache.posts.store(url, post)
|
||||
|
||||
is_post = True if is_post else False
|
||||
is_post = True if post else False
|
||||
|
||||
logging.verbose(f'Result of post check for {url}: {is_post}')
|
||||
|
||||
s.put_history(url, tab.title, is_post)
|
||||
|
||||
run_in_gui_thread(tab.set_current_post, post)
|
||||
tab._data.post = post
|
|
@ -4,7 +4,7 @@ from bs4 import BeautifulSoup
|
|||
from izzylib.exceptions import DNSResolverError
|
||||
|
||||
from .web_settings import WebSettings
|
||||
from .web_view_handler import WebviewHandler
|
||||
from .web_tab_webview_handler import WebviewHandler
|
||||
|
||||
from ..functions import run_in_gui_thread, surface_to_pixbuf
|
||||
from ..widgets import Box, FileChooser
|
||||
|
@ -28,15 +28,13 @@ class Webview(Gtk.Box):
|
|||
self.app = window.app
|
||||
self.window = window
|
||||
self.db = window.app.db
|
||||
self.context = window.context
|
||||
#self.user_content = WebContentManager(self)
|
||||
self.settings = WebSettings(self)
|
||||
self.webview = None
|
||||
|
||||
self.tabid = tabid
|
||||
self.tabid = tabid or row.tabid
|
||||
self.row = row
|
||||
self.iconsize = window.iconsize
|
||||
self.client = None
|
||||
|
||||
## Initiate variables that'll get used later
|
||||
self.history = {}
|
||||
|
@ -61,6 +59,11 @@ class Webview(Gtk.Box):
|
|||
self.load_url(starturl)
|
||||
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
self.window.context
|
||||
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
if self.state:
|
||||
|
@ -490,11 +493,11 @@ class SearchBar(Box):
|
|||
self.handle_search(selection)
|
||||
|
||||
|
||||
def handle_search(self, text=None):
|
||||
options = 16
|
||||
def handle_search(self, text=None, insensitive=True):
|
||||
options = WebKit2.FindOptions.WRAP_AROUND
|
||||
|
||||
if self.insensitive:
|
||||
options += 1
|
||||
if insensitive:
|
||||
options += WebKit2.FindOptions.CASE_INSENSITIVE
|
||||
|
||||
self.find.search(text or self['text'].get_text(), options, 1000)
|
||||
|
||||
|
|
|
@ -1,41 +1,19 @@
|
|||
import json, sys, threading, time
|
||||
|
||||
from .menu_bar import MenuBar
|
||||
from izzylib_sql import Row
|
||||
|
||||
from .status_bar import StatusBar
|
||||
from .web_context import WebContext
|
||||
from .web_view import Webview
|
||||
from .web_tab import WebTab
|
||||
|
||||
from .. import var, __software__
|
||||
from ..functions import Thread, run_in_gui_thread, connect, get_app, icon_set
|
||||
from ..enums import LibraryPage
|
||||
from ..functions import BuilderBase, Thread, run_in_gui_thread, connect, get_app, icon_set
|
||||
from ..objects import WebviewState
|
||||
from ..themes import Themes
|
||||
from ..widgets import FileChooser, Menu, MenuButtonRefresh
|
||||
|
||||
|
||||
page_widget_focus = {
|
||||
'bookmarks': 'library-bookmarks-search',
|
||||
'downloads': None,
|
||||
'history': 'library-history-search',
|
||||
'passwords': 'library-passwords-search',
|
||||
'search': None,
|
||||
'fediverse': 'library-fediverse-domain',
|
||||
'extensions': None,
|
||||
'preferences': None
|
||||
}
|
||||
|
||||
|
||||
class Window(Gtk.ApplicationWindow):
|
||||
webview_editing_actions = {
|
||||
'copy': WebKit2.EDITING_COMMAND_COPY,
|
||||
'link': WebKit2.EDITING_COMMAND_CREATE_LINK,
|
||||
'cut': WebKit2.EDITING_COMMAND_CUT,
|
||||
'insert_image': WebKit2.EDITING_COMMAND_INSERT_IMAGE,
|
||||
'paste': WebKit2.EDITING_COMMAND_PASTE,
|
||||
'paste_plain': WebKit2.EDITING_COMMAND_PASTE_AS_PLAIN_TEXT,
|
||||
'redo': WebKit2.EDITING_COMMAND_REDO,
|
||||
'select': WebKit2.EDITING_COMMAND_SELECT_ALL,
|
||||
'undo': WebKit2.EDITING_COMMAND_UNDO,
|
||||
}
|
||||
|
||||
class Window(BuilderBase, Gtk.ApplicationWindow):
|
||||
iconsize = Gtk.IconSize.BUTTON
|
||||
is_fullscreen = False
|
||||
startup = True
|
||||
|
@ -43,7 +21,8 @@ class Window(Gtk.ApplicationWindow):
|
|||
autocomplete_timer = None
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(application=app, title=__software__)
|
||||
Gtk.ApplicationWindow.__init__(self, application=app, title=__software__)
|
||||
BuilderBase.__init__(self, app.path.resources.join('main.ui'))
|
||||
|
||||
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||
self.tabdata = {}
|
||||
|
@ -51,7 +30,6 @@ class Window(Gtk.ApplicationWindow):
|
|||
self.accounts = {}
|
||||
self.closed = {}
|
||||
|
||||
self.ui = Gtk.Builder.new_from_file(app.path.resources.join('main.ui'))
|
||||
self.set_resizable(True)
|
||||
self.set_position(Gtk.WindowPosition.CENTER)
|
||||
self.set_icon_from_file(app.path.resources.join('icon.png'))
|
||||
|
@ -67,7 +45,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
self.move(*self.window_location_check(config.location))
|
||||
|
||||
|
||||
self.set_fullscreen(config.fullscreen)
|
||||
self.fullscreen_set(config.fullscreen)
|
||||
self.set_default_size(*self.window_size_check(config.size))
|
||||
self['tabs'].set_tab_pos(getattr(Gtk.PositionType, config.tab_side.upper()))
|
||||
|
||||
|
@ -75,28 +53,8 @@ class Window(Gtk.ApplicationWindow):
|
|||
self.setup_signals()
|
||||
|
||||
## Connect window signals
|
||||
self.connect('window-state-event', self.handle_window_state)
|
||||
self.connect('delete-event', self.handle_window_close)
|
||||
|
||||
|
||||
def __getitem__(self, name):
|
||||
widget = self.ui.get_object(name)
|
||||
|
||||
if not widget:
|
||||
raise KeyError(f'Widget with ID "{name}" does not exist.')
|
||||
|
||||
return widget
|
||||
|
||||
|
||||
def __setitem__(self, name, widget):
|
||||
try:
|
||||
self[name]
|
||||
raise KeyError(f'Widget ID already exists: {name}')
|
||||
|
||||
except KeyError:
|
||||
self.ui.expose_object(name, widget)
|
||||
|
||||
return widget
|
||||
Gtk.ApplicationWindow.connect(self, 'window-state-event', self.handle_window_state)
|
||||
Gtk.ApplicationWindow.connect(self, 'delete-event', self.handle_window_close)
|
||||
|
||||
|
||||
@property
|
||||
|
@ -105,17 +63,6 @@ class Window(Gtk.ApplicationWindow):
|
|||
return tabs.get_nth_page(tabs.get_current_page())
|
||||
|
||||
|
||||
@active_tab.setter
|
||||
def active_tab(self, tabid):
|
||||
page_num = self['tabs'].page_num(self.tabdata[tabid])
|
||||
self['tabs'].set_current_page(page_num)
|
||||
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return self.get_application()
|
||||
|
||||
|
||||
@property
|
||||
def dimensions(self):
|
||||
width, height = self.get_size()
|
||||
|
@ -134,101 +81,50 @@ class Window(Gtk.ApplicationWindow):
|
|||
return DotDict(width=screen.get_width(), height=screen.get_height())
|
||||
|
||||
|
||||
def Connect(self, name, signal, callback, *args, **kwargs):
|
||||
widget = self[name]
|
||||
return connect(widget, signal, callback, *args, **kwargs)
|
||||
|
||||
|
||||
def close_tab(self, tabid=None):
|
||||
if not tabid:
|
||||
tabid = self.active_tab.tabid
|
||||
|
||||
tab = self.tabdata[tabid]
|
||||
|
||||
def file_open(self):
|
||||
with self.app.db.session as s:
|
||||
tab_limit = s.get_config('closed_tabs_limit')
|
||||
s.remove('tabs', tabid=tabid)
|
||||
download_dir = s.get_config('download_dir')
|
||||
|
||||
if tab_limit:
|
||||
while len(self.closed) >= tab_limit:
|
||||
self.remove_closed_tab(list(self.closed.keys())[0])
|
||||
fc = FileChooser(self, download_dir, save=False)
|
||||
fc.new_filter('HTML Document', '*htm', '*.html', '*.xhtml', '*.mhtml')
|
||||
fc.new_filter('XML', '*.xml', '*.xslt')
|
||||
fc.new_filter('Text', '*.txt')
|
||||
path = fc.run_dialog()
|
||||
fc.destroy()
|
||||
|
||||
self.closed[tabid] = DotDict(
|
||||
state = tab.webview.get_session_state(),
|
||||
title = tab.title,
|
||||
url = tab.url
|
||||
)
|
||||
if not path:
|
||||
logging.verbose('Canceled web page download')
|
||||
return
|
||||
|
||||
self['tabs-closed-menu'].new_action_at_pos(2, tabid, (tab.title or 'Untitled'), self.reopen_tab, tabid)
|
||||
self['tabs-closed-menu'][tabid].set_tooltip_text(tab.url)
|
||||
|
||||
if len(self.tabdata) > 1 and self.active_tab == tab:
|
||||
new_tabid = self.taborder[self.taborder.index(tabid)+1]
|
||||
|
||||
try:
|
||||
self.active_tab = new_tabid
|
||||
except KeyError:
|
||||
logging.debug('window.close_tab: Cannot find tabid:', new_tabid)
|
||||
|
||||
tab.webview.destroy()
|
||||
tab.destroy()
|
||||
del self.tabdata[tabid]
|
||||
self.taborder.remove(tabid)
|
||||
|
||||
if self['tabs'].get_current_page() < 0:
|
||||
self.new_tab()
|
||||
self.new_tab(path, switch=True)
|
||||
|
||||
|
||||
def fullscreen_toggle(self):
|
||||
self.set_fullscreen(not self.is_fullscreen)
|
||||
def fullscreen_set(self, is_fullscreen=None):
|
||||
if is_fullscreen == None:
|
||||
is_fullscreen = not self.is_fullscreen
|
||||
|
||||
|
||||
def new_tab(self, url=None, row=None, switch=False):
|
||||
if type(row) == str:
|
||||
row = None
|
||||
|
||||
if not row:
|
||||
while (tabid := random_str()) in self.tabdata:
|
||||
continue
|
||||
if is_fullscreen:
|
||||
self.fullscreen()
|
||||
|
||||
else:
|
||||
tabid = row.tabid
|
||||
self.unfullscreen()
|
||||
|
||||
tab = Webview(window=self, starturl=url, row=row, tabid=tabid)
|
||||
|
||||
## Re-initialize a saved webview and switch if it was the active tab
|
||||
with self.app.db.session as s:
|
||||
if row:
|
||||
switch = row.active
|
||||
def library_open(self, page=LibraryPage.HOME):
|
||||
if not isinstance(page, LibraryPage):
|
||||
try:
|
||||
page = LibraryPage[page.upper()]
|
||||
|
||||
if not switch and not s.get_config('load_tabs'):
|
||||
tab.label.set_favicon('reload')
|
||||
except KeyError:
|
||||
raise KeyError(f'Invalid library page: {page}')
|
||||
|
||||
elif not url:
|
||||
tab.homepage()
|
||||
url = var.local + '/' + page.value
|
||||
|
||||
if not self.startup and s.get_config('tab_after_current'):
|
||||
page_num = self['tabs'].insert_page(tab, tab.label, self['tabs'].get_current_page()+1)
|
||||
#for tab in self.tabdata.values():
|
||||
#if tab.url.startswith(url):
|
||||
#return self.switch_tab(tab)
|
||||
|
||||
else:
|
||||
page_num = self['tabs'].append_page(tab, tab.label)
|
||||
|
||||
self.tabdata[tab.tabid] = tab
|
||||
self.taborder.append(tab.tabid)
|
||||
self['tabs'].set_tab_reorderable(tab, True)
|
||||
|
||||
#move this to tab.label
|
||||
self['tabs'].set_menu_label(tab, tab.label.menu_label)
|
||||
|
||||
if switch:
|
||||
self.active_tab = tab.tabid
|
||||
|
||||
if not self.startup:
|
||||
self.save_tabs()
|
||||
|
||||
logging.verbose('New tab:', tab.tabid, url if not row else 'from saved state')
|
||||
|
||||
return tab
|
||||
self.tab_new(url, switch=True)
|
||||
|
||||
|
||||
def notification(self, text, level='INFO', timeout=5, system=False):
|
||||
|
@ -249,114 +145,20 @@ class Window(Gtk.ApplicationWindow):
|
|||
logging.log(logging.LogLevel[level.upper()], text)
|
||||
|
||||
|
||||
def open_file(self):
|
||||
with self.app.db.session as s:
|
||||
download_dir = s.get_config('download_dir')
|
||||
def set_button_state(self, tabid=None):
|
||||
try:
|
||||
tab = self.active_tab if not tabid else self.tabdata[tabid]
|
||||
|
||||
fc = FileChooser(self, download_dir, save=False)
|
||||
fc.new_filter('HTML Document', '*htm', '*.html', '*.xhtml', '*.mhtml')
|
||||
fc.new_filter('XML', '*.xml', '*.xslt')
|
||||
fc.new_filter('Text', '*.txt')
|
||||
path = fc.run_dialog()
|
||||
fc.destroy()
|
||||
except KeyError:
|
||||
if not self.startup:
|
||||
logging.warning('window.set_button_state: Cannot find tab with id:', tabid)
|
||||
|
||||
if not path:
|
||||
logging.verbose('Canceled web page download')
|
||||
return
|
||||
|
||||
self.new_tab(path, switch=True)
|
||||
active = tab.url and not any(map(tab.url.startswith, [var.local, 'source']))
|
||||
|
||||
|
||||
def open_library(self, page=''):
|
||||
url = var.local + '/' + page
|
||||
|
||||
for tab in self.tabdata.values():
|
||||
if tab.url.startswith(url):
|
||||
return self.switch_tab(tab)
|
||||
|
||||
self.new_tab(url, switch=True)
|
||||
|
||||
|
||||
def remove_closed_tab(self, tabid):
|
||||
del self.closed[tabid]
|
||||
self['tabs-closed-menu'].remove_item(tabid)
|
||||
|
||||
|
||||
def reopen_tab(self, tabid):
|
||||
data = self.closed[tabid]
|
||||
tab = Webview(window=self, tabid=tabid)
|
||||
tab.webview.restore_session_state(data.state)
|
||||
tab.page_action('reload')
|
||||
|
||||
## Re-initialize a saved webview and switch if it was the active tab
|
||||
with self.app.db.session as s:
|
||||
if s.get_config('tab_after_current'):
|
||||
page_num = self['tabs'].insert_page(tab, tab.label, self['tabs'].get_current_page()+1)
|
||||
|
||||
else:
|
||||
page_num = self['tabs'].append_page(tab, tab.label)
|
||||
|
||||
self.tabdata[tab.tabid] = tab
|
||||
self.taborder.append(tab.tabid)
|
||||
self['tabs'].set_tab_reorderable(tab, True)
|
||||
|
||||
#move this to tab.label
|
||||
self['tabs'].set_menu_label(tab, tab.label.menu_label)
|
||||
|
||||
self.switch_tab(tab)
|
||||
self.save_tabs()
|
||||
|
||||
self.remove_closed_tab(tabid)
|
||||
|
||||
logging.verbose('Restored tab:', tab.tabid, data.url)
|
||||
|
||||
|
||||
def save_tabs(self):
|
||||
active_tab = self.active_tab
|
||||
active_tab_exists = False
|
||||
|
||||
## save tab state
|
||||
for tabid, tab in self.tabdata.items():
|
||||
if getattr(tab, 'webview', None) and tab.url not in [None, 'about:blank']:
|
||||
tab.state_store()
|
||||
|
||||
if active_tab == tab:
|
||||
active_tab_exists = True
|
||||
|
||||
with self.app.db.session as s:
|
||||
if not active_tab_exists:
|
||||
row = s.fetch('tabs').one()
|
||||
|
||||
if row:
|
||||
s.update_row(row, active=True)
|
||||
|
||||
## removed closed tabs from the database
|
||||
for row in s.fetch('tabs',):
|
||||
if row.tabid not in self.tabdata:
|
||||
s.remove_row(row)
|
||||
|
||||
|
||||
def set_button_state(self, tab):
|
||||
settings = self['statusbar-siteoptions']
|
||||
bookmark = self['statusbar-bookmark']
|
||||
password = self['statusbar-logins']
|
||||
|
||||
self.set_navbar_url(tab.url)
|
||||
self['navbar-stop'].set_sensitive(not tab.idle)
|
||||
self['navbar-refresh'].set_sensitive(tab.idle)
|
||||
|
||||
for button, action in {'navbar-prev': tab.webview.can_go_back(), 'navbar-next': tab.webview.can_go_forward()}.items():
|
||||
self[button].set_sensitive(action)
|
||||
|
||||
if tab.url and not any(map(tab.url.startswith, [var.local, 'source'])):
|
||||
settings.set_sensitive(True)
|
||||
bookmark.set_sensitive(True)
|
||||
password.set_sensitive(True)
|
||||
|
||||
else:
|
||||
settings.set_sensitive(False)
|
||||
bookmark.set_sensitive(False)
|
||||
password.set_sensitive(False)
|
||||
for widget_name in ['siteoptions', 'bookmark', 'logins']:
|
||||
self[f'statusbar-{widget_name}'].set_sensitive(active)
|
||||
|
||||
widgets = ['reply', 'boost', 'favorite']
|
||||
|
||||
|
@ -366,26 +168,160 @@ class Window(Gtk.ApplicationWindow):
|
|||
self['statusbar-toot'].set_sensitive(acct_count > 0)
|
||||
|
||||
for widget in widgets:
|
||||
self[f'statusbar-{widget}'].set_sensitive(tab.fedi_post and acct_count > 0)
|
||||
self[f'statusbar-{widget}'].set_sensitive(tab._data.post and acct_count > 0)
|
||||
|
||||
|
||||
def set_navbar_url(self, url):
|
||||
navurl = self['navbar-url']
|
||||
def tab_close(self, tabid=None):
|
||||
tab = self.active_tab if not tabid else self.tabdata[tabid]
|
||||
|
||||
if not navurl.has_focus():
|
||||
navurl.set_text(url or '')
|
||||
with self.app.db.session as s:
|
||||
tab_limit = s.get_config('closed_tabs_limit')
|
||||
s.remove('tabs', tabid=tab.id)
|
||||
|
||||
if tab_limit:
|
||||
while len(self.closed) >= tab_limit:
|
||||
self.remove_closed_tab(list(self.closed.keys())[0])
|
||||
|
||||
self.closed[tab.id] = tab.state_unload()
|
||||
|
||||
self['tabs-closed-menu'].new_action_at_pos(2, tab.id, (tab.title or 'Untitled'), self.tab_reopen, tab.id)
|
||||
self['tabs-closed-menu'][tab.id].set_tooltip_text(tab.url)
|
||||
|
||||
if len(self.tabdata) > 1 and self.active_tab == tab:
|
||||
new_tabid = self.taborder[self.taborder.index(tab.id)+1]
|
||||
|
||||
try:
|
||||
self.tab_switch(new_tabid)
|
||||
|
||||
except KeyError:
|
||||
logging.warning('window.close_tab: Cannot find tabid:', new_tabid)
|
||||
|
||||
tab.destroy()
|
||||
del self.tabdata[tabid]
|
||||
self.taborder.remove(tabid)
|
||||
|
||||
if not self['tabs'].get_n_pages():
|
||||
self.new_tab()
|
||||
|
||||
|
||||
def set_fullscreen(self, isfull):
|
||||
if isfull:
|
||||
self.fullscreen()
|
||||
def tab_new(self, url=None, row=None, switch=False):
|
||||
if type(row) == str:
|
||||
logging.warning(f'window.tab_new: row set to a string: {repr(row)}')
|
||||
row = None
|
||||
|
||||
else:
|
||||
self.unfullscreen()
|
||||
with self.db.session as s:
|
||||
if url == None:
|
||||
url = s.get_config('homepage')
|
||||
|
||||
if isinstance(row, WebviewState):
|
||||
tab = WebTab.new_from_state(row, s.get_config('load_tabs'))
|
||||
|
||||
elif isinstance(row, Row):
|
||||
tab = WebTab.new_from_row(row, s.get_config('load_tabs'))
|
||||
|
||||
else:
|
||||
tab = WebTab(url)
|
||||
|
||||
self['tabs'].append_page_menu(tab, tab['label'], tab['menu'])
|
||||
|
||||
if not self.startup and s.get_config('tab_after_current'):
|
||||
self['tabs'].reorder_child(tab, self['tabs'].get_current_page() + 1)
|
||||
|
||||
self.tabdata[tab.id] = tab
|
||||
self.taborder.append(tab.id)
|
||||
|
||||
self['tabs'].set_tab_reorderable(tab, True)
|
||||
|
||||
if switch:
|
||||
self.tab_switch(tab.id)
|
||||
|
||||
if not self.startup:
|
||||
self.tabs_save()
|
||||
|
||||
logging.verbose('New tab:', tab.id, url if not row else 'from saved state')
|
||||
|
||||
return tab
|
||||
|
||||
|
||||
def switch_tab(self, tab):
|
||||
self.active_tab = tab.tabid
|
||||
def tab_new_id(self):
|
||||
while (tabid := random_str()) in self.tabdata:
|
||||
continue
|
||||
|
||||
return tabid
|
||||
|
||||
|
||||
def tab_remove_closed(self, tabid):
|
||||
del self.closed[tabid]
|
||||
self['tabs-closed-menu'].remove_item(tabid)
|
||||
|
||||
|
||||
def tab_reopen(self, tabid):
|
||||
data = self.closed[tabid]
|
||||
tab = WebTab.new_from_state(data, True)
|
||||
|
||||
## Re-initialize a saved webview and switch if it was the active tab
|
||||
with self.app.db.session as s:
|
||||
self['tabs'].append_page_menu(tab, tab['label'], tab['menu'])
|
||||
|
||||
if s.get_config('tab_after_current'):
|
||||
self['tabs'].reorder_child(tab, self['tabs'].get_current_page() + 1)
|
||||
|
||||
self.tabdata[tab.id] = tab
|
||||
self.taborder.append(tab.id)
|
||||
self['tabs'].set_tab_reorderable(tab, True)
|
||||
|
||||
self.tab_switch(tab)
|
||||
self.tabs_save()
|
||||
|
||||
self.tab_remove_closed(tabid)
|
||||
|
||||
logging.verbose('Restored tab:', tab.id, data.url)
|
||||
|
||||
|
||||
def tab_switch(self, tabid):
|
||||
if isinstance(tabid, WebTab):
|
||||
tabid = tabid.id
|
||||
|
||||
page_num = self['tabs'].page_num(self.tabdata[tabid])
|
||||
self['tabs'].set_current_page(page_num)
|
||||
|
||||
|
||||
def tabs_save(self):
|
||||
active_tab_exists = False
|
||||
|
||||
with self.db.session as s:
|
||||
for tab in self.tabdata.values():
|
||||
state = tab.get_state()
|
||||
index = self['tabs'].page_num(tab)
|
||||
active = self['tabs'].get_current_page() == index
|
||||
|
||||
if index == -1:
|
||||
logging.debug('tabs_save: failed to get tab index', tab.id)
|
||||
index = 0
|
||||
|
||||
if active:
|
||||
active_tab_exists = True
|
||||
|
||||
with self.db.session as s:
|
||||
if state.url in [None, 'about:blank']:
|
||||
continue
|
||||
|
||||
s.put_tab(
|
||||
tab.id,
|
||||
state.title,
|
||||
state.url,
|
||||
state.to_bytes(),
|
||||
active,
|
||||
index
|
||||
)
|
||||
|
||||
for idx, row in enumerate(s.fetch('tabs')):
|
||||
if row.tabid not in self.tabdata:
|
||||
s.remove_row(row)
|
||||
|
||||
if not active_tab_exists:
|
||||
if (first_tab := s.fetch('tabs').one()):
|
||||
s.update_row(first_tab, active=True)
|
||||
|
||||
|
||||
def window_location_check(self, location):
|
||||
|
@ -421,68 +357,6 @@ class Window(Gtk.ApplicationWindow):
|
|||
return width, height
|
||||
|
||||
|
||||
def handle_button(self, name, arg=None):
|
||||
tab = self.active_tab
|
||||
|
||||
if arg == 'menu':
|
||||
self['navbar-menu-popover'].popdown()
|
||||
|
||||
if name == 'library':
|
||||
self.new_tab(var.local, switch=True)
|
||||
|
||||
elif name == 'about':
|
||||
self.open_library('help')
|
||||
|
||||
elif name == 'fullscreen':
|
||||
self.fullscreen_toggle()
|
||||
|
||||
elif name == 'newtab':
|
||||
self.new_tab(switch=True)
|
||||
|
||||
elif name == 'reply':
|
||||
pass
|
||||
|
||||
elif name == 'boost':
|
||||
pass
|
||||
|
||||
elif name == 'favorite':
|
||||
pass
|
||||
|
||||
elif name == 'quit':
|
||||
self.app.quit()
|
||||
|
||||
elif tab and tab.webview:
|
||||
if name == 'go':
|
||||
tab.load_url(self['navbar-url'].get_text())
|
||||
|
||||
elif name == 'prev':
|
||||
tab.page_action('back')
|
||||
|
||||
elif name == 'next':
|
||||
tab.page_action('forward')
|
||||
|
||||
elif name == 'stop':
|
||||
tab.page_action('stop')
|
||||
|
||||
elif name == 'refresh':
|
||||
tab.page_action('reload')
|
||||
|
||||
elif name == 'search':
|
||||
tab.search_toggle()
|
||||
|
||||
elif name == 'home':
|
||||
tab.homepage()
|
||||
|
||||
else:
|
||||
logging.verbose('window.handle_button: Button not handled:', name)
|
||||
|
||||
if name == 'search':
|
||||
tab.search.items.text.grab_focus()
|
||||
|
||||
else:
|
||||
tab.webview.grab_focus()
|
||||
|
||||
|
||||
def handle_clear_closed_tabs(self):
|
||||
for tabid in list(self.closed.keys()):
|
||||
del self.closed[tabid]
|
||||
|
@ -493,64 +367,77 @@ class Window(Gtk.ApplicationWindow):
|
|||
self['tabs-closed-menu']['clear'].set_sensitive(not len(self.closed) == 0)
|
||||
|
||||
|
||||
def handle_page_switch(self, notebook, tab, pagenum):
|
||||
with self.app.db.session as s:
|
||||
if s.get_config('load_switch') and not self.startup and tab.webview and tab.state:
|
||||
tab.page_action('reload')
|
||||
def handle_menu_button(self, name, arg=None):
|
||||
tab = self.active_tab
|
||||
|
||||
if tab.tabid in self.taborder:
|
||||
self.taborder.remove(tab.tabid)
|
||||
if name == 'new_tab':
|
||||
with self.db.session as s:
|
||||
self.tab_new(s.get_config('homepage'), switch=True)
|
||||
|
||||
self.taborder.insert(0, tab.tabid)
|
||||
self.set_button_state(tab)
|
||||
elif name == 'search':
|
||||
self.active_tab.search_action('open')
|
||||
|
||||
progress = tab.webview.get_estimated_load_progress()
|
||||
self['navbar-url'].set_progress_fraction(0 if progress == 1.0 else progress)
|
||||
elif name == 'fullscreen':
|
||||
self.fullscreen_set()
|
||||
|
||||
elif name == 'library':
|
||||
self.library_open()
|
||||
|
||||
def handle_url_keys(self, widget, event):
|
||||
if event.keyval == Gdk.KEY_Escape:
|
||||
widget.select_region(0,0)
|
||||
widget.set_text(self.active_tab.url or '')
|
||||
return self.active_tab.webview.grab_focus()
|
||||
elif name == 'about':
|
||||
self.library_open('help')
|
||||
|
||||
elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter]:
|
||||
return self.handle_button('go')
|
||||
|
||||
with self.app.db.session as s:
|
||||
if not s.get_config('enable_autocomplete'):
|
||||
return
|
||||
|
||||
if not self.autocomplete_timer:
|
||||
self.autocomplete_timer = AutocompleteTimeout()
|
||||
elif name == 'quit':
|
||||
self.app.quit()
|
||||
|
||||
else:
|
||||
self.autocomplete_timer.refresh()
|
||||
raise KeyError('window.handle_button: Button not handled:', name)
|
||||
|
||||
self['tabs-menu-popover'].popdown()
|
||||
|
||||
|
||||
def handle_url_popup(self, entry, menu):
|
||||
if not self.clipboard.wait_is_text_available():
|
||||
return
|
||||
|
||||
item = Gtk.MenuItem.new_with_label('Paste and go')
|
||||
item.connect('activate', self.handle_url_popup_go)
|
||||
item.show()
|
||||
|
||||
menu.insert(item, 3)
|
||||
|
||||
return menu
|
||||
def handle_menubar_file_action(self, action):
|
||||
self.active_tab.page_action(action)
|
||||
|
||||
|
||||
def handle_url_popup_go(self, item):
|
||||
self['navbar-url'].set_text(self.clipboard.wait_for_text())
|
||||
self.handle_button('go')
|
||||
def handle_menubar_edit_action(self, action):
|
||||
tab = self.active_tab
|
||||
|
||||
if action == 'unselect':
|
||||
tab.run_js(Javascript.Deselect)
|
||||
|
||||
elif action == 'delete':
|
||||
tab.run_js(Javascript.DeleteSelected)
|
||||
|
||||
elif action == 'inspect':
|
||||
tab.inspector_toggle()
|
||||
|
||||
elif action == 'source':
|
||||
tab.page_action('source')
|
||||
|
||||
else:
|
||||
tab.editing_action(action)
|
||||
|
||||
|
||||
def handle_page_switch(self, notebook, tab, pagenum):
|
||||
with self.app.db.session as s:
|
||||
if tab._data.state and not self.startup and s.get_config('load_switch'):
|
||||
tab.page_action('refresh')
|
||||
|
||||
if tab.id in self.taborder:
|
||||
self.taborder.remove(tab.id)
|
||||
|
||||
self.taborder.insert(0, tab.id)
|
||||
self.set_button_state(tab.id)
|
||||
|
||||
#progress = tab.webview.get_estimated_load_progress()
|
||||
#self['navbar-url'].set_progress_fraction(0 if progress == 1.0 else progress)
|
||||
|
||||
|
||||
## save tab urls and titles on close to reopen them on startup
|
||||
def handle_window_close(self, *args):
|
||||
logging.verbose('Saving data')
|
||||
|
||||
self.save_tabs()
|
||||
self.tabs_save()
|
||||
|
||||
with self.app.db.session as s:
|
||||
s.put_config('maximized', self.is_maximized())
|
||||
|
@ -571,14 +458,8 @@ class Window(Gtk.ApplicationWindow):
|
|||
|
||||
|
||||
def set_icons(self):
|
||||
## navigation bar
|
||||
icon_set(self['navbar-prev-icon'], 'previous', 24)
|
||||
icon_set(self['navbar-next-icon'], 'next', 24)
|
||||
icon_set(self['navbar-stop-icon'], 'stop', 24)
|
||||
icon_set(self['navbar-refresh-icon'], 'refresh', 24)
|
||||
icon_set(self['navbar-home-icon'], 'home', 24)
|
||||
icon_set(self['navbar-go-icon'], 'go', 24)
|
||||
icon_set(self['navbar-menu-icon'], 'menu', 24)
|
||||
## tab bar
|
||||
self.set_icon_from_resource('navbar-menu-icon', 'menu.svg', 24)
|
||||
|
||||
## status bar
|
||||
icon_set(self['statusbar-debug-icon'], 'debug', 16)
|
||||
|
@ -595,69 +476,41 @@ class Window(Gtk.ApplicationWindow):
|
|||
|
||||
|
||||
def setup_signals(self):
|
||||
signals = DotDict({
|
||||
'notification-close': [
|
||||
{'signal': 'clicked', 'callback': self['notification'].set_reveal_child, 'args': [False]}
|
||||
],
|
||||
'navbar-menu-newtab': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['newtab']}
|
||||
],
|
||||
'navbar-menu-search': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['search']}
|
||||
],
|
||||
'navbar-menu-fullscreen': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['fullscreen']}
|
||||
],
|
||||
'navbar-menu-library': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['library']}
|
||||
],
|
||||
'navbar-menu-about': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['about']}
|
||||
],
|
||||
'navbar-menu-quit': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['quit']}
|
||||
],
|
||||
'navbar-prev': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['prev']}
|
||||
],
|
||||
'navbar-next': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['next']}
|
||||
],
|
||||
'navbar-stop': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['stop']}
|
||||
],
|
||||
'navbar-refresh': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['refresh']}
|
||||
],
|
||||
'navbar-home': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['home']}
|
||||
],
|
||||
'navbar-url': [
|
||||
{'signal': 'key-press-event', 'callback': self.handle_url_keys, 'kwargs': {'original_args': True}},
|
||||
{'signal': 'populate-popup', 'callback': self.handle_url_popup, 'kwargs': {'original_args': True}}
|
||||
],
|
||||
'navbar-go': [
|
||||
{'signal': 'clicked', 'callback': self.handle_button, 'args': ['go']}
|
||||
],
|
||||
#'navbar-menu': {'signal': 'clicked', 'callback': self.handle_button},
|
||||
'tabs': [
|
||||
{'signal': 'switch-page', 'callback': self.handle_page_switch, 'kwargs': {'original_args': True}}
|
||||
],
|
||||
'tabs-new': [
|
||||
{'signal': 'clicked', 'callback': self.new_tab, 'kwargs': {'switch': True}}
|
||||
],
|
||||
'tabs-closed-menu': [
|
||||
{'signal': 'show', 'callback': self.handle_closed_tabs_show}
|
||||
]
|
||||
})
|
||||
## Misc
|
||||
self.connect('notification-close', 'clicked', self['notification'].set_reveal_child, False)
|
||||
|
||||
for name, sigs in signals.items():
|
||||
for data in sigs:
|
||||
data = DotDict(data)
|
||||
if name.startswith('menu'):
|
||||
data.args.append('menu')
|
||||
## Tab bar
|
||||
self.connect('tabs', 'switch-page', self.handle_page_switch, original_args=True)
|
||||
self.connect('tabs-new', 'clicked', self.tab_new, switch=True)
|
||||
self.connect('tabs-closed-menu', 'show', self.handle_closed_tabs_show)
|
||||
|
||||
self.Connect(name, data.signal, data.callback, *data.get('args', []), **data.get('kwargs', {}))
|
||||
## Tab bar menu
|
||||
self.connect('tabs-menu-new_tab', 'clicked', self.handle_menu_button, 'new_tab')
|
||||
self.connect('tabs-menu-search', 'clicked', self.handle_menu_button, 'search')
|
||||
self.connect('tabs-menu-fullscreen', 'clicked', self.handle_menu_button, 'fullscreen')
|
||||
self.connect('tabs-menu-library', 'clicked', self.handle_menu_button, 'library')
|
||||
self.connect('tabs-menu-about', 'clicked', self.handle_menu_button, 'about')
|
||||
self.connect('tabs-menu-quit', 'clicked', self.handle_menu_button, 'quit')
|
||||
|
||||
## File menu
|
||||
self.connect('menubar-file-new_tab', 'activate', self.tab_new, switch=True)
|
||||
self.connect('menubar-file-close_tab', 'activate', self.tab_close)
|
||||
self.connect('menubar-file-open', 'activate', self.file_open)
|
||||
self.connect('menubar-file-save', 'activate', self.handle_menubar_file_action, 'save')
|
||||
self.connect('menubar-file-print', 'activate', self.handle_menubar_file_action, 'print')
|
||||
self.connect('menubar-file-quit', 'activate', self.app.quit)
|
||||
|
||||
## Edit menu
|
||||
for name in ['cut', 'copy', 'paste', 'delete', 'undo', 'redo', 'select', 'unselect', 'source', 'inspect']:
|
||||
self.connect(f'menubar-edit-{name}', 'activate', self.handle_menubar_edit_action, name)
|
||||
|
||||
## Library menu
|
||||
for name in ['bookmarks', 'downloads', 'history', 'passwords', 'search', 'fediverse', 'extensions', 'preferences']:
|
||||
self.connect(f'menubar-library-{name}', 'activate', self.library_open, name)
|
||||
|
||||
## Help menu
|
||||
self.connect('menubar-help-usage', 'activate', self.library_open, 'help')
|
||||
self.connect('menubar-help-about', 'activate', self.library_open, 'help')
|
||||
|
||||
|
||||
def setup_widgets(self):
|
||||
|
@ -671,9 +524,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
self.add(self['main-overlay'])
|
||||
|
||||
self.themes = Themes(self)
|
||||
self.menubar = MenuBar(self)
|
||||
self.statusbar = StatusBar(self)
|
||||
self.context = WebContext(self)
|
||||
|
||||
self.set_icons()
|
||||
|
||||
|
@ -724,8 +575,6 @@ class AutocompleteTimeout(threading.Thread):
|
|||
if self.current_time <= 0:
|
||||
break
|
||||
|
||||
print('loop', self.current_time)
|
||||
|
||||
text = self.url_entry.get_text()
|
||||
|
||||
with self.app.db.session as s:
|
||||
|
|
78
barkshark_web/enums.py
Normal file
78
barkshark_web/enums.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class EditAction(Enum):
|
||||
COPY = WebKit2.EDITING_COMMAND_COPY
|
||||
LINK = WebKit2.EDITING_COMMAND_CREATE_LINK
|
||||
CUT = WebKit2.EDITING_COMMAND_CUT
|
||||
IMAGE = WebKit2.EDITING_COMMAND_INSERT_IMAGE
|
||||
PASTE = WebKit2.EDITING_COMMAND_PASTE
|
||||
PLAIN = WebKit2.EDITING_COMMAND_PASTE_AS_PLAIN_TEXT
|
||||
REDO = WebKit2.EDITING_COMMAND_REDO
|
||||
SELECT = WebKit2.EDITING_COMMAND_SELECT_ALL
|
||||
UNDO = WebKit2.EDITING_COMMAND_UNDO
|
||||
|
||||
|
||||
class Javascript(Enum):
|
||||
SELECTION = 'window.getSelection().toString()'
|
||||
EXEC = 'window.execCommand("{}")'
|
||||
PRINT = 'window.print()'
|
||||
DESELECT = 'document.getSelection().removeAllRanges()'
|
||||
DELETE = 'document.activeElement.setRangeText("")'
|
||||
|
||||
|
||||
class LibraryPage(Enum):
|
||||
HOME = ''
|
||||
BOOKMARKS = 'bookmarks'
|
||||
DOWNLOADS = 'downloads'
|
||||
HISTORY = 'history'
|
||||
PASSWORDS = 'passwords'
|
||||
SEARCH = 'search'
|
||||
FEDIVERSE = 'fediverse'
|
||||
EXTENSIONS = 'extensions'
|
||||
PREFERENCES = 'preferences'
|
||||
HELP = 'help'
|
||||
|
||||
|
||||
class WebviewContextActions(Enum):
|
||||
AUDIO_COPY = WebKit2.ContextMenuAction.COPY_AUDIO_LINK_TO_CLIPBOARD
|
||||
AUDIO_DOWNLOAD = WebKit2.ContextMenuAction.DOWNLOAD_AUDIO_TO_DISK
|
||||
FRAME_OPEN = WebKit2.ContextMenuAction.OPEN_FRAME_IN_NEW_WINDOW
|
||||
AUDIO_TAB = WebKit2.ContextMenuAction.OPEN_AUDIO_IN_NEW_WINDOW
|
||||
GO_BACK = WebKit2.ContextMenuAction.GO_BACK
|
||||
GO_FORWARD = WebKit2.ContextMenuAction.GO_FORWARD
|
||||
GO_RELOAD = WebKit2.ContextMenuAction.RELOAD
|
||||
GO_STOP = WebKit2.ContextMenuAction.STOP
|
||||
IMAGE_COPY = WebKit2.ContextMenuAction.COPY_IMAGE_URL_TO_CLIPBOARD
|
||||
IMAGE_COPY_FULL = WebKit2.ContextMenuAction.COPY_IMAGE_TO_CLIPBOARD
|
||||
IMAGE_DOWNLOAD = WebKit2.ContextMenuAction.DOWNLOAD_IMAGE_TO_DISK
|
||||
IMAGE_TAB = WebKit2.ContextMenuAction.OPEN_IMAGE_IN_NEW_WINDOW
|
||||
INSPECT = WebKit2.ContextMenuAction.INSPECT_ELEMENT
|
||||
LINK_COPY = WebKit2.ContextMenuAction.COPY_LINK_TO_CLIPBOARD
|
||||
LINK_DOWNLOAD = WebKit2.ContextMenuAction.DOWNLOAD_LINK_TO_DISK
|
||||
LINK_OPEN = WebKit2.ContextMenuAction.OPEN_LINK
|
||||
LINK_TAB = WebKit2.ContextMenuAction.OPEN_LINK_IN_NEW_WINDOW
|
||||
MEDIA_TOGGLE = WebKit2.ContextMenuAction.TOGGLE_MEDIA_CONTROLS
|
||||
MEDIA_MUTE = WebKit2.ContextMenuAction.MEDIA_MUTE
|
||||
MEDIA_LOOP = WebKit2.ContextMenuAction.TOGGLE_MEDIA_LOOP
|
||||
MEDIA_PAUSE = WebKit2.ContextMenuAction.MEDIA_PAUSE
|
||||
MEDIA_PLAY = WebKit2.ContextMenuAction.MEDIA_PLAY
|
||||
SPELL_ADD = WebKit2.ContextMenuAction.LEARN_SPELLING
|
||||
SPELL_GUESS = WebKit2.ContextMenuAction.SPELLING_GUESS
|
||||
SPELL_IGNORE_GRAMMAR = WebKit2.ContextMenuAction.IGNORE_GRAMMAR
|
||||
SPELL_IGNORE = WebKit2.ContextMenuAction.IGNORE_SPELLING
|
||||
SPELL_NO_GUESS = WebKit2.ContextMenuAction.NO_GUESSES_FOUND
|
||||
TEXT_BOLD = WebKit2.ContextMenuAction.BOLD
|
||||
TEXT_COPY = WebKit2.ContextMenuAction.COPY
|
||||
TEXT_CUT = WebKit2.ContextMenuAction.CUT
|
||||
TEXT_DELETE = WebKit2.ContextMenuAction.DELETE
|
||||
TEXT_EMOJI = WebKit2.ContextMenuAction.INSERT_EMOJI
|
||||
TEXT_ITALIC = WebKit2.ContextMenuAction.ITALIC
|
||||
TEXT_PASTE = WebKit2.ContextMenuAction.PASTE
|
||||
TEXT_PASTE_PLAIN = WebKit2.ContextMenuAction.PASTE_AS_PLAIN_TEXT
|
||||
TEXT_SELECT = WebKit2.ContextMenuAction.SELECT_ALL
|
||||
TEXT_UNDERLINE = WebKit2.ContextMenuAction.UNDERLINE
|
||||
VIDEO_COPY = WebKit2.ContextMenuAction.COPY_VIDEO_LINK_TO_CLIPBOARD
|
||||
VIDEO_DOWNLOAD = WebKit2.ContextMenuAction.DOWNLOAD_VIDEO_TO_DISK
|
||||
VIDEO_FULLSCREEN = WebKit2.ContextMenuAction.ENTER_VIDEO_FULLSCREEN
|
||||
VIDEO_TAB = WebKit2.ContextMenuAction.OPEN_VIDEO_IN_NEW_WINDOW
|
|
@ -1,4 +1,9 @@
|
|||
import multiprocessing, socket, threading, time, traceback
|
||||
import cairo
|
||||
import multiprocessing
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from ctypes import cdll, create_string_buffer, byref
|
||||
from izzylib.http_client import HttpClient
|
||||
|
@ -153,26 +158,283 @@ def set_proc_name(name='pyweb'):
|
|||
return ret
|
||||
|
||||
|
||||
def surface_to_pixbuf(surface, new_size):
|
||||
if not surface:
|
||||
return
|
||||
|
||||
width = surface.get_width()
|
||||
height = surface.get_height()
|
||||
def scale_pixbuf(pixbuf, new_size=16, keep='height'):
|
||||
width = pixbuf.get_width()
|
||||
height = pixbuf.get_height()
|
||||
|
||||
## (size ratio) * width * width to height ratio
|
||||
new_width = (new_size / width) * width * (width/height)
|
||||
new_width = (new_size / width) * width
|
||||
new_height = (new_size / height) * height
|
||||
|
||||
image = Gdk.pixbuf_get_from_surface(surface, 0, 0, width, height)
|
||||
image = image.scale_simple(new_width, new_height, GdkPixbuf.InterpType.BILINEAR)
|
||||
if keep == 'width':
|
||||
new_height *= height/width
|
||||
|
||||
return image.copy()
|
||||
elif keep == 'height':
|
||||
new_width *= width/height
|
||||
|
||||
return pixbuf.scale_simple(new_width, new_height, GdkPixbuf.InterpType.BILINEAR)
|
||||
|
||||
|
||||
def set_image(widget, image, size=16):
|
||||
try:
|
||||
image = new_pixbuf(image, size)
|
||||
widget.set_from_pixbuf(image)
|
||||
return widget
|
||||
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if isinstance(image, str):
|
||||
widget.set_from_icon_name(image, Gtk.IconSize.SMALL_TOOLBAR)
|
||||
widget.set_pixel_size(size)
|
||||
|
||||
else:
|
||||
raise TypeError('Image is not a Cairo Surface, Pixbuf, Path object, or an icon name (str)')
|
||||
|
||||
return widget
|
||||
|
||||
|
||||
def new_pixbuf(image, size=16):
|
||||
if isinstance(image, (cairo.Surface, cairo.ImageSurface)):
|
||||
image = Gdk.pixbuf_get_from_surface(image, 0, 0, image.get_width(), image.get_height())
|
||||
|
||||
elif isinstance(image, GdkPixbuf.Pixbuf):
|
||||
pass
|
||||
|
||||
elif isinstance(image, Path):
|
||||
return GdkPixbuf.Pixbuf.new_from_file_at_scale(image, -1, size, True)
|
||||
|
||||
else:
|
||||
raise TypeError('Image is not a Cairo Surface, Pixbuf, or Path object')
|
||||
|
||||
return scale_pixbuf(image, size)
|
||||
|
||||
|
||||
def surface_to_pixbuf(surface, new_size):
|
||||
logging.warning('DeprecationWarning: use new_pixbuf instead of surface_to_pixbuf')
|
||||
|
||||
return new_pixbuf(surface, new_size)
|
||||
|
||||
|
||||
class BuilderBase:
|
||||
def __init__(self, path):
|
||||
self._path = path
|
||||
self._builder = Gtk.Builder.new_from_file(path)
|
||||
self._connected_sigs = {}
|
||||
|
||||
|
||||
def __getitem__(self, name):
|
||||
if not (widget := self._builder.get_object(name)):
|
||||
raise KeyError(f'Widget with ID "{name}" does not exist.')
|
||||
|
||||
return widget
|
||||
|
||||
|
||||
def __setitem__(self, name, widget):
|
||||
try:
|
||||
self[name]
|
||||
raise KeyError(f'Widget ID already exists: {name}')
|
||||
|
||||
except KeyError:
|
||||
self._builder.expose_object(name, widget)
|
||||
|
||||
return widget
|
||||
|
||||
|
||||
def __delitem__(self, name):
|
||||
self[name].destroy()
|
||||
|
||||
|
||||
def __del__(self):
|
||||
self.destroy()
|
||||
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return Gio.Application.get_default()
|
||||
|
||||
|
||||
@property
|
||||
def db(self):
|
||||
return self.app.db
|
||||
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path
|
||||
|
||||
|
||||
def block_signals(self, *names):
|
||||
signals = {name: self.get_signals(name) for name in names}
|
||||
|
||||
return SignalBlock(self, signals)
|
||||
|
||||
|
||||
def connect(self, name, signal, callback, *args, **kwargs):
|
||||
widget = self[name]
|
||||
|
||||
if not self._connected_sigs.get(name):
|
||||
self._connected_sigs[name] = {}
|
||||
|
||||
if signal in self._connected_sigs[name]:
|
||||
raise KeyError(f'Signal for "{name}" already connected: {signal}')
|
||||
|
||||
self._connected_sigs[name][signal] = connect(widget, signal, callback, *args, **kwargs)
|
||||
return self._connected_sigs[name][signal]
|
||||
|
||||
|
||||
def disconnect(self, name, signal):
|
||||
try:
|
||||
sigid = self._connected_sigs[name].pop(signal)
|
||||
self[name].disconnect(sigid)
|
||||
|
||||
except KeyError:
|
||||
raise KeyError(f'Signal for "{name}" not connected: {signal}')
|
||||
|
||||
|
||||
def destroy(self):
|
||||
for widget in self._builder.get_objects():
|
||||
try: widget.destroy()
|
||||
except: pass
|
||||
|
||||
self._connected_sigs = {}
|
||||
|
||||
|
||||
def get_signals(self, name):
|
||||
return self._connected_sigs[name]
|
||||
|
||||
|
||||
def reload(self):
|
||||
self.destroy(False)
|
||||
self._builder.add_from_file(self.path)
|
||||
|
||||
|
||||
def set_icon(self, name, obj, size=16):
|
||||
widget = self[name]
|
||||
|
||||
if not isinstance(widget, Gtk.Image):
|
||||
raise TypeError(f'Widget is not a Gtk.Image object: {name}')
|
||||
|
||||
if isinstance(obj, cairo.Surface):
|
||||
image = surface_to_pixbuf(obj, size)
|
||||
widget.set_from_pixbuf(image)
|
||||
|
||||
elif isinstance(obj, GdkPixbuf.Pixbuf):
|
||||
image = scale_pixbuf(obj, size)
|
||||
widget.set_from_pixbuf(image)
|
||||
|
||||
elif isinstance(obj, Path):
|
||||
image = GdkPixbuf.Pixbuf.new_from_file_at_scale(obj, -1, size, True)
|
||||
widget.set_from_pixbuf(image)
|
||||
|
||||
elif isinstance(obj, str):
|
||||
widget.set_from_icon_name(obj, Gtk.IconSize.SMALL_TOOLBAR)
|
||||
widget.set_pixel_size(size)
|
||||
|
||||
else:
|
||||
raise TypeError(f'Invalid icon type for "{name}": {class_name(obj)}')
|
||||
|
||||
|
||||
def set_icon_from_resource(self, name, filename, size=16):
|
||||
path = self.app.path.resources.join('icons').join(f'{filename}')
|
||||
self.set_icon(name, path, size)
|
||||
|
||||
|
||||
class ComponentBase:
|
||||
signals = {}
|
||||
|
||||
def __init__(self, prefix=None):
|
||||
self._prefix = prefix
|
||||
self._app = None
|
||||
self._db = None
|
||||
self._window = None
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
key = f'{self._prefix}-{key}' if self._prefix else key
|
||||
return self.window[key]
|
||||
|
||||
|
||||
def __setitem__(self, key, widget):
|
||||
key = f'{self._prefix}-{key}' if self._prefix else key
|
||||
self.window[key] = widget
|
||||
|
||||
|
||||
def __delitem__(self, key):
|
||||
key = f'{self._prefix}-{key}' if self._prefix else key
|
||||
del self.window[key]
|
||||
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return self._app or Gio.Application.get_default()
|
||||
|
||||
|
||||
@app.setter
|
||||
def app(self, value):
|
||||
self._app = value
|
||||
|
||||
|
||||
@property
|
||||
def db(self):
|
||||
return self._db or self.app.db
|
||||
|
||||
|
||||
@db.setter
|
||||
def db(self, value):
|
||||
self._db = value
|
||||
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
return self._window or self.app.window
|
||||
|
||||
|
||||
@window.setter
|
||||
def window(self, value):
|
||||
self._window = value
|
||||
|
||||
|
||||
def block_signals(self, *names):
|
||||
return BuilderBase.block_signals(self, *names)
|
||||
|
||||
|
||||
def connect(self, name, signal, callback, *args, **kwargs):
|
||||
name = f'{self._prefix}-{name}' if self._prefix else name
|
||||
return self.window.connect(name, signal, callback, *args, **kwargs)
|
||||
|
||||
|
||||
def disconnect(self, name, signal):
|
||||
name = f'{self._prefix}-{name}' if self._prefix else name
|
||||
return self.window.disconnect(name, signal)
|
||||
|
||||
|
||||
def get_signals(self, name):
|
||||
return self._window._connected_sigs[f'{self._prefix}-{name}']
|
||||
|
||||
|
||||
def set_icon(self, name, obj, size=16):
|
||||
BuilderBase.set_icon(self, name, obj, size)
|
||||
|
||||
|
||||
def set_icon_from_resource(self, name, filename, size=16):
|
||||
Builder.set_icon_from_resource(self, name, filename, size)
|
||||
|
||||
|
||||
def setup_signals(self):
|
||||
for name, sigs in self.signals.items():
|
||||
for data in sigs:
|
||||
data = DotDict(data)
|
||||
if name.startswith('menu'):
|
||||
data.args.append('menu')
|
||||
|
||||
self.connect(name, data.signal, data.callback, *data.get('args', []), **data.get('kwargs', {}))
|
||||
|
||||
|
||||
class SignalBlock:
|
||||
def __init__(self, signal_pairs):
|
||||
self.widgets = signal_pairs
|
||||
def __init__(self, parent, signals):
|
||||
self.parent = parent
|
||||
self.signals = signals
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
|
@ -184,12 +446,13 @@ class SignalBlock:
|
|||
|
||||
|
||||
def set_block(self, block):
|
||||
for signal, widget in self.widgets:
|
||||
if block:
|
||||
widget.handler_block(signal)
|
||||
for name, signals in self.signals.items():
|
||||
for signame, sigid in signals.items():
|
||||
if block:
|
||||
self.parent[name].handler_block(sigid)
|
||||
|
||||
else:
|
||||
widget.handler_unblock(signal)
|
||||
else:
|
||||
self.parent[name].handler_unblock(sigid)
|
||||
|
||||
|
||||
class Thread(threading.Thread):
|
||||
|
|
|
@ -17,16 +17,17 @@
|
|||
%img.image.grid-item src='{{var.local}}/icon/menu.svg' title='Toggle Text'
|
||||
%span.menu-item-text.grid-item style='display:none;' << Menu
|
||||
|
||||
=menu_item('Home', '/', 'home')
|
||||
=menu_item('Bookmarks', '/bookmarks', 'bookmark')
|
||||
=menu_item('Downloads', '/downloads', 'download')
|
||||
=menu_item('History', '/history', 'history')
|
||||
=menu_item('Passwords', '/passwords', 'password')
|
||||
=menu_item('Search', '/search', 'search')
|
||||
=menu_item('Fediverse', '/fediverse', 'toot')
|
||||
=menu_item('Extensions', '/extensions', 'extension')
|
||||
=menu_item('Preferences','/preferences', 'settings')
|
||||
=menu_item('Help', '/help', 'help')
|
||||
=menu_item('Home', '/', 'home.svg')
|
||||
=menu_item('Bookmarks', '/bookmarks', 'bookmark.svg')
|
||||
=menu_item('Downloads', '/downloads', 'download.svg')
|
||||
=menu_item('History', '/history', 'history.svg')
|
||||
=menu_item('Passwords', '/passwords', 'password.svg')
|
||||
=menu_item('Search', '/search', 'search.svg')
|
||||
=menu_item('Fediverse', '/fediverse', 'toot.svg')
|
||||
=menu_item('Extensions', '/extensions', 'extension.svg')
|
||||
=menu_item('Preferences','/preferences', 'settings.svg')
|
||||
=menu_item('Help', '/help', 'help.svg')
|
||||
=menu_item('About', '/about', 'app.png')
|
||||
|
||||
#content-body
|
||||
#header.section
|
||||
|
|
|
@ -22,6 +22,6 @@
|
|||
|
||||
-macro menu_item(label, path, icon):
|
||||
%a.menu-item.grid-container href='{{var.local}}{{path}}'
|
||||
%img.image.grid-item src='{{var.local}}/icon/{{icon}}.svg' title='{{label}}'
|
||||
%img.image.grid-item src='{{var.local}}/icon/{{icon}}' title='{{label}}'
|
||||
%span.menu-item-text.grid-item style='display:none;' -> =label
|
||||
|
||||
|
|
22
barkshark_web/localweb/page/about.haml
Normal file
22
barkshark_web/localweb/page/about.haml
Normal file
|
@ -0,0 +1,22 @@
|
|||
-set page = 'About'
|
||||
-extends 'base.haml'
|
||||
|
||||
-block meta
|
||||
%link rel='stylesheet' type='text/css' href='{{var.local}}/css/about.css'
|
||||
|
||||
-block content
|
||||
%center
|
||||
.info
|
||||
.title -> %a href='https://git.barkshark.xyz/izaliamae/barkshark-web' << Barkshark Web
|
||||
.version << Version {{version}}
|
||||
.icon -> %img src='{{var.local}}/icon/app.png' width='320' height='320'
|
||||
|
||||
.credits
|
||||
.title << Credits
|
||||
.icon
|
||||
Application icon by
|
||||
%a href='https://chitter.xyz/users/efi' << Efi@Chitter
|
||||
|
||||
.font
|
||||
%a href='https://github.com/googlefonts/NunitoSans' << Nunito Sans
|
||||
© Vernon Adams
|
|
@ -1,6 +1,13 @@
|
|||
from izzylib import ObjectBase
|
||||
from mastodon import Mastodon
|
||||
|
||||
from .widgets import Box
|
||||
from .functions import (
|
||||
TimeoutCallback,
|
||||
connect,
|
||||
get_app,
|
||||
run_in_gui_thread
|
||||
)
|
||||
|
||||
|
||||
class Account:
|
||||
def __init__(self, row):
|
||||
|
@ -190,3 +197,162 @@ class AccountRow(Box):
|
|||
def fetch_post(self, url):
|
||||
data = self.api.search_v2(url, resolve=True, result_type='statuses')['statuses']
|
||||
return data[0] if len(data) else None
|
||||
|
||||
|
||||
class LoginRowBase:
|
||||
def __getitem__(self, key):
|
||||
return self.ui.get_object(key)
|
||||
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return get_app()
|
||||
|
||||
|
||||
@property
|
||||
def db(self):
|
||||
return self.app.db
|
||||
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
return self.app.window
|
||||
|
||||
|
||||
def connect(self, name, signal, callback, *args, original_args=False, **kwargs):
|
||||
if original_args:
|
||||
connect(self[name], signal, lambda *origargs: callback(*origargs, *args, **kwargs))
|
||||
|
||||
else:
|
||||
connect(self[name], signal, lambda *origargs: callback(*args, **kwargs))
|
||||
|
||||
|
||||
class SavedLoginRow(LoginRowBase):
|
||||
def __init__(self, row, page_url):
|
||||
self.ui = Gtk.Builder.new_from_file(self.app.path.resources.join('password_saved.ui'))
|
||||
self.row = row
|
||||
|
||||
self['username'].set_text(self.row['username'])
|
||||
|
||||
self.connect('fill', 'clicked', self.handle_fill_password)
|
||||
self.connect('copy-password', 'clicked', self.handle_copy_password)
|
||||
self.connect('copy-username', 'clicked', self.row.copy_username)
|
||||
|
||||
self['container'].show_all()
|
||||
|
||||
|
||||
@property
|
||||
def tab(self):
|
||||
return self.window.active_tab
|
||||
|
||||
|
||||
def handle_copy_password(self):
|
||||
self.row.copy_password(60)
|
||||
self.window.notification('Copied password to clipboard for 5 seconds')
|
||||
|
||||
self.window['statusbar-logins-popover'].popdown()
|
||||
|
||||
|
||||
def handle_fill_password(self):
|
||||
with dirs.resources.join('ext_js/autofill.js').open() as fd:
|
||||
self.tab.run_js(fd.read().replace('USERNAME_VALUE', self.row.username).replace('PASSWORD_VALUE', self.row.password))
|
||||
|
||||
self.window['statusbar-logins-popover'].popdown()
|
||||
|
||||
|
||||
class UnsavedLoginRow(LoginRowBase):
|
||||
def __init__(self, ext, form):
|
||||
self.ui = Gtk.Builder.new_from_file(self.app.path.resources.join('password_unsaved.ui'))
|
||||
self.ext = ext
|
||||
self.form = form
|
||||
|
||||
self.set_data(form.username[1], form.password[1])
|
||||
|
||||
self.connect('save', 'clicked', self.handle_save)
|
||||
self.connect('cancel', 'clicked', self.handle_cancel)
|
||||
self.connect('password', 'icon-press', self.handle_pass_visibility)
|
||||
|
||||
self['container'].show_all()
|
||||
|
||||
|
||||
def get_data(self):
|
||||
url = Url(self.form.url)
|
||||
data = DotDict(
|
||||
url = url,
|
||||
form_url = url.without_query,
|
||||
username = self.form.username[1],
|
||||
password = self.form.password[1],
|
||||
userfield = self.form.username[0],
|
||||
passfield = self.form.password[0],
|
||||
note = None
|
||||
)
|
||||
|
||||
if data.url.endswith('/'):
|
||||
data.url = data.url[:-1]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def set_data(self, username, password):
|
||||
self['username'].set_text(username or '')
|
||||
self['password'].set_text(password or '')
|
||||
|
||||
|
||||
def handle_cancel(self):
|
||||
self.window['statusbar-logins-unsaved-list'].remove(self['container'])
|
||||
self['container'].destroy()
|
||||
del self.ext.unsaved_forms[self.form.form_url]
|
||||
|
||||
|
||||
def handle_pass_visibility(self):
|
||||
self['password'].set_visibility(not self['password'].get_visibility())
|
||||
|
||||
|
||||
def handle_save(self):
|
||||
data = self.get_data()
|
||||
|
||||
with get_app().db.session as s:
|
||||
s.put_password(data.username, data.password, data.url, name=None, note=data.note)
|
||||
|
||||
self.handle_cancel()
|
||||
|
||||
|
||||
class WebviewState:
|
||||
def __init__(self, tabid, title, url, session):
|
||||
self.tabid = tabid
|
||||
self.title = title
|
||||
self.url = url
|
||||
self.session = session
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return f'WebViewState(tabid="{self.tabid}", title="{self.title}", url="{self.url}")'
|
||||
|
||||
|
||||
@classmethod
|
||||
def new_from_row(cls, row):
|
||||
return cls(
|
||||
row.tabid,
|
||||
row.title,
|
||||
row.url,
|
||||
WebKit2.WebViewSessionState.new(GLib.Bytes.new(row.state))
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def new_from_tab(cls, tab):
|
||||
return cls(
|
||||
tab.id,
|
||||
tab.title,
|
||||
tab.url,
|
||||
tab.webview.get_session_state()
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return get_app()
|
||||
|
||||
|
||||
def to_bytes(self):
|
||||
return self.session.serialize().get_data()
|
|
@ -1,119 +0,0 @@
|
|||
from ..functions import TimeoutCallback, connect, get_app, run_in_gui_thread
|
||||
|
||||
|
||||
class LoginRowBase:
|
||||
def __getitem__(self, key):
|
||||
return self.ui.get_object(key)
|
||||
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return get_app()
|
||||
|
||||
|
||||
@property
|
||||
def db(self):
|
||||
return self.app.db
|
||||
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
return self.app.window
|
||||
|
||||
|
||||
def connect(self, name, signal, callback, *args, original_args=False, **kwargs):
|
||||
if original_args:
|
||||
connect(self[name], signal, lambda *origargs: callback(*origargs, *args, **kwargs))
|
||||
|
||||
else:
|
||||
connect(self[name], signal, lambda *origargs: callback(*args, **kwargs))
|
||||
|
||||
|
||||
class SavedLoginRow(LoginRowBase):
|
||||
def __init__(self, row, page_url):
|
||||
self.ui = Gtk.Builder.new_from_file(self.app.path.resources.join('password_saved.ui'))
|
||||
self.row = row
|
||||
|
||||
self['username'].set_text(self.row['username'])
|
||||
|
||||
self.connect('fill', 'clicked', self.handle_fill_password)
|
||||
self.connect('copy-password', 'clicked', self.handle_copy_password)
|
||||
self.connect('copy-username', 'clicked', self.row.copy_username)
|
||||
|
||||
self['container'].show_all()
|
||||
|
||||
|
||||
@property
|
||||
def tab(self):
|
||||
return self.window.active_tab
|
||||
|
||||
|
||||
def handle_copy_password(self):
|
||||
self.row.copy_password(60)
|
||||
self.window.notification('Copied password to clipboard for 5 seconds')
|
||||
|
||||
self.window['statusbar-logins-popover'].popdown()
|
||||
|
||||
|
||||
def handle_fill_password(self):
|
||||
with dirs.resources.join('ext_js/autofill.js').open() as fd:
|
||||
self.tab.run_js(fd.read().replace('USERNAME_VALUE', self.row.username).replace('PASSWORD_VALUE', self.row.password))
|
||||
|
||||
self.window['statusbar-logins-popover'].popdown()
|
||||
|
||||
|
||||
class UnsavedLoginRow(LoginRowBase):
|
||||
def __init__(self, ext, form):
|
||||
self.ui = Gtk.Builder.new_from_file(self.app.path.resources.join('password_unsaved.ui'))
|
||||
self.ext = ext
|
||||
self.form = form
|
||||
|
||||
self.set_data(form.username[1], form.password[1])
|
||||
|
||||
self.connect('save', 'clicked', self.handle_save)
|
||||
self.connect('cancel', 'clicked', self.handle_cancel)
|
||||
self.connect('password', 'icon-press', self.handle_pass_visibility)
|
||||
|
||||
self['container'].show_all()
|
||||
|
||||
|
||||
def get_data(self):
|
||||
url = Url(self.form.url)
|
||||
data = DotDict(
|
||||
url = url,
|
||||
form_url = url.without_query,
|
||||
username = self.form.username[1],
|
||||
password = self.form.password[1],
|
||||
userfield = self.form.username[0],
|
||||
passfield = self.form.password[0],
|
||||
note = None
|
||||
)
|
||||
|
||||
if data.url.endswith('/'):
|
||||
data.url = data.url[:-1]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def set_data(self, username, password):
|
||||
self['username'].set_text(username or '')
|
||||
self['password'].set_text(password or '')
|
||||
|
||||
|
||||
def handle_cancel(self):
|
||||
self.window['statusbar-logins-unsaved-list'].remove(self['container'])
|
||||
self['container'].destroy()
|
||||
del self.ext.unsaved_forms[self.form.form_url]
|
||||
|
||||
|
||||
def handle_pass_visibility(self):
|
||||
self['password'].set_visibility(not self['password'].get_visibility())
|
||||
|
||||
|
||||
def handle_save(self):
|
||||
data = self.get_data()
|
||||
|
||||
with get_app().db.session as s:
|
||||
s.put_password(data.username, data.password, data.url, name=None, note=data.note)
|
||||
|
||||
self.handle_cancel()
|
|
@ -65,6 +65,11 @@ def search_web(handler, request):
|
|||
return request.redirect(row.compile(text))
|
||||
|
||||
|
||||
@Local.route('/about')
|
||||
def about(handler, request):
|
||||
return request.page('about')
|
||||
|
||||
|
||||
### Bookmarks ###
|
||||
@Local.route('/bookmarks')
|
||||
def bookmarks_home(handler, request):
|
||||
|
@ -462,39 +467,7 @@ def search_default(handler, request, searchid):
|
|||
return request.ok_or_redirect('Set default search')
|
||||
|
||||
|
||||
### Resources ###
|
||||
@Local.route('/css/{filename}')
|
||||
def css(handler, request, filename):
|
||||
context = DotDict(
|
||||
primary = Color('#e7a'),
|
||||
secondary = Color('#e7a'),
|
||||
background = Color('#191919'),
|
||||
positive = Color('#ada'),
|
||||
negative = Color('#daa'),
|
||||
speed = 350
|
||||
)
|
||||
|
||||
return request.template(f'css/{filename}', context)
|
||||
|
||||
|
||||
@Local.route('/js/{filename}')
|
||||
def javascript(handler, request, filename):
|
||||
with handler.app.path.localweb.join('js', filename).open() as fd:
|
||||
return request.response(fd.read(), 'application/javascript')
|
||||
|
||||
|
||||
@Local.route('/font/{name}/{filename}')
|
||||
def font(handler, request, name, filename):
|
||||
with handler.app.path.localweb.join('fonts', name, filename).open('rb') as fd:
|
||||
return request.response(fd.read(), request.mimetype)
|
||||
|
||||
|
||||
@Local.route('/icon/{name}')
|
||||
def icon(handler, request, name):
|
||||
with handler.app.path.resources.join('icons', name).open('rb') as fd:
|
||||
return request.response(fd.read(), 'image/svg+xml')
|
||||
|
||||
|
||||
## Debug
|
||||
@Local.route('/debug')
|
||||
def debug_home(handler, request):
|
||||
return request.page('debug')
|
||||
|
@ -527,3 +500,44 @@ def debug_leaking(handler, request):
|
|||
)
|
||||
|
||||
return request.page('debug', context)
|
||||
|
||||
|
||||
### Resources ###
|
||||
@Local.route('/css/{filename}')
|
||||
def css(handler, request, filename):
|
||||
context = DotDict(
|
||||
primary = Color('#e7a'),
|
||||
secondary = Color('#e7a'),
|
||||
background = Color('#191919'),
|
||||
positive = Color('#ada'),
|
||||
negative = Color('#daa'),
|
||||
speed = 350
|
||||
)
|
||||
|
||||
return request.template(f'css/{filename}', context)
|
||||
|
||||
|
||||
@Local.route('/js/{filename}')
|
||||
def javascript(handler, request, filename):
|
||||
with handler.app.path.localweb.join('js', filename).open() as fd:
|
||||
return request.response(fd.read(), 'application/javascript')
|
||||
|
||||
|
||||
@Local.route('/font/{name}/{filename}')
|
||||
def font(handler, request, name, filename):
|
||||
with handler.app.path.localweb.join('fonts', name, filename).open('rb') as fd:
|
||||
return request.response(fd.read(), request.mimetype)
|
||||
|
||||
|
||||
@Local.route('/icon/{name}')
|
||||
def icon(handler, request, name):
|
||||
if name == 'app.png':
|
||||
path = handler.app.path.resources.join('icon.png')
|
||||
mime = 'image/png'
|
||||
|
||||
else:
|
||||
path = handler.app.path.resources.join('icons', name)
|
||||
mime = 'image/svg+xml'
|
||||
|
||||
with path.open('rb') as fd:
|
||||
return request.response(fd.read(), mime)
|
||||
|
|
|
@ -1,17 +1,41 @@
|
|||
@define-color pyweb_border_color shade(@theme_bg_color, 1.25);
|
||||
@define-color barkshark_border_color shade(black, 1.25);
|
||||
@define-color barkshark_button_color shade(@theme_bg_color, 1.15);
|
||||
|
||||
list {
|
||||
entry {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
entry image {
|
||||
padding: 5px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
transition-property: border-color;
|
||||
transition-property: background-image;
|
||||
transition-duration: 0.5s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
|
||||
entry image:hover {
|
||||
border-color: @barkshark_border_color;
|
||||
background-image: radial-gradient(circle farthest-side at center, @barkshark_button_color, @theme_bg_color);
|
||||
}
|
||||
|
||||
|
||||
.entry-recolor {
|
||||
background-color: @theme_bg_color;
|
||||
border: 1px solid @theme_bg_color;
|
||||
transition: background-color 0.5s ease-out;
|
||||
}
|
||||
|
||||
|
||||
.entry-recolor:focus {
|
||||
background-color: @theme_base_color;
|
||||
border: 1px solid @pyweb_border_color;
|
||||
}
|
||||
|
||||
popover {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
popover button {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.notification-container {
|
||||
background-color: @theme_base_color;
|
||||
|
@ -20,71 +44,8 @@ popover button {
|
|||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.dropdown-shadow {
|
||||
-gtk-icon-shadow: 2px 2px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.dropdown-shadow:hover {
|
||||
background-color: @theme_base_color;
|
||||
border: 1px solid @pyweb_border_color;
|
||||
-gtk-icon-effect: highlight;
|
||||
-gtk-icon-shadow: 3px 3px black;
|
||||
}
|
||||
|
||||
.notebook-button {
|
||||
border-radius: 2px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/*.notebook-button:hover, button {
|
||||
border-radius: 2px;
|
||||
border-width: 1px;
|
||||
}*/
|
||||
|
||||
/*.overlay-widget {
|
||||
background-color: @theme_base_color;
|
||||
}*/
|
||||
|
||||
.scrolled-no-border {
|
||||
border: 0px solid transparent;
|
||||
}
|
||||
|
||||
/*.stack-switcher .radio {
|
||||
margin: 0px 0px 5px 0px;
|
||||
}*/
|
||||
|
||||
.statusbar-login-list {
|
||||
background-color: @theme_base_color;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.tab-favicon, .tab-close {
|
||||
border: 1px solid @theme_bg_color;
|
||||
background-color: @theme_bg_color;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.tab-favicon {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.tab-close {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.tab-favicon:hover, .tab-close:hover{
|
||||
background-color: shade(@theme_bg_color,0.50);
|
||||
border-color: shade(@theme_bg_color, 1.25);
|
||||
}
|
||||
|
||||
.urlbar {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
background: @theme_bg_color;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.urlbar:focus {
|
||||
border: 1px solid shade(@theme_bg_color, 1.25);
|
||||
background: @theme_base_color;
|
||||
}
|
||||
|
|
|
@ -7,19 +7,6 @@
|
|||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">user-bookmarks</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-go-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">system-run</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-home-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">go-home</property>
|
||||
<property name="icon_size">0</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-menu-about-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
|
@ -56,7 +43,7 @@
|
|||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find</property>
|
||||
</object>
|
||||
<object class="GtkPopover" id="navbar-menu-popover">
|
||||
<object class="GtkPopover" id="tabs-menu-popover">
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
|
@ -68,12 +55,13 @@
|
|||
<property name="margin-bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-menu-newtab">
|
||||
<object class="GtkButton" id="tabs-menu-new_tab">
|
||||
<property name="label" translatable="yes">New Tab</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Opens a new tab</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="image">navbar-menu-newtab-icon</property>
|
||||
|
@ -86,12 +74,13 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-menu-search">
|
||||
<object class="GtkButton" id="tabs-menu-search">
|
||||
<property name="label" translatable="yes">Search</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Toggle the current tab's search bar</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="image">navbar-menu-search-icon</property>
|
||||
|
@ -104,12 +93,13 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-menu-fullscreen">
|
||||
<object class="GtkButton" id="tabs-menu-fullscreen">
|
||||
<property name="label" translatable="yes">Fullscreen</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Toggle fullscreen mode</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="image">navbar-menu-fullscreen-icon</property>
|
||||
|
@ -133,12 +123,13 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-menu-library">
|
||||
<object class="GtkButton" id="tabs-menu-library">
|
||||
<property name="label" translatable="yes">Library</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Open the library in a new tab</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="image">navbar-menu-preferences-icon</property>
|
||||
|
@ -162,12 +153,13 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-menu-about">
|
||||
<object class="GtkButton" id="tabs-menu-about">
|
||||
<property name="label" translatable="yes">About</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Open the about page in a new tab</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="image">navbar-menu-about-icon</property>
|
||||
|
@ -180,12 +172,13 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-menu-quit">
|
||||
<object class="GtkButton" id="tabs-menu-quit">
|
||||
<property name="label" translatable="yes">Quit</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Quit the browser</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="image">navbar-menu-quit-icon</property>
|
||||
|
@ -200,34 +193,6 @@
|
|||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-next-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">go-next</property>
|
||||
<property name="icon_size">3</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-prev-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">go-previous</property>
|
||||
<property name="icon_size">3</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-refresh-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">view-refresh</property>
|
||||
<property name="icon_size">3</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-stop-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">process-stop</property>
|
||||
<property name="icon_size">3</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="notification-close-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
|
@ -385,6 +350,11 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="wrap-mode">word</property>
|
||||
<property name="left-margin">8</property>
|
||||
<property name="right-margin">8</property>
|
||||
<property name="top-margin">8</property>
|
||||
<property name="bottom-margin">8</property>
|
||||
<property name="accepts-tab">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
|
@ -1390,172 +1360,6 @@
|
|||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="navbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-prev">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Previous</property>
|
||||
<property name="image">navbar-prev-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-next">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Forward</property>
|
||||
<property name="image">navbar-next-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-stop">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Stop</property>
|
||||
<property name="image">navbar-stop-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-refresh">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Refresh</property>
|
||||
<property name="image">navbar-refresh-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-home">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Go Home</property>
|
||||
<property name="image">navbar-home-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="navbar-url">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="max-length">2048</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="secondary-icon-name">edit-clear</property>
|
||||
<property name="secondary-icon-tooltip-text" translatable="yes">Clear text</property>
|
||||
<property name="placeholder-text" translatable="yes">URL</property>
|
||||
<property name="input-purpose">url</property>
|
||||
<style>
|
||||
<class name="urlbar"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-go">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Go</property>
|
||||
<property name="image">navbar-go-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="navbar-menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Menu</property>
|
||||
<property name="image">navbar-menu-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="popover">navbar-menu-popover</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkNotebook" id="tabs">
|
||||
<property name="visible">True</property>
|
||||
|
@ -1599,17 +1403,49 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child type="action-end">
|
||||
<object class="GtkButton" id="tabs-new">
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">New tab</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">tabs-new-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="notebook-button"/>
|
||||
</style>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="tabs-new">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">New tab</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">tabs-new-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="tab-button"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="navbar-menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Menu</property>
|
||||
<property name="image">navbar-menu-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="popover">tabs-menu-popover</property>
|
||||
<style>
|
||||
<class name="tab-button"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab-fill">False</property>
|
||||
|
@ -1619,7 +1455,7 @@
|
|||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -1828,7 +1664,7 @@
|
|||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
|
386
barkshark_web/resources/tab.ui
Normal file
386
barkshark_web/resources/tab.ui
Normal file
|
@ -0,0 +1,386 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<object class="GtkImage" id="label-close-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">window-close</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="label-favicon-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">image-x-generic</property>
|
||||
</object>
|
||||
<object class="GtkBox" id="label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="label-favicon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="image">label-favicon-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="tab-button"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label-title">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Untitled Tab</property>
|
||||
<property name="justify">fill</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="single-line-mode">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="label-close">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="image">label-close-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="tab-button"/>
|
||||
<class name="tab-close"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkBox" id="menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="menu-favicon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="menu-title">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-go-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">system-run</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-home-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">go-home</property>
|
||||
<property name="icon_size">0</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-next-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">go-next</property>
|
||||
<property name="icon_size">3</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-prev-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">go-previous</property>
|
||||
<property name="icon_size">3</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-refresh-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">view-refresh</property>
|
||||
<property name="icon_size">3</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="navbar-stop-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixel-size">24</property>
|
||||
<property name="icon-name">process-stop</property>
|
||||
<property name="icon_size">3</property>
|
||||
</object>
|
||||
<object class="GtkBox" id="navbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-prev">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Previous</property>
|
||||
<property name="image">navbar-prev-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-next">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Forward</property>
|
||||
<property name="image">navbar-next-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-stop">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Stop</property>
|
||||
<property name="image">navbar-stop-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-refresh">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Refresh</property>
|
||||
<property name="image">navbar-refresh-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-home">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Go Home</property>
|
||||
<property name="image">navbar-home-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="navbar-url">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="max-length">2048</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="secondary-icon-name">edit-clear</property>
|
||||
<property name="secondary-icon-tooltip-text" translatable="yes">Clear text</property>
|
||||
<property name="secondary-icon-tooltip-markup" translatable="yes">Clear text</property>
|
||||
<property name="placeholder-text" translatable="yes">URL</property>
|
||||
<property name="input-purpose">url</property>
|
||||
<style>
|
||||
<class name="entry-recolor"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="navbar-go">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Go</property>
|
||||
<property name="image">navbar-go-icon</property>
|
||||
<property name="relief">none</property>
|
||||
<style>
|
||||
<class name="dropdown-shadow"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="navbar"/>
|
||||
</style>
|
||||
</object>
|
||||
<object class="GtkImage" id="search-close-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">window-close</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="search-find-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="search-next-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">go-next</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="search-previous-icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">go-previous</property>
|
||||
</object>
|
||||
<object class="GtkBox" id="search">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="no-show-all">True</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="search-previous">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="image">search-previous-icon</property>
|
||||
<property name="relief">none</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="search-next">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="image">search-next-icon</property>
|
||||
<property name="relief">none</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="search-find">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="image">search-find-icon</property>
|
||||
<property name="relief">none</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="search-text">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="secondary-icon-name">edit-clear</property>
|
||||
<style>
|
||||
<class name="entry-recolor"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="search-close">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="image">search-close-icon</property>
|
||||
<property name="relief">none</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -16,8 +16,8 @@ class Themes(DotDict):
|
|||
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
return self._window
|
||||
def current(self):
|
||||
return self._current_theme
|
||||
|
||||
|
||||
@property
|
||||
|
@ -25,6 +25,11 @@ class Themes(DotDict):
|
|||
return Gdk.Screen.get_default()
|
||||
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
return self._window
|
||||
|
||||
|
||||
def load(self, name):
|
||||
logging.verbose('Loading theme:', name)
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
"LOG_LEVEL": "DEBUG"
|
||||
},
|
||||
"ext": [
|
||||
"css",
|
||||
"ui",
|
||||
"py",
|
||||
"pyx",
|
||||
|
@ -95,4 +96,4 @@
|
|||
"pyvenv.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue