1
mirror of https://invent.kde.org/network/falkon.git synced 2024-11-11 09:32:12 +01:00

Add RunAction extension

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.
This commit is contained in:
David Rosca 2018-03-03 12:40:54 +01:00
parent 63cf3dbade
commit 0495b46d16
No known key found for this signature in database
GPG Key ID: EBC3FC294452C6D8
23 changed files with 531 additions and 1 deletions

View File

@ -6,3 +6,4 @@ function(install_python_script name)
endfunction()
install_python_script(hellopython)
install_python_script(runaction)

View File

@ -8,4 +8,11 @@ XGETTEXT_FLAGS_PYTHON="\
-ki18n:1 -ki18np:1,2 \
"
$XGETTEXT_PROGRAM $XGETTEXT_FLAGS_PYTHON `find hellopython -name '*.py'` -o $podir/falkon_hellopython.pot
python_scripts="
hellopython
runaction
"
for script in $python_scripts; do
$XGETTEXT_PROGRAM $XGETTEXT_FLAGS_PYTHON `find $script -name '*.py'` -o $podir/falkon_$script.pot
done

View File

@ -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 *

View File

@ -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

View File

@ -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)

View File

@ -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}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -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}"

View File

@ -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}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -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}"

View File

@ -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}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

View File

@ -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"

View File

@ -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)

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 12 4 C 11.32479 4.004 10.65279 4.092995 10 4.265625 L 10 5.2988281 L 10 5.3105469 A 7 7 0 0 0 8.6835938 5.8554688 L 8.6757812 5.8476562 L 7.9433594 5.1152344 C 6.7764094 5.8032844 5.8032844 6.7764094 5.1152344 7.9433594 L 5.6367188 8.4648438 L 5.8554688 8.6835938 A 7 7 0 0 0 5.3085938 10 L 5.2949219 10 L 4.2597656 10 C 4.0891457 10.65304 4.00187 11.32505 4 12 C 4.004 12.67521 4.093005 13.34721 4.265625 14 L 5.2988281 14 L 5.3105469 14 A 7 7 0 0 0 5.8554688 15.316406 L 5.6367188 15.535156 L 5.1152344 16.056641 C 5.8032844 17.223601 6.7763994 18.196716 7.9433594 18.884766 L 8.4648438 18.363281 L 8.6835938 18.144531 A 7 7 0 0 0 10 18.691406 L 10 19 L 10 19.740234 C 10.65304 19.910854 11.32504 19.99813 12 20 C 12.67496 19.9981 13.34696 19.91085 14 19.740234 L 14 19 L 14 18.691406 A 7 7 0 0 0 15.316406 18.144531 L 15.535156 18.363281 L 16.056641 18.884766 C 17.223601 18.196716 18.196716 17.223601 18.884766 16.056641 L 18.363281 15.535156 L 18.144531 15.316406 A 7 7 0 0 0 18.689453 14 L 18.701172 14 L 19.734375 14 C 19.907004 13.34721 19.996 12.67521 20 12 C 19.9981 11.32505 19.91085 10.65304 19.740234 10 L 18.705078 10 L 18.691406 10 A 7 7 0 0 0 18.144531 8.6835938 L 18.363281 8.4648438 L 18.884766 7.9433594 C 18.196716 6.7764094 17.223591 5.8032844 16.056641 5.1152344 L 15.324219 5.8476562 L 15.316406 5.8554688 A 7 7 0 0 0 14 5.3105469 L 14 5.2988281 L 14 4.265625 C 13.34721 4.092995 12.67521 4.004 12 4 z M 12 5 C 12.33491 5.0012 12.66956 5.0273719 13 5.0761719 L 13 5.0800781 L 13 6 L 13 6.0859375 A 6 6 0 0 1 15.470703 7.1152344 L 15.535156 7.0507812 L 16.185547 6.4003906 L 16.1875 6.3984375 C 16.72484 6.7991075 17.201446 7.2742469 17.603516 7.8105469 L 17.599609 7.8144531 L 16.949219 8.4648438 L 16.886719 8.5273438 A 6 6 0 0 1 17.910156 11 L 18 11 L 18.921875 11 L 18.925781 11 C 18.974081 11.33106 18.9995 11.66455 19 12 C 18.9988 12.33491 18.972628 12.66955 18.923828 13 L 18.919922 13 L 18 13 L 17.914062 13 A 6 6 0 0 1 16.884766 15.470703 L 16.949219 15.535156 L 17.599609 16.185547 L 17.601562 16.1875 C 17.200893 16.72484 16.725753 17.201456 16.189453 17.603516 L 16.185547 17.599609 L 15.535156 16.949219 L 15.472656 16.886719 A 6 6 0 0 1 13 17.910156 L 13 18 L 13 18.921875 L 13 18.925781 C 12.66894 18.974081 12.33545 18.9995 12 19 C 11.66455 18.9995 11.33106 18.974081 11 18.925781 L 11 18.921875 L 11 18 L 11 17.910156 A 6 6 0 0 1 8.5273438 16.886719 L 8.4648438 16.949219 L 7.8144531 17.599609 L 7.8105469 17.603516 C 7.2742507 17.201463 6.7991075 16.72484 6.3984375 16.1875 L 6.4003906 16.185547 L 7.0507812 15.535156 L 7.1152344 15.470703 A 6 6 0 0 1 6.0859375 13 L 6 13 L 5.0800781 13 L 5.0761719 13 C 5.0273719 12.66955 5.0012 12.33491 5 12 C 5.0005 11.66455 5.0259187 11.33106 5.0742188 11 L 5.078125 11 L 6 11 L 6.0898438 11 A 6 6 0 0 1 7.1132812 8.5273438 L 7.0507812 8.4648438 L 6.4003906 7.8144531 L 6.3964844 7.8105469 C 6.7985582 7.2742507 7.27516 6.7991075 7.8125 6.3984375 L 7.8144531 6.4003906 L 8.4648438 7.0507812 L 8.5292969 7.1152344 A 6 6 0 0 1 11 6.0859375 L 11 6 L 11 5.0800781 L 11 5.0761719 C 11.33044 5.0273719 11.66509 5.0012 12 5 z M 10 9 L 10 15 L 15 12 L 10 9 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -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

View File

@ -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())

View File

@ -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>

View File

@ -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()