Merge pull request 'version 0.1.7' (#2) from dev into main
This commit is contained in:
commit
554da51046
18
README.md
18
README.md
|
@ -4,11 +4,13 @@ Web browser written in Python and Qt with PyQt5
|
|||
|
||||
## Features
|
||||
|
||||
*
|
||||
* Quick access to various page functions in the status bar
|
||||
* Login to a Mastodon account
|
||||
* Quick access to domain permissions in the status bar
|
||||
* Create new Mastodon posts without going to the webui
|
||||
* Interact with Mastodon posts by right-clicking the post's url
|
||||
* Control the browser via cli from a terminal
|
||||
* Ability to add user scripts (basic support atm)
|
||||
* Custom searches via keyword
|
||||
* Remote web dev tools (localhost only atm)
|
||||
|
||||
note: I may add full pleroma support in the future
|
||||
|
||||
|
@ -31,7 +33,7 @@ Make sure you add Python to PATH
|
|||
### Python dependencies
|
||||
|
||||
Windows:
|
||||
|
||||
|
||||
pip3 install -r requirements.txt -r requirements-win.txt
|
||||
|
||||
Linux:
|
||||
|
@ -44,20 +46,23 @@ Linux (pyqt via pip):
|
|||
|
||||
## ToDo
|
||||
|
||||
* Create a new icon for the toot button
|
||||
* Create download manager
|
||||
* Handle cookies
|
||||
* Add folder structure for bookmarks
|
||||
* Create history tab
|
||||
* Add proxy support
|
||||
* Ability to add new custom searches via local or remote json
|
||||
* Add optional auto-updater with stable/testing channels
|
||||
* Add ability to 'freeze' tabs
|
||||
* Add closed tab history
|
||||
* Rename the damn project
|
||||
|
||||
## Bugs
|
||||
|
||||
* Resetting a page search sometimes scrolls the page to the top or an element on the page
|
||||
* Web notifications use the wrong icon sometimes
|
||||
* Switching between PyQt versions resets cookies
|
||||
* Stop and reload buttons occasionally don't get set properly after finished loading
|
||||
* The nitter redirect only works when loading a url from the navbar
|
||||
* Web inspector is kinda broken
|
||||
* The webview is really blurry and glitchy in virtualbox with the VboxSVGA controller with 3d accel enabled
|
||||
|
@ -67,3 +72,6 @@ Linux (pyqt via pip):
|
|||
|
||||
* No web notification support in QWebEngine
|
||||
* Download urls with query parameters will probably produce junk names atm because QWebEngineDownloadItem.suggestedFileName() doesn't exist
|
||||
* Can't login to twitch.tv and channels sometimes don't load
|
||||
* Can't browse github code trees. Use right click > Open Link in New Tab to work around the issue for now
|
||||
* Gitlab servers respond with a 422 when trying to login
|
||||
|
|
|
@ -24,9 +24,10 @@ class httpClient:
|
|||
self.headers['User-Agent'] = self.agent
|
||||
|
||||
|
||||
def _fetch(self, url, headers={}, method='GET', data=None, cached=True):
|
||||
def _fetch(self, url, new_headers={}, method='GET', data=None, cached=True):
|
||||
cached_data = self.cache.fetch(url)
|
||||
#url = url.split('#')[0]
|
||||
headers = self.headers.copy()
|
||||
headers.update(new_headers)
|
||||
|
||||
if cached and cached_data:
|
||||
logging.debug(f'Returning cached data for {url}')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
__application__ = 'QtWeb'
|
||||
__version_info__ = (0,1,6,9)
|
||||
__version_info__ = (0,1,7)
|
||||
__version__ = '.'.join(str(v) for v in __version_info__)
|
||||
__author__ = 'Zoey Mae'
|
||||
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
import sys
|
||||
|
||||
from collections import namedtuple
|
||||
from os.path import abspath, dirname, join, isdir, expanduser
|
||||
from os.path import abspath, dirname, join, isdir
|
||||
from os import environ, makedirs, walk
|
||||
from random import randint
|
||||
|
||||
from . import __version__, __application__, isWindows
|
||||
from .arguments import profile
|
||||
|
||||
from .Lib.IzzyLib import logging
|
||||
from .Lib.IzzyLib.misc import randomgen
|
||||
|
||||
import PyQt5
|
||||
from PyQt5.QtCore import QStandardPaths
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineProfile
|
||||
|
||||
|
@ -41,12 +38,21 @@ variables = {
|
|||
'lock': join(datapath, profile, 'lock.pid'),
|
||||
'home': home,
|
||||
'temp': join(temp, 'qtweb'),
|
||||
'local': 'web:'
|
||||
'local': 'qtweb'
|
||||
}
|
||||
|
||||
|
||||
default_vars = {
|
||||
'homepage': 'https://ddg.gg',
|
||||
'nitter': 'https://nitter.net',
|
||||
'mhost': '127.0.0.1',
|
||||
'mport': 8008
|
||||
}
|
||||
|
||||
|
||||
# Set various variables
|
||||
var = namedtuple('Variables', variables.keys())(*variables.values())
|
||||
default = namedtuple('Default', default_vars.keys())(*default_vars.values())
|
||||
|
||||
## create necessary data dirs
|
||||
if not isdir(var.profilepath):
|
||||
|
@ -55,4 +61,7 @@ if not isdir(var.profilepath):
|
|||
if not isdir(var.temp):
|
||||
makedirs(var.temp, exist_ok=True)
|
||||
|
||||
if not isdir(join(var.datapath, 'UserScripts')):
|
||||
makedirs(join(var.datapath, 'UserScripts'), exist_ok=True)
|
||||
|
||||
environ['QTWEBENGINE_DICTIONARIES_PATH'] = join(var.resources, 'dictionaries')
|
||||
|
|
|
@ -9,7 +9,6 @@ from .connection import db, CreateDatabase
|
|||
|
||||
from .. import dbversion
|
||||
|
||||
|
||||
dbverrow = get.config('version', 0) if db.TableCheck('config') else 0
|
||||
current_dbversion = 202007110621 if int(dbverrow) == 2020071110621 else dbverrow
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import shutil, sqlite3, traceback
|
|||
from os.path import isfile
|
||||
from collections import OrderedDict
|
||||
from sqlite3 import OperationalError
|
||||
from dataclasses import make_dataclass, field, asdict
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
@ -16,78 +15,68 @@ from DBUtils.PooledDB import PooledDB
|
|||
|
||||
from .. import dbversion, prev_dbversion
|
||||
from ..config import var
|
||||
from ..functions import DateTime
|
||||
from ..functions import DotDict
|
||||
|
||||
|
||||
tables = {
|
||||
'config': OrderedDict([
|
||||
('id', 'INTEGER PRIMARY KEY'),
|
||||
('key', 'TEXT'),
|
||||
('value', 'TEXT')
|
||||
]),
|
||||
'bookmarks': OrderedDict([
|
||||
('id', 'INTEGER PRIMARY KEY'),
|
||||
('name', 'TEXT'),
|
||||
('url', 'TEXT UNIQUE'),
|
||||
('description', 'TEXT'),
|
||||
('category', 'TEXT'),
|
||||
('lastupdate', 'DATETIME')
|
||||
]),
|
||||
'links': OrderedDict([
|
||||
('id', 'INTEGER PRIMARY KEY'),
|
||||
('linkid', 'INTEGER')
|
||||
]),
|
||||
'mastodon': OrderedDict([
|
||||
('id', 'INTEGER PRIMARY KEY'),
|
||||
('username', 'TEXT'),
|
||||
('displayname', 'TEXT'),
|
||||
('domain', 'TEXT'),
|
||||
('fullname', 'TEXT'),
|
||||
('apikey', 'TEXT'),
|
||||
('tootlimit', 'INTEGER'),
|
||||
('avatar', 'TEXT'),
|
||||
('lastupdate', 'DATETIME')
|
||||
]),
|
||||
'siteoptions': OrderedDict([
|
||||
('id', 'INTEGER PRIMARY KEY'),
|
||||
('domain', 'TEXT'),
|
||||
('microphone', 'BOOLEAN DEFAULT 0'),
|
||||
('notification', 'BOOLEAN DEFAULT 0'),
|
||||
('camera', 'BOOLEAN DEFAULT 0'),
|
||||
('location', 'BOOLEAN DEFAULT 0'),
|
||||
('fullscreen', 'BOOLEAN DEFAULT 1'),
|
||||
('javascript', 'BOOLEAN DEFAULT 1'),
|
||||
('images', 'BOOLEAN DEFAULT 1'),
|
||||
('adblock', 'BOOLEAN DEFAULT 1'),
|
||||
('mastodon', 'BOOLEAN DEFAULT 0'),
|
||||
('allowhttp', 'BOOLEAN DEFAULT 1'),
|
||||
('capture', 'BOOLEAN DEFAULT 0'),
|
||||
('cookies', 'BOOLEAN DEFAULT 0'),
|
||||
('lastupdate', 'DATETIME')
|
||||
]),
|
||||
'tabs': OrderedDict([
|
||||
('id', 'INTEGER PRIMARY KEY'),
|
||||
('title', 'TEXT'),
|
||||
('url', 'TEXT'),
|
||||
('tabid', 'INTEGER')
|
||||
]),
|
||||
'search': OrderedDict([
|
||||
('id', 'INTEGER PRIMARY KEY'),
|
||||
('name', 'TEXT'),
|
||||
('keyword', 'TEXT UNIQUE'),
|
||||
('url', 'TEXT')
|
||||
]),
|
||||
#('cookies', {
|
||||
#'id': 'INTEGER PRIMARY KEY',
|
||||
#'name': 'TEXT NOT NULL',
|
||||
#'value': 'TEXT NOT NULL',
|
||||
#'domain': 'TEXT NOT NULL',
|
||||
#'path': 'TEXT NOT NULL',
|
||||
#'expires': 'INTEGER NOT NULL',
|
||||
#'http_only': 'BOOLEAN NOT NULL',
|
||||
#'secure': 'BOOLEAN NOT NULL'
|
||||
#})
|
||||
}
|
||||
tables = DotDict({
|
||||
'config': {
|
||||
'id': {'type': 'integer', 'options': ['primary key']},
|
||||
'key': {'type': 'text', 'options': ['unique']},
|
||||
'value': {'type': 'text'},
|
||||
},
|
||||
'bookmarks': {
|
||||
'id': {'type': 'integer', 'options': ['primary key']},
|
||||
'name': {'type': 'text'},
|
||||
'url': {'type': 'text', 'options': ['unique']},
|
||||
'description': {'type': 'text'},
|
||||
'category': {'type': 'text'},
|
||||
'lastupdate': {'type': 'datetime'},
|
||||
},
|
||||
'links': {
|
||||
'id': {'type': 'integer', 'options': ['primary key']},
|
||||
'linkid': {'type': 'integer'}
|
||||
},
|
||||
'mastodon': {
|
||||
'id': {'type': 'integer', 'options': ['primary key']},
|
||||
'username': {'type': 'text'},
|
||||
'displayname': {'type': 'text'},
|
||||
'domain': {'type': 'text'},
|
||||
'fullname': {'type': 'text', 'options': 'unique'},
|
||||
'apikey': {'type': 'text'},
|
||||
'tootlimit': {'type': 'integer'},
|
||||
'avatar': {'type': 'text'},
|
||||
'lastupdate': {'type': 'datetime'},
|
||||
},
|
||||
'siteoptions': {
|
||||
'id': {'type': 'integer', 'options': ['primary key']},
|
||||
'domain': {'type': 'text', 'options': 'unique'},
|
||||
'microphone': {'type': 'BOOLEAN', 'default': False},
|
||||
'notification': {'type': 'BOOLEAN', 'default': False},
|
||||
'camera': {'type': 'BOOLEAN', 'default': False},
|
||||
'location': {'type': 'BOOLEAN', 'default': False},
|
||||
'fullscreen': {'type': 'BOOLEAN', 'default': True},
|
||||
'javascript': {'type': 'BOOLEAN', 'default': True},
|
||||
'images': {'type': 'BOOLEAN', 'default': True},
|
||||
'adblock': {'type': 'BOOLEAN', 'default': True},
|
||||
'mastodon': {'type': 'BOOLEAN', 'default': False},
|
||||
'allowhttp': {'type': 'BOOLEAN', 'default': True},
|
||||
'capture': {'type': 'BOOLEAN', 'default': False},
|
||||
'cookies': {'type': 'BOOLEAN', 'default': False},
|
||||
'lastupdate': {'type': 'datetime'}
|
||||
},
|
||||
'tabs': {
|
||||
'id': {'type': 'integer', 'options': ['primary key']},
|
||||
'title': {'type': 'text'},
|
||||
'url': {'type': 'text'},
|
||||
'tabid': {'type': 'integer'}
|
||||
},
|
||||
'search': {
|
||||
'id': {'type': 'integer', 'options': ['primary key']},
|
||||
'name': {'type': 'text'},
|
||||
'keyword': {'type': 'text', 'options': ['unique']},
|
||||
'url': {'type': 'text'}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
SearchEngines = [
|
||||
|
@ -135,12 +124,12 @@ def ParseData(table, row):
|
|||
class DB():
|
||||
def __init__(self, dbfile=var.database):
|
||||
self.db = PooledDB(sqlite3, database=dbfile, maxconnections=50, mincached=5, maxusage=1)
|
||||
self.window = None
|
||||
self.cache = {}
|
||||
self.resclass = {}
|
||||
|
||||
for table in tables.keys():
|
||||
self._setup_cache(table)
|
||||
self._setup_result_class(table)
|
||||
|
||||
# function aliases
|
||||
self.get = self.fetch
|
||||
|
@ -154,23 +143,6 @@ class DB():
|
|||
self.cache[table] = LRUCache(128)
|
||||
|
||||
|
||||
def _setup_result_class(self, table):
|
||||
def UpdateDict(dict1, dict2):
|
||||
dict1.update(dict2)
|
||||
return dict1
|
||||
|
||||
self.resclass[table] = make_dataclass(
|
||||
table.title(),
|
||||
[(key, 'typing.Any', field(default=None)) for key in self._table_keys(table)],
|
||||
namespace={
|
||||
'_asdict': lambda self: asdict(self), #remove later
|
||||
'asdict': lambda self: asdict(self),
|
||||
'Update': lambda self, data=None: db.update(table, self.id, UpdateDict(self.asdict(), data if data else {})),
|
||||
'Remove': lambda self: db.remove(table, self.id)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def close(self):
|
||||
self.db.close()
|
||||
|
||||
|
@ -221,39 +193,33 @@ class DB():
|
|||
def fetch(self, table, rowid=None, single=True, sort=None, **kwargs):
|
||||
db = self
|
||||
table = table if tables.get(table) else table.replace('_migrate', '')
|
||||
tablekeys = self._table_keys(table)
|
||||
querysort = f'ORDER BY {sort}'
|
||||
Result = self.resclass.get(table, None)
|
||||
|
||||
if not Result:
|
||||
logging.error('Invalid Result object for table:', table)
|
||||
return
|
||||
with self.GetCursor() as cursor:
|
||||
resultOpts = [self, table, cursor]
|
||||
|
||||
if rowid:
|
||||
result = self.query(f"SELECT * FROM {table} WHERE id = ?", [rowid])
|
||||
if rowid:
|
||||
cursor.execute(f"SELECT * FROM {table} WHERE id = ?", [rowid])
|
||||
|
||||
elif kwargs:
|
||||
placeholders = [f'{k} = ?' for k in kwargs.keys()]
|
||||
values = kwargs.values()
|
||||
elif kwargs:
|
||||
placeholders = [f'{k} = ?' for k in kwargs.keys()]
|
||||
values = kwargs.values()
|
||||
|
||||
where = ' and '.join(placeholders)
|
||||
query = f"SELECT * FROM {table} WHERE {where} {querysort if sort else ''}"
|
||||
result = self.query(query, list(values))
|
||||
|
||||
else:
|
||||
result = self.query(f'SELECT * FROM {table} {querysort if sort else ""}')
|
||||
|
||||
if result:
|
||||
if single:
|
||||
|
||||
res = Result(*ParseData(table, result[0]))
|
||||
where = ' and '.join(placeholders)
|
||||
query = f"SELECT * FROM {table} WHERE {where} {querysort if sort else ''}"
|
||||
cursor.execute(query, list(values))
|
||||
|
||||
else:
|
||||
res = [Result(*ParseData(table, row)) for row in result]
|
||||
cursor.execute(f'SELECT * FROM {table} {querysort if sort else ""}')
|
||||
|
||||
return res
|
||||
rows = cursor.fetchall()
|
||||
|
||||
if rows:
|
||||
if single:
|
||||
return DBResult(rows[0] if single else rows, *resultOpts)
|
||||
|
||||
return [DBResult(row, *resultOpts) for row in rows]
|
||||
|
||||
else:
|
||||
return None if single else []
|
||||
|
||||
|
||||
|
@ -286,8 +252,7 @@ class DB():
|
|||
def query(self, string, values=[]):
|
||||
with self.GetCursor() as cursor:
|
||||
cursor.execute(string, values)
|
||||
rows = cursor.fetchall()
|
||||
return rows
|
||||
return cursor.fetchall()
|
||||
|
||||
|
||||
def GetFields(self, table, ignore_fields=[]):
|
||||
|
@ -306,17 +271,24 @@ class DB():
|
|||
|
||||
|
||||
def CreateTable(self, table):
|
||||
layout = tables[table]
|
||||
layout = DotDict(tables.get(table))
|
||||
|
||||
if not layout:
|
||||
logging.error('Table config doesn\'t exist:', table)
|
||||
return
|
||||
|
||||
cmd = f'CREATE TABLE IF NOT EXISTS {table}('
|
||||
items = []
|
||||
|
||||
for k, v in layout.items():
|
||||
if k != 'foreignkey':
|
||||
items.append(f'{k} {v}')
|
||||
options = ' '.join(v.get('options', []))
|
||||
default = v.get('default')
|
||||
item = f'{k} {v.type.upper()} {options}'
|
||||
|
||||
else:
|
||||
field, ffield, fcol = layout[k]
|
||||
items.append(f'FOREIGN KEY({field}) REFERENCES {ffield}({fcol})')
|
||||
if default:
|
||||
item += f'DEFAULT {default}'
|
||||
|
||||
items.append(item)
|
||||
|
||||
cmd += ', '.join(items) + ')'
|
||||
|
||||
|
@ -333,7 +305,8 @@ class DB():
|
|||
|
||||
def UpVersion(self, v):
|
||||
row = self.fetch('config', key='version')
|
||||
self.update('config', row.id, {'value': str(v)})
|
||||
row.value = str(v)
|
||||
row.Update()
|
||||
|
||||
|
||||
def AddColumn(self, table, name, datatype, default=None, options=None):
|
||||
|
@ -380,11 +353,67 @@ class DB():
|
|||
return cookie
|
||||
|
||||
|
||||
class DBResult(DotDict):
|
||||
def __init__(self, row, db, table, cursor):
|
||||
super().__init__()
|
||||
|
||||
self.db = db
|
||||
self.table = table
|
||||
|
||||
for idx, col in enumerate(cursor.description):
|
||||
self[col[0]] = row[idx]
|
||||
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name not in ['db', 'table']:
|
||||
return self.__setitem__(name, value)
|
||||
|
||||
else:
|
||||
return super().__setattr__(name, value)
|
||||
|
||||
|
||||
def __delattr__(self, name):
|
||||
if name not in ['db', 'table']:
|
||||
return self.__delitem__(name)
|
||||
|
||||
else:
|
||||
return super().__delattr__(name)
|
||||
|
||||
|
||||
def __getattr__(self, value, default=None):
|
||||
options = [value]
|
||||
|
||||
if default:
|
||||
options.append(default)
|
||||
|
||||
if value in self.keys():
|
||||
val = super().__getitem__(*options)
|
||||
return DotDict(val) if isinstance(val, dict) else val
|
||||
|
||||
else:
|
||||
return dict.__getattr__(*options)
|
||||
|
||||
|
||||
# Kept for backwards compatibility. Delete later.
|
||||
def asdict(self):
|
||||
return self
|
||||
|
||||
|
||||
def Update(self, data={}):
|
||||
self.update(data)
|
||||
self.db.update(self.table, self.id, self)
|
||||
|
||||
|
||||
def Remove(self):
|
||||
self.db.remove(self.table, self.id)
|
||||
|
||||
|
||||
def CreateDatabase():
|
||||
for name in tables.keys():
|
||||
db.CreateTable(name)
|
||||
|
||||
db.insert('config', {'key': 'version', 'value': dbversion})
|
||||
db.insert('config', {'key': 'search', 'value': 'ddg'})
|
||||
|
||||
for engine in SearchEngines:
|
||||
db.insert('search', engine)
|
||||
|
|
|
@ -6,9 +6,12 @@ from ..Lib.IzzyLib.misc import boolean
|
|||
from ..Lib.IzzyLib import logging
|
||||
|
||||
from .connection import db, tables, ParseData
|
||||
from ..functions import psl, DateTime
|
||||
from ..functions import psl, DotDict
|
||||
|
||||
from PyQt5.QtGui import QPalette, QColor
|
||||
|
||||
|
||||
# todo: add column to specify type instead of just sending everything through Boolean
|
||||
def config(key=None, default=None):
|
||||
cache = db.cache['config']
|
||||
if len(cache.items()) == 0:
|
||||
|
@ -47,23 +50,32 @@ def permissions(domain=None, defaults=False):
|
|||
else:
|
||||
rows = [db.cache['siteoptions'].fetch(domain)] if domain else cache.values()
|
||||
|
||||
for row in rows:
|
||||
if not row and defaults:
|
||||
values = [None, None, False, False, False, False, True, True, True, True, False, True, False, False, None]
|
||||
row = db.resclass['siteoptions'](*values)
|
||||
if rows == [None]:
|
||||
rows = None
|
||||
|
||||
for k, v in row.asdict().items():
|
||||
setattr(row, k, boolean(v) if k not in ['id', 'domain', 'lastupdate'] else v)
|
||||
if not rows and defaults:
|
||||
row = DotDict()
|
||||
|
||||
if domain:
|
||||
return row
|
||||
for k, v in tables.siteoptions.items():
|
||||
default = v.get('default')
|
||||
row[k] = default if default else None
|
||||
|
||||
data.append(row)
|
||||
return row
|
||||
|
||||
else:
|
||||
for row in rows:
|
||||
for k, v in row.items():
|
||||
row[k] = boolean(v) if k not in ['id', 'domain', 'lastupdate'] else v
|
||||
|
||||
if domain:
|
||||
return row
|
||||
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def mastoaccount(name=None):
|
||||
def mastoaccount(name=None, default=False):
|
||||
cache = db.cache['mastodon']
|
||||
if len(cache.items()) == 0:
|
||||
rows = db.fetch('mastodon', single=False)
|
||||
|
@ -71,10 +83,10 @@ def mastoaccount(name=None):
|
|||
for row in rows:
|
||||
cache.store(row.fullname, row)
|
||||
|
||||
if not name:
|
||||
if not name and not default:
|
||||
return cache.values()
|
||||
|
||||
row = db.cache['mastodon'].fetch(name)
|
||||
row = db.cache['mastodon'].fetch(name if not name and default else get.config('defaccount'))
|
||||
|
||||
if not row:
|
||||
row = db.fetch('mastodon', fullname=name)
|
||||
|
@ -85,14 +97,15 @@ def mastoaccount(name=None):
|
|||
|
||||
def search(keyword):
|
||||
cache = db.cache['search']
|
||||
default = db.fetch('search', keyword=config('search'))
|
||||
row = cache.fetch(keyword)
|
||||
default = config('search')
|
||||
|
||||
if not row:
|
||||
row = db.fetch('search', keyword=keyword)
|
||||
|
||||
if not row:
|
||||
row = default
|
||||
row = db.fetch('search', keyword=default)
|
||||
keyword = default
|
||||
|
||||
if not row:
|
||||
logging.error('Not a search keyword:', keyword)
|
||||
|
@ -101,3 +114,24 @@ def search(keyword):
|
|||
cache.store(keyword, row)
|
||||
|
||||
return row
|
||||
|
||||
|
||||
def theme(name=None):
|
||||
if name:
|
||||
row = db.fetch('themes', name=name)
|
||||
|
||||
if not row:
|
||||
logging.error('Cannot find color theme:', name)
|
||||
return
|
||||
|
||||
row.palette = db.window.themes.ColorToPalette(row.asdict())
|
||||
|
||||
return row
|
||||
|
||||
else:
|
||||
rows = db.fetch('themes', single=False)
|
||||
|
||||
for row in rows:
|
||||
row.palette = db.window.themes.ColorToPalette(row.asdict())
|
||||
|
||||
return rows
|
||||
|
|
|
@ -22,7 +22,6 @@ from PyQt5.QtCore import QDateTime
|
|||
def run(version):
|
||||
global db
|
||||
backupdb = var.database + f'.backup-{version}'
|
||||
version_row = db.fetch('config', key='version')
|
||||
version = int(version)
|
||||
|
||||
if not isfile(backupdb):
|
||||
|
@ -89,4 +88,5 @@ def run(version):
|
|||
|
||||
db.UpVersion(202008150250)
|
||||
|
||||
|
||||
logging.info(f'Updated database to version {dbversion} :3')
|
||||
|
|
|
@ -106,8 +106,18 @@ def tab(tabid, url, title):
|
|||
tabrow = db.fetch('tabs', url=url)
|
||||
|
||||
if not tabrow:
|
||||
result = db.insert('tabs', {'title': title, 'url': url, 'tabid': tabid})
|
||||
op = 'insert'
|
||||
db.insert('tabs', {'title': title, 'url': url, 'tabid': tabid})
|
||||
|
||||
else:
|
||||
result = db.update('tabs', tabrow.id, {'title': title, 'url': url, 'tabid': tabid})
|
||||
op = 'update'
|
||||
tabrow.Update({'title': title, 'url': url, 'tabid': tabid})
|
||||
|
||||
|
||||
def theme(name, palette):
|
||||
row = get.theme(name)
|
||||
data = db.window.themes.PaletteToColor(palette)
|
||||
|
||||
if row:
|
||||
row.Update(data)
|
||||
|
||||
else:
|
||||
db.insert('themes', data)
|
||||
|
|
127
qtweb/dialogs.py
127
qtweb/dialogs.py
|
@ -15,10 +15,13 @@ from PyQt5.QtWidgets import *
|
|||
from PyQt5 import uic
|
||||
|
||||
|
||||
class EditBookmark(object):
|
||||
def __init__(self, title=None, url=None, row=None):
|
||||
self.ui = uic.loadUi(join(var.resources, 'editbookmark.ui'))
|
||||
self.ui.setWindowTitle('Add/Edit Bookmark')
|
||||
class EditBookmark(QDialog):
|
||||
def __init__(self, parent, title=None, url=None, row=None):
|
||||
super().__init__(parent)
|
||||
self.ui = uic.loadUi(join(var.resources, 'dialog-bookmark.ui'))
|
||||
self.parent = parent
|
||||
self.setLayout(self.ui.layout())
|
||||
self.setWindowTitle('Add/Edit Bookmark')
|
||||
|
||||
self.row = row if row else db.fetch('bookmarks', url=url)
|
||||
|
||||
|
@ -57,19 +60,23 @@ class EditBookmark(object):
|
|||
if action:
|
||||
action['cmd'](*action.get('args', []), **action.get('kwargs', {}))
|
||||
|
||||
self.ui.deleteLater()
|
||||
del self
|
||||
self.close()
|
||||
self.deleteLater()
|
||||
del self.parent.open_dialogs['bookmarkedit']
|
||||
|
||||
|
||||
class SiteSettings(object):
|
||||
def __init__(self, host):
|
||||
self.ui = uic.loadUi(join(var.resources, 'sitesettings.ui'))
|
||||
class SiteSettings(QDialog):
|
||||
def __init__(self, parent, host):
|
||||
super().__init__(parent)
|
||||
self.ui = uic.loadUi(join(var.resources, 'dialog-sitesettings.ui'))
|
||||
self.setLayout(self.ui.layout())
|
||||
self.parent = parent
|
||||
self.domain = host
|
||||
self.ui.setWindowTitle(self.domain.title())
|
||||
self.setWindowTitle(self.domain.title())
|
||||
|
||||
self.row = get.permissions(self.domain, defaults=True)
|
||||
|
||||
if not self.row:
|
||||
if not self.row.domain:
|
||||
self.ui.reset.setEnabled(False)
|
||||
|
||||
else:
|
||||
|
@ -82,6 +89,7 @@ class SiteSettings(object):
|
|||
self.ui.location.setChecked(self.row.location)
|
||||
self.ui.camera.setChecked(self.row.camera)
|
||||
self.ui.allowhttp.setChecked(self.row.allowhttp)
|
||||
self.ui.mastodon.setChecked(self.row.mastodon)
|
||||
|
||||
self.ui.save.clicked.connect(lambda : self._button_click('save'))
|
||||
self.ui.reset.clicked.connect(lambda : self._button_click('reset'))
|
||||
|
@ -100,7 +108,8 @@ class SiteSettings(object):
|
|||
'location': self.ui.location.isChecked(),
|
||||
'fullscreen': self.ui.fullscreen.isChecked(),
|
||||
'adblock': self.ui.adblock.isChecked(),
|
||||
'allowhttp': self.ui.allowhttp.isChecked()
|
||||
'allowhttp': self.ui.allowhttp.isChecked(),
|
||||
'mastodon': self.ui.mastodon.isChecked()
|
||||
}
|
||||
|
||||
actions = {
|
||||
|
@ -113,17 +122,20 @@ class SiteSettings(object):
|
|||
if action:
|
||||
action['cmd'](*action.get('args', []), **action.get('kwargs', {}))
|
||||
|
||||
self.close()
|
||||
self.ui.deleteLater()
|
||||
del self
|
||||
del self.parent.open_dialogs['siteedit']
|
||||
|
||||
|
||||
class Toot():
|
||||
def __init__(self, window, mastodon, data=None, reply=None):
|
||||
self.window = window
|
||||
self.mastodon = mastodon
|
||||
class Toot(QDialog):
|
||||
def __init__(self, parent, data=None, reply=None):
|
||||
super().__init__(parent)
|
||||
self.ui = uic.loadUi(join(var.resources, 'dialog-toot.ui'))
|
||||
self.setLayout(self.ui.layout())
|
||||
self.setWindowTitle('New Toot')
|
||||
|
||||
self.ui = uic.loadUi(join(var.resources, 'toot.ui'))
|
||||
self.ui.setWindowTitle('New Toot')
|
||||
self.parent = parent
|
||||
self.mastodon = parent.mastodon
|
||||
self.accounts = {}
|
||||
self.reply = reply
|
||||
self.post = None
|
||||
|
@ -196,7 +208,7 @@ class Toot():
|
|||
def _get_post_data(self):
|
||||
currentacct = self.ui.account.currentData()
|
||||
|
||||
data = self.window.mastodon.GetPostData(self.reply, currentacct)
|
||||
data = self.mastodon.GetPostData(self.reply, currentacct)
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
@ -253,34 +265,34 @@ class Toot():
|
|||
if action:
|
||||
action['cmd'](*action.get('args', []), **action.get('kwargs', {}))
|
||||
|
||||
self.close()
|
||||
self.ui.deleteLater()
|
||||
del self
|
||||
|
||||
|
||||
def show(self):
|
||||
self.ui.exec_()
|
||||
del self.parent.open_dialogs['toot']
|
||||
|
||||
|
||||
class NewDownload(QDialog):
|
||||
def __init__(self, download, profile):
|
||||
super().__init__()
|
||||
def __init__(self, profile, download):
|
||||
super().__init__(profile.window)
|
||||
|
||||
self.ui = uic.loadUi(join(var.resources, 'newdownload.ui'))
|
||||
self.ui = uic.loadUi(join(var.resources, 'dialog-download.ui'))
|
||||
self.setWindowTitle('Download File: ')
|
||||
self.setModal(True)
|
||||
self.setFixedHeight(200)
|
||||
self.setLayout(self.ui.layout())
|
||||
|
||||
self.download = download[0]
|
||||
self.md5 = GetMD5(self.download.url().toString() + '.md5') if get.config('automd5') else None
|
||||
self.download = download
|
||||
self.md5 = GetMD5(self.download.item.url().toString() + '.md5') if get.config('automd5') else None
|
||||
|
||||
download.md5 = self.md5
|
||||
|
||||
self.profile = profile
|
||||
self.finished = False
|
||||
|
||||
if debstable:
|
||||
## This is a half-assed fix and needs to be improved later
|
||||
self.file = Path(self.download.url().path()).name
|
||||
self.file = Path(self.download.item.url().path()).name
|
||||
else:
|
||||
self.file = self.download.suggestedFileName()
|
||||
self.file = self.download.item.suggestedFileName()
|
||||
|
||||
self.dir = get.config('dlpath', var.downloadpath)
|
||||
self.ui.md5.setText(self.md5)
|
||||
|
@ -292,12 +304,11 @@ class NewDownload(QDialog):
|
|||
self.ui.reset.clicked.connect(self._reset)
|
||||
self.ui.cancel.clicked.connect(lambda *args: self._handle_close_button('cancel'))
|
||||
self.ui.save.clicked.connect(lambda *args: self._handle_close_button('save'))
|
||||
self.download.finished.connect(lambda: profile._download_finished([self.download, self.md5]))
|
||||
|
||||
self._reset()
|
||||
self.show()
|
||||
|
||||
url = self.download.url().toString()
|
||||
url = self.download.item.url().toString()
|
||||
urltext = QFontMetrics(self.ui.urlfilename.font()).elidedText(url, Qt.TextElideMode.ElideMiddle, self.ui.urlfilename.width() - 20)
|
||||
self.ui.urlfilename.setText(urltext)
|
||||
|
||||
|
@ -312,7 +323,7 @@ class NewDownload(QDialog):
|
|||
|
||||
def _hash_changed(self):
|
||||
text = self.ui.md5.text()
|
||||
self.md5 = None if text == '' else text
|
||||
self.download.item.md5 = None if text == '' else text
|
||||
|
||||
|
||||
def _pick_filename(self):
|
||||
|
@ -328,17 +339,17 @@ class NewDownload(QDialog):
|
|||
|
||||
|
||||
def _handle_close_button(self, button):
|
||||
url = self.download.url().toString()
|
||||
url = self.download.item.url().toString()
|
||||
|
||||
if button == 'cancel':
|
||||
logging.verbose('Cancelled download of', url)
|
||||
self.download.cancel()
|
||||
self.download.item.cancel()
|
||||
|
||||
elif button == 'save':
|
||||
logging.verbose('Starting download of', url)
|
||||
self.SetFilename()
|
||||
self.download.accept()
|
||||
self.profile.notif.New('Starting Download', url)
|
||||
self.download.item.accept()
|
||||
self.download.item.finished.connect(lambda: self.profile._download_finished(self.download))
|
||||
|
||||
self.finished = True
|
||||
self.close()
|
||||
|
@ -349,8 +360,40 @@ class NewDownload(QDialog):
|
|||
filename = Path(join(self.ui.filename.text()))
|
||||
|
||||
if debstable:
|
||||
self.download.setPath(str(filename))
|
||||
self.download.item.setPath(str(filename))
|
||||
|
||||
else:
|
||||
self.download.setDownloadDirectory(str(filename.parent))
|
||||
self.download.setDownloadFileName(str(filename.name))
|
||||
self.download.item.setDownloadDirectory(str(filename.parent))
|
||||
self.download.item.setDownloadFileName(str(filename.name))
|
||||
|
||||
|
||||
class HttpAuth(QDialog):
|
||||
def __init__(self, url, authenticator, parent):
|
||||
super().__init__(parent.window)
|
||||
|
||||
self.ui = uic.loadUi(join(var.resources, 'dialog-authentication.ui'))
|
||||
self.ui.savepass.setEnabled(False)
|
||||
self.setLayout(self.ui.layout())
|
||||
|
||||
self.webview = parent
|
||||
self.url = url
|
||||
self.auth = authenticator
|
||||
self.username = None
|
||||
self.password = None
|
||||
self.result = False
|
||||
|
||||
text = self.ui.realm.text().format(host=url.host(), msg=self.auth.realm())
|
||||
self.ui.realm.setText(text)
|
||||
|
||||
self.ui.login.clicked.connect(lambda : self._handle_close_button('login'))
|
||||
self.ui.cancel.clicked.connect(self._handle_close_button)
|
||||
|
||||
|
||||
def _handle_close_button(self, button=None):
|
||||
if button == 'login':
|
||||
self.auth.setUser(self.ui.username.text())
|
||||
self.auth.setPassword(self.ui.password.text())
|
||||
self.result = True
|
||||
|
||||
self.close()
|
||||
self.deleteLater()
|
||||
|
|
106
qtweb/filter.py
106
qtweb/filter.py
|
@ -1,106 +0,0 @@
|
|||
import threading, sys, os
|
||||
|
||||
from datetime import datetime
|
||||
from os.path import join
|
||||
|
||||
from .Lib.IzzyLib import logging
|
||||
from publicsuffixlist import PublicSuffixList
|
||||
from publicsuffixlist.update import updatePSL
|
||||
|
||||
from . import pyqtver, debstable
|
||||
from .database import db, get, put
|
||||
from .functions import GetSoftware, httpclient
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
|
||||
|
||||
|
||||
#if hasattr(sys, '_MEIPASS'):
|
||||
#os.makedirs(join(sys._MEIPASS, 'publicsuffixlist'), exist_ok=True)
|
||||
#with open(join(sys._MEIPASS, 'publicsuffixlist', 'public_suffix_list.dat'), 'w') as fd:
|
||||
#resp = httpclient.request('GET', 'https://publicsuffix.org/list/public_suffix_list.dat')
|
||||
#fd.write(resp.data.decode())
|
||||
|
||||
|
||||
psl = PublicSuffixList()
|
||||
extract = psl.privatesuffix
|
||||
|
||||
|
||||
## blocked domains and urls (will be optional in the future)
|
||||
blockedDomain = [
|
||||
'doubleclick.net',
|
||||
'rubiconproject.com',
|
||||
'outbrain.com',
|
||||
'googletagservices.com',
|
||||
'amazon-adsystem.com',
|
||||
'optimizely.com',
|
||||
'adswizz.com'
|
||||
]
|
||||
|
||||
blockedURL = {
|
||||
'youtube.com': [
|
||||
'/api/stats/ads',
|
||||
'/pagead/adview',
|
||||
'/pagead/lvz'
|
||||
'/get_midroll/info'
|
||||
],
|
||||
'soundcloud.com': [
|
||||
'/audio-ads'
|
||||
],
|
||||
'facebook.com': [
|
||||
'/tr']
|
||||
}
|
||||
|
||||
|
||||
class WebFilter(QWebEngineUrlRequestInterceptor):
|
||||
def __init__(self, window):
|
||||
super().__init__()
|
||||
self.window = window
|
||||
self.threads = {}
|
||||
|
||||
def interceptRequest(self, info):
|
||||
requrl = info.requestUrl()
|
||||
reqdomain = requrl.host()
|
||||
reqtopdomain = extract(reqdomain)
|
||||
reqpath = requrl.path()
|
||||
|
||||
pageurl = info.firstPartyUrl()
|
||||
pagedomain = pageurl.host()
|
||||
pagetopdomain = extract(pagedomain)
|
||||
pagepath = pageurl.path()
|
||||
|
||||
pagerow = get.permissions(pagedomain, defaults=True)
|
||||
|
||||
## Prevent secure pages from loading insecure content
|
||||
if not pagerow.allowhttp and requrl.scheme() == 'http' and pageurl.scheme() == 'https':
|
||||
logging.verbose('Blocked insecure content:', requrl.toString().split('?', 1)[0])
|
||||
info.block(True)
|
||||
return
|
||||
|
||||
## Block requests if they're in the block list
|
||||
if self.window.adblock and pagerow.adblock and (reqpath in blockedURL.get(reqdomain, []) or reqdomain in blockedDomain or reqtopdomain in blockedDomain):
|
||||
logging.verbose('Blocked content:', requrl.toString().split('?', 1)[0])
|
||||
info.block(True)
|
||||
return
|
||||
|
||||
## Set custom headers
|
||||
info.setHttpHeader("trans-rights".encode(), "are human rights".encode())
|
||||
|
||||
## Check if domain is a mastodon instance
|
||||
if not pagerow.domain and not pagerow.mastodon and not any(map(requrl.toString().startswith, ['about:', 'chrome://'])):
|
||||
if not pagedomain:
|
||||
return
|
||||
|
||||
if self.threads.get(pagedomain):
|
||||
return
|
||||
|
||||
self.threads[pagedomain] = threading.Thread(target=GetSoftware, args=[pagedomain])
|
||||
self.threads[pagedomain].start()
|
||||
|
||||
## Delete the thread object once it's finished
|
||||
elif self.threads.get(pagedomain):
|
||||
del self.threads[pagedomain]
|
||||
|
||||
## Google is a fuck and is very picky about what browsers are supported
|
||||
#if 'google.com' in [pagetopdomain, reqtopdomain]:
|
||||
#info.setHttpHeader('user-agent'.encode(), 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0'.encode())
|
|
@ -7,6 +7,7 @@ from urllib.parse import urlparse
|
|||
from datetime import datetime
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
from collections import OrderedDict
|
||||
|
||||
import urllib3
|
||||
|
||||
|
@ -47,14 +48,19 @@ class Notifications(object):
|
|||
self.New('Error', message)
|
||||
|
||||
|
||||
def Icon(name):
|
||||
def Icon(name, size=None):
|
||||
filename = join(var.iconpath, name + '.svg')
|
||||
|
||||
if not isfile(filename):
|
||||
logging.error('Invalid icon name:', name)
|
||||
return QIcon(join(var.iconpath, 'missing.svg'))
|
||||
icon = QIcon(join(var.iconpath, 'missing.svg'))
|
||||
|
||||
return QIcon(filename)
|
||||
icon = QIcon(filename)
|
||||
|
||||
if size:
|
||||
icon = QIcon(icon.pixmap(16))
|
||||
|
||||
return icon
|
||||
|
||||
|
||||
def AddShortcut(key, action, widget):
|
||||
|
@ -243,25 +249,162 @@ def Decode(byte):
|
|||
return TextCodec.toUnicode(byte)
|
||||
|
||||
|
||||
## subclass QMenu because there's no way to get the number of items
|
||||
## subclass QWebEngineScript to add extra functions
|
||||
class WebEngineScript(QWebEngineScript):
|
||||
def __init__(self, collection=None, filename=None, user=True):
|
||||
super().__init__()
|
||||
|
||||
self.collection = collection
|
||||
self.user = user
|
||||
self.filename = None
|
||||
self.enabled = False
|
||||
|
||||
if filename:
|
||||
self.initLoad(filename)
|
||||
|
||||
|
||||
def initLoad(self, filename):
|
||||
self.filename = Path(filename)
|
||||
|
||||
with self.filename.open() as fd:
|
||||
self.setSourceCode(fd.read())
|
||||
|
||||
|
||||
def enable(self):
|
||||
if not self.enabled:
|
||||
logging.verbose('Enabling script:', self.name())
|
||||
self.enabled = True
|
||||
return self.collection.insert(self)
|
||||
|
||||
|
||||
def disable(self):
|
||||
if self.enabled:
|
||||
logging.debug('Disabling script:', self.name())
|
||||
self.enabled = False
|
||||
return self.collection.remove(self)
|
||||
|
||||
|
||||
## subclass QMenu because there's no easy way to get specific items
|
||||
class Menu(QMenu):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.count = 0
|
||||
def __init__(self, parent, title=None):
|
||||
super().__init__(parent)
|
||||
self.items = OrderedDict()
|
||||
|
||||
def AddAction(self, name, callback):
|
||||
self.addAction(name, callback)
|
||||
self.count += 1
|
||||
if title:
|
||||
self.setTitle(title)
|
||||
|
||||
|
||||
def AddItem(self, label, callback, *args, icon=None, **kwargs):
|
||||
if self.items.get(label):
|
||||
logging.error('An action with this name already exists:', label)
|
||||
return
|
||||
|
||||
self.items[label] = (self.addAction(label, lambda : callback(*args, **kwargs)))
|
||||
|
||||
if icon:
|
||||
self.items[label].setIcon(icon if type(icon) == QIcon else QIcon(icon))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def AddItemAfter(self, indexLabel, label, callback, *args, **kwargs):
|
||||
index = self.items.get(indexLabel)
|
||||
newdict = self.items.__class__()
|
||||
|
||||
if not index:
|
||||
self.error('Not a menu action:', indexLabel)
|
||||
return
|
||||
|
||||
self.clear()
|
||||
|
||||
for name, action in self.items.items():
|
||||
newdict[name] = action
|
||||
|
||||
if name == indexLabel:
|
||||
newdict[label] = self.AddItem(label, callback, *args, **kwargs)
|
||||
|
||||
self.items.clear()
|
||||
self.items.update(newdict)
|
||||
return True
|
||||
|
||||
|
||||
def AddActionAfter(self, action, indexAction):
|
||||
label = action.text()
|
||||
indexLabel = index.text()
|
||||
index = self.items.get(indexLabel)
|
||||
newdict = self.items.__class__()
|
||||
|
||||
if not index:
|
||||
self.error('Not a menu action:', indexLabel)
|
||||
return
|
||||
|
||||
self.clear()
|
||||
|
||||
for name, action in self.items.items():
|
||||
newdict[name] = self.addAction(name, action)
|
||||
|
||||
if name == indexLabel:
|
||||
newdict[label] = self.AddItem(label, callback, *args, **kwargs)
|
||||
|
||||
self.items.clear()
|
||||
self.items.update(newdict)
|
||||
return True
|
||||
|
||||
|
||||
def MoveItem(self, label, indexLabel):
|
||||
action = self.items.get(label)
|
||||
index = self.items.get(indexLabel)
|
||||
newdict = self.items.__class__()
|
||||
|
||||
if None in [action, index]:
|
||||
logging.error(f'Invalid action or index action: {label}: {action}, {indexLabel}: {index}')
|
||||
return
|
||||
|
||||
self.clear()
|
||||
|
||||
for name, action in self.items.items():
|
||||
newdict[name] = self.addAction(name, action)
|
||||
|
||||
if name == indexLabel:
|
||||
newdict[label] = self.addAction(label, action)
|
||||
|
||||
self.items.clear()
|
||||
self.items.update(newdict)
|
||||
return True
|
||||
|
||||
|
||||
def RemoveItem(self, label):
|
||||
action = self.items.get(label)
|
||||
|
||||
if not action:
|
||||
logging.error('Invalid action:', label)
|
||||
return
|
||||
|
||||
self.removeAction(action)
|
||||
action.DeleteLater()
|
||||
del action
|
||||
return True
|
||||
|
||||
|
||||
def AddSeparator(self, name='separator'):
|
||||
item = self.items.get(name)
|
||||
count = 0
|
||||
|
||||
while item:
|
||||
count += 1
|
||||
item = self.items.get(name + str(count))
|
||||
|
||||
label = name + str(count) if count > 0 else name
|
||||
self.items[label] = self.addSeparator()
|
||||
return True
|
||||
|
||||
def AddSeparator(self):
|
||||
self.addSeparator()
|
||||
self.count += 1
|
||||
|
||||
def MoreThan(self, num):
|
||||
return self.count > num
|
||||
return len(self.items) > num
|
||||
|
||||
|
||||
def LessThan(self, num):
|
||||
return self.count < num
|
||||
return len(self.items) < num
|
||||
|
||||
|
||||
def DateTime(timestamp):
|
||||
|
@ -288,38 +431,98 @@ def PrintMethods(obj):
|
|||
|
||||
|
||||
def ParseSoundcloudInfo(data):
|
||||
if not data:
|
||||
return False
|
||||
|
||||
data = data.split(':::')
|
||||
|
||||
if len(data) != 6:
|
||||
logging.debug('Wrong number of soundcloud data slices:', data)
|
||||
return
|
||||
return False
|
||||
|
||||
artist, artistLink, title, titleLink, progress, length = data
|
||||
|
||||
info = {
|
||||
info = DotDict({
|
||||
'artist': unescape(artist),
|
||||
'artistLink': artistLink,
|
||||
'title': unescape(title.replace('Current track: ', '')),
|
||||
'titleLink': titleLink,
|
||||
'progress': progress,
|
||||
'length': length
|
||||
}
|
||||
})
|
||||
|
||||
return info
|
||||
|
||||
|
||||
class DotDict(dict):
|
||||
def __init__(self, value=None, **kwargs):
|
||||
super().__init__()
|
||||
self.__setattr__ = dict.__setitem__
|
||||
self.__delattr__ = dict.__delitem__
|
||||
self.__getitem__ = self.__getattr__
|
||||
|
||||
if value.__class__ == str:
|
||||
self.FromJson(value)
|
||||
|
||||
elif value.__class__ in [dict, DotDict]:
|
||||
self.update(value)
|
||||
|
||||
elif value:
|
||||
raise TypeError('The value must be a JSON string, dict, or another DotDict object, not', value.__class__)
|
||||
|
||||
if kwargs:
|
||||
print('From kwargs')
|
||||
self.update(kwargs)
|
||||
|
||||
|
||||
def ToJson(self, **kwargs):
|
||||
return self.__str__(**kwargs)
|
||||
|
||||
|
||||
def FromJson(self, string):
|
||||
data = json.loads(string)
|
||||
self.update(data)
|
||||
|
||||
|
||||
def __parse_item(self, data):
|
||||
return DotDict(data) if type(data) == dict else data
|
||||
|
||||
|
||||
def items(self):
|
||||
data = []
|
||||
|
||||
for k, v in super(DotDict, self).items():
|
||||
value = self.__parse_item(v)
|
||||
data.append((k, value))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def __str__(self, **kwargs):
|
||||
return json.dumps(self, **kwargs)
|
||||
|
||||
|
||||
def __getattr__(self, value, default=None):
|
||||
val = self.get(value, default) if default else self[value]
|
||||
return self.__parse_item(val)
|
||||
|
||||
|
||||
def ExceptionHandler(exctype, value, tb):
|
||||
lines = [line for line in traceback.format_exception(exctype, value, tb)]
|
||||
sys._excepthook(exctype, value, tb)
|
||||
|
||||
if lines[-1] in ['\n', '\n ']:
|
||||
lines.remove(lines)
|
||||
# Apparently the line above is enough to not bring down the whole browser
|
||||
# Gonna keep custom handler here for now though
|
||||
#lines = [line for line in traceback.format_exception(exctype, value, tb)]
|
||||
|
||||
error = ''.join(lines)
|
||||
print(f'\n------------\n{error}------------')
|
||||
#if lines[-1] in ['\n', '\n ']:
|
||||
#lines.remove(lines)
|
||||
|
||||
#error = ''.join(lines)
|
||||
#print(f'\n------------\n{error}------------')
|
||||
|
||||
|
||||
TextCodec = QTextCodec.codecForMib(106)
|
||||
useragent = UserAgentGen()
|
||||
httptimeout = urllib3.Timeout(connect=5, read=2)
|
||||
httpclient = urllib3.PoolManager(num_pools=100, timeout=httptimeout, headers={'user-agent': useragent})
|
||||
httpclient = urllib3.PoolManager(num_pools=100, timeout=httptimeout, headers={'user-agent': f'{__application__}/{__version__}'})
|
||||
psl = PublicSuffixList()
|
||||
|
|
264
qtweb/main.py
264
qtweb/main.py
|
@ -1,29 +1,27 @@
|
|||
import sys, os, time, json, signal, asyncio, tracemalloc
|
||||
import sys, os, json, signal, asyncio, tracemalloc
|
||||
|
||||
from optparse import OptionParser
|
||||
from urllib.parse import urlencode, urlparse, quote_plus
|
||||
from urllib.parse import urlparse, quote_plus
|
||||
from os.path import join, abspath
|
||||
from base64 import b64encode, b64decode
|
||||
from pathlib import Path
|
||||
|
||||
from .Lib.IzzyLib import logging
|
||||
from .Lib.IzzyLib.misc import boolean
|
||||
|
||||
from . import isWindows, isLinux, debstable, dialogs, tabs, __version__
|
||||
from . import isWindows, debstable, dialogs, tabs, __version__
|
||||
from .urlhandlers import LocalHandler, RegisterScheme
|
||||
from .webview import WebEngineView
|
||||
from .profile import WebEngineProfile
|
||||
from .webprofile import WebEngineProfile
|
||||
from .config import var
|
||||
from .database import db, get, put
|
||||
from .mastodon import Mastodon
|
||||
from .mbus import Client, Server
|
||||
from .themes import SetupThemes, ChangeTheme
|
||||
#from .themes import ColorTheme
|
||||
from .arguments import args
|
||||
from .functions import (
|
||||
Icon, AddShortcut, UserAgentGen,
|
||||
ExceptionHandler, PrintMethods, Notifications, Menu
|
||||
Icon, AddShortcut, UserAgentGen, DotDict,
|
||||
ExceptionHandler, Notifications, Menu
|
||||
)
|
||||
|
||||
|
||||
if isWindows:
|
||||
import ctypes
|
||||
|
||||
|
@ -42,6 +40,7 @@ from PyQt5 import uic
|
|||
|
||||
|
||||
## Prevent errors from bringing down the whole browser
|
||||
sys._excepthook = sys.excepthook
|
||||
sys.excepthook = ExceptionHandler
|
||||
|
||||
logging.setConfig({'level': get.config('loglevel', 'INFO')})
|
||||
|
@ -62,11 +61,15 @@ class Browser(QMainWindow):
|
|||
else:
|
||||
self.snapshot = None
|
||||
|
||||
SetupThemes(app)
|
||||
if not debstable:
|
||||
RegisterScheme(var.local)
|
||||
|
||||
db.window = self
|
||||
|
||||
self.jsdebug = False
|
||||
self.darktheme = True if get.config('darktheme') in [1, '1'] else False
|
||||
self.app = app
|
||||
self.default_style = app.style().objectName().lower()
|
||||
self.default_palette = app.style().standardPalette()
|
||||
#self.themes = ColorTheme(self)
|
||||
self.clipboard = app.clipboard()
|
||||
self.threadpool = QThreadPool()
|
||||
self.notif = Notifications()
|
||||
|
@ -77,7 +80,7 @@ class Browser(QMainWindow):
|
|||
'y': 100,
|
||||
'maximized': True
|
||||
}
|
||||
|
||||
|
||||
self.statedata.update(json.loads(get.config('state', "{}")))
|
||||
|
||||
## Setup empty variables for use later
|
||||
|
@ -86,17 +89,22 @@ class Browser(QMainWindow):
|
|||
self.socket = None
|
||||
self.urlhover = None
|
||||
self.fullscreen = False
|
||||
self.widgettabs = {}
|
||||
self.shutdown = False
|
||||
self.widgettabs = DotDict()
|
||||
self.open_dialogs = {}
|
||||
|
||||
WebEngineProfile(self)
|
||||
|
||||
#app.setStyle('fusion')
|
||||
#ChangeTheme()
|
||||
self.profile = WebEngineProfile(self)
|
||||
self.local_scheme_handler = LocalHandler(self)
|
||||
self.mastodon = Mastodon()
|
||||
|
||||
#logging.setConfig({'systemnotif': self.notif})
|
||||
|
||||
if self.darktheme:
|
||||
ChangeTheme('dark')
|
||||
#app.setStyle('fusion')
|
||||
#self.themes.ChangeTheme()
|
||||
|
||||
# Re-enable when color themes are fixed
|
||||
#if theme != 'Default':
|
||||
#self.themes.ChangeTheme(theme)
|
||||
|
||||
self.ui = uic.loadUi(join(var.resources, 'window.ui'))
|
||||
self.setCentralWidget(self.ui)
|
||||
|
@ -142,19 +150,15 @@ class Browser(QMainWindow):
|
|||
self.ui.toot.clicked.connect(lambda : self._open_dialog('toot'))
|
||||
|
||||
## Keyboard shortcuts
|
||||
AddShortcut('Ctrl+L', self._focus_urlbar, self.ui.url)
|
||||
AddShortcut('Ctrl+T', lambda *args: self.NewWebTab(switch=True), self.ui)
|
||||
AddShortcut('Ctrl+R', lambda *args: self.ui.tabs.currentWidget().reload(), self.ui)
|
||||
AddShortcut('Ctrl+W', lambda *args: self.CloseTab(), self.ui)
|
||||
AddShortcut('Ctrl+N', lambda *args: self.NewWebTab('https://www.youtube.com/watch?v=dQw4w9WgXcQ', switch=True), self.ui)
|
||||
AddShortcut('Ctrl+F', lambda *args: self.ui.tabs.currentWidget().searchbar.Search('toggle'), self.ui)
|
||||
AddShortcut('F3', lambda *args: self.ui.tabs.currentWidget().searchbar.Search('toggle'), self.ui)
|
||||
AddShortcut('F11', lambda *args: self.Fullscreen(), self.ui)
|
||||
|
||||
# keeping this here for testing purposes
|
||||
#self.profile.setHttpUserAgent('Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0')
|
||||
|
||||
self.mastodon = Mastodon(self.profile.httpUserAgent())
|
||||
AddShortcut('Ctrl+L', self._focus_urlbar, self)
|
||||
AddShortcut('Ctrl+T', lambda *args: self.NewWebTab(switch=True), self)
|
||||
AddShortcut('Ctrl+R', lambda *args: self.ui.tabs.currentWidget().reload(), self)
|
||||
AddShortcut('Ctrl+W', lambda *args: self.CloseTab(), self)
|
||||
AddShortcut('Ctrl+N', lambda *args: self.NewWebTab('https://www.youtube.com/watch?v=dQw4w9WgXcQ', switch=True), self)
|
||||
AddShortcut('Ctrl+F', lambda *args: self.ui.tabs.currentWidget().searchbar.Search('toggle'), self)
|
||||
AddShortcut('F3', lambda *args: self.ui.tabs.currentWidget().searchbar.Search('toggle'), self)
|
||||
AddShortcut('F5', lambda *args: self.ui.tabs.currentWidget().reload(), self)
|
||||
AddShortcut('F11', lambda *args: self.Fullscreen(), self)
|
||||
|
||||
for url in urls:
|
||||
self.NewWebTab(url)
|
||||
|
@ -162,13 +166,8 @@ class Browser(QMainWindow):
|
|||
for row in db.fetch('tabs', sort='tabid', single=False):
|
||||
self.NewWebTab(row.url)
|
||||
|
||||
try:
|
||||
activetab = int(get.config('activetab', 0))
|
||||
self.ui.tabs.setCurrentIndex(activetab)
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
activetab = int(get.config('activetab', 0))
|
||||
self.ui.tabs.setCurrentIndex(activetab)
|
||||
self._prevent_empty_tabview()
|
||||
|
||||
## Restore the window position and size
|
||||
|
@ -203,49 +202,14 @@ class Browser(QMainWindow):
|
|||
self.ui.url.selectAll()
|
||||
|
||||
|
||||
def _close_window(self, event=None):
|
||||
lock = Path(var.lock)
|
||||
opentabs = []
|
||||
tabrange = list(reversed(range(0, self.ui.tabs.count())))
|
||||
put.config('activetab', str(self.ui.tabs.currentIndex()))
|
||||
self.SaveWinState()
|
||||
|
||||
for tabid in ([0] if len(tabrange) < 1 else tabrange):
|
||||
widget = self.ui.tabs.widget(tabid)
|
||||
|
||||
if widget.windowTitle():
|
||||
continue
|
||||
|
||||
title = widget.title()
|
||||
url = widget.url().toString()
|
||||
|
||||
opentabs.append(url)
|
||||
put.tab(tabid, url, title)
|
||||
|
||||
self.CloseTab(tabid, shutdown=True)
|
||||
|
||||
for row in db.fetch('tabs', single=False):
|
||||
if row.url not in opentabs:
|
||||
db.remove('tabs', row.id)
|
||||
|
||||
db.close()
|
||||
logging.verbose('Saved state and tabs. Exiting...')
|
||||
|
||||
for srv in [self.server, self.socket]:
|
||||
if srv:
|
||||
srv.stop()
|
||||
|
||||
if event:
|
||||
event.accept()
|
||||
|
||||
if lock.exists():
|
||||
lock.unlink()
|
||||
|
||||
|
||||
def _button_click(self, button):
|
||||
widget = self.ui.tabs.currentWidget()
|
||||
homepage = get.config('homepage', 'https://ddg.gg')
|
||||
|
||||
if button == 'newtab':
|
||||
self.NewWebTab(switch=True)
|
||||
return
|
||||
|
||||
if hasattr(widget, 'LoadUrl'):
|
||||
actions = {
|
||||
'back': {'cmd': widget.back},
|
||||
|
@ -253,7 +217,6 @@ class Browser(QMainWindow):
|
|||
'stop': {'cmd': widget.stop},
|
||||
'refresh': {'cmd': widget.reload},
|
||||
'home': {'cmd': widget.LoadUrl, 'args': [homepage]},
|
||||
'newtab': {'cmd': self.NewWebTab, 'kwargs': {'switch': True}},
|
||||
'go': {'cmd': widget.LoadUrl, 'args': [self.ui.url.text()]}
|
||||
}
|
||||
|
||||
|
@ -277,8 +240,10 @@ class Browser(QMainWindow):
|
|||
logging.error('No mastodon accounts')
|
||||
return
|
||||
|
||||
widget = dialogs.Toot(self, self.mastodon)
|
||||
widget.show()
|
||||
widget = self.open_dialogs.get(dialog)
|
||||
|
||||
if widget:
|
||||
widget.activateWindow()
|
||||
return
|
||||
|
||||
webview = self.ui.tabs.currentWidget()
|
||||
|
@ -292,6 +257,10 @@ class Browser(QMainWindow):
|
|||
'siteedit': {
|
||||
'cmd': dialogs.SiteSettings,
|
||||
'args': [webview.url().host()]
|
||||
},
|
||||
'toot': {
|
||||
'cmd': dialogs.Toot,
|
||||
'args': [self]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,7 +269,8 @@ class Browser(QMainWindow):
|
|||
if not action:
|
||||
return
|
||||
|
||||
action['cmd'](*action.get('args', []), **action.get('kwargs', {})).ui.show()
|
||||
self.open_dialogs[dialog] = action['cmd'](self, *action.get('args', []), **action.get('kwargs', {}))
|
||||
self.open_dialogs[dialog].show()
|
||||
|
||||
|
||||
def _update_webaction_buttons(self, name=None, state=None):
|
||||
|
@ -360,6 +330,55 @@ class Browser(QMainWindow):
|
|||
self.NewWebTab()
|
||||
|
||||
|
||||
def closeEvent(self, event=None):
|
||||
## Make sure this function only fires once
|
||||
if self.shutdown:
|
||||
return
|
||||
|
||||
self.shutdown = True
|
||||
|
||||
if event:
|
||||
event.accept()
|
||||
|
||||
lock = Path(var.lock)
|
||||
opentabs = []
|
||||
tabrange = list(reversed(range(0, self.ui.tabs.count())))
|
||||
put.config('activetab', str(self.ui.tabs.currentIndex()))
|
||||
self.SaveWinState()
|
||||
|
||||
for tabid in ([0] if len(tabrange) < 1 else tabrange):
|
||||
widget = self.ui.tabs.widget(tabid)
|
||||
|
||||
if widget.windowTitle():
|
||||
continue
|
||||
|
||||
title = widget.title()
|
||||
url = widget.url().toString()
|
||||
|
||||
if widget.devpage.view:
|
||||
widget.devpage.closeEvent()
|
||||
widget.devpage.destroy()
|
||||
|
||||
opentabs.append(url)
|
||||
put.tab(tabid, url, title)
|
||||
|
||||
self.CloseTab(tabid, shutdown=True)
|
||||
|
||||
for row in db.fetch('tabs', single=False):
|
||||
if row.url not in opentabs:
|
||||
db.remove('tabs', row.id)
|
||||
|
||||
db.close()
|
||||
logging.verbose('Saved state and tabs. Exiting...')
|
||||
|
||||
for srv in [self.server, self.socket]:
|
||||
if srv:
|
||||
srv.stop()
|
||||
|
||||
if lock.exists():
|
||||
lock.unlink()
|
||||
|
||||
|
||||
def SaveWinState(self, width=None, height=None, x=None, y=None):
|
||||
winstate = self.windowState() in [Qt.WindowActive, Qt.WindowNoState]
|
||||
maximized = self.windowState() == Qt.WindowMaximized
|
||||
|
@ -413,82 +432,82 @@ class Browser(QMainWindow):
|
|||
|
||||
|
||||
def NewWebTab(self, url=None, switch=False, nexttab=False):
|
||||
#layout = QVBoxLayout()
|
||||
webview = WebEngineView(self, url)
|
||||
#layout.addWidget(webview.search)
|
||||
#layout.addWidget(webview)
|
||||
webview = WebEngineView(self, url if url else get.config('homepage', 'https://ddg.gg'))
|
||||
|
||||
if nexttab and get.config('nexttab', True):
|
||||
index = self.ui.tabs.currentIndex() + 1
|
||||
self.ui.tabs.insertTab(index, webview, 'New Tab')
|
||||
current_index = self.ui.tabs.currentIndex() + 1
|
||||
index = self.ui.tabs.insertTab(current_index, webview, 'New Tab')
|
||||
|
||||
else:
|
||||
self.ui.tabs.addTab(webview, 'New Tab')
|
||||
index = self.ui.tabs.addTab(webview, 'New Tab')
|
||||
|
||||
if switch:
|
||||
index = self.ui.tabs.indexOf(webview)
|
||||
self.ui.tabs.setCurrentIndex(index)
|
||||
|
||||
if not url:
|
||||
self.ui.url.setFocus()
|
||||
self.ui.url.selectAll()
|
||||
|
||||
return webview
|
||||
|
||||
|
||||
def NewWidgetTab(self, widgetname, switch=True):
|
||||
widgetname = widgetname.title()
|
||||
currwidget = self.widgettabs.get(widgetname)
|
||||
|
||||
if currwidget:
|
||||
index = self.ui.tabs.indexOf(currwidget.ui)
|
||||
index = self.ui.tabs.indexOf(currwidget)
|
||||
self.ui.tabs.setCurrentIndex(index)
|
||||
return
|
||||
|
||||
widgettabs = {
|
||||
'bookmarks': tabs.Bookmarks,
|
||||
'history': tabs.History,
|
||||
'preferences': tabs.Preferences
|
||||
'Bookmarks': tabs.Bookmarks,
|
||||
'History': tabs.History,
|
||||
'Downloads': tabs.Downloads,
|
||||
'Preferences': tabs.Preferences
|
||||
}
|
||||
|
||||
widget = widgettabs.get(widgetname)
|
||||
|
||||
if not widget:
|
||||
logging.error('OOF!')
|
||||
logging.error('Failed to open widget tab:', widgetname)
|
||||
return
|
||||
|
||||
self.widgettabs[widgetname] = widget(self)
|
||||
newwidget = self.widgettabs[widgetname]
|
||||
|
||||
self.ui.tabs.addTab(newwidget.ui, newwidget.title)
|
||||
widget = self.widgettabs[widgetname]
|
||||
widget.setWindowTitle(widgetname)
|
||||
index = self.ui.tabs.addTab(widget, widget.windowTitle())
|
||||
|
||||
if switch:
|
||||
index = self.ui.tabs.indexOf(newwidget.ui)
|
||||
self.ui.tabs.setCurrentIndex(index)
|
||||
|
||||
return widget
|
||||
|
||||
|
||||
def CloseTab(self, index=None, shutdown=False):
|
||||
widgettabs = self.widgettabs
|
||||
tabs = self.ui.tabs
|
||||
|
||||
if index == None:
|
||||
index = -1
|
||||
|
||||
index = int(index)
|
||||
|
||||
if index < 0:
|
||||
index = self.ui.tabs.currentIndex()
|
||||
index = tabs.currentIndex()
|
||||
|
||||
widget = self.ui.tabs.widget(index)
|
||||
tabtitle = self.ui.tabs.tabText(index).lower()
|
||||
widget = tabs.widget(index)
|
||||
tabtitle = widget.windowTitle()
|
||||
|
||||
# not even sure if this helps with clearing memory, so it's staying for now
|
||||
try:
|
||||
widget.ui.deleteLater()
|
||||
except:
|
||||
'heck'
|
||||
if widgettabs.get(tabtitle):
|
||||
del widgettabs[tabtitle]
|
||||
|
||||
tabs.removeTab(index)
|
||||
widget.deleteLater()
|
||||
|
||||
if tabtitle in ['preferences', 'bookmarks', 'history']:
|
||||
del self.widgettabs[tabtitle]
|
||||
|
||||
self.ui.tabs.removeTab(index)
|
||||
|
||||
if not shutdown:
|
||||
self._prevent_empty_tabview()
|
||||
tabs.currentWidget().setFocus()
|
||||
|
||||
|
||||
class UrlBarEsc(QObject):
|
||||
|
@ -513,7 +532,7 @@ class UrlBarEsc(QObject):
|
|||
## Custom tabbar class
|
||||
class TabBar(QTabBar):
|
||||
def __init__(self, window):
|
||||
super().__init__()
|
||||
super().__init__(window)
|
||||
self.setElideMode(Qt.TextElideMode.ElideRight)
|
||||
self.setAutoHide(int(get.config('hidetabs', 1)))
|
||||
self.setMovable(True)
|
||||
|
@ -523,6 +542,11 @@ class TabBar(QTabBar):
|
|||
self.setDocumentMode(True)
|
||||
self.window = window
|
||||
|
||||
self.setStyleSheet('''
|
||||
QTabBar::tab:selected {font: bold;}
|
||||
QTabBar::tab-bar {alignment: center;}
|
||||
''')
|
||||
|
||||
## Set tabs to even take up full width
|
||||
def tabSizeHint(self, index):
|
||||
compensation = 0 #I'll need this later
|
||||
|
@ -586,6 +610,11 @@ def main():
|
|||
app.setOrganizationDomain('barkshark.xyz')
|
||||
app.setWindowIcon(Icon('icon'))
|
||||
|
||||
if get.config('remotedebug', False):
|
||||
debugport = get.config('remotedebugport', '8018')
|
||||
debughost = 'still gotta sort this out'
|
||||
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = f'127.0.0.1:{remotedebugport}'
|
||||
|
||||
host = get.config('mhost', 'localhost')
|
||||
port = get.config('mport', 8008)
|
||||
|
||||
|
@ -595,7 +624,7 @@ def main():
|
|||
mbus = Client(host, port)
|
||||
|
||||
else:
|
||||
mbus = Client(sock=True)
|
||||
mbus = Client(sock=Path(var.profilepath, 'mbus.sock'))
|
||||
|
||||
if mbus.setup():
|
||||
logging.info('QtWeb already open. Bringing up window...')
|
||||
|
@ -614,9 +643,10 @@ def main():
|
|||
|
||||
SetPid(lock)
|
||||
|
||||
|
||||
window = Browser(app)
|
||||
window.server = Server(window)
|
||||
app.aboutToQuit.connect(window._close_window)
|
||||
app.aboutToQuit.connect(window.close)
|
||||
|
||||
if not isWindows:
|
||||
window.socket = Server(window, True)
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import json, shutil
|
||||
|
||||
from urllib import request
|
||||
from urllib.parse import urlparse, urlencode
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import urlparse
|
||||
from os.path import join, isfile, basename
|
||||
from os import makedirs, remove
|
||||
|
||||
from .Lib.IzzyLib import logging
|
||||
from .config import var
|
||||
from .database import db, put, get
|
||||
from .functions import httpclient, UserAgentGen
|
||||
from .functions import httpclient
|
||||
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
|
@ -17,9 +15,8 @@ from PyQt5.QtGui import *
|
|||
|
||||
# I was thinking about replacing this with Mastodon.py, but it works fine, so I'm not sure now
|
||||
class Mastodon(object):
|
||||
def __init__(self, ua):
|
||||
def __init__(self):
|
||||
self.accounts = {}
|
||||
self.ua = ua
|
||||
|
||||
|
||||
def _request(self, api, fullname=None, method='GET', data=None, domain=None, apikey=None):
|
||||
|
@ -41,8 +38,7 @@ class Mastodon(object):
|
|||
|
||||
options = {
|
||||
'headers': {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': UserAgentGen()
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import asyncio, socket, json, sys, traceback, tracemalloc, argparse, datetime, time, linecache
|
||||
import asyncio, socket, json, sys, traceback, tracemalloc, argparse, datetime, linecache
|
||||
|
||||
from os.path import join
|
||||
from pathlib import Path
|
||||
|
@ -78,7 +78,10 @@ class Server(object):
|
|||
self.loop.close()
|
||||
|
||||
if self.socket:
|
||||
Path(self.socket).unlink()
|
||||
sock = Path(self.socket)
|
||||
|
||||
if sock.exists():
|
||||
sock.unlink()
|
||||
|
||||
|
||||
def restart(self):
|
||||
|
@ -113,11 +116,11 @@ class Server(object):
|
|||
return False
|
||||
|
||||
if action == 'info':
|
||||
return webview.JsAction.SoundcloudInfo(self.get_soundcloud_info)
|
||||
webview.JsAction.SoundcloudInfo(self.get_soundcloud_info)
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
text = self.scinfo
|
||||
text = self.scinfo.copy() if self.scinfo else None
|
||||
self.scinfo = None
|
||||
return text
|
||||
|
||||
|
@ -136,8 +139,16 @@ class Server(object):
|
|||
self.scinfo = ParseSoundcloudInfo(value)
|
||||
|
||||
|
||||
async def search(self, *args, **kwargs):
|
||||
self.GetWebview().search.Search(*args, **kwargs)
|
||||
async def search(self, *args):
|
||||
searchbar = self.GetWebview().searchbar
|
||||
|
||||
if not args:
|
||||
searchbar.Search('reset')
|
||||
searchbar.hide()
|
||||
return
|
||||
|
||||
searchbar.Search(text=' '.join(args))
|
||||
searchbar.show()
|
||||
|
||||
|
||||
async def new_tab(self, *args, **kwargs):
|
||||
|
@ -219,9 +230,8 @@ class Server(object):
|
|||
def trace_top(self, num=15, key_type='lineno'):
|
||||
if not self.window.snapshot:
|
||||
self.trace_snapshot()
|
||||
snapshot = self.window.snapshot
|
||||
|
||||
snapshot = snapshot.filter_traces((
|
||||
snapshot = self.window.snapshot.filter_traces((
|
||||
tracemalloc.Filter(False, '<frozen importlib._bootstrap>'),
|
||||
tracemalloc.Filter(False, '<unknown>'),
|
||||
))
|
||||
|
@ -266,7 +276,7 @@ class Server(object):
|
|||
try:
|
||||
reqdata = (await reader.read(81920)).decode('utf8')
|
||||
request = json.loads(reqdata)
|
||||
response = {'msg': 'OK'}
|
||||
response = {}
|
||||
|
||||
cmd = request.get('cmd')
|
||||
args = request.get('args', [])
|
||||
|
@ -290,11 +300,14 @@ class Server(object):
|
|||
output = await command(*args, **kwargs)
|
||||
|
||||
if type(output) == dict:
|
||||
response = output
|
||||
response = {'msg': output}
|
||||
|
||||
elif output == False:
|
||||
response = {'err': 'Server error'}
|
||||
|
||||
elif output == None:
|
||||
response = {'msg': 'OK'}
|
||||
|
||||
elif output:
|
||||
response['msg'] = output
|
||||
|
||||
|
@ -322,8 +335,8 @@ class Server(object):
|
|||
|
||||
class Client(object):
|
||||
def __init__(self, host=None, port=None, sock=None):
|
||||
self.socket = join(var.profilepath, 'mbus.sock') if sock else None
|
||||
self.conn = (socket.gethostbyname(host), port) if not sock else self.socket
|
||||
self.socket = Path(sock) if type(sock) == str else sock
|
||||
self.conn = (socket.gethostbyname(host), port) if not sock else str(self.socket)
|
||||
|
||||
|
||||
def send(self, cmd, *args, **kwargs):
|
||||
|
@ -361,7 +374,7 @@ class Client(object):
|
|||
|
||||
|
||||
def setup(self):
|
||||
if self.socket and not Path(self.socket).exists():
|
||||
if self.socket and not self.socket.exists():
|
||||
logging.verbose('Unix socket does not exist')
|
||||
return False
|
||||
|
||||
|
@ -386,6 +399,7 @@ def main(args=sys.argv[1:]):
|
|||
parser.add_argument('args', nargs=argparse.REMAINDER, help='The arguments to pass to the command')
|
||||
parser.add_argument('-a', '--address', help='Address to connect to. Default: localhost')
|
||||
parser.add_argument('-p', '--port', help='Port number to connect to. Default: 8008')
|
||||
parser.add_argument('-e', '--profile', help='Profile to use for a unix socket connection')
|
||||
pargs = parser.parse_args(args)
|
||||
|
||||
host = '127.0.0.1' if not pargs.address else pargs.address
|
||||
|
@ -411,7 +425,8 @@ def main(args=sys.argv[1:]):
|
|||
api = Client(host, port)
|
||||
|
||||
else:
|
||||
api = Client(sock=join(var.profilepath, 'mbus.sock'))
|
||||
profilepath = Path(var.datapath).joinpath('Default' if not pargs.profile else pargs.profile)
|
||||
api = Client(sock=profilepath.joinpath('mbus.sock'))
|
||||
|
||||
if not api.setup():
|
||||
logging.error('Failed to connect to browser')
|
||||
|
@ -435,6 +450,9 @@ def main(args=sys.argv[1:]):
|
|||
if msg:
|
||||
logging.info(msg)
|
||||
|
||||
else:
|
||||
logging.error('No output')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
109
qtweb/resources/dialog-authentication.ui
Normal file
109
qtweb/resources/dialog-authentication.ui
Normal file
|
@ -0,0 +1,109 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>479</width>
|
||||
<height>292</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="username"/>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="savepass">
|
||||
<property name="statusTip">
|
||||
<string>Does nothing rn</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="QPushButton" name="login">
|
||||
<property name="text">
|
||||
<string>Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>169</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QPushButton" name="cancel">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="realm">
|
||||
<property name="text">
|
||||
<string>Authentication requested from {host}. Server message: {msg}</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>username</tabstop>
|
||||
<tabstop>password</tabstop>
|
||||
<tabstop>savepass</tabstop>
|
||||
<tabstop>cancel</tabstop>
|
||||
<tabstop>login</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
115
qtweb/resources/dialog-bookmark.ui
Normal file
115
qtweb/resources/dialog-bookmark.ui
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>568</width>
|
||||
<height>447</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="titlelabel">
|
||||
<property name="text">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="urllabel">
|
||||
<property name="text">
|
||||
<string>Url</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="categorylabel">
|
||||
<property name="text">
|
||||
<string>Category</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" colspan="4">
|
||||
<widget class="QLineEdit" name="category"/>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QLabel" name="descriptionlabel">
|
||||
<property name="text">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2" colspan="4">
|
||||
<widget class="QPlainTextEdit" name="description">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>230</width>
|
||||
<height>170</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="QPushButton" name="discard">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="4">
|
||||
<widget class="QPushButton" name="remove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="5">
|
||||
<widget class="QPushButton" name="save">
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" colspan="4">
|
||||
<widget class="QLineEdit" name="url">
|
||||
<property name="maxLength">
|
||||
<number>2048</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2" colspan="4">
|
||||
<widget class="QLineEdit" name="title"/>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>228</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -1,21 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>324</width>
|
||||
<height>345</height>
|
||||
<height>434</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>324</width>
|
||||
|
@ -29,15 +23,32 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>55</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="discard">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="frameShape">
|
||||
|
@ -52,7 +63,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>316</width>
|
||||
<height>304</height>
|
||||
<height>393</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -179,6 +190,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="mastodon">
|
||||
<property name="toolTip">
|
||||
<string>Is this domain a mastodon instance? Hint: Enable 'Auto-detect Mastodon instances' under advanced settings to enable auto-detection</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Mastodon Instance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
@ -196,39 +217,6 @@
|
|||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>55</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="discard">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="reset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="save">
|
||||
<property name="text">
|
||||
|
@ -242,23 +230,18 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="reset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>scrollArea</tabstop>
|
||||
<tabstop>adblock</tabstop>
|
||||
<tabstop>allowhttp</tabstop>
|
||||
<tabstop>fullscreen</tabstop>
|
||||
<tabstop>cookies</tabstop>
|
||||
<tabstop>notifications</tabstop>
|
||||
<tabstop>capture</tabstop>
|
||||
<tabstop>microphone</tabstop>
|
||||
<tabstop>location</tabstop>
|
||||
<tabstop>camera</tabstop>
|
||||
<tabstop>discard</tabstop>
|
||||
<tabstop>reset</tabstop>
|
||||
<tabstop>save</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -1,44 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>486</width>
|
||||
<height>306</height>
|
||||
<width>516</width>
|
||||
<height>337</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="6">
|
||||
<widget class="QLabel" name="replytext">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::NoTextInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="6">
|
||||
<widget class="QLineEdit" name="warning">
|
||||
<property name="placeholderText">
|
||||
|
@ -46,57 +21,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="6">
|
||||
<widget class="QPlainTextEdit" name="content">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustIgnored</enum>
|
||||
</property>
|
||||
<property name="tabChangesFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="backgroundVisible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="centerOnScroll">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Message body</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="count">
|
||||
<property name="text">
|
||||
<string>500</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QComboBox" name="account"/>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QComboBox" name="privacy">
|
||||
<item>
|
||||
|
@ -121,16 +45,6 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4">
|
||||
<widget class="QPushButton" name="cancel">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="5">
|
||||
<widget class="QPushButton" name="post">
|
||||
<property name="text">
|
||||
|
@ -141,16 +55,85 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QComboBox" name="account"/>
|
||||
</item>
|
||||
<item row="3" column="4">
|
||||
<widget class="QPushButton" name="cancel">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="6">
|
||||
<widget class="QLabel" name="replytext">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::NoTextInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="count">
|
||||
<property name="text">
|
||||
<string>500</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="6">
|
||||
<widget class="QPlainTextEdit" name="content">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustIgnored</enum>
|
||||
</property>
|
||||
<property name="tabChangesFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="backgroundVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="centerOnScroll">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Message body</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>warning</tabstop>
|
||||
<tabstop>content</tabstop>
|
||||
<tabstop>account</tabstop>
|
||||
<tabstop>privacy</tabstop>
|
||||
<tabstop>cancel</tabstop>
|
||||
<tabstop>post</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
BIN
qtweb/resources/dictionaries/en-US.bdict
Normal file
BIN
qtweb/resources/dictionaries/en-US.bdict
Normal file
Binary file not shown.
117
qtweb/resources/download-item.ui
Normal file
117
qtweb/resources/download-item.ui
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>461</width>
|
||||
<height>132</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>132</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>132</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QLabel" name="url">
|
||||
<property name="text">
|
||||
<string>OwO what's this?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QLabel" name="percent">
|
||||
<property name="text">
|
||||
<string>OwO %</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Location</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QProgressBar" name="progress">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="invertedAppearance">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="textDirection">
|
||||
<enum>QProgressBar::TopToBottom</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QLabel" name="location">
|
||||
<property name="text">
|
||||
<string>*notices your download*</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Progress</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>MD5 Hash</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="md5"/>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="update">
|
||||
<property name="text">
|
||||
<string>Update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -1,149 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>467</width>
|
||||
<height>320</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="4">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="horizontalSpacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="urllabel">
|
||||
<property name="text">
|
||||
<string>Url</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="url">
|
||||
<property name="maxLength">
|
||||
<number>2048</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="categorylabel">
|
||||
<property name="text">
|
||||
<string>Category</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="category"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="descriptionlabel">
|
||||
<property name="text">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPlainTextEdit" name="description">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>230</width>
|
||||
<height>170</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="title"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="titlelabel">
|
||||
<property name="text">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>198</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="discard">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="remove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="save">
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>title</tabstop>
|
||||
<tabstop>url</tabstop>
|
||||
<tabstop>category</tabstop>
|
||||
<tabstop>description</tabstop>
|
||||
<tabstop>discard</tabstop>
|
||||
<tabstop>remove</tabstop>
|
||||
<tabstop>save</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
58
qtweb/resources/tab-downloads.ui
Normal file
58
qtweb/resources/tab-downloads.ui
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>481</width>
|
||||
<height>418</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="pause">
|
||||
<property name="text">
|
||||
<string>Pause All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="clear">
|
||||
<property name="text">
|
||||
<string>Clear Finished</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="resume">
|
||||
<property name="text">
|
||||
<string>Resume All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="4">
|
||||
<widget class="QListWidget" name="downloads"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>677</width>
|
||||
<height>420</height>
|
||||
<width>696</width>
|
||||
<height>489</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -30,7 +30,7 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<widget class="QTabWidget" name="tabs">
|
||||
<property name="tabPosition">
|
||||
<enum>QTabWidget::West</enum>
|
||||
</property>
|
||||
|
@ -42,13 +42,141 @@
|
|||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="heckinfuck">
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="hidetabs">
|
||||
<item row="3" column="0" colspan="4">
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="4" column="2">
|
||||
<widget class="QPushButton" name="nitterreset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="reusetab">
|
||||
<property name="toolTip">
|
||||
<string>If a web page tries to create a new tab, use the current tab instead</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reuse tabs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="hidetabs">
|
||||
<property name="toolTip">
|
||||
<string>Hide the tab bar when only one tab is open</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Auto-hide tabs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="nitter">
|
||||
<property name="toolTip">
|
||||
<string>Redirect Twitter links to a Nitter instance</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable Nitter redirect</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="nitterurl">
|
||||
<property name="placeholderText">
|
||||
<string>ex. https://nitter.net</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Please don't use this. It's not fully implemented yet</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<widget class="QPushButton" name="setdefault">
|
||||
<property name="toolTip">
|
||||
<string>Set QtWeb as the default web browser</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set Default</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="adblock">
|
||||
<property name="toolTip">
|
||||
<string>Block ads on web pages by denying hosts. Can be turned off for individual sites</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable ad blocker</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="createdownloaddir">
|
||||
<property name="text">
|
||||
<string>Create Dir</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="homepage">
|
||||
<property name="toolTip">
|
||||
<string>Hide the tab bar when only one tab is open</string>
|
||||
<string>The default web page to load in a new tab</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>2048</number>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QPushButton" name="selectdownloaddir">
|
||||
<property name="text">
|
||||
<string>Pick Directory</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="downloaddir">
|
||||
<property name="toolTip">
|
||||
<string>Default location for downloads</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Auto-hide tabs</string>
|
||||
<string>Cache Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -68,26 +196,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Homepage</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="homepage">
|
||||
<property name="toolTip">
|
||||
<string>The default web page to load in a new tab</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>2048</number>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
|
@ -95,107 +203,61 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QCheckBox" name="adblock">
|
||||
<property name="toolTip">
|
||||
<string>Block ads on web pages. Can be turned off for individual sites</string>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Enable ad blocker</string>
|
||||
<string>Homepage</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QCheckBox" name="darktheme">
|
||||
<property name="toolTip">
|
||||
<string>Set the browser to a dark theme. Does not affect web pages</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use dark theme</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QPushButton" name="selectdownloaddir">
|
||||
<property name="text">
|
||||
<string>Pick Directory</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Cache Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="downloaddir">
|
||||
<property name="toolTip">
|
||||
<string>Default location for downloads</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="createdownloaddir">
|
||||
<property name="text">
|
||||
<string>Create Dir</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0" colspan="4">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Please don't use this. It's not fully implemented yet</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="setdefault">
|
||||
<property name="toolTip">
|
||||
<string>Set QtWeb as the default web browser</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set Default</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="10" column="1" colspan="3">
|
||||
<spacer name="verticalSpacer">
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="appearance">
|
||||
<attribute name="title">
|
||||
<string>Appearance</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="3">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="themedelete">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="themenew">
|
||||
<property name="text">
|
||||
<string>New</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="themeedit">
|
||||
<property name="text">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="5">
|
||||
<widget class="QListWidget" name="theme">
|
||||
<property name="uniformItemSizes">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="search">
|
||||
|
@ -325,48 +387,13 @@
|
|||
<string>Mastodon</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Domain</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="7">
|
||||
<widget class="QPushButton" name="login">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item row="3" column="6">
|
||||
<widget class="QPushButton" name="reset">
|
||||
<property name="toolTip">
|
||||
<string>Attempt to login to the account</string>
|
||||
<string>Empty the form fields</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="7">
|
||||
<widget class="QPushButton" name="logout">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Logout of the selected account</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Logout</string>
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -380,27 +407,22 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="7">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
<item row="5" column="7">
|
||||
<widget class="QPushButton" name="logout">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>E-Mail</string>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="6">
|
||||
<widget class="QPushButton" name="reset">
|
||||
<property name="toolTip">
|
||||
<string>Empty the form fields</string>
|
||||
<string>Logout of the selected account</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
<string>Logout</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -472,6 +494,9 @@
|
|||
</item>
|
||||
<item row="5" column="6">
|
||||
<widget class="QPushButton" name="mdefault">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
|
@ -486,10 +511,32 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<item row="3" column="7">
|
||||
<widget class="QPushButton" name="login">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Attempt to login to the account</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
<string>Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Domain</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -509,8 +556,25 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="7">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="5">
|
||||
<widget class="QPushButton" name="refresh">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
|
@ -525,6 +589,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>E-Mail</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="advanced">
|
||||
|
@ -532,95 +603,10 @@
|
|||
<string>Advanced</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="mbushost">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The host MBus listens on</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>127.0.0.1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>0.0.0.0</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>False</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<item row="2" column="3">
|
||||
<widget class="QPushButton" name="logreset">
|
||||
<property name="text">
|
||||
<string>MBus Host</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QSpinBox" name="mbusport">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The port MBus listens on</string>
|
||||
</property>
|
||||
<property name="accelerated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>25565</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8008</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Stdout Log Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="jsdebug">
|
||||
<property name="text">
|
||||
<string>Send JS output to stdout</string>
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -664,26 +650,6 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>MBus Port</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="portreset">
|
||||
<property name="sizePolicy">
|
||||
|
@ -697,6 +663,77 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QSpinBox" name="mbusport">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The port MBus listens on</string>
|
||||
</property>
|
||||
<property name="accelerated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>25565</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8008</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="mbushost">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The host MBus listens on</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>127.0.0.1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>0.0.0.0</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>False</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QPushButton" name="hostreset">
|
||||
<property name="sizePolicy">
|
||||
|
@ -717,12 +754,18 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QPushButton" name="logreset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
<item row="7" column="1">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="automd5">
|
||||
|
@ -734,6 +777,44 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>MBus Host</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Stdout Log Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="jsdebug">
|
||||
<property name="text">
|
||||
<string>Send JS output to stdout</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>MBus Port</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="mastocheck">
|
||||
<property name="toolTip">
|
||||
<string>Query nodeinfo on previously unvisited sites to check if the domain is a mastodon instance</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Auto-detect Mastodon instances</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
@ -741,17 +822,22 @@
|
|||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>tabs</tabstop>
|
||||
<tabstop>homepage</tabstop>
|
||||
<tabstop>cachesize</tabstop>
|
||||
<tabstop>downloaddir</tabstop>
|
||||
<tabstop>createdownloaddir</tabstop>
|
||||
<tabstop>selectdownloaddir</tabstop>
|
||||
<tabstop>hidetabs</tabstop>
|
||||
<tabstop>adblock</tabstop>
|
||||
<tabstop>darktheme</tabstop>
|
||||
<tabstop>reusetab</tabstop>
|
||||
<tabstop>nitter</tabstop>
|
||||
<tabstop>nitterurl</tabstop>
|
||||
<tabstop>nitterreset</tabstop>
|
||||
<tabstop>themenew</tabstop>
|
||||
<tabstop>themeedit</tabstop>
|
||||
<tabstop>themedelete</tabstop>
|
||||
<tabstop>theme</tabstop>
|
||||
<tabstop>sdefault</tabstop>
|
||||
<tabstop>setdefault</tabstop>
|
||||
<tabstop>snew</tabstop>
|
||||
<tabstop>sdelete</tabstop>
|
||||
<tabstop>sengines</tabstop>
|
||||
|
@ -772,6 +858,8 @@
|
|||
<tabstop>logreset</tabstop>
|
||||
<tabstop>jsdebug</tabstop>
|
||||
<tabstop>tracemalloc</tabstop>
|
||||
<tabstop>automd5</tabstop>
|
||||
<tabstop>mastocheck</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
12
qtweb/scripts/flo-collapse-sidebar.sys.js
Normal file
12
qtweb/scripts/flo-collapse-sidebar.sys.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
// ==UserScript==
|
||||
// @name FLO Sidebar Collapser
|
||||
// @namespace barkshark.xyz
|
||||
// @version 1.0
|
||||
// @author Zoey Mae
|
||||
// @description Collapse the FLO sidebar by default
|
||||
// @match https://furrylife.online/*
|
||||
// ==/UserScript==
|
||||
|
||||
if (document.getElementsByClassName('ta_wrapperSidebar')[0].offsetWidth === 300) {
|
||||
document.getElementsByClassName('ta_menuToggle')[0].click()
|
||||
}
|
13
qtweb/scripts/reuse-tabs.js
Normal file
13
qtweb/scripts/reuse-tabs.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
// ==UserScript==
|
||||
// @name Tab Reuser
|
||||
// @namespace barkshark.xyz
|
||||
// @version 1.0
|
||||
// @author Zoey Mae
|
||||
// @description Prevent links from opening tabs
|
||||
// @match https://*
|
||||
// ==/UserScript==
|
||||
|
||||
for (let link of document.getElementsByTagName("a")) {
|
||||
if (link.target === "_blank")
|
||||
link.removeAttribute("target")
|
||||
}
|
17
qtweb/scripts/soundcloud-hide-reposts.sys.js
Normal file
17
qtweb/scripts/soundcloud-hide-reposts.sys.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
// ==UserScript==
|
||||
// @name SoundCloud hide reposts
|
||||
// @namespace http://abs.ezw.me
|
||||
// @version 1.2
|
||||
// @author ABS
|
||||
// @description Only see new songs in your SoundCloud stream
|
||||
// @match https://soundcloud.com/stream
|
||||
// ==/UserScript==
|
||||
|
||||
function norepost(){
|
||||
for (let repost of document.getElementsByClassName("soundList__item")) {
|
||||
if (repost.getElementsByClassName("sc-ministats-reposts").length > 0)
|
||||
repost.remove();
|
||||
// console.log(repost);
|
||||
}
|
||||
}
|
||||
window.addEventListener("DOMNodeInserted", norepost, false);
|
260
qtweb/tabs.py
260
qtweb/tabs.py
|
@ -1,16 +1,15 @@
|
|||
import re, json
|
||||
|
||||
from pathlib import Path, PurePosixPath, PureWindowsPath
|
||||
from pathlib import Path
|
||||
from os.path import join, isfile, abspath
|
||||
from urllib.parse import urlparse
|
||||
from configparser import ConfigParser
|
||||
|
||||
from . import isLinux, isWindows
|
||||
from .config import var
|
||||
from . import isWindows
|
||||
from .config import var, default
|
||||
from .database import db, put, get, delete
|
||||
from .functions import Icon, NewDesktopEntry
|
||||
from .dialogs import EditBookmark
|
||||
from .themes import ChangeTheme
|
||||
|
||||
from .Lib.IzzyLib import logging
|
||||
from .Lib.IzzyLib.misc import boolean
|
||||
|
@ -21,20 +20,21 @@ from PyQt5.QtWidgets import *
|
|||
from PyQt5 import uic
|
||||
|
||||
|
||||
class Bookmarks(object):
|
||||
def __init__(self, window):
|
||||
self.ui = uic.loadUi(join(var.resources, 'bookmarktab.ui'))
|
||||
self.title = 'Bookmarks'
|
||||
self.url = self.title
|
||||
self.window = window
|
||||
class Bookmarks(QWidget):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.ui = uic.loadUi(join(var.resources, 'tab-bookmarks.ui'))
|
||||
self.setLayout(self.ui.layout())
|
||||
self.window = parent
|
||||
|
||||
self.ui.add.clicked.connect(lambda *args: self._row_action('new'))
|
||||
self.ui.edit.clicked.connect(lambda *args: self._row_action('edit'))
|
||||
self.ui.open.clicked.connect(lambda *args: self._row_action('open'))
|
||||
self.ui.refresh.clicked.connect(lambda *args: self._row_action('refresh'))
|
||||
self.ui.table.cellDoubleClicked.connect(lambda *args: self._row_action('edit'))
|
||||
self.ui.table.cellDoubleClicked.connect(lambda *args: self._row_action('open'))
|
||||
|
||||
for size in [(0, 150), (1, 350), (2, 80)]:
|
||||
for size in [(0, 350), (1, 200), (2, 80)]:
|
||||
self.ui.table.setColumnWidth(*size)
|
||||
|
||||
self._populate_table()
|
||||
|
@ -98,26 +98,61 @@ class Bookmarks(object):
|
|||
'maybe later'
|
||||
|
||||
|
||||
class History(object):
|
||||
def __init__(self, window):
|
||||
self.title = 'History'
|
||||
self.window = window
|
||||
class History(QWidget):
|
||||
def __init__(self, parent):
|
||||
self.window = parent
|
||||
|
||||
|
||||
class Preferences(object):
|
||||
def __init__(self, window):
|
||||
self.ui = uic.loadUi(join(var.resources, 'preferences.ui'))
|
||||
self.title = 'Preferences'
|
||||
self.window = window
|
||||
def CloseTab(self):
|
||||
del self.window.widgettabs.history
|
||||
|
||||
|
||||
## I'll get this working later
|
||||
class Downloads(QWidget):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.ui = uic.loadUi(join(var.resources, 'tab-downloads.ui'))
|
||||
self.setLayout(self.ui.layout())
|
||||
self.setWindowTitle('Downloads')
|
||||
self.window = parent
|
||||
|
||||
|
||||
def populate_table():
|
||||
self.ui.downloads.setRowCount(0)
|
||||
|
||||
for download, md5 in self.window.profile.downloads:
|
||||
listItem = QListWidgetItem()
|
||||
dlItem = DownloadItem(self, download, md5)
|
||||
|
||||
self.ui.downloads.addItem(listItem)
|
||||
self.ui.downloads.setItemWidget(listItem, dlItem)
|
||||
|
||||
|
||||
def CloseTab(self):
|
||||
del self.window.widgettabs.downloads
|
||||
|
||||
|
||||
class Preferences(QWidget):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.ui = uic.loadUi(join(var.resources, 'tab-preferences.ui'))
|
||||
self.setLayout(self.ui.layout())
|
||||
self.window = parent
|
||||
|
||||
dldir = get.config('dlpath', var.downloadpath)
|
||||
nitter_state = get.config('nitter', 0)
|
||||
|
||||
## General tab
|
||||
self.ui.homepage.setText(get.config('homepage', 'http://ddg.gg'))
|
||||
self.ui.downloaddir.setText(dldir)
|
||||
self.ui.adblock.setChecked(int(get.config('adblock', 1)))
|
||||
self.ui.hidetabs.setChecked(int(get.config('hidetabs', 1)))
|
||||
self.ui.reusetab.setChecked(int(get.config('reusetab', False)))
|
||||
self.ui.cachesize.setValue(int(get.config('cache', 100)))
|
||||
self.ui.darktheme.setChecked(int(get.config('darktheme', 0)))
|
||||
self.ui.nitter.setChecked(int(nitter_state))
|
||||
self.ui.nitterurl.setText(get.config('nitterurl', 'https://nitter.net'))
|
||||
self._general_set_nitterurl_state(nitter_state)
|
||||
|
||||
if isWindows:
|
||||
self.ui.setdefault.setDisabled(True)
|
||||
|
@ -142,10 +177,7 @@ class Preferences(object):
|
|||
self.ui.jsdebug.setChecked(get.config('jsdebug', False))
|
||||
self.ui.tracemalloc.setChecked(get.config('tracestart', False))
|
||||
self.ui.automd5.setChecked(get.config('automd5', False))
|
||||
|
||||
# this breaks for some reason, so keeping it disabled until I figure out what's going on
|
||||
self.ui.hostreset.setToolTip('Button broken. Just set the host to "127.0.0.1"')
|
||||
self.ui.hostreset.setDisabled(True)
|
||||
self.ui.mastocheck.setChecked(get.config('mastocheck', False))
|
||||
|
||||
## General tab signals
|
||||
self.ui.setdefault.clicked.connect(self._general_set_default)
|
||||
|
@ -157,7 +189,13 @@ class Preferences(object):
|
|||
self.ui.cachesize.editingFinished.connect(self._general_update_cachesize)
|
||||
self.ui.hidetabs.stateChanged.connect(lambda state: self._general_handle_checkbox(state, 'hidetabs'))
|
||||
self.ui.adblock.stateChanged.connect(lambda state: self._general_handle_checkbox(state, 'adblock'))
|
||||
self.ui.darktheme.stateChanged.connect(lambda state: self._general_handle_checkbox(state, 'darktheme'))
|
||||
self.ui.nitter.stateChanged.connect(lambda state: self._general_handle_checkbox(state, 'nitter'))
|
||||
self.ui.reusetab.stateChanged.connect(lambda state: self._general_handle_checkbox(state, 'reusetab'))
|
||||
self.ui.nitterurl.editingFinished.connect(self._general_update_nitter_url)
|
||||
self.ui.nitterreset.clicked.connect(self._general_reset_nitter_url)
|
||||
|
||||
## Appearance tab signals
|
||||
#self.ui.theme.itemClicked.connect(self._appearance_theme_change)
|
||||
|
||||
## Search engine tab signals
|
||||
self.ui.snew.clicked.connect(lambda *args: self._search_button_handler('new'))
|
||||
|
@ -175,21 +213,28 @@ class Preferences(object):
|
|||
self.ui.domain.returnPressed.connect(lambda *args: self.ui.username.setFocus())
|
||||
self.ui.username.returnPressed.connect(lambda *args: self.ui.password.setFocus())
|
||||
self.ui.password.returnPressed.connect(self.mastodon_login)
|
||||
self.ui.accounts.itemSelectionChanged.connect(self.mastodon_row_selected)
|
||||
|
||||
## Advanced tab signals
|
||||
self.ui.mbushost.currentIndexChanged.connect(self._adv_mbushost_changed)
|
||||
self.ui.mbusport.editingFinished.connect(self._adv_mbusport_changed)
|
||||
self.ui.loglevel.currentIndexChanged.connect(lambda index: self._adv_changed('loglevel', index))
|
||||
self.ui.hostreset.clicked.connect(lambda *args: self._adv_reset('mhost'))
|
||||
self.ui.portreset.clicked.connect(lambda *args: self._adv_reset('mport'))
|
||||
self.ui.hostreset.clicked.connect(lambda *args: self._adv_mbushost_changed(int(default.mhost)))
|
||||
self.ui.portreset.clicked.connect(lambda *args: self._adv_mbusport_changed(default.mport))
|
||||
self.ui.logreset.clicked.connect(lambda *args: self._adv_reset('loglevel'))
|
||||
self.ui.jsdebug.stateChanged.connect(lambda state: self._adv_handle_checkbox('jsdebug', state))
|
||||
self.ui.tracemalloc.stateChanged.connect(lambda state: self._adv_handle_checkbox('tracestart', state))
|
||||
self.ui.automd5.stateChanged.connect(lambda state: self._adv_handle_checkbox('automd5', state))
|
||||
self.ui.mastocheck.stateChanged.connect(lambda state: self._adv_handle_checkbox('mastocheck', state))
|
||||
|
||||
## Populate tables
|
||||
self.mastodon_populate_table()
|
||||
self.populate_search_table()
|
||||
#self._appearance_populate_view()
|
||||
|
||||
# Remove the appearance tab until I can get it fixed
|
||||
self.ui.tabs.widget(1).deleteLater()
|
||||
self.ui.tabs.removeTab(1)
|
||||
|
||||
|
||||
### General Tab ###
|
||||
|
@ -293,10 +338,6 @@ class Preferences(object):
|
|||
self.window.profile.setHttpCacheMaximumSize(1024 * 1024 * value)
|
||||
|
||||
|
||||
def _general_homepage_reset(self, *args):
|
||||
self.ui.homepage.setText(self.window.homepage)
|
||||
|
||||
|
||||
def _general_update_homepage(self, *args):
|
||||
newurl = self.ui.homepage.text()
|
||||
|
||||
|
@ -314,21 +355,81 @@ class Preferences(object):
|
|||
if name == 'hidetabs':
|
||||
self.window.tabbar.setAutoHide(state.real)
|
||||
|
||||
if name == 'adblock':
|
||||
self.window.adblock = value
|
||||
|
||||
if name == 'darktheme':
|
||||
if name == 'nitter':
|
||||
value = boolean(value)
|
||||
self._general_set_nitterurl_state(value)
|
||||
|
||||
if value:
|
||||
ChangeTheme('dark')
|
||||
else:
|
||||
ChangeTheme()
|
||||
|
||||
def _general_set_nitterurl_state(self, value=None):
|
||||
if not value:
|
||||
value = get.config('nitter', False)
|
||||
|
||||
self.ui.nitterurl.setEnabled(value)
|
||||
self.ui.nitterreset.setEnabled(value)
|
||||
|
||||
|
||||
def _general_update_nitter_url(self):
|
||||
url = self.ui.nitterurl.text()
|
||||
|
||||
if not any(map(url.startswith, ['http://', 'https://'])):
|
||||
url = 'https://' + url
|
||||
self.ui.nitterurl.setText(url)
|
||||
|
||||
if not put.config('nitterurl', url):
|
||||
logging.error('Failed to update "nitterurl" config')
|
||||
|
||||
|
||||
def _general_reset_nitter_url(self):
|
||||
if not put.config('nitterurl', default.nitter):
|
||||
logging.error('Failed to update "nitterurl" config')
|
||||
return
|
||||
|
||||
self.ui.nitterurl.setText(default.nitter)
|
||||
|
||||
|
||||
### Appearance Tab ###
|
||||
def _appearance_populate_view(self):
|
||||
self.ui.theme.clear()
|
||||
size = QSize()
|
||||
size.setHeight(24)
|
||||
|
||||
default = QListWidgetItem('Default')
|
||||
default.setSizeHint(size)
|
||||
|
||||
if get.config('theme', 'Default') == 'Default':
|
||||
default.setIcon(Icon('okay', 16))
|
||||
|
||||
self.ui.theme.addItem(default)
|
||||
|
||||
for theme in get.theme():
|
||||
item = QListWidgetItem(theme.name)
|
||||
item.setForeground(theme.palette.text())
|
||||
item.setBackground(theme.palette.base())
|
||||
item.setSizeHint(size)
|
||||
|
||||
if get.config('theme') == theme.name:
|
||||
item.setIcon(Icon('okay', 16))
|
||||
|
||||
self.ui.theme.addItem(item)
|
||||
|
||||
|
||||
def _appearance_theme_change(self, item):
|
||||
listwidget = self.ui.theme
|
||||
theme = item.text()
|
||||
|
||||
if put.config('theme', theme):
|
||||
items = [listwidget.item(i) for i in range(listwidget.count())]
|
||||
|
||||
for item in items:
|
||||
if item.text() != theme:
|
||||
item.setIcon(QIcon())
|
||||
else:
|
||||
item.setIcon(Icon('okay', 16))
|
||||
|
||||
self.window.themes.ChangeTheme(theme)
|
||||
|
||||
|
||||
### Search Tab ###
|
||||
# todo: prefix functions with 'search'
|
||||
|
||||
def _search_button_handler(self, button):
|
||||
table = self.ui.sengines
|
||||
tablerow = table.currentRow()
|
||||
|
@ -368,7 +469,6 @@ class Preferences(object):
|
|||
return
|
||||
|
||||
table = self.ui.sengines
|
||||
cell = table.item(x, y)
|
||||
cells = [0, table.item(x, 1), table.item(x, 2), table.item(x, 3)]
|
||||
|
||||
data = {
|
||||
|
@ -429,6 +529,19 @@ class Preferences(object):
|
|||
logging.error('Invalid message type:', msgtype)
|
||||
|
||||
|
||||
def mastodon_row_selected(self):
|
||||
row = self.mastodon_selected_row_data()
|
||||
logging.debug(row)
|
||||
|
||||
if not row:
|
||||
self.ui.mdefault.setEnabled(False)
|
||||
self.ui.logout.setEnabled(False)
|
||||
|
||||
else:
|
||||
self.ui.mdefault.setEnabled(row['default'])
|
||||
self.ui.logout.setEnabled(True)
|
||||
|
||||
|
||||
def mastodon_selected_row_data(self):
|
||||
table = self.ui.accounts
|
||||
tablerow = table.currentRow() if table.currentRow() >= 0 else None
|
||||
|
@ -436,9 +549,10 @@ class Preferences(object):
|
|||
if tablerow == None:
|
||||
return
|
||||
|
||||
cells = [0, table.item(tablerow, 1), table.item(tablerow, 2), table.item(tablerow, 3)]
|
||||
cells = [table.item(tablerow, 0), table.item(tablerow, 1), table.item(tablerow, 2), table.item(tablerow, 3)]
|
||||
|
||||
data = {
|
||||
'default': cells[0].icon().isNull(),
|
||||
'displayname': cells[1].text() if cells[1] else None,
|
||||
'username': cells[2].text() if cells[2] else None,
|
||||
'domain': cells[3].text() if cells[3] else None
|
||||
|
@ -450,9 +564,13 @@ class Preferences(object):
|
|||
|
||||
def mastodon_populate_table(self):
|
||||
self.ui.accounts.setRowCount(0)
|
||||
accounts = get.mastoaccount()
|
||||
|
||||
for widget in [self.ui.mdefault, self.ui.logout]:
|
||||
widget.setEnabled(False)
|
||||
|
||||
#for row in db.get('mastodon', single=False):
|
||||
for row in get.mastoaccount():
|
||||
for row in accounts:
|
||||
self.mastodon_insert_row(row)
|
||||
|
||||
|
||||
|
@ -534,7 +652,7 @@ class Preferences(object):
|
|||
## Advanced tab
|
||||
def _adv_mbushost_changed(self, index):
|
||||
new = self.ui.mbushost.itemText(index)
|
||||
old = get.config('mhost')
|
||||
old = get.config('mhost', default.mhost)
|
||||
|
||||
if old != new:
|
||||
put.config('mhost', new)
|
||||
|
@ -546,8 +664,10 @@ class Preferences(object):
|
|||
self.window.server.restart()
|
||||
|
||||
|
||||
def _adv_mbusport_changed(self):
|
||||
value = self.ui.mbusport.value()
|
||||
def _adv_mbusport_changed(self, value=None):
|
||||
if not value:
|
||||
value = self.ui.mbusport.value()
|
||||
|
||||
put.config('mport', value)
|
||||
|
||||
self.window.server.restart()
|
||||
|
@ -574,20 +694,44 @@ class Preferences(object):
|
|||
|
||||
db.remove('config', row.id)
|
||||
|
||||
if field == 'mhost':
|
||||
self.ui.mbushost.setCurrentIndex(0)
|
||||
|
||||
if field == 'mport':
|
||||
self.ui.mbusport.setValue(8008)
|
||||
|
||||
if field in ['mhost', 'mport']:
|
||||
self.window.server.restart()
|
||||
|
||||
if field == 'loglevel':
|
||||
self.mbushost.setCurrentIndex(3)
|
||||
logging.setConfig({'level': 'INFO'})
|
||||
|
||||
|
||||
def CloseTab(self):
|
||||
del self.window.widgettabs.preferences
|
||||
|
||||
|
||||
class DownloadItem(QWidget):
|
||||
def __init__(self, tab, download, md5=None):
|
||||
self.ui = uic.loadUi(join(var.resources, 'download-item.ui'))
|
||||
self.setLayout(self.ui.layout())
|
||||
self.tab = tab
|
||||
self.finished = False
|
||||
|
||||
location = Path(download.downloadDirectory()).joinpath(download.downloadFileName())
|
||||
url = download.url().toString()
|
||||
|
||||
self.ui.location.setText(str(location))
|
||||
self.ui.url.setText(url)
|
||||
|
||||
download.downloadProgress.connect(self._update_progress)
|
||||
|
||||
|
||||
def _update_progress(self, current, total):
|
||||
precent = current / total * 100
|
||||
current_mb = current / (1024 ** 2)
|
||||
total_mb = total / (1024 ** 2)
|
||||
|
||||
for value in [percent, current_mb, total_mb]:
|
||||
if type(value) == float:
|
||||
value = round(value, 2)
|
||||
|
||||
progress = f'{percent}% - {current_mb}MiB/{total_mb}MiB'
|
||||
self.ui.percent.setText(progress)
|
||||
|
||||
|
||||
class RefreshAccounts(QRunnable):
|
||||
def __init__(self, *args, tab=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
141
qtweb/themes.py
141
qtweb/themes.py
|
@ -1,111 +1,74 @@
|
|||
from .Lib.IzzyLib import logging
|
||||
|
||||
from . import isWindows
|
||||
from .database import get, put
|
||||
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import QStyleFactory
|
||||
from PyQt5.QtWidgets import QStyleFactory, QApplication
|
||||
|
||||
|
||||
color_themes = {}
|
||||
default_style = None
|
||||
current_theme = None
|
||||
app = None
|
||||
class ColorTheme(object):
|
||||
def __init__(self, window):
|
||||
self.app = window.app
|
||||
self.window = window
|
||||
self.default_style = self.app.style().objectName().lower()
|
||||
self.default_palette = self.app.style().standardPalette()
|
||||
self.appcolor = self.app.palette()
|
||||
self.colors = [
|
||||
'Window', 'WindowText', 'Base', 'AlternateBase', 'ToolTipBase', 'ToolTipText',
|
||||
'Button', 'ButtonText', 'Text', 'Highlight', 'HighlightedText', 'Link',
|
||||
'Light', 'Midlight', 'Dark', 'Mid', 'Shadow', 'LinkVisited', 'BrightText'
|
||||
]
|
||||
self.colors.sort()
|
||||
|
||||
logging.verbose('Default style:', self.default_style)
|
||||
logging.verbose('Available styles:', ', '.join(QStyleFactory.keys()))
|
||||
|
||||
|
||||
dark = {
|
||||
'AlternateBase': QColor(53, 53, 53),
|
||||
'Base': QColor(25, 25, 25),
|
||||
'Button': QColor(53, 53, 53),
|
||||
'ButtonText': Qt.white,
|
||||
'BrightText': Qt.red,
|
||||
'Highlight': QColor(42, 130, 218),
|
||||
'HighlightedText': Qt.black,
|
||||
'Link': QColor(42, 130, 218),
|
||||
'Text': Qt.white,
|
||||
'ToolTipBase': Qt.black,
|
||||
'ToolTipText': Qt.white,
|
||||
'Window': QColor(53, 53, 53),
|
||||
'WindowText': Qt.white,
|
||||
}
|
||||
def ChangeTheme(self, theme=None):
|
||||
theme = 'Default' if not theme else theme
|
||||
|
||||
if theme == 'Default':
|
||||
palette = self.default_palette
|
||||
|
||||
def AddTheme(name, colors):
|
||||
name = name.title()
|
||||
theme = QPalette()
|
||||
|
||||
for colname, color in colors.items():
|
||||
theme.setColor(getattr(QPalette, colname), QColor(color) if type(color) == str else color)
|
||||
|
||||
color_themes[name] = theme
|
||||
|
||||
|
||||
def RemoveTheme(name):
|
||||
name = name.title()
|
||||
if not color_themes.get(name):
|
||||
logging.error('Not a valid theme:', name)
|
||||
return
|
||||
|
||||
del color_themes[name]
|
||||
|
||||
|
||||
def GetTheme(name=None):
|
||||
if not name:
|
||||
return color_themes
|
||||
|
||||
name = name.title()
|
||||
theme = color_themes.get(name)
|
||||
|
||||
if not theme:
|
||||
logging.error('Not a valid theme:', name)
|
||||
return
|
||||
|
||||
return theme
|
||||
|
||||
|
||||
def ChangeTheme(theme=None):
|
||||
global current_theme
|
||||
|
||||
theme = 'Default' if not theme else theme.title()
|
||||
newtheme = color_themes.get(theme)
|
||||
|
||||
if not newtheme:
|
||||
logging.error('Not a valid theme:', theme)
|
||||
return
|
||||
|
||||
if isWindows:
|
||||
if theme != 'Default':
|
||||
app.setStyle('fusion')
|
||||
else:
|
||||
app.setStyle(default_style)
|
||||
row = get.theme(theme)
|
||||
|
||||
logging.verbose('Changing color theme to', theme)
|
||||
if not row:
|
||||
logging.error('Not a valid theme:', theme)
|
||||
return
|
||||
|
||||
current_theme = theme
|
||||
app.setPalette(newtheme)
|
||||
palette = row.palette
|
||||
|
||||
logging.verbose('Changing color theme to', theme)
|
||||
put.config('theme', theme)
|
||||
self.app.setPalette(palette)
|
||||
|
||||
if isWindows:
|
||||
if theme != 'Default':
|
||||
self.app.setStyle('fusion')
|
||||
|
||||
else:
|
||||
self.app.setStyle(self.default_style)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def SetupThemes(appclass):
|
||||
global default_style
|
||||
global app
|
||||
def ColorToPalette(self, data):
|
||||
palette = self.app.style().standardPalette()
|
||||
|
||||
app = appclass
|
||||
default_style = app.style().objectName().lower()
|
||||
appcolor = app.palette()
|
||||
for name, color in data.items():
|
||||
if name in self.colors and color != None:
|
||||
palette.setColor(getattr(QPalette, name), QColor(color))
|
||||
|
||||
colors = {}
|
||||
defcolors = [
|
||||
'Window', 'WindowText', 'Base', 'AlternateBase', 'ToolTipBase', 'ToolTipText', 'Button', 'ButtonText',
|
||||
'BrightText', 'Light', 'Midlight', 'Dark', 'Mid', 'Shadow', 'Highlight', 'HighlightedText', 'Link', 'LinkVisited'
|
||||
]
|
||||
return palette
|
||||
|
||||
for color in defcolors:
|
||||
lowercolor = color[0].lower() + color[1:]
|
||||
colors[color] = getattr(appcolor, lowercolor)().color()
|
||||
|
||||
AddTheme('Default', colors)
|
||||
AddTheme('dark', dark)
|
||||
def PaletteToColor(self, palette):
|
||||
data = {}
|
||||
|
||||
logging.verbose('Default style:', default_style)
|
||||
logging.verbose('Available styles:', ', '.join(QStyleFactory.keys()))
|
||||
logging.verbose('Available themes:', ', '.join(color_themes.keys()))
|
||||
for color in self.colors:
|
||||
data[color] = getattr(palette, color[0].lower() + ''.join(color[1:]))().color().name()
|
||||
|
||||
return data
|
||||
|
|
|
@ -1,49 +1,71 @@
|
|||
import sys
|
||||
|
||||
from . import debstable
|
||||
from .config import var
|
||||
from .database import db, put
|
||||
|
||||
from .Lib.IzzyLib import logging
|
||||
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWebEngineCore import *
|
||||
|
||||
|
||||
def RegisterScheme(name):
|
||||
scheme = QWebEngineUrlScheme(name.encode())
|
||||
scheme.setFlags(QWebEngineUrlScheme.SecureScheme)
|
||||
QWebEngineUrlScheme.registerScheme(scheme)
|
||||
scheme = QWebEngineUrlScheme(name.encode())
|
||||
scheme.setFlags(
|
||||
#QWebEngineUrlScheme.LocalScheme |
|
||||
QWebEngineUrlScheme.LocalAccessAllowed |
|
||||
QWebEngineUrlScheme.ViewSourceAllowed
|
||||
)
|
||||
|
||||
QWebEngineUrlScheme.registerScheme(scheme)
|
||||
|
||||
|
||||
class LocalHandler(QWebEngineUrlSchemeHandler):
|
||||
def __init__(self, webview, window):
|
||||
super().__init__(window)
|
||||
self.webview = webview
|
||||
def __init__(self, parent):
|
||||
self.window = parent
|
||||
|
||||
def requestStarted(self, job):
|
||||
url = job.requestUrl()
|
||||
host = url.host()
|
||||
if not debstable:
|
||||
scheme = QWebEngineUrlScheme.schemeByName(var.local.encode())
|
||||
|
||||
if scheme.name() == b'':
|
||||
logging.critical(f'Failed to register scheme: {var.local}. Exiting...')
|
||||
parent.close()
|
||||
|
||||
super().__init__(parent.profile)
|
||||
parent.profile.installUrlSchemeHandler(var.local.encode(), self)
|
||||
|
||||
|
||||
def _check_initiator(self, request):
|
||||
initiator = request.initiator()
|
||||
requrl = request.requestUrl()
|
||||
|
||||
if requrl.scheme() != var.local:
|
||||
logging.warning(f'Blocking malicious request from {initiator.toDisplayString()} to {request_url.toDisplayString()}')
|
||||
request.fail(QWebEngineUrlRequestJob.RequestDenied)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def requestStarted(self, request):
|
||||
url = request.requestUrl()
|
||||
path = url.path()
|
||||
|
||||
job.fail(QWebEngineUrlRequestJob.UrlNotFound)
|
||||
if not self._check_initiator(request):
|
||||
return
|
||||
|
||||
if url.host() == 'home':
|
||||
mimetype = 'text/html'
|
||||
data = b'<html><head><title>MERP!</title><body>UvU</body></html>'
|
||||
|
||||
blockedDomain = [
|
||||
'doubleclick.net',
|
||||
'rubiconproject.com',
|
||||
'outbrain.com',
|
||||
'googletagservices.com',
|
||||
'amazon-adsystem.com',
|
||||
'optimizely.com',
|
||||
'adswizz.com'
|
||||
]
|
||||
else:
|
||||
mimetype = 'text/html'
|
||||
data = '<html><head><title>QtWeb</title><body>heck</body></html>'
|
||||
|
||||
blockedURL = {
|
||||
'youtube.com': [
|
||||
'/api/stats/ads',
|
||||
'/pagead/adview',
|
||||
'/pagead/lvz'
|
||||
'/get_midroll/info'
|
||||
],
|
||||
'soundcloud.com': [
|
||||
'/audio-ads'
|
||||
],
|
||||
'facebook.com': [
|
||||
'/tr']
|
||||
}
|
||||
buf = QBuffer(parent=self)
|
||||
buf.open(QIODevice.WriteOnly)
|
||||
buf.write(data)
|
||||
buf.seek(0)
|
||||
buf.close()
|
||||
request.reply(mimetype.encode('ascii'), buf)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import threading, sys, os, time
|
||||
import threading, sys, os
|
||||
|
||||
from datetime import datetime
|
||||
from os.path import join
|
||||
|
@ -6,16 +6,16 @@ from pathlib import Path
|
|||
|
||||
from .Lib.IzzyLib import logging
|
||||
|
||||
from . import pyqtver, debstable, isWindows
|
||||
from . import __application__, __version__, pyqtver, debstable, isWindows
|
||||
from . import dialogs
|
||||
from .config import var
|
||||
from .database import db, get, put, delete
|
||||
from .functions import useragent, GetSoftware, httpclient, Decode, psl, DateTime, MD5Check
|
||||
from .functions import useragent, GetSoftware, httpclient, Decode, psl, MD5Check, WebEngineScript, DotDict
|
||||
|
||||
from PyQt5.QtWebEngineWidgets import *
|
||||
from PyQt5.QtCore import QUrl, QDateTime
|
||||
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
|
||||
from PyQt5.QtNetwork import QNetworkCookie, QNetworkCookieJar
|
||||
from PyQt5.QtNetwork import QNetworkCookie
|
||||
|
||||
|
||||
AddHeader = lambda info, k, v: info.setHttpHeader(k.encode(), v.encode())
|
||||
|
@ -26,7 +26,8 @@ blockedDomain = [
|
|||
'googletagservices.com',
|
||||
'amazon-adsystem.com',
|
||||
'optimizely.com',
|
||||
'adswizz.com'
|
||||
'adswizz.com',
|
||||
'scorecardsearch.com'
|
||||
]
|
||||
|
||||
blockedURL = {
|
||||
|
@ -37,21 +38,24 @@ blockedURL = {
|
|||
'/get_midroll/info'
|
||||
],
|
||||
'soundcloud.com': [
|
||||
'/audio-ads'
|
||||
'/audio-ads',
|
||||
'/me'
|
||||
],
|
||||
'facebook.com': [
|
||||
'/tr']
|
||||
'/tr',
|
||||
'/brandlift.php'
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class WebEngineProfile(QWebEngineProfile):
|
||||
def __init__(self, parent):
|
||||
super().__init__(var.profile)
|
||||
super().__init__(var.profile, parent)
|
||||
|
||||
parent.profile = self
|
||||
self.interceptor = WebFilter(parent)
|
||||
self.window = parent
|
||||
self.notif = parent.notif
|
||||
self.user_scripts = []
|
||||
self.cookies = []
|
||||
self.downloads = {}
|
||||
|
||||
|
@ -61,7 +65,7 @@ class WebEngineProfile(QWebEngineProfile):
|
|||
self.setCachePath(var.profilepath)
|
||||
self.setHttpCacheMaximumSize(int(get.config('cache', 100))*1024*1024)
|
||||
self.setPersistentStoragePath(var.profilepath)
|
||||
self.setHttpUserAgent(useragent)
|
||||
self.setHttpUserAgent(f'{self.httpUserAgent()} {__application__}/{__version__}')
|
||||
self.setSpellCheckEnabled(True)
|
||||
self.setSpellCheckLanguages(['en-US'])
|
||||
|
||||
|
@ -87,6 +91,8 @@ class WebEngineProfile(QWebEngineProfile):
|
|||
for k, v in self.attrs.items():
|
||||
self.settings().setAttribute(k, v)
|
||||
|
||||
self.LoadAllScripts()
|
||||
|
||||
# Will enable this once I figure out how to properly handle cookies
|
||||
#self.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
|
||||
#self.LoadCookies()
|
||||
|
@ -114,35 +120,33 @@ class WebEngineProfile(QWebEngineProfile):
|
|||
|
||||
|
||||
def _download_request(self, download):
|
||||
self.downloads[download.id()] = [download, None]
|
||||
dldialog = dialogs.NewDownload(self.downloads[download.id()], self)
|
||||
self.downloads[download.id()] = DotDict({'item': download, 'md5': None})
|
||||
dldialog = dialogs.NewDownload(self, self.downloads[download.id()])
|
||||
dldialog.exec_()
|
||||
|
||||
|
||||
def _download_finished(self, request):
|
||||
download, md5 = request
|
||||
|
||||
def _download_finished(self, download):
|
||||
if debstable:
|
||||
filename = Path(download.path())
|
||||
|
||||
else:
|
||||
filename = Path(join(download.downloadDirectory(), download.downloadFileName()))
|
||||
filename = Path(download.item.downloadDirectory(), download.item.downloadFileName())
|
||||
|
||||
url = download.url().toString()
|
||||
url = download.item.url().toString()
|
||||
|
||||
if md5 and filename.exists() and MD5Check(filename, md5) == False:
|
||||
if download.md5 and filename.exists() and MD5Check(filename, download.md5) == False:
|
||||
logging.debug(f'Download failed md5 check: {filename}')
|
||||
self.window.notif.New('Failed md5 check', url)
|
||||
self.window.notif.New('Download failed md5 check', filename)
|
||||
filename.unlink()
|
||||
|
||||
elif download.state() == QWebEngineDownloadItem.DownloadCompleted:
|
||||
elif download.item.state() == QWebEngineDownloadItem.DownloadCompleted:
|
||||
logging.debug(f'Finished downloading {url} to {filename}')
|
||||
self.window.notif.New('Finished Download', str(filename))
|
||||
|
||||
#if download.state() == QWebEngineDownloadItem.DownloadCancelled:
|
||||
#if download.item.state() == QWebEngineDownloadItem.DownloadCancelled:
|
||||
#self.notif.New('Cancelled Download', filename)
|
||||
|
||||
del self.downloads[download.id()]
|
||||
del self.downloads[download.item.id()]
|
||||
|
||||
|
||||
def _cookie_filter(self, request):
|
||||
|
@ -154,13 +158,42 @@ class WebEngineProfile(QWebEngineProfile):
|
|||
return True
|
||||
|
||||
|
||||
def _new_cookie(self, cookie):
|
||||
if not cookie.isSessionCookie():
|
||||
put.cookie(cookie)
|
||||
def GetScript(self, index):
|
||||
return self.user_scripts[index]
|
||||
|
||||
|
||||
def _rem_cookie(self, cookie):
|
||||
delete.cookie(cookie)
|
||||
def LoadScript(self, filename):
|
||||
script = Path(filename)
|
||||
webscript = WebEngineScript(self.scripts(), script, script.match(f'{var.scriptpath}/*'))
|
||||
webscript.enable()
|
||||
|
||||
self.user_scripts.append(webscript)
|
||||
|
||||
|
||||
def UnloadScript(self, index):
|
||||
script = index if type(index) == WebEngineScript else self.user_scripts[index]
|
||||
|
||||
return script.disable()
|
||||
|
||||
|
||||
def DeleteSCript(self, index):
|
||||
script = index if type(index) == WebEngineScript else self.user_scripts[index]
|
||||
|
||||
if not script.user:
|
||||
logging.error('Refusing to unload a system script')
|
||||
|
||||
script.disable()
|
||||
script.filename.unlink()
|
||||
|
||||
self.user_scripts.remove(script)
|
||||
|
||||
|
||||
def LoadAllScripts(self):
|
||||
scripts = [s for s in Path(var.scriptpath, 'scripts').rglob('*.sys.js')]
|
||||
scripts += [s for s in Path(var.profilepath, 'Scripts').rglob('*.user.js')]
|
||||
|
||||
for script in scripts:
|
||||
self.LoadScript(script)
|
||||
|
||||
|
||||
def LoadCookies(self):
|
||||
|
@ -197,30 +230,28 @@ class WebFilter(QWebEngineUrlRequestInterceptor):
|
|||
return
|
||||
|
||||
## Block requests if they're in the block list
|
||||
if get.config('adblock') and pagerow.adblock and (reqpath in blockedURL.get(reqdomain, []) or reqdomain in blockedDomain or reqtopdomain in blockedDomain):
|
||||
if get.config('adblock') and pagerow.adblock and (reqpath in blockedURL.get(reqtopdomain, []) or reqtopdomain in blockedDomain or reqdomain in blockedDomain):
|
||||
logging.verbose('Blocked content:', requrl.toString().split('?', 1)[0])
|
||||
info.block(True)
|
||||
return
|
||||
|
||||
## Check if domain is a mastodon instance
|
||||
if not pagerow.domain and not pagerow.mastodon and not any(map(requrl.toString().startswith, ['about:', 'chrome://'])):
|
||||
if not pagedomain:
|
||||
return
|
||||
if get.config('mastocheck', False):
|
||||
if not pagerow.domain and not pagerow.mastodon and not any(map(requrl.toString().startswith, ['about:', 'chrome://'])):
|
||||
if not pagedomain:
|
||||
return
|
||||
|
||||
if self.threads.get(pagedomain):
|
||||
return
|
||||
if self.threads.get(pagedomain):
|
||||
return
|
||||
|
||||
self.threads[pagedomain] = threading.Thread(target=GetSoftware, args=[pagedomain])
|
||||
self.threads[pagedomain].start()
|
||||
logging.verbose('Checking for presence of nodeinfo')
|
||||
self.threads[pagedomain] = threading.Thread(target=GetSoftware, args=[pagedomain])
|
||||
self.threads[pagedomain].start()
|
||||
|
||||
## Delete the thread object once it's finished
|
||||
elif self.threads.get(pagedomain):
|
||||
del self.threads[pagedomain]
|
||||
## Delete the thread object once it's finished
|
||||
elif self.threads.get(pagedomain):
|
||||
del self.threads[pagedomain]
|
||||
|
||||
## Set custom headers
|
||||
AddHeader(info, 'Trans-Rights', 'Are Human Rights')
|
||||
del pagerow
|
||||
|
||||
## Google is a fuck and is very picky about what browsers are supported
|
||||
#if 'google.com' in [pagetopdomain, reqtopdomain]:
|
||||
#info.setHttpHeader('user-agent'.encode(), 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0'.encode())
|
||||
# This breaks gmx.com
|
||||
#AddHeader(info, 'X-Trans-Rights', 'Are Human Rights')
|
267
qtweb/webview.py
267
qtweb/webview.py
|
@ -5,12 +5,13 @@ import validators
|
|||
|
||||
from .Lib.IzzyLib import logging
|
||||
from .Lib.IzzyLib.misc import boolean
|
||||
from .Lib.IzzyLib.cache import LRUCache
|
||||
|
||||
from . import debstable, dialogs
|
||||
from .config import var
|
||||
from . import debstable, dialogs, debstable
|
||||
from .config import var, default
|
||||
from .database import db, get, put
|
||||
from .mastodon import Mastodon
|
||||
from .functions import Menu, Icon
|
||||
from .functions import Menu, Icon, httpclient
|
||||
from .webviewjs import JsFuncs
|
||||
|
||||
from PyQt5.QtCore import *
|
||||
|
@ -40,6 +41,7 @@ class WebEngineView(QWebEngineView):
|
|||
super().__init__()
|
||||
|
||||
self.window = window
|
||||
self.jscache = LRUCache(64)
|
||||
self.webview = True
|
||||
self.tabs = window.ui.tabs
|
||||
self.webpage = WebEnginePage(self.window.profile, self)
|
||||
|
@ -54,15 +56,17 @@ class WebEngineView(QWebEngineView):
|
|||
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self._web_context_menu)
|
||||
#self.loadStarted.connect(lambda *args: setattr(self, 'loading', True))
|
||||
self.loadProgress.connect(self._progress_loading)
|
||||
#self.loadStarted.connect(self._started_loading)
|
||||
self.loadFinished.connect(self._finished_loading)
|
||||
|
||||
self.webpage.titleChanged.connect(self._set_tab_text)
|
||||
self.webpage.iconChanged.connect(self._set_tab_icon)
|
||||
self.webpage.urlChanged.connect(self._set_tab_url)
|
||||
self.webpage.linkHovered.connect(self._link_hover)
|
||||
self.webpage.fullScreenRequested.connect(lambda *args: self._permission_request('fullscreen', *args))
|
||||
self.webpage.featurePermissionRequested.connect(self._feature_request)
|
||||
self.webpage.fullScreenRequested.connect(self._fullscreen_request)
|
||||
self.webpage.featurePermissionRequested.connect(self._permission_request)
|
||||
self.webpage.authenticationRequired.connect(self._handle_auth)
|
||||
|
||||
self.webaction_state = {
|
||||
'previous': False,
|
||||
|
@ -79,15 +83,41 @@ class WebEngineView(QWebEngineView):
|
|||
self.JsAction = JsFuncs(self)
|
||||
|
||||
if url != 'newtab':
|
||||
self.LoadUrl(url if url else get.config('homepage', 'https://ddg.gg'))
|
||||
self.LoadUrl(url if url else get.config('homepage', default.homepage))
|
||||
|
||||
|
||||
def _progress_loading(self, progress):
|
||||
current_index = self.window.ui.tabs.currentIndex()
|
||||
tab_index = self.window.ui.tabs.indexOf(self)
|
||||
loading = progress != 100
|
||||
percent = progress / 100
|
||||
|
||||
self.webaction_state['stop'] = loading
|
||||
self.webaction_state['reload'] = not loading
|
||||
|
||||
if current_index == tab_index:
|
||||
#if loading:
|
||||
#self.window.ui.url.setStyleSheet('background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop: 0 #1bb77b, stop: ' + str(percent) + ' #1bb77b, stop: ' + str(percent+ 0.001) + ' rgba(0, 0, 0, 0), stop: 1 white)')
|
||||
#else:
|
||||
#self.window.ui.url.setStyleSheet(None)
|
||||
|
||||
self.window._update_webaction_buttons('stop', self.webaction_state['stop'])
|
||||
self.window._update_webaction_buttons('reload', self.webaction_state['reload'])
|
||||
|
||||
|
||||
def _finished_loading(self, arg):
|
||||
jsfile = Path(var.scriptpath, 'web', 'js', self.url().host() + '.js')
|
||||
host = self.url().host()
|
||||
jsfile = Path(var.scriptpath, 'scripts', host + '.js')
|
||||
jscache = self.jscache.fetch(host)
|
||||
|
||||
if jsfile.exists():
|
||||
with jsfile.open('r') as fd:
|
||||
self.webpage.runJavaScript(fd.read())
|
||||
if jsfile.is_file():
|
||||
if not jscache:
|
||||
with jsfile.open('r') as fd:
|
||||
data = fd.read()
|
||||
self.jscache.store(host, data)
|
||||
jscache = data
|
||||
|
||||
self.webpage.runJavaScript(jscache)
|
||||
|
||||
|
||||
def _set_tab_text(self, title):
|
||||
|
@ -121,13 +151,16 @@ class WebEngineView(QWebEngineView):
|
|||
action = recv_action.text().lower()
|
||||
state = recv_action.isEnabled()
|
||||
|
||||
if action in ['stop', 'reload']:
|
||||
return
|
||||
|
||||
self.webaction_state[action] = state
|
||||
|
||||
if current_index == tab_index:
|
||||
self.window._update_webaction_buttons(action, state)
|
||||
|
||||
|
||||
def _feature_request(self, origin, feature):
|
||||
def _permission_request(self, origin, feature):
|
||||
host = origin.host()
|
||||
site = get.permissions(host)
|
||||
|
||||
|
@ -154,28 +187,43 @@ class WebEngineView(QWebEngineView):
|
|||
logging.verbose(f'Rejected permission for {host}:', name)
|
||||
|
||||
|
||||
def _permission_request(self, action, request):
|
||||
host = self.url().host()
|
||||
site = get.permissions(host)
|
||||
permissions = {
|
||||
'fullscreen': (site.fullscreen, 'Fullscreen')
|
||||
}
|
||||
def _fullscreen_request(self, request):
|
||||
host = request.origin().host()
|
||||
site = get.permissions(host, defaults=True)
|
||||
|
||||
permission, name = permissions.get(action, (None, 'Invalid permission'))
|
||||
|
||||
if action == 'fullscreen':
|
||||
if site.fullscreen:
|
||||
logging.verbose('Allowing fullscreen for', host)
|
||||
request.accept()
|
||||
self.Fullscreen(request.toggleOn())
|
||||
|
||||
else:
|
||||
logging.verbose('Disallowing fullscreen for ', host)
|
||||
|
||||
|
||||
def _handle_auth(self, location, authenticator):
|
||||
url = location.toString()
|
||||
login = dialogs.HttpAuth(location, authenticator, self)
|
||||
login.exec_()
|
||||
|
||||
if login.result:
|
||||
logging.verbose('Authenticated page:', url)
|
||||
|
||||
else:
|
||||
logging.verbose('Canceled authentication for page:', url)
|
||||
self.stop()
|
||||
|
||||
if self.window.ui.tabs.currentWidget() == self:
|
||||
self.window.ui.url.setText(url)
|
||||
|
||||
|
||||
def _web_context_menu(self, point):
|
||||
cursor = QCursor().pos() #point puts the menu in the wrong location for some reason
|
||||
cursor = QCursor().pos()
|
||||
data = self.webpage.contextMenuData()
|
||||
clipboard = self.window.clipboard
|
||||
mimedata = clipboard.mimeData()
|
||||
currenturl = self.url()
|
||||
spliturl = currenturl.path().split('/')
|
||||
selected = data.selectedText()
|
||||
search = get.search(get.config('search', 'ddg'))
|
||||
|
||||
urlismasto = get.permissions(currenturl.host(), defaults=True).mastodon
|
||||
urlispost = (len(spliturl) == 3 and spliturl[1].startswith('@'))
|
||||
|
@ -188,16 +236,7 @@ class WebEngineView(QWebEngineView):
|
|||
acctcount = db.count('mastodon') > 0
|
||||
|
||||
mediaurl = data.mediaUrl().toString()
|
||||
menu = Menu('heck', self)
|
||||
addItem = lambda label, name, data=None: menu.AddAction(label, lambda : self._menu_actions(name, data))
|
||||
addMastoItem = lambda label, name, data=None: menu.AddAction(label, lambda : self._mastodon_action(name, data))
|
||||
|
||||
if mediaurl != '':
|
||||
addItem('Open Media in New Tab', 'newtab', mediaurl)
|
||||
addItem('Open Media in New Tab and Switch', 'newtabswitch', mediaurl)
|
||||
menu.AddSeparator()
|
||||
addItem('Download Media', 'download', mediaurl)
|
||||
addItem('Copy Media URL', 'copy', mediaurl)
|
||||
menu = Menu(self)
|
||||
|
||||
## Not sure if I wanna enable this
|
||||
#if acctcount and urlismasto and urlispost:
|
||||
|
@ -210,47 +249,69 @@ class WebEngineView(QWebEngineView):
|
|||
linkurl = data.selectedText()
|
||||
|
||||
if linkurl != '':
|
||||
addItem('Open Link in New Tab', 'newtab', linkurl)
|
||||
addItem('Open Link in New Tab and Switch', 'newtabswitch', linkurl)
|
||||
menu.AddItem('Open Link in New Tab', self._menu_actions, 'newtab', linkurl, icon=Icon('newtab'))
|
||||
menu.AddItem('Open Link in New Tab and Switch', self._menu_actions, 'newtabswitch', linkurl, icon=Icon('newtab'))
|
||||
menu.AddSeparator()
|
||||
addItem('Download Link', 'download', linkurl)
|
||||
menu.AddSeparator()
|
||||
|
||||
if acctcount and linkispost and linkismasto:
|
||||
addMastoItem('Reply to Linked Post', 'reply', linkurl)
|
||||
addMastoItem('Favorite Linked Post', 'favourite', linkurl)
|
||||
addMastoItem('Boost Linked Post', 'reblog', linkurl)
|
||||
addMastoItem('Bookmark Linked Post', 'bookmark', linkurl)
|
||||
|
||||
menu.AddSeparator()
|
||||
addItem('Copy Link URL', 'copyurl')
|
||||
addItem('Copy Link Text', 'copy', linktext)
|
||||
menu.AddItem('Copy Link Location', self._menu_actions, 'copyurl')
|
||||
menu.AddItem('Copy Link Text', self._menu_actions, 'copy', linktext)
|
||||
|
||||
if selected:
|
||||
if data.CanCut:
|
||||
addItem('Cut', 'cut', selected)
|
||||
menu.AddItem('Cut', self._menu_actions, 'cut', selected)
|
||||
|
||||
if data.CanCopy:
|
||||
addItem('Copy', 'copy', selected)
|
||||
menu.AddItem('Copy', self._menu_actions, 'copy', selected)
|
||||
|
||||
if data.isContentEditable():
|
||||
if data.CanPaste:
|
||||
addItem('Paste', 'paste', clipboard.text('plain'))
|
||||
menu.AddItem('Paste', self._menu_actions, 'paste', clipboard.text('plain'))
|
||||
|
||||
if data.CanUndo:
|
||||
menu.AddSeparator()
|
||||
addItem('Undo', 'undo')
|
||||
menu.AddItem('Undo', self._menu_actions, 'undo')
|
||||
|
||||
if data.CanRedo:
|
||||
addItem('Redo', 'Redo')
|
||||
menu.AddItem('Redo', self._menu_actions, 'Redo')
|
||||
|
||||
if linkurl != '':
|
||||
menu.AddItem('Download Link', self._menu_actions, 'download', linkurl)
|
||||
|
||||
if acctcount and linkispost and linkismasto:
|
||||
menu.AddSeparator()
|
||||
menu.AddItem('Reply to Linked Post', self._mastodon_action, 'reply', linkurl)
|
||||
menu.AddItem('Favorite Linked Post', self._mastodon_action, 'favourite', linkurl)
|
||||
menu.AddItem('Boost Linked Post', self._mastodon_action, 'reblog', linkurl)
|
||||
menu.AddItem('Bookmark Linked Post', self._mastodon_action, 'bookmark', linkurl)
|
||||
#menu.AddItem('Add Domain Block', self._mastodon_action, 'domainblock', data.linkUrl().host())
|
||||
|
||||
if menu.MoreThan(0):
|
||||
menu.AddSeparator()
|
||||
|
||||
addItem('Inspect Page', 'inspect_page')
|
||||
addItem('Inspect Element', 'inspect')
|
||||
if selected and search:
|
||||
menu.AddSeparator()
|
||||
menu.AddItem(f'Search with {search.name}', self._menu_actions, 'newtabswitch', search.url.format(q=selected))
|
||||
searchmenu = Menu(self, 'Search with...')
|
||||
|
||||
for row in db.fetch('search', single=False):
|
||||
if row.keyword != search.keyword:
|
||||
searchmenu.AddItem(row.name, self._menu_actions, 'newtabswitch', row.url.format(q=selected))
|
||||
|
||||
menu.addMenu(searchmenu)
|
||||
menu.AddSeparator()
|
||||
|
||||
if mediaurl != '':
|
||||
menu.AddItem('Open Media in New Tab', self._menu_actions, 'newtab', mediaurl, icon=Icon('newtab'))
|
||||
menu.AddItem('Open Media in New Tab and Switch', self._menu_actions, 'newtabswitch', mediaurl, icon=Icon('newtab'))
|
||||
menu.AddSeparator()
|
||||
menu.AddItem('Copy Media Location', self._menu_actions, 'copy', mediaurl)
|
||||
menu.AddItem('Download Media', self._menu_actions, 'download', mediaurl)
|
||||
menu.AddSeparator()
|
||||
|
||||
menu.AddItem('Inspect Page', self._menu_actions, 'inspect_page')
|
||||
menu.AddItem('Inspect Element', self._menu_actions, 'inspect')
|
||||
menu.AddSeparator()
|
||||
addItem('View Page Source', 'source')
|
||||
menu.AddItem('View Page Source', self._menu_actions, 'source')
|
||||
|
||||
menu.aboutToHide.connect(menu.deleteLater) #not sure if needed
|
||||
menu.popup(cursor)
|
||||
|
||||
|
@ -285,6 +346,9 @@ class WebEngineView(QWebEngineView):
|
|||
|
||||
|
||||
def _mastodon_action(self, action, url):
|
||||
if action == 'domainblock':
|
||||
account = get.mastoaccount(default=True)
|
||||
self.window.NewWebTab(f'https://{account.domain}/admin/domain_blocks/new?_domain={url}')
|
||||
if action == 'reply':
|
||||
dialogs.Toot(self.window, self.window.mastodon, reply=url).show()
|
||||
|
||||
|
@ -306,6 +370,11 @@ class WebEngineView(QWebEngineView):
|
|||
logging.debug('js:', message)
|
||||
|
||||
|
||||
## Does nothing right now, but will be used later
|
||||
def CloseTab(self):
|
||||
pass
|
||||
|
||||
|
||||
def Fullscreen(self, gofull):
|
||||
fullscreen = self.window.FullscreenState()
|
||||
|
||||
|
@ -390,10 +459,12 @@ class WebEngineView(QWebEngineView):
|
|||
twitter_paths = any(map(parsed.path.startswith, ['/settings', '/home', '/login', '/logout', '/explore', '/sessions', '/account']))
|
||||
twitter_files = any(map(parsed.path.endswith, ['.js', '.jpeg', '.jpg', '.png', '.css']))
|
||||
|
||||
if parsed.netloc == 'twitter.com' and get.config('twiredir', True) and parsed.path != '/' and not twitter_paths and not twitter_files:
|
||||
newurl = f'https://nitter.net{parsed.path}'
|
||||
if parsed.netloc == 'twitter.com' and get.config('nitter', False) and parsed.path != '/' and not twitter_paths and not twitter_files:
|
||||
nitterurl = get.config('nitterurl', default.nitter)
|
||||
newurl = nitterurl + parsed.path
|
||||
|
||||
self.webpage.setUrl(QUrl(newurl))
|
||||
self.setFocus()
|
||||
|
||||
|
||||
class SearchBar(QFrame):
|
||||
|
@ -403,7 +474,7 @@ class SearchBar(QFrame):
|
|||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 2)
|
||||
layout.setContentsMargins(0, 6, 0, 0)
|
||||
layout.setSpacing(4)
|
||||
#layout.setSizeConstraint(QLayout.SetFixedSize)
|
||||
self.setLayout(layout)
|
||||
|
@ -418,10 +489,6 @@ class SearchBar(QFrame):
|
|||
close.setIcon(Icon('close'))
|
||||
|
||||
for widget in [previous, next, self.search, self.sensitive, reset, close]:
|
||||
#if widget == self.search:
|
||||
#widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
|
||||
#else:
|
||||
#widget.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
|
||||
self.layout().addWidget(widget)
|
||||
|
||||
self.search.installEventFilter(FindEsc(self))
|
||||
|
@ -434,17 +501,18 @@ class SearchBar(QFrame):
|
|||
close.clicked.connect(lambda *args: self.Search('toggle'))
|
||||
|
||||
|
||||
def Search(self, action=None):
|
||||
def Search(self, action=None, text=None):
|
||||
if text:
|
||||
self.search.setText(text)
|
||||
text = self.search.text()
|
||||
webview = self.webview
|
||||
selected = webview.selectedText()
|
||||
selected = self.webview.selectedText()
|
||||
sensitive = self.sensitive.isChecked()
|
||||
|
||||
if action == 'previous':
|
||||
webview.Search(text, reverse=True, sensitive=sensitive)
|
||||
self.webview.Search(text, reverse=True, sensitive=sensitive)
|
||||
|
||||
elif action == 'reset':
|
||||
webview.Search()
|
||||
self.webview.Search()
|
||||
self.sensitive.setChecked(False)
|
||||
self.search.setText('')
|
||||
|
||||
|
@ -461,12 +529,12 @@ class SearchBar(QFrame):
|
|||
self.Search()
|
||||
|
||||
else:
|
||||
## Reset the search and focus the webview
|
||||
## Reset the search and focus the window
|
||||
self.Search('reset')
|
||||
webview.setFocus()
|
||||
self.webview.setFocus()
|
||||
|
||||
else:
|
||||
webview.Search(text, sensitive=sensitive)
|
||||
self.webview.Search(text, sensitive=sensitive)
|
||||
|
||||
|
||||
class FindEsc(QObject):
|
||||
|
@ -474,7 +542,6 @@ class FindEsc(QObject):
|
|||
super().__init__(parent)
|
||||
self.webview = parent
|
||||
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if event.type() == QEvent.KeyPress and event.key() == 16777216:
|
||||
self.webview.Search('toggle')
|
||||
|
@ -512,12 +579,14 @@ class WebEnginePage(QWebEnginePage):
|
|||
|
||||
def createWindow(self, wintype):
|
||||
options = {'nexttab': True}
|
||||
#wintypes = [
|
||||
#QWebEnginePage.WebBrowserTab,
|
||||
#QWebEnginePage.WebBrowserWindow,
|
||||
#QWebEnginePage.WebBrowserBackgroundTab,
|
||||
#QWebEnginePage.WebDialog
|
||||
#]
|
||||
wintypes = {
|
||||
QWebEnginePage.WebBrowserTab: 'new tab',
|
||||
QWebEnginePage.WebBrowserWindow: 'new window',
|
||||
QWebEnginePage.WebBrowserBackgroundTab: 'new background tab',
|
||||
QWebEnginePage.WebDialog: 'new dialog'
|
||||
}
|
||||
|
||||
typename = wintypes.get(wintype, f'Invalid window type: {type(wintype)}')
|
||||
|
||||
if wintype != QWebEnginePage.WebDialog:
|
||||
options['switch'] = True
|
||||
|
@ -525,9 +594,31 @@ class WebEnginePage(QWebEnginePage):
|
|||
return self.webview.window.NewWebTab('newtab' ,**options).webpage
|
||||
|
||||
|
||||
def triggerAction(self, action, checked=False):
|
||||
if action == QWebEnginePage.OpenLinkInNewWindow:
|
||||
self.createWindow(QWebEnginePage.WebBrowserWindow)
|
||||
return
|
||||
|
||||
return super(WebEnginePage, self).triggerAction(action, checked)
|
||||
|
||||
|
||||
def acceptNavigationRequest(self, url, Type, isMainFrame):
|
||||
parsed = urlparse(url.toString())
|
||||
|
||||
types = {
|
||||
QWebEnginePage.NavigationTypeLinkClicked: 'link clicked',
|
||||
QWebEnginePage.NavigationTypeTyped: 'loaded url',
|
||||
QWebEnginePage.NavigationTypeFormSubmitted: 'form submission',
|
||||
QWebEnginePage.NavigationTypeBackForward: 'back/forward action',
|
||||
QWebEnginePage.NavigationTypeReload: 'reloaded',
|
||||
QWebEnginePage.NavigationTypeOther: 'other type'
|
||||
}
|
||||
|
||||
if not debstable:
|
||||
types[QWebEnginePage.NavigationTypeRedirect] = 'redirected'
|
||||
|
||||
typename = types.get(Type, 'Invalid nav type')
|
||||
|
||||
return QWebEnginePage.acceptNavigationRequest(self, url, Type, isMainFrame)
|
||||
|
||||
|
||||
|
@ -537,36 +628,36 @@ class DevPage(QMainWindow):
|
|||
self.profile = profile
|
||||
self.window = parent
|
||||
self.view = None
|
||||
self.page = None
|
||||
#self.setGeometry(100, 100, 800, 600)
|
||||
self.resize(800, 600)
|
||||
|
||||
def Setup(self, url):
|
||||
self.view = QWebEngineView()
|
||||
self.view = QWebEngineView(self.window)
|
||||
self.page = DevWebEnginePage(self.profile, self.window)
|
||||
self.view.setPage(self.page)
|
||||
self.setCentralWidget(self.view)
|
||||
self.setWindowTitle(f'QtWeb Inspector: {url}')
|
||||
self.show()
|
||||
self.activateWindow()
|
||||
|
||||
return self.page
|
||||
|
||||
|
||||
def closeEvent(self, event):
|
||||
def closeEvent(self, event=None):
|
||||
logging.debug('closing dev tools')
|
||||
#self.view.deleteLater()
|
||||
#self.page.deleteLater()
|
||||
#del self.view
|
||||
#del self.page
|
||||
event.accept()
|
||||
self.view.deleteLater()
|
||||
self.page.deleteLater()
|
||||
self.view = None
|
||||
self.page = None
|
||||
|
||||
|
||||
def heck(self):
|
||||
print('HECK!')
|
||||
if event:
|
||||
event.accept()
|
||||
|
||||
|
||||
class DevWebEnginePage(QWebEnginePage):
|
||||
def __init__(self, profile, parent):
|
||||
super().__init__(profile)
|
||||
super().__init__(profile, parent)
|
||||
self.window = parent
|
||||
|
||||
|
||||
|
|
|
@ -4,9 +4,6 @@ from .config import var
|
|||
|
||||
from .Lib.IzzyLib import logging
|
||||
|
||||
from PyQt5.QtWebChannel import QWebChannel
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
|
||||
|
||||
|
||||
class JsFuncs(object):
|
||||
def __init__(self, webview):
|
||||
|
|
|
@ -4,3 +4,5 @@ publicsuffixlist
|
|||
asyncqt
|
||||
urllib3
|
||||
dbutils
|
||||
jinja2
|
||||
hamlpy3
|
||||
|
|
Reference in a new issue