diff --git a/README.md b/README.md
index fe212d0..ad0e7bb 100644
--- a/README.md
+++ b/README.md
@@ -37,21 +37,12 @@ Using this framework, you can use PyQt develop powerful graphics programs to ext
## Installation
-1. Install python libraries:
+1. Install python dependences:
-### ArchLinux
+Make sure python3 and pip3 has install in your operating system, then execute below command:
```Bash
-sudo pacman -S python-xlib python-pyqt5
-sudo pip install dbus-python PyMuPDF grip qrcode
-```
-
-### Debian
-
-```Bash
-sudo apt-get update
-sudo apt-get install python3-xlib python3-pyqt5
-sudo pip3 install dbus-python PyMuPDF grip qrcode
+sudo pip3 install dbus-python PyMuPDF grip qrcode pyqt5 python-xlib
```
### Package description.
diff --git a/app/browser/buffer.py b/app/browser/buffer.py
index dde45dc..170171c 100644
--- a/app/browser/buffer.py
+++ b/app/browser/buffer.py
@@ -19,13 +19,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from PyQt5 import QtWebEngineWidgets
-from PyQt5 import QtCore
-from PyQt5.QtCore import Qt, QEvent, QPointF, QEventLoop, QVariant, QTimer
-from PyQt5.QtCore import QUrl, Qt
-from PyQt5.QtGui import QColor, QDesktopServices
-from PyQt5.QtWidgets import QApplication, QWidget
-
+from PyQt5.QtCore import QUrl
+from PyQt5.QtGui import QColor
+from core.browser import BrowserView
from core.buffer import Buffer
class AppBuffer(Buffer):
@@ -41,205 +37,3 @@ class AppBuffer(Buffer):
# We need send key event to QWebEngineView's child, not QWebEngineView.
for child in self.buffer_widget.children():
QApplication.sendEvent(child, event)
-
-class BrowserView(QtWebEngineWidgets.QWebEngineView):
-
- open_url_in_new_tab = QtCore.pyqtSignal(str)
-
- def __init__(self):
- super(QtWebEngineWidgets.QWebEngineView, self).__init__()
-
- self.installEventFilter(self)
-
- self.web_page = BrowserPage()
- self.setPage(self.web_page)
-
- def event(self, event):
- if event.type() == QEvent.ChildAdded:
- obj = event.child()
- if isinstance(obj, QWidget):
- obj.installEventFilter(self)
-
- return QtWebEngineWidgets.QWebEngineView.event(self, event)
-
- def eventFilter(self, obj, event):
- if event.type() == QEvent.MouseButtonRelease:
- hit = self.web_page.hitTestContent(event.pos())
- clicked_url = hit.linkUrl()
- base_url = hit.baseUrl()
-
- if clicked_url != base_url and clicked_url != '':
- result = ""
- if 'http://' in clicked_url or 'https://' in clicked_url:
- result = clicked_url
- elif clicked_url == "#":
- result = base_url + clicked_url
- else:
- result = "http://" + base_url.split("/")[2] + clicked_url
-
- modifiers = QApplication.keyboardModifiers()
-
- if modifiers != Qt.ControlModifier:
- # Load url in current tab.
- self.setUrl(QUrl(result))
- else:
- # Load url in new tab if user press ctrl modifier.
- self.open_url_in_new_tab.emit(result)
-
- return True
-
- event.accept()
- return True
-
- return super(QtWebEngineWidgets.QWebEngineView, self).eventFilter(obj, event)
-
-class BrowserPage(QtWebEngineWidgets.QWebEnginePage):
-
- def __init__(self):
- QtWebEngineWidgets.QWebEnginePage.__init__(self)
-
- def hitTestContent(self, pos):
- return WebHitTestResult(self, pos)
-
- def mapToViewport(self, pos):
- return QPointF(pos.x(), pos.y())
-
- def executeJavaScript(self, scriptSrc):
- self.loop = QEventLoop()
- self.result = QVariant()
- QTimer.singleShot(250, self.loop.quit)
-
- self.runJavaScript(scriptSrc, self.callbackJS)
- self.loop.exec_()
- self.loop = None
- return self.result
-
- def callbackJS(self, res):
- if self.loop is not None and self.loop.isRunning():
- self.result = res
- self.loop.quit()
-
-class WebHitTestResult():
- def __init__(self, page, pos):
- self.page = page
- self.pos = pos
- self.m_linkUrl = self.page.url().toString()
- self.m_baseUrl = self.page.url().toString()
- self.viewportPos = self.page.mapToViewport(self.pos)
- self.source = """(function() {
- let e = document.elementFromPoint(%1, %2);
- if (!e)
- return;
- function isMediaElement(e) {
- return e.tagName == 'AUDIO' || e.tagName == 'VIDEO';
- };
- function isEditableElement(e) {
- if (e.isContentEditable)
- return true;
- if (e.tagName === 'INPUT' || e.tagName === 'TEXTAREA')
- return e.getAttribute('readonly') != 'readonly';
- return false;
- };
- function isSelected(e) {
- let selection = window.getSelection();
- if (selection.type !== 'Range')
- return false;
- return window.getSelection().containsNode(e, true);
- };
- let res = {
- baseUrl: document.baseURI,
- alternateText: e.getAttribute('alt'),
- boundingRect: '',
- imageUrl: '',
- contentEditable: isEditableElement(e),
- contentSelected: isSelected(e),
- linkTitle: '',
- linkUrl: '',
- mediaUrl: '',
- tagName: e.tagName.toLowerCase()
- };
- let r = e.getBoundingClientRect();
- res.boundingRect = [r.top, r.left, r.width, r.height];
- if (e.tagName == 'IMG')
- res.imageUrl = e.getAttribute('src');
- if (e.tagName == 'A') {
- res.linkTitle = e.text;
- res.linkUrl = e.getAttribute('href');
- }
- while (e) {
- if (res.linkTitle === '' && e.tagName === 'A') {
- res.linkTitle = e.text;
- if(res.linkUrl === '') {
- res.linkUrl = e.getAttribute('href');
- }
- }
- if (res.mediaUrl === '' && isMediaElement(e)) {
- res.mediaUrl = e.currentSrc;
- res.mediaPaused = e.paused;
- res.mediaMuted = e.muted;
- }
- e = e.parentElement;
- }
- return res;
- })()"""
-
- self.js = self.source.replace("%1", str(self.viewportPos.x())).replace("%2", str(self.viewportPos.y()))
- self.dic = self.page.executeJavaScript(self.js)
- if self.dic is None:
- return
-
- self.m_isNull = False
- self.m_baseUrl = self.dic["baseUrl"]
- self.m_alternateText = self.dic["alternateText"]
- self.m_imageUrl = self.dic["imageUrl"]
- self.m_isContentEditable = self.dic["contentEditable"]
- self.m_isContentSelected = self.dic["contentSelected"]
- self.m_linkTitle = self.dic["linkTitle"]
- self.m_linkUrl = self.dic["linkUrl"]
- self.m_mediaUrl = self.dic["mediaUrl"]
- try:
- self.m_mediaPaused = self.dic["mediaPaused"]
- self.m_mediaMuted = self.dic["mediaMuted"]
- except:
- pass
- self.m_tagName = self.dic["tagName"]
-
- def linkUrl(self):
- return self.m_linkUrl
-
- def isContentEditable(self):
- return self.m_isContentEditable
-
- def isContentSelected(self):
- return self.m_isContentSelected
-
- def imageUrl(self):
- try:
- return self.m_imageUrl
- except:
- return ""
-
- def mediaUrl(self):
- return self.m_mediaUrl
-
- def baseUrl(self):
- return self.m_baseUrl
-
- def updateWithContextMenuData(self, data):
- if data.isValid():
- pass
- else:
- return
-
- self.m_linkTitle = data.linkText()
- self.m_linkUrl = data.linkUrl().toString()
- self.m_isContentEditable = data.isContentEditable()
- if data.selectedText() == "":
- self.m_isContentSelected = False
- else:
- self.m_isContentSelected = True
-
- if data.mediaType() == QWebEngineContextMenuData.MediaTypeImage:
- self.m_imageUrl = data.mediaUrl().toString()
- elif data.mediaType() == QWebEngineContextMenuData.MediaTypeAudio or data.mediaType() == QWebEngineContextMenuData.MediaTypeVideo:
- self.m_mediaUrl = data.mediaUrl().toString()
diff --git a/app/markdownpreviewer/buffer.py b/app/markdownpreviewer/buffer.py
index 0f52a6e..25f78ab 100644
--- a/app/markdownpreviewer/buffer.py
+++ b/app/markdownpreviewer/buffer.py
@@ -19,12 +19,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from PyQt5 import QtCore
from PyQt5.QtCore import QUrl, Qt
from PyQt5.QtGui import QColor
-from PyQt5.QtWebKitWidgets import QWebView, QWebPage
-from PyQt5.QtWidgets import QApplication
-from PyQt5.QtWebKit import QWebSettings
+from core.browser import BrowserView
from core.buffer import Buffer
from core.utils import PostGui
import socket
@@ -44,7 +41,7 @@ class AppBuffer(Buffer):
subprocess.Popen("grip {0} {1}".format(url, self.port), shell=True)
# Init widget.
- self.add_widget(BrowserWidget())
+ self.add_widget(BrowserView())
# Add timer make load markdown preview link after grip process start finish.
timer = threading.Timer(2, self.load_markdown_server)
@@ -69,41 +66,3 @@ class AppBuffer(Buffer):
paths = os.path.split(self.url)
if len(paths) > 0:
self.change_title(paths[-1])
-
-class BrowserWidget(QWebView):
-
- def __init__(self):
- super(QWebView, self).__init__()
-
- self.web_page = WebPage()
- self.setPage(self.web_page)
-
- self.settings().setAttribute(QWebSettings.PluginsEnabled, True)
- self.settings().setAttribute(QWebSettings.JavascriptEnabled, True)
- self.settings().setAttribute(QWebSettings.JavascriptCanOpenWindows, True)
-
-class WebPage(QWebPage):
-
- open_url_in_new_tab = QtCore.pyqtSignal(str)
-
- def __init__(self):
- super(WebPage, self).__init__()
-
- def acceptNavigationRequest(self, frame, request, type):
- modifiers = QApplication.keyboardModifiers()
-
- # Handle myself if got user event.
- if type == QWebPage.NavigationTypeLinkClicked:
- if modifiers == Qt.ControlModifier:
- self.open_url_in_new_tab.emit(request.url().toString())
- else:
- self.view().load(request.url())
-
- # Return False to stop default behavior.
- return False
-
- # # Otherwise, use default behavior.
- return QWebPage.acceptNavigationRequest(self, frame, request, type)
-
- def javaScriptConsoleMessage(self, msg, lineNumber, sourceID):
- pass
diff --git a/app/orgpreviewer/buffer.py b/app/orgpreviewer/buffer.py
index a59463e..bf3f6d9 100644
--- a/app/orgpreviewer/buffer.py
+++ b/app/orgpreviewer/buffer.py
@@ -19,17 +19,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from PyQt5 import QtCore
from PyQt5.QtCore import QUrl, Qt
from PyQt5.QtGui import QColor
-from PyQt5.QtWebKitWidgets import QWebView, QWebPage
-from PyQt5.QtWidgets import QApplication
-from PyQt5.QtWebKit import QWebSettings
+from core.browser import BrowserView
from core.buffer import Buffer
-from core.utils import PostGui
-import socket
-import subprocess
-import threading
import os
class AppBuffer(Buffer):
@@ -37,7 +30,7 @@ class AppBuffer(Buffer):
Buffer.__init__(self, buffer_id, url, False, QColor(255, 255, 255, 255))
self.url = url
- self.add_widget(BrowserWidget())
+ self.add_widget(BrowserView())
self.load_org_html_file()
@@ -47,41 +40,3 @@ class AppBuffer(Buffer):
def update_with_data(self, update_data):
self.load_org_html_file()
self.buffer_widget.reload()
-
-class BrowserWidget(QWebView):
-
- def __init__(self):
- super(QWebView, self).__init__()
-
- self.web_page = WebPage()
- self.setPage(self.web_page)
-
- self.settings().setAttribute(QWebSettings.PluginsEnabled, True)
- self.settings().setAttribute(QWebSettings.JavascriptEnabled, True)
- self.settings().setAttribute(QWebSettings.JavascriptCanOpenWindows, True)
-
-class WebPage(QWebPage):
-
- open_url_in_new_tab = QtCore.pyqtSignal(str)
-
- def __init__(self):
- super(WebPage, self).__init__()
-
- def acceptNavigationRequest(self, frame, request, type):
- modifiers = QApplication.keyboardModifiers()
-
- # Handle myself if got user event.
- if type == QWebPage.NavigationTypeLinkClicked:
- if modifiers == Qt.ControlModifier:
- self.open_url_in_new_tab.emit(request.url().toString())
- else:
- self.view().load(request.url())
-
- # Return False to stop default behavior.
- return False
-
- # # Otherwise, use default behavior.
- return QWebPage.acceptNavigationRequest(self, frame, request, type)
-
- def javaScriptConsoleMessage(self, msg, lineNumber, sourceID):
- pass
diff --git a/core/browser.py b/core/browser.py
new file mode 100644
index 0000000..b52c5ed
--- /dev/null
+++ b/core/browser.py
@@ -0,0 +1,229 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2018 Andy Stewart
+#
+# Author: Andy Stewart
+# Maintainer: Andy Stewart
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from PyQt5 import QtCore
+from PyQt5 import QtWebEngineWidgets
+from PyQt5.QtCore import QUrl, Qt
+from PyQt5.QtCore import Qt, QEvent, QPointF, QEventLoop, QVariant, QTimer
+from PyQt5.QtGui import QColor
+from PyQt5.QtWidgets import QApplication, QWidget
+
+class BrowserView(QtWebEngineWidgets.QWebEngineView):
+
+ open_url_in_new_tab = QtCore.pyqtSignal(str)
+
+ def __init__(self):
+ super(QtWebEngineWidgets.QWebEngineView, self).__init__()
+
+ self.installEventFilter(self)
+
+ self.web_page = BrowserPage()
+ self.setPage(self.web_page)
+
+ def event(self, event):
+ if event.type() == QEvent.ChildAdded:
+ obj = event.child()
+ if isinstance(obj, QWidget):
+ obj.installEventFilter(self)
+
+ return QtWebEngineWidgets.QWebEngineView.event(self, event)
+
+ def eventFilter(self, obj, event):
+ if event.type() == QEvent.MouseButtonRelease:
+ hit = self.web_page.hitTestContent(event.pos())
+ clicked_url = hit.linkUrl()
+ base_url = hit.baseUrl()
+
+ if clicked_url != base_url and clicked_url != '':
+ result = ""
+ if 'http://' in clicked_url or 'https://' in clicked_url:
+ result = clicked_url
+ elif clicked_url == "#":
+ result = base_url + clicked_url
+ else:
+ result = "http://" + base_url.split("/")[2] + clicked_url
+
+ modifiers = QApplication.keyboardModifiers()
+
+ if modifiers != Qt.ControlModifier:
+ # Load url in current tab.
+ self.setUrl(QUrl(result))
+ else:
+ # Load url in new tab if user press ctrl modifier.
+ self.open_url_in_new_tab.emit(result)
+
+ return True
+
+ event.accept()
+ return True
+
+ return super(QtWebEngineWidgets.QWebEngineView, self).eventFilter(obj, event)
+
+class BrowserPage(QtWebEngineWidgets.QWebEnginePage):
+
+ def __init__(self):
+ QtWebEngineWidgets.QWebEnginePage.__init__(self)
+
+ def hitTestContent(self, pos):
+ return WebHitTestResult(self, pos)
+
+ def mapToViewport(self, pos):
+ return QPointF(pos.x(), pos.y())
+
+ def executeJavaScript(self, scriptSrc):
+ self.loop = QEventLoop()
+ self.result = QVariant()
+ QTimer.singleShot(250, self.loop.quit)
+
+ self.runJavaScript(scriptSrc, self.callbackJS)
+ self.loop.exec_()
+ self.loop = None
+ return self.result
+
+ def callbackJS(self, res):
+ if self.loop is not None and self.loop.isRunning():
+ self.result = res
+ self.loop.quit()
+
+class WebHitTestResult():
+ def __init__(self, page, pos):
+ self.page = page
+ self.pos = pos
+ self.m_linkUrl = self.page.url().toString()
+ self.m_baseUrl = self.page.url().toString()
+ self.viewportPos = self.page.mapToViewport(self.pos)
+ self.source = """(function() {
+ let e = document.elementFromPoint(%1, %2);
+ if (!e)
+ return;
+ function isMediaElement(e) {
+ return e.tagName == 'AUDIO' || e.tagName == 'VIDEO';
+ };
+ function isEditableElement(e) {
+ if (e.isContentEditable)
+ return true;
+ if (e.tagName === 'INPUT' || e.tagName === 'TEXTAREA')
+ return e.getAttribute('readonly') != 'readonly';
+ return false;
+ };
+ function isSelected(e) {
+ let selection = window.getSelection();
+ if (selection.type !== 'Range')
+ return false;
+ return window.getSelection().containsNode(e, true);
+ };
+ let res = {
+ baseUrl: document.baseURI,
+ alternateText: e.getAttribute('alt'),
+ boundingRect: '',
+ imageUrl: '',
+ contentEditable: isEditableElement(e),
+ contentSelected: isSelected(e),
+ linkTitle: '',
+ linkUrl: '',
+ mediaUrl: '',
+ tagName: e.tagName.toLowerCase()
+ };
+ let r = e.getBoundingClientRect();
+ res.boundingRect = [r.top, r.left, r.width, r.height];
+ if (e.tagName == 'IMG')
+ res.imageUrl = e.getAttribute('src');
+ if (e.tagName == 'A') {
+ res.linkTitle = e.text;
+ res.linkUrl = e.getAttribute('href');
+ }
+ while (e) {
+ if (res.linkTitle === '' && e.tagName === 'A') {
+ res.linkTitle = e.text;
+ if(res.linkUrl === '') {
+ res.linkUrl = e.getAttribute('href');
+ }
+ }
+ if (res.mediaUrl === '' && isMediaElement(e)) {
+ res.mediaUrl = e.currentSrc;
+ res.mediaPaused = e.paused;
+ res.mediaMuted = e.muted;
+ }
+ e = e.parentElement;
+ }
+ return res;
+ })()"""
+
+ self.js = self.source.replace("%1", str(self.viewportPos.x())).replace("%2", str(self.viewportPos.y()))
+ self.dic = self.page.executeJavaScript(self.js)
+ if self.dic is None:
+ return
+
+ self.m_isNull = False
+ self.m_baseUrl = self.dic["baseUrl"]
+ self.m_alternateText = self.dic["alternateText"]
+ self.m_imageUrl = self.dic["imageUrl"]
+ self.m_isContentEditable = self.dic["contentEditable"]
+ self.m_isContentSelected = self.dic["contentSelected"]
+ self.m_linkTitle = self.dic["linkTitle"]
+ self.m_linkUrl = self.dic["linkUrl"]
+ self.m_mediaUrl = self.dic["mediaUrl"]
+ try:
+ self.m_mediaPaused = self.dic["mediaPaused"]
+ self.m_mediaMuted = self.dic["mediaMuted"]
+ except:
+ pass
+ self.m_tagName = self.dic["tagName"]
+
+ def linkUrl(self):
+ return self.m_linkUrl
+
+ def isContentEditable(self):
+ return self.m_isContentEditable
+
+ def isContentSelected(self):
+ return self.m_isContentSelected
+
+ def imageUrl(self):
+ try:
+ return self.m_imageUrl
+ except:
+ return ""
+
+ def mediaUrl(self):
+ return self.m_mediaUrl
+
+ def baseUrl(self):
+ return self.m_baseUrl
+
+ def updateWithContextMenuData(self, data):
+ if data.isValid():
+ pass
+ else:
+ return
+
+ self.m_linkTitle = data.linkText()
+ self.m_linkUrl = data.linkUrl().toString()
+ self.m_isContentEditable = data.isContentEditable()
+ if data.selectedText() == "":
+ self.m_isContentSelected = False
+ else:
+ self.m_isContentSelected = True
+
+ if data.mediaType() == QWebEngineContextMenuData.MediaTypeImage:
+ self.m_imageUrl = data.mediaUrl().toString()
+ elif data.mediaType() == QWebEngineContextMenuData.MediaTypeAudio or data.mediaType() == QWebEngineContextMenuData.MediaTypeVideo:
+ self.m_mediaUrl = data.mediaUrl().toString()