Allows to run actions on sites. Actions are specified in desktop files and allow to either load url or run command depending on defined conditions. This extension is a generic replacement for ImageFinder and Videoner plugins.remotes/origin/anmolgautam
parent
63cf3dbade
commit
0495b46d16
23 changed files with 531 additions and 1 deletions
@ -0,0 +1,18 @@ |
||||
# ============================================================ |
||||
# RunAction plugin for Falkon |
||||
# Copyright (C) 2018 David Rosca <nowrep@gmail.com> |
||||
# |
||||
# 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 |
||||
# (at your option) 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 <http://www.gnu.org/licenses/>. |
||||
# ============================================================ |
||||
from .runaction import * |
||||
@ -0,0 +1,73 @@ |
||||
# ============================================================ |
||||
# RunAction plugin for Falkon |
||||
# Copyright (C) 2018 David Rosca <nowrep@gmail.com> |
||||
# |
||||
# 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 |
||||
# (at your option) 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 <http://www.gnu.org/licenses/>. |
||||
# ============================================================ |
||||
import Falkon |
||||
import os, re, enum, shlex |
||||
from PySide2 import QtCore, QtGui |
||||
|
||||
class Action(): |
||||
class Type(enum.Enum): |
||||
Invalid, Url, Command = range(3) |
||||
|
||||
class TypeCondition(enum.Enum): |
||||
Page, Link, Image, Media, Text = range(5) |
||||
|
||||
id = "" |
||||
title = "" |
||||
menuTitle = "" |
||||
icon = QtGui.QIcon() |
||||
actionType = Type.Invalid |
||||
typeCondition = [ TypeCondition.Page, TypeCondition.Link, TypeCondition.Image, TypeCondition.Media ] |
||||
urlCondition = ".*" |
||||
submenu = "" |
||||
normalExec = "" |
||||
textExec = "" |
||||
supported = False |
||||
|
||||
def __init__(self, fileName): |
||||
data = Falkon.DesktopFile(fileName) |
||||
self.id = os.path.splitext(os.path.basename(fileName))[0] |
||||
self.title = data.name() |
||||
self.menuTitle = data.comment() |
||||
self.icon = QtGui.QIcon.fromTheme(data.icon(), QtGui.QIcon(os.path.join(os.path.dirname(fileName), data.icon()))) |
||||
self.actionType = Action.Type[data.value("X-RunAction-Type")] |
||||
self.typeCondition = list(map(lambda s: Action.TypeCondition[s], data.value("X-RunAction-TypeCondition").split(";"))) |
||||
self.urlCondition = data.value("X-RunAction-UrlCondition") or self.urlCondition |
||||
self.submenu = data.value("X-RunAction-Submenu") or self.submenu |
||||
self.normalExec = data.value("X-RunAction-Exec") or self.normalExec |
||||
self.textExec = data.value("X-RunAction-TextExec") or self.normalExec |
||||
self.supported = data.tryExec() |
||||
|
||||
def testAction(self, condition, url): |
||||
if not self.supported: return False |
||||
if not condition in self.typeCondition: return False |
||||
if not re.match(self.urlCondition, url.toString()): return False |
||||
return True |
||||
|
||||
def execAction(self, url, text=""): |
||||
url = str(url.toEncoded()) |
||||
if self.actionType == Action.Type.Command: |
||||
url = shlex.quote(url) |
||||
text = shlex.quote(text) |
||||
elif self.actionType == Action.Type.Url: |
||||
url = str(QtCore.QUrl.toPercentEncoding(url)) |
||||
text = str(QtCore.QUrl.toPercentEncoding(text)) |
||||
command = self.normalExec if text == "" else self.textExec |
||||
command = command.replace("{url}", url) |
||||
command = command.replace("{text}", text) |
||||
command = command.replace("{lang}", QtCore.QLocale.system().name()[:2]) |
||||
return command |
||||
@ -0,0 +1,113 @@ |
||||
# ============================================================ |
||||
# RunAction plugin for Falkon |
||||
# Copyright (C) 2018 David Rosca <nowrep@gmail.com> |
||||
# |
||||
# 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 |
||||
# (at your option) 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 <http://www.gnu.org/licenses/>. |
||||
# ============================================================ |
||||
import Falkon |
||||
import os, subprocess |
||||
from PySide2 import QtCore, QtGui, QtWidgets, QtUiTools |
||||
from runaction.action import Action |
||||
from runaction.settingsdialog import SettingsDialog |
||||
|
||||
class ActionManager(QtCore.QObject): |
||||
actions = [] |
||||
|
||||
def __init__(self, settingsPath, parent=None): |
||||
super().__init__(parent) |
||||
|
||||
self.settingsPath = settingsPath |
||||
settings = QtCore.QSettings(self.settingsPath + "/extensions.ini", QtCore.QSettings.IniFormat) |
||||
self._disabledActions = settings.value("RunAction/disabledActions") or [] |
||||
self.loadActions() |
||||
|
||||
def getActions(self, webView, r=None): |
||||
out = [] |
||||
menus = {} |
||||
|
||||
for action in list(filter(lambda a: a.id not in self.disabledActions, self.actions)): |
||||
url = webView.url() |
||||
text = "" |
||||
if r and webView.selectedText(): |
||||
cond = Action.TypeCondition.Text |
||||
text = webView.selectedText() |
||||
elif r and not r.linkUrl().isEmpty(): |
||||
cond = Action.TypeCondition.Link |
||||
url = r.linkUrl() |
||||
elif r and not r.imageUrl().isEmpty(): |
||||
cond = Action.TypeCondition.Image |
||||
url = r.imageUrl() |
||||
elif r and not r.mediaUrl().isEmpty(): |
||||
cond = Action.TypeCondition.Media |
||||
url = r.mediaUrl() |
||||
else: |
||||
cond = Action.TypeCondition.Page |
||||
|
||||
if action.testAction(cond, url): |
||||
act = Falkon.Action(action.icon, action.title, self) |
||||
act.triggered.connect(lambda a=action, w=webView, u=url, t=text: self.execAction(a, w, u, t)) |
||||
if action.submenu: |
||||
if not action.submenu in menus: |
||||
menu = Falkon.Menu(action.menuTitle, webView) |
||||
menus[action.submenu] = menu |
||||
out.append(menu) |
||||
menus[action.submenu].addAction(act) |
||||
else: |
||||
out.append(act) |
||||
|
||||
return out |
||||
|
||||
@property |
||||
def disabledActions(self): |
||||
return self._disabledActions |
||||
|
||||
@disabledActions.setter |
||||
def disabledActions(self, value): |
||||
settings = QtCore.QSettings(self.settingsPath + "/extensions.ini", QtCore.QSettings.IniFormat) |
||||
settings.setValue("RunAction/disabledActions", value) |
||||
self._disabledActions = value |
||||
|
||||
def showSettings(self, parent=None): |
||||
dialog = SettingsDialog(self, parent) |
||||
dialog.exec_() |
||||
|
||||
def execAction(self, action, webView, url, text=""): |
||||
command = action.execAction(url, text) |
||||
if action.actionType == Action.Type.Command: |
||||
subprocess.Popen(command, shell=True) |
||||
elif action.actionType == Action.Type.Url: |
||||
webView.openUrlInNewTab(QtCore.QUrl(command), Falkon.Qz.NT_SelectedTab) |
||||
|
||||
def loadActions(self): |
||||
self.actions = [] |
||||
|
||||
paths = [ |
||||
os.path.join(os.path.dirname(__file__), "actions"), |
||||
os.path.join(self.settingsPath, "runaction") |
||||
] |
||||
|
||||
for path in paths: |
||||
if not os.path.exists(path): |
||||
continue |
||||
for file in os.listdir(path): |
||||
if not file.endswith(".desktop"): |
||||
continue |
||||
fileName = os.path.join(path, file) |
||||
try: |
||||
action = Action(fileName) |
||||
except Exception as e: |
||||
print("Failed to parse {}: {}".format(fileName, e)) |
||||
finally: |
||||
if action.supported: |
||||
self.actions.append(action) |
||||
@ -0,0 +1,8 @@ |
||||
[Desktop Entry] |
||||
Name=Dictionary |
||||
|
||||
Type=Service |
||||
|
||||
X-RunAction-Type="Url" |
||||
X-RunAction-TypeCondition="Text" |
||||
X-RunAction-Exec="http://{lang}.wiktionary.org/wiki/Special:Search?search={text}" |
||||
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,11 @@ |
||||
[Desktop Entry] |
||||
Name=Google |
||||
Comment=Search with... |
||||
|
||||
Icon=google.png |
||||
Type=Service |
||||
|
||||
X-RunAction-Type="Url" |
||||
X-RunAction-TypeCondition="Image" |
||||
X-RunAction-Submenu="search_with" |
||||
X-RunAction-Exec="https://www.google.com/searchbyimage?site=search&image_url={url}" |
||||
@ -0,0 +1,10 @@ |
||||
[Desktop Entry] |
||||
Name=mpv |
||||
|
||||
Icon=mpv |
||||
TryExec=mpv |
||||
Type=Service |
||||
|
||||
X-RunAction-Type="Command" |
||||
X-RunAction-TypeCondition="Page;Link;Media" |
||||
X-RunAction-Exec="mpv {url}" |
||||
|
After Width: | Height: | Size: 2.5 KiB |
@ -0,0 +1,11 @@ |
||||
[Desktop Entry] |
||||
Name=TinEye |
||||
Comment=Search with... |
||||
|
||||
Icon=tineye.png |
||||
Type=Service |
||||
|
||||
X-RunAction-Type="Url" |
||||
X-RunAction-TypeCondition="Image" |
||||
X-RunAction-Submenu="search_with" |
||||
X-RunAction-Exec="http://www.tineye.com/search?url={url}" |
||||
@ -0,0 +1,9 @@ |
||||
[Desktop Entry] |
||||
Name=Translate page |
||||
|
||||
Icon=translate.png |
||||
Type=Service |
||||
|
||||
X-RunAction-Type="Url" |
||||
X-RunAction-TypeCondition="Page" |
||||
X-RunAction-Exec="http://translate.google.com/translate?sl=auto&tl={lang}&u={url}" |
||||
|
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,9 @@ |
||||
[Desktop Entry] |
||||
Name=Validate page |
||||
|
||||
Icon=w3.png |
||||
Type=Service |
||||
|
||||
X-RunAction-Type="Url" |
||||
X-RunAction-TypeCondition="Page" |
||||
X-RunAction-Exec="http://validator.w3.org/check?uri={url}" |
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 669 B |
@ -0,0 +1,11 @@ |
||||
[Desktop Entry] |
||||
Name=Yandex |
||||
Comment=Search with... |
||||
|
||||
Icon=yandex.png |
||||
Type=Service |
||||
|
||||
X-RunAction-Type="Url" |
||||
X-RunAction-TypeCondition="Image" |
||||
X-RunAction-Submenu="search_with" |
||||
X-RunAction-Exec="https://yandex.com/images/search?&img_url={url}&rpt=imageview" |
||||
@ -0,0 +1,50 @@ |
||||
# ============================================================ |
||||
# RunAction plugin for Falkon |
||||
# Copyright (C) 2018 David Rosca <nowrep@gmail.com> |
||||
# |
||||
# 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 |
||||
# (at your option) 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 <http://www.gnu.org/licenses/>. |
||||
# ============================================================ |
||||
import Falkon |
||||
import os |
||||
from PySide2 import QtGui, QtWidgets |
||||
from runaction.i18n import i18n |
||||
|
||||
class RunActionButton(Falkon.AbstractButtonInterface): |
||||
def __init__(self, manager): |
||||
super().__init__() |
||||
self.manager = manager |
||||
|
||||
self.setIcon(QtGui.QIcon(os.path.join(os.path.dirname(__file__), "icon.svg"))) |
||||
self.setTitle(i18n("Run Action")) |
||||
self.setToolTip(i18n("Run action on current page")) |
||||
|
||||
self.clicked.connect(self.onClicked) |
||||
|
||||
def id(self): |
||||
return "runaction-button" |
||||
|
||||
def name(self): |
||||
return i18n("RunAction button") |
||||
|
||||
def onClicked(self, controller): |
||||
self.menu = QtWidgets.QMenu() |
||||
|
||||
for action in self.manager.getActions(self.webView()): |
||||
self.menu.addAction(action) |
||||
|
||||
self.menu.addSeparator() |
||||
self.menu.addAction(QtGui.QIcon.fromTheme("configure"), i18n("Configure..."), self.manager.showSettings) |
||||
|
||||
self.menu.popup(controller.callPopupPosition(self.menu.sizeHint())) |
||||
self.menu.aboutToHide.connect(controller.callPopupClosed) |
||||
|
After Width: | Height: | Size: 3.4 KiB |
@ -0,0 +1,11 @@ |
||||
[Desktop Entry] |
||||
Name=Run Action |
||||
Comment=Run various actions on sites |
||||
|
||||
Icon=icon.svg |
||||
Type=Service |
||||
|
||||
X-Falkon-Author=David Rosca |
||||
X-Falkon-Email=nowrep@gmail.com |
||||
X-Falkon-Version=0.1.0 |
||||
X-Falkon-Settings=true |
||||
@ -0,0 +1,70 @@ |
||||
# ============================================================ |
||||
# RunAction plugin for Falkon |
||||
# Copyright (C) 2018 David Rosca <nowrep@gmail.com> |
||||
# |
||||
# 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 |
||||
# (at your option) 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 <http://www.gnu.org/licenses/>. |
||||
# ============================================================ |
||||
import Falkon |
||||
from PySide2 import QtCore |
||||
from runaction import actionmanager, button |
||||
|
||||
class RunActionPlugin(Falkon.PluginInterface, QtCore.QObject): |
||||
buttons = {} |
||||
manager = None |
||||
|
||||
def init(self, state, settingsPath): |
||||
plugins = Falkon.MainApplication.instance().plugins() |
||||
plugins.mainWindowCreated.connect(self.mainWindowCreated) |
||||
plugins.mainWindowDeleted.connect(self.mainWindowDeleted) |
||||
|
||||
self.manager = actionmanager.ActionManager(settingsPath) |
||||
|
||||
if state == Falkon.PluginInterface.LateInitState: |
||||
for window in Falkon.MainApplication.instance().windows(): |
||||
self.mainWindowCreated(window) |
||||
|
||||
def unload(self): |
||||
for window in Falkon.MainApplication.instance().windows(): |
||||
self.mainWindowDeleted(window) |
||||
|
||||
self.manager = None |
||||
|
||||
def testPlugin(self): |
||||
return True |
||||
|
||||
def populateWebViewMenu(self, menu, view, r): |
||||
for action in self.manager.getActions(view, r): |
||||
if action.inherits("QMenu"): |
||||
menu.addMenu(action).setParent(menu) |
||||
else: |
||||
action.setParent(menu) |
||||
menu.addAction(action) |
||||
|
||||
def showSettings(self, parent): |
||||
self.manager.showSettings(parent) |
||||
|
||||
def mainWindowCreated(self, window): |
||||
b = button.RunActionButton(self.manager) |
||||
window.statusBar().addButton(b) |
||||
window.navigationBar().addToolButton(b) |
||||
self.buttons[window] = b |
||||
|
||||
def mainWindowDeleted(self, window): |
||||
if not window in self.buttons: return |
||||
b = self.buttons[window] |
||||
window.statusBar().removeButton(b) |
||||
window.navigationBar().removeToolButton(b) |
||||
del self.buttons[window] |
||||
|
||||
Falkon.registerPlugin(RunActionPlugin()) |
||||
@ -0,0 +1,47 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>RunActionSettings</class> |
||||
<widget class="QWidget" name="RunActionSettings"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>544</width> |
||||
<height>492</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Dialog</string> |
||||
</property> |
||||
<layout class="QVBoxLayout" name="verticalLayout"> |
||||
<item> |
||||
<widget class="QLabel" name="label"/> |
||||
</item> |
||||
<item> |
||||
<widget class="QListWidget" name="listWidget"> |
||||
<property name="iconSize"> |
||||
<size> |
||||
<width>16</width> |
||||
<height>16</height> |
||||
</size> |
||||
</property> |
||||
<property name="uniformItemSizes"> |
||||
<bool>true</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QDialogButtonBox" name="buttonBox"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="standardButtons"> |
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<resources/> |
||||
<connections/> |
||||
</ui> |
||||
@ -0,0 +1,58 @@ |
||||
# ============================================================ |
||||
# RunAction plugin for Falkon |
||||
# Copyright (C) 2018 David Rosca <nowrep@gmail.com> |
||||
# |
||||
# 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 |
||||
# (at your option) 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 <http://www.gnu.org/licenses/>. |
||||
# ============================================================ |
||||
import Falkon |
||||
import os |
||||
from PySide2 import QtCore, QtGui, QtWidgets, QtUiTools |
||||
from runaction.i18n import i18n |
||||
|
||||
class SettingsDialog(QtWidgets.QDialog): |
||||
def __init__(self, manager, parent=None): |
||||
super().__init__(parent) |
||||
|
||||
self.manager = manager |
||||
|
||||
file = QtCore.QFile(os.path.join(os.path.dirname(__file__), "settings.ui")) |
||||
file.open(QtCore.QFile.ReadOnly) |
||||
self.ui = QtUiTools.QUiLoader().load(file, self) |
||||
layout = QtWidgets.QVBoxLayout(self) |
||||
layout.addWidget(self.ui) |
||||
|
||||
self.setMinimumSize(400, 250) |
||||
self.setWindowTitle(i18n("Run Action Settings")) |
||||
self.ui.label.setText("<b>{}</b>".format(i18n("Available actions"))) |
||||
|
||||
for action in self.manager.actions: |
||||
item = QtWidgets.QListWidgetItem(self.ui.listWidget) |
||||
item.setText(action.title) |
||||
item.setIcon(action.icon) |
||||
item.setData(QtCore.Qt.UserRole, action.id) |
||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) |
||||
item.setCheckState(QtCore.Qt.Unchecked if action.id in self.manager.disabledActions else QtCore.Qt.Checked) |
||||
self.ui.listWidget.addItem(item) |
||||
|
||||
self.ui.buttonBox.accepted.connect(self.accept) |
||||
self.ui.buttonBox.rejected.connect(self.reject) |
||||
|
||||
def accept(self): |
||||
disabled = [] |
||||
for i in range(self.ui.listWidget.count()): |
||||
item = self.ui.listWidget.item(i) |
||||
if item.checkState() == QtCore.Qt.Unchecked: |
||||
disabled.append(item.data(QtCore.Qt.UserRole)) |
||||
self.manager.disabledActions = disabled |
||||
super().accept() |
||||
Loading…
Reference in new issue