diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc5c5474..f66ea4ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,26 +8,34 @@ the changes in the CHANGELOG formatting.
## [Unreleased]
-## [0.4.41] - 2020-12-02
+## [0.4.41] - 2020-12-04
### Added
- Tor support
- - Automatic use of Tor when bootstrapping succeeds
- - Tor proxying for all ipfs-search and cyber requests
+ - Tor proxying on all platforms (enabled manually from the
+ status bar for now, very soon there'll be finer control
+ of the relays via [stem](https://stem.torproject.org/))
+ - Proxying of ipfs-search and cyber requests via Tor
- Add a new *anonymous* web profile
- Automatically fetch favicons when hashmarking an http(s) website
- Handle SSL certificate errors
+- New python dependencies
+ - [aiohttp-socks](https://pypi.org/project/aiohttp-socks/) >=0.5.5
+ - validators >= 0.18.1
+
### Changed
-- Bookmarking of any type of URLs
-- Browser UI
+- Browser tab UI
- Use a block-style cursor for the address bar
- Typing an .eth domain name automatically loads it through *ens://*
- Run IPFS searches or searches with popular engines (duckduckgo, ..)
from the address bar
- - Nicer history lookup interface
+ - Change the history lookup interface
-- The @Earth workspace is now the default workspace in the stack
+- The @Earth workspace is now the first/default workspace in the WS stack
+- Workspace APIs
+ - Changed wsRegisterTab(): accept a *position* argument to insert tabs
+ at the end of the tabs list or after the current tab
### Fixed
- Bookmarking of clearnet URLs
diff --git a/README.rst b/README.rst
index d432e73e..19f055f7 100644
--- a/README.rst
+++ b/README.rst
@@ -122,6 +122,7 @@ for more information).
of files in the MFS)
- File sharing
- BitTorrent to IPFS bridge
+- Tor support
- Search content with the ipfs-search_ search engine as well as with cyber_
- Built-in blog with Atom feeds
- Webcam to IPFS capture (image and videos)
diff --git a/galacteek/application.py b/galacteek/application.py
index 4c1e8a12..981e4ec2 100644
--- a/galacteek/application.py
+++ b/galacteek/application.py
@@ -511,7 +511,8 @@ def initMisc(self):
self.tempDirWeb = self.tempDirCreate(
self.tempDir.path(), 'webdownloads')
- self.tor = TorLauncher(self._torConfigLocation)
+ self.tor = TorLauncher(self._torConfigLocation,
+ self._torDataDirLocation)
self._goIpfsBinPath = self.suitableGoIpfsBinary()
def tempDirCreate(self, basedir, name=None):
@@ -1008,6 +1009,7 @@ def setupPaths(self):
self._mHashDbLocation = os.path.join(self.dataLocation, 'mhashmetadb')
self._sqliteDbLocation = os.path.join(self.dataLocation, 'db.sqlite')
self._torConfigLocation = os.path.join(self.dataLocation, 'torrc')
+ self._torDataDirLocation = os.path.join(self.dataLocation, 'tor-data')
self._pLockLocation = os.path.join(self.dataLocation, 'profile.lock')
self._mainDbLocation = os.path.join(
self.dataLocation, 'db_main.sqlite3')
@@ -1041,6 +1043,7 @@ def setupPaths(self):
for dir in [self._mHashDbLocation,
self._logsLocation,
self.ipfsBinLocation,
+ self._torDataDirLocation,
self.marksDataLocation,
self.cryptoDataLocation,
self.eccDataLocation,
diff --git a/galacteek/core/glogger.py b/galacteek/core/glogger.py
index 306318ba..ccba239f 100644
--- a/galacteek/core/glogger.py
+++ b/galacteek/core/glogger.py
@@ -50,6 +50,9 @@ class LogRecordStyler:
'basecolor': 'darkred',
'red': -0.1
},
+ 'galacteek.core.tor': {
+ 'basecolor': 'darkred'
+ },
# crypto modules
'galacteek.crypto': {
diff --git a/galacteek/core/tor.py b/galacteek/core/tor.py
index 99dd6525..967a7eb3 100644
--- a/galacteek/core/tor.py
+++ b/galacteek/core/tor.py
@@ -1,16 +1,56 @@
import asyncio
import re
-import signal
import psutil
import platform
import subprocess
-import tempfile
+import secrets
+from os import urandom
+from binascii import b2a_hex
+from hashlib import sha1
from galacteek import log
from galacteek import ensure
from galacteek import AsyncSignal
from galacteek.core.asynclib import asyncWriteFile
-from galacteek.core import unusedTcpPort
+
+
+def getTorHashedPassword(secret):
+ '''
+ https://gist.github.com/jamesacampbell/2f170fc17a328a638322078f42e04cbc
+ '''
+ # static 'count' value later referenced as "c"
+ indicator = chr(96)
+ # generate salt and append indicator value so that it
+ salt = "%s%s" % (urandom(8), indicator)
+ c = ord(salt[8])
+ # generate an even number that can be divided in subsequent sections.
+ # (Thanks Roman)
+ EXPBIAS = 6
+ count = (16 + (c & 15)) << ((c >> 4) + EXPBIAS)
+ d = sha1()
+ # take the salt and append the password
+ tmp = salt[:8] + secret
+ # hash the salty password
+ slen = len(tmp)
+ while count:
+ if count > slen:
+ d.update(tmp.encode())
+ count -= slen
+ else:
+ d.update(tmp[:count].encode())
+ count = 0
+ hashed = d.digest()
+
+ # Put it all together into the proprietary Tor format.
+ salt = b2a_hex(salt[:8].encode()).decode().upper()
+ ind = b2a_hex(indicator.encode()).decode()
+ h = b2a_hex(hashed).decode().upper()
+
+ return '16:{salt}{i}{h}'.format(
+ salt=salt,
+ i=ind,
+ h=h
+ )
class TorProtocol(asyncio.SubprocessProtocol):
@@ -65,16 +105,18 @@ def process_exited(self):
AutomapHostsOnResolve 1
AutomapHostsSuffixes .exit,.onion
DataDirectory {dataDir}
+HashedControlPassword {hashedControlPass}
'''
class TorConfigBuilder:
- def __init__(self):
+ def __init__(self, dataDir):
self._socksPort = None
self._controlPort = None
self._dnsPort = None
self._hostname = '127.0.0.1'
- self._dataDir = tempfile.mkdtemp(prefix='gtor')
+ self._dataDir = dataDir
+ self.__controlPass = secrets.token_hex(8)
@property
def socksPort(self):
@@ -103,12 +145,14 @@ def __str__(self):
socksPort=self.socksPort,
controlPort=self.controlPort,
dnsPort=self.dnsPort,
- dataDir=self._dataDir
+ dataDir=self._dataDir,
+ hashedControlPass=getTorHashedPassword(self.__controlPass)
)
class TorLauncher:
- def __init__(self, configPath, torPath='tor', debug=True, loop=None):
+ def __init__(self, configPath, dataDirPath,
+ torPath='tor', debug=True, loop=None):
self.loop = loop if loop else asyncio.get_event_loop()
self.exitFuture = asyncio.Future(loop=self.loop)
@@ -118,9 +162,10 @@ def __init__(self, configPath, torPath='tor', debug=True, loop=None):
self._process = None
self.torPath = torPath
self.configPath = configPath
+ self.dataDirPath = dataDirPath
self.debug = debug
self.transport, self.proto = None, None
- self.torCfg = TorConfigBuilder()
+ self.torCfg = TorConfigBuilder(self.dataDirPath)
self.torProto = TorProtocol(self.loop, self.exitFuture,
self.startedFuture,
debug=self.debug)
@@ -158,9 +203,9 @@ async def start(self):
startupInfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupInfo.wShowWindow = subprocess.SW_HIDE
- # for socksPort in range(9052, 9080):
- for x in range(0, 12):
- socksPort = unusedTcpPort()
+ # for x in range(0, 12):
+ for socksPort in range(9050, 9080):
+ # socksPort = unusedTcpPort()
self.torCfg.socksPort = socksPort
await asyncWriteFile(self.configPath, str(self.torCfg), 'w+t')
@@ -184,8 +229,20 @@ async def start(self):
self._procPid = self.transport.get_pid()
self.process = psutil.Process(self._procPid)
- except Exception:
- log.debug(f'Starting TOR failed on port {socksPort}')
+
+ # Wait a bit, if there are port binding issues
+ # tor will exit immediately
+ await asyncio.sleep(3)
+
+ status = self.process.status()
+ assert status in [
+ psutil.STATUS_RUNNING,
+ psutil.STATUS_SLEEPING
+ ]
+ except Exception as err:
+ log.debug(f'Starting TOR failed on port {socksPort} : '
+ f'error {err}')
+ self.transport.close()
continue
else:
log.debug(f'Starting TOR OK on port {socksPort}')
@@ -197,12 +254,7 @@ def stop(self):
if not self.process:
raise Exception('Process not found')
- if platform.system() == 'Windows':
- self.process.kill()
- else:
- self.process.send_signal(signal.SIGINT)
- self.process.send_signal(signal.SIGHUP)
-
+ self.transport.terminate()
self._procPid = None
return True
except Exception as err:
diff --git a/galacteek/core/webprofiles.py b/galacteek/core/webprofiles.py
index 2726d34f..dca6c3f9 100644
--- a/galacteek/core/webprofiles.py
+++ b/galacteek/core/webprofiles.py
@@ -70,6 +70,7 @@ def setSettings(self):
True)
self.webSettings.setAttribute(QWebEngineSettings.LocalStorageEnabled,
True)
+ self.setHttpCacheType(QWebEngineProfile.NoCache)
def installHandler(self, scheme, handler):
sch = scheme if isinstance(scheme, bytes) else scheme.encode()
diff --git a/galacteek/docs/manual/__init__.py b/galacteek/docs/manual/__init__.py
index 9cd32d66..f91247df 100644
--- a/galacteek/docs/manual/__init__.py
+++ b/galacteek/docs/manual/__init__.py
@@ -1 +1 @@
-__manual_en_version__ = '20201022'
+__manual_en_version__ = '20201204'
diff --git a/galacteek/docs/manual/en/browsing.rst b/galacteek/docs/manual/en/browsing.rst
index 05a6bc51..d8b408a2 100644
--- a/galacteek/docs/manual/en/browsing.rst
+++ b/galacteek/docs/manual/en/browsing.rst
@@ -19,10 +19,9 @@ will hide the results.
You can also use specific syntax to search with certain
search engines:
-- Use the **d** command to search with the
- [DuckDuckGo](https://duckduckgo.com/) web search engine.
+- Use the **d** prefix to search with the DuckDuckGo_ web search engine.
Example: **d distributed web**
-- Use the **i** or **ip** command to run a search on the IPFS
+- Use the **i** or **ip** prefix to run a search on the IPFS
network. Example: **i distributed web**
CID status icon
@@ -170,7 +169,7 @@ and the result is cached.
Web profiles
------------
-There are 3 distinct web profiles that can be used when accessing a
+There are 4 distinct web profiles that can be used when accessing a
webpage. The current profile can be changed from a browser tab by
opening the IPFS menu and selecting a profile from the *Web profile*
submenu.
@@ -208,3 +207,4 @@ a *Web3* instance (from the *web3.js* JS library) available as
*window.web3* in the main Javascript world
.. _ENS: https://ens.domains/
+.. _DuckDuckGo: https://duckduckgo.com
diff --git a/galacteek/ui/browser.py b/galacteek/ui/browser.py
index e8f5e5cc..f19e42b2 100644
--- a/galacteek/ui/browser.py
+++ b/galacteek/ui/browser.py
@@ -27,6 +27,10 @@
from PyQt5.QtCore import QTimer
from PyQt5.QtCore import QPoint
from PyQt5.QtCore import QSize
+from PyQt5.QtCore import QEvent
+
+from PyQt5.QtGui import QPalette
+from PyQt5.QtGui import QColor
from PyQt5 import QtWebEngineWidgets
@@ -68,6 +72,7 @@
from galacteek.core.schemes import DAGProxySchemeHandler
from galacteek.core.schemes import MultiDAGProxySchemeHandler
from galacteek.core.schemes import IPFSObjectProxyScheme
+from galacteek.core.schemes import isUrlSupported
from galacteek.core.webprofiles import WP_NAME_IPFS
from galacteek.core.webprofiles import WP_NAME_MINIMAL
@@ -246,6 +251,11 @@ def iInvalidUrl(text):
'Invalid URL: {0}').format(text)
+def iUnsupportedUrl():
+ return QCoreApplication.translate('BrowserTabForm',
+ 'Unsupported URL type')
+
+
def iInvalidObjectPath(text):
return QCoreApplication.translate(
'BrowserTabForm',
@@ -341,13 +351,13 @@ def __init__(self, webProfile, parent):
super(DefaultBrowserWebPage, self).__init__(webProfile, parent)
self.app = QCoreApplication.instance()
self.fullScreenRequested.connect(self.onFullScreenRequest)
- self.setBackgroundColor(desertStrike1)
+ self.setBackgroundColor(desertStrikeColor)
def certificateError(self, error):
- return not questionBox(
- error.url.toString(),
+ return questionBox(
+ error.url().toString(),
f'Certificate error: {error.errorDescription()}.'
- 'Continue ?')
+ 'Continue ?')
def onFullScreenRequest(self, req):
# Accept fullscreen requests unconditionally
@@ -438,6 +448,7 @@ def __init__(self, browserTab, webProfile, enablePlugins=False,
self.browserTab = browserTab
self.linkInfoTimer = QTimer()
self.linkInfoTimer.timeout.connect(self.onLinkInfoTimeout)
+ # self.setMouseTracking(True)
self.webPage = None
self.altSearchPage = None
@@ -466,7 +477,10 @@ def createWindow(self, wintype):
# Disabled for now
if wintype == QWebEnginePage.WebBrowserTab:
- tab = self.app.mainWindow.addBrowserTab(current=True)
+ tab = self.app.mainWindow.addBrowserTab(
+ current=True,
+ position='nextcurrent'
+ )
return tab.webEngineView
def onViewSource(self):
@@ -756,11 +770,11 @@ def hashmarkPath(self, path):
iUnknown()))
def openInTab(self, path):
- tab = self.browserTab.gWindow.addBrowserTab()
+ tab = self.browserTab.gWindow.addBrowserTab(position='nextcurrent')
tab.browseFsPath(path)
def openUrlInTab(self, url):
- tab = self.browserTab.gWindow.addBrowserTab()
+ tab = self.browserTab.gWindow.addBrowserTab(position='nextcurrent')
tab.enterUrl(url)
def downloadLink(self, menudata):
@@ -875,8 +889,16 @@ def __init__(self, history, historyView, parent):
@property
def editTimeoutMs(self):
return 400
- timeout = self.app.settingsMgr.urlHistoryEditTimeout
- return timeout if isinstance(timeout, int) else 200
+
+ @property
+ def matchesVisible(self):
+ return self.historyMatches.isVisible()
+
+ def setupPalette(self):
+ palette = self.palette()
+ palette.setColor(QPalette.HighlightedText, ipfsColor1)
+ palette.setColor(QPalette.Highlight, QColor('transparent'))
+ self.setPalette(palette)
def keyPressEvent(self, ev):
if ev.key() == Qt.Key_Up:
@@ -928,10 +950,13 @@ def focusOutEvent(self, event):
super(URLInputWidget, self).focusOutEvent(event)
+ def onUrlTextChanged(self, text):
+ pass
+
def onUrlUserEdit(self, text):
self.urlInput = text
- if not self.urlEditing:
+ if not self.hasFocus():
return
self.startEditTimer()
@@ -1729,14 +1754,12 @@ def onToggledPinAll(self, checked):
def onFocusUrl(self):
self.focusUrlZone(True, reason=Qt.ShortcutFocusReason)
- def focusUrlZone(self, select=False, reason=Qt.OtherFocusReason):
+ def focusUrlZone(self, select=False, reason=Qt.ActiveWindowFocusReason):
self.urlZone.setFocus(reason)
if select:
self.urlZone.setSelection(0, len(self.urlZone.text()))
- self.urlZone.urlEditing = True
-
def onReloadPage(self):
self.reloadPage()
@@ -1781,8 +1804,6 @@ def onSavePageContained(self):
page.save(path, QWebEngineDownloadItem.CompleteHtmlSaveFormat)
def onHashmarkPage(self):
- # if self.currentUrl and isEnsUrl(self.currentUrl):
-
if self.currentIpfsObject and self.currentIpfsObject.valid:
scheme = self.currentUrl.scheme()
ensure(addHashmarkAsync(
@@ -1790,13 +1811,13 @@ def onHashmarkPage(self):
title=self.currentPageTitle,
schemePreferred=scheme
))
- elif self.currentUrl:
+ elif self.currentUrl and isUrlSupported(self.currentUrl):
ensure(addHashmarkAsync(
self.currentUrl.toString(),
title=self.currentPageTitle
))
else:
- messageBox(iNotAnIpfsResource())
+ messageBox(iUnsupportedUrl())
def onPinResult(self, f):
try:
@@ -2004,14 +2025,11 @@ async def onUrlChanged(self, url):
background-color: #C3D7DF;
}''')
- # self.urlZone.clear()
- # self.urlZone.insert(url.toString())
self.urlZoneInsert(url.toString())
self._currentIpfsObject = IPFSPath(url.toString())
self._currentUrl = url
self.ipfsObjectVisited.emit(self.currentIpfsObject)
- # self.ui.hashmarkThisPage.setEnabled(True)
self.ui.pinToolButton.setEnabled(True)
self.followIpnsAction.setEnabled(
diff --git a/galacteek/ui/colors.py b/galacteek/ui/colors.py
index 9821d653..c96ec29a 100644
--- a/galacteek/ui/colors.py
+++ b/galacteek/ui/colors.py
@@ -10,4 +10,4 @@
ipfsColor1 = QColor('#4b9fa2')
ipfsColor2 = QColor('#4a9ea1')
brownColor1 = QColor('#CFCC94')
-desertStrike1 = QColor('#e0dbc8')
+desertStrikeColor = QColor('#e0dbc8')
diff --git a/galacteek/ui/dialogs.py b/galacteek/ui/dialogs.py
index da7f6a09..30b34d01 100644
--- a/galacteek/ui/dialogs.py
+++ b/galacteek/ui/dialogs.py
@@ -180,7 +180,6 @@ async def initDialog(self, ipfsop):
@ipfsOp
async def fetchFavIcon(self, ipfsop, qurl):
qurl.setPath('/favicon.ico')
- print('fetching', qurl.toString())
try:
async with self.app.webClientSession() as session:
@@ -189,13 +188,12 @@ async def fetchFavIcon(self, ipfsop, qurl):
async with session.get(qurl.toString()) as resp:
while True:
b = await resp.content.read(1024)
- print('read', len(b))
if not b:
break
_data.extend(b)
- if len(_data) > 1024 * 1024:
- raise Exception('bb')
+ if len(_data) > 512 * 1024:
+ raise Exception('Too large, get lost')
icon = getIconFromImageData(_data)
if not icon:
@@ -206,13 +204,6 @@ async def fetchFavIcon(self, ipfsop, qurl):
self.iconSelector.injectCustomIcon(
icon, entry['Hash'],
qurl.toString())
-
- if 0:
- entry = await ipfsop.addBytes(_data)
- icoPath = IPFSPath(entry['Hash'])
- print(icoPath)
- if not icoPath.valid:
- raise Exception('Invalid icon')
except Exception as err:
log.debug(f'Could not load favicon: {err}')
@@ -1416,6 +1407,7 @@ def onTimerOut(self):
class DefaultProgressDialog(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
+ self.setObjectName('statusProgressDialog')
self.vl = QVBoxLayout(self)
self.cube = AnimatedLabel(RotatingCubeClipSimple())
self.pBar = QProgressBar()
@@ -1431,6 +1423,8 @@ def __init__(self, parent=None):
QSpacerItem(10, 50, QSizePolicy.Expanding, QSizePolicy.Expanding))
self.cube.clip.setScaledSize(QSize(128, 128))
+ self.setContentsMargins(0, 0, 0, 0)
+ self.vl.setContentsMargins(0, 0, 0, 0)
def spin(self):
self.cube.startClip()
@@ -1451,6 +1445,10 @@ def paintEvent(self, event):
w, h = 420, 420 # it's a coincidence
painter = QPainter(self)
+ b = QBrush(desertStrikeColor, Qt.SolidPattern)
+ painter.setBrush(b)
+ painter.fillRect(self.rect(), b)
+
painter.setBrush(QBrush(brownColor1, Qt.SolidPattern))
painter.setPen(QPen(ipfsColor1, 2, Qt.SolidLine))
painter.drawEllipse(center.x() - w / 2, center.y() - h / 2, w, h)
diff --git a/galacteek/ui/dwebspace/__init__.py b/galacteek/ui/dwebspace/__init__.py
index 52a95bdd..0a66997e 100644
--- a/galacteek/ui/dwebspace/__init__.py
+++ b/galacteek/ui/dwebspace/__init__.py
@@ -361,11 +361,17 @@ def nextWorkspace(self):
return self.stack.nextWorkspace(self)
def wsRegisterTab(self, tab, name, icon=None, current=False,
- tooltip=None):
+ tooltip=None,
+ position='append'):
+ if position == 'append':
+ atIdx = self.tabWidget.count()
+ elif position == 'nextcurrent':
+ atIdx = self.tabWidget.currentIndex() + 1
+
if icon:
- idx = self.tabWidget.addTab(tab, icon, name)
+ idx = self.tabWidget.insertTab(atIdx, tab, icon, name)
else:
- idx = self.tabWidget.addTab(tab, name)
+ idx = self.tabWidget.insertTab(atIdx, tab, name)
tab.workspaceAttach(self)
@@ -478,6 +484,9 @@ def planet(self):
def wsToolTip(self):
return f'Planet workspace: {self.planet}'
+ def onHelpBrowsing(self):
+ self.app.manuals.browseManualPage('browsing.html')
+
async def loadDapps(self):
if self.app.cmdArgs.enablequest and 0:
await self.loadQuestService()
@@ -486,6 +495,12 @@ async def loadQuestService(self):
from galacteek.dweb.quest import loadQuestService
self.qView, self.qPage = await loadQuestService()
+ def setupWorkspace(self):
+ super().setupWorkspace()
+ self.wsAddCustomAction(
+ 'help-browsing', getIcon('help.png'),
+ iHelp(), self.onHelpBrowsing)
+
class WorkspaceCore(TabbedWorkspace):
def __init__(self, stack):
diff --git a/galacteek/ui/ipfssearch.py b/galacteek/ui/ipfssearch.py
index 0baaaa63..50a5c8c2 100644
--- a/galacteek/ui/ipfssearch.py
+++ b/galacteek/ui/ipfssearch.py
@@ -182,7 +182,7 @@ def __init__(
self.handler = IPFSSearchHandler(self)
self.channel.registerObject('ipfssearch', self.handler)
self.setWebChannel(self.channel)
- self.setBackgroundColor(desertStrike1)
+ self.setBackgroundColor(desertStrikeColor)
self.app = QApplication.instance()
diff --git a/galacteek/ui/mainui.py b/galacteek/ui/mainui.py
index 0cb3b81e..023bdfba 100644
--- a/galacteek/ui/mainui.py
+++ b/galacteek/ui/mainui.py
@@ -630,9 +630,9 @@ def wsAddGlobalCustomAction(self, *args, **kw):
for widx, ws in self.workspaces():
ws.wsAddCustomAction(*args, **kw)
- def wsAddGlobalAction(self, action: QAction):
+ def wsAddGlobalAction(self, action: QAction, default=False):
for widx, ws in self.workspaces():
- ws.wsAddAction(action)
+ ws.wsAddAction(action, default=default)
def wsActivityNotify(self, workspace):
widx, curWorkspace = self.currentWorkspace()
@@ -1181,7 +1181,7 @@ def setupWorkspaces(self):
self.stack.addWorkspace(self.wspaceMultimedia)
self.stack.addWorkspace(self.wspaceMisc)
- self.stack.wsAddGlobalAction(self.browseAction)
+ self.stack.wsAddGlobalAction(self.browseAction, default=True)
self.stack.activateWorkspaces(False)
def onSeedAppImage(self):
@@ -1428,7 +1428,8 @@ def statusMessage(self, msg):
None, QRect(0, 0, 0, 0), 2400)
def registerTab(self, tab, name, icon=None, current=True,
- tooltip=None, workspace=None):
+ tooltip=None, workspace=None,
+ position='append'):
if workspace is None:
sidx, wspace = self.stack.currentWorkspace()
elif isinstance(workspace, str):
@@ -1442,7 +1443,8 @@ def registerTab(self, tab, name, icon=None, current=True,
sidx, wspace = self.stack.currentWorkspace()
wspace.wsRegisterTab(tab, name, icon=icon, current=current,
- tooltip=tooltip)
+ tooltip=tooltip,
+ position=position)
if self.stack.currentWorkspace() is not wspace:
wspace.wsSwitch()
@@ -1587,7 +1589,7 @@ def onOpenMediaPlayer(self):
self.getMediaPlayer()
def onOpenBrowserTabClicked(self, pinBrowsed=False):
- self.addBrowserTab(pinBrowsed=pinBrowsed)
+ self.addBrowserTab(pinBrowsed=pinBrowsed, urlFocus=True)
def onWriteNewDocumentClicked(self):
w = textedit.AddDocumentWidget(self, parent=self.tabWidget)
@@ -1615,15 +1617,20 @@ def onHelpDonatePatreon(self):
def addBrowserTab(self, label='No page loaded', pinBrowsed=False,
minProfile=None, current=True,
- workspace=None):
+ workspace=None,
+ urlFocus=False,
+ position='append'):
icon = getIconIpfsIce()
tab = browser.BrowserTab(self,
minProfile=minProfile,
pinBrowsed=pinBrowsed)
self.registerTab(tab, label, icon=icon, current=current,
- workspace=workspace)
+ workspace=workspace,
+ position=position)
+
+ if urlFocus:
+ tab.focusUrlZone()
- tab.focusUrlZone()
return tab
def addEventLogTab(self, current=False):
diff --git a/galacteek/ui/widgets.py b/galacteek/ui/widgets.py
index 0290fb12..4c3d73f7 100644
--- a/galacteek/ui/widgets.py
+++ b/galacteek/ui/widgets.py
@@ -1707,6 +1707,12 @@ def __init__(self, torInstance, *args, **kw):
self.toggled.connect(self.onToggled)
+ def sysTrayMessage(self, msg):
+ self.app.systemTrayMessage(
+ 'Tor',
+ msg
+ )
+
def useTorProxy(self, use=True):
if use is True:
proxy = TorNetworkProxy(self.tor.torCfg)
@@ -1716,8 +1722,7 @@ def useTorProxy(self, use=True):
f'TOR is used as proxy '
f'(socks port: {proxy.port()})'))
- self.app.systemTrayMessage(
- 'Tor',
+ self.sysTrayMessage(
'Tor is now used as proxy (click on the onion to disable it)'
)
else:
@@ -1731,6 +1736,10 @@ def useTorProxy(self, use=True):
QWebEngineSettings.XSSAuditingEnabled,
use
)
+ self.app.allWebProfilesSetAttribute(
+ QWebEngineSettings.DnsPrefetchEnabled,
+ not use
+ )
def onToggled(self, checked):
self.useTorProxy(checked)
@@ -1750,7 +1759,7 @@ async def onTorBootstrapStatus(self, pct, status):
self.setToolTip(self.tt(f'TOR bootstrap: {pct}% complete'))
if pct == 100:
- self.useTorProxy()
-
- self.setChecked(True)
self.setEnabled(True)
+ self.sysTrayMessage(
+ 'Tor is ready, click on the onion to enable it'
+ )
diff --git a/packaging/windows/galacteek-installer.nsi b/packaging/windows/galacteek-installer.nsi
index b6365d6b..bb055ad1 100644
--- a/packaging/windows/galacteek-installer.nsi
+++ b/packaging/windows/galacteek-installer.nsi
@@ -14,7 +14,7 @@ RequestExecutionLevel admin
!define VERSIONMAJOR 0
!define VERSIONMINOR 4
-!define VERSIONBUILD 39
+!define VERSIONBUILD 41
Name "${APPNAME}"
Icon "share/icons/galacteek.ico"
diff --git a/share/static/qss/default/galacteek.qss b/share/static/qss/default/galacteek.qss
index 8f8c8b91..89b17fca 100644
--- a/share/static/qss/default/galacteek.qss
+++ b/share/static/qss/default/galacteek.qss
@@ -568,10 +568,8 @@ QGroupBox::title[niceBox="true"] {
}
-/* Other QToolButtons
+/* Other QToolButtons */
-QToolButton::pressed {
- background-color: #eec146;
+QToolButton#torControlButton::checked {
+ background-color: #4b9fa2;
}
-
-*/
diff --git a/share/translations/galacteek_en.ts b/share/translations/galacteek_en.ts
index 366a4b4f..500036f2 100644
--- a/share/translations/galacteek_en.ts
+++ b/share/translations/galacteek_en.ts
@@ -148,27 +148,27 @@
-
+
Download
-
+
PIN
-
+
PIN (this page)
-
+
PIN (recursive)
-
+
Load IPNS key dialog
@@ -183,27 +183,27 @@
-
+
Enter an IPFS CID
-
+
Load IPFS CID dialog
-
+
Browse IPNS resource from hash/name
-
+
Enter an IPNS hash/name
-
+
{0} is an invalid IPFS CID (Content IDentifier)
@@ -213,52 +213,52 @@
-
+
Go to home page
-
+
Follow IPNS resource
-
+
Browse IPFS resource (CID)
-
+
Browse multiple IPFS resources (CID)
-
+
IPNS add feed dialog
-
+
Open with
-
+
Hashmarked {0}
-
+
Hashmark title
-
+
Invalid URL: {0}
-
+
Not an IPFS resource
@@ -283,12 +283,12 @@
-
+
Open link in new tab
-
+
Open http/https link in new tab
@@ -298,47 +298,47 @@
-
+
IPFS profile
-
+
Web3 profile
-
+
Minimal profile
-
+
Javascript console
-
+
Browse current clipboard item
-
+
Create quick-access mapping
-
+
Invalid IPFS object path: {0}
-
+
Save full webpage to the "Web Pages" folder
-
+
<p>
<img src='{0}' width='32' height='32'/>
@@ -357,7 +357,7 @@
-
+
<p>
<img src='{0}' width='32' height='32'/>
@@ -374,7 +374,7 @@
-
+
Link to Quick Access toolbar
@@ -394,7 +394,7 @@
-
+
Save selected text to IPFS
@@ -404,22 +404,22 @@
-
+
Save webpage to PDF
-
+
Error saving webpage to PDF file
-
+
Saved to PDF file: {0}
-
+
Print (text)
diff --git a/share/translations/galacteek_fr.ts b/share/translations/galacteek_fr.ts
index d40430e9..5757df83 100644
--- a/share/translations/galacteek_fr.ts
+++ b/share/translations/galacteek_fr.ts
@@ -233,87 +233,87 @@
Ouvrir le lien dans une nouvelle tab
-
+
Open with
Ouvrir avec
-
+
Download
Télécharger
-
+
PIN
PIN
-
+
PIN (this page)
Pin (clouer) (cette page)
-
+
PIN (recursive)
Pin (clouer) (récursif)
-
+
Follow IPNS resource
Suivre la clé IPNS
-
+
Enter an IPFS CID
Entrez un CID IPFS
-
+
Go to home page
Allez a la page d'accueil
-
+
Browse IPFS resource (CID)
Accéder a un CID
-
+
Browse multiple IPFS resources (CID)
Accéder a plusieurs CIDs
-
+
Load IPFS CID dialog
Dialogue de chargement de CID
-
+
IPNS add feed dialog
Dialogue d'ajout de flux IPNS
-
+
Browse IPNS resource from hash/name
Accéder a une ressource IPNS (hash/nom)
-
+
Enter an IPNS hash/name
Entrez un nom/hash IPNS
-
+
Load IPNS key dialog
Dialogue de chargement IPNS
-
+
Hashmarked {0}
Hash-marqué: {0}
-
+
Hashmark title
Titre du hashmark
@@ -323,12 +323,12 @@
URL invalide
-
+
{0} is an invalid IPFS CID (Content IDentifier)
{0} est un CID invalide
-
+
Invalid URL: {0}
URL invalide: {0}
@@ -343,7 +343,7 @@
<html><head/><body><p>Dézoomer</p></body></html>
-
+
Not an IPFS resource
@@ -353,57 +353,57 @@
<html><head/><body><p>Arreter</p></body></html>
-
+
Open link in new tab
-
+
Open http/https link in new tab
-
+
IPFS profile
-
+
Web3 profile
-
+
Minimal profile
-
+
Javascript console
-
+
Browse current clipboard item
-
+
Create quick-access mapping
-
+
Invalid IPFS object path: {0}
-
+
Save full webpage to the "Web Pages" folder
-
+
<p>
<img src='{0}' width='32' height='32'/>
@@ -422,7 +422,7 @@
-
+
<p>
<img src='{0}' width='32' height='32'/>
@@ -439,7 +439,7 @@
-
+
Link to Quick Access toolbar
@@ -459,7 +459,7 @@
-
+
Save selected text to IPFS
@@ -469,22 +469,22 @@
-
+
Save webpage to PDF
-
+
Error saving webpage to PDF file
-
+
Saved to PDF file: {0}
-
+
Print (text)
diff --git a/tests/core/test_multisearch.py b/tests/core/test_multisearch.py
index 4fe522b9..0d33d4de 100644
--- a/tests/core/test_multisearch.py
+++ b/tests/core/test_multisearch.py
@@ -1,4 +1,3 @@
-import asyncio
import pytest
from galacteek.ipfs.search import multiSearch
diff --git a/travis/Info.plist b/travis/Info.plist
index 250c7dcf..e67fe7c8 100644
--- a/travis/Info.plist
+++ b/travis/Info.plist
@@ -17,9 +17,9 @@
CFBundlePackageType
APPL
CFBundleVersion
- 0.4.30
+ 0.4.41
CFBundleShortVersionString
- 0.4.30
+ 0.4.41
LSMinimumSystemVersion
10.7.0
LSUIElement
@@ -27,7 +27,7 @@
NSHighResolutionCapable
NSHumanReadableCopyright
- © 2019 galacteek
+ © 2020 galacteek
NSPrincipalClass
NSApplication
LSEnvironment