Added permission request dialog functionality
This commit is contained in:
parent
048d4e448b
commit
8cfab6ea30
13
README.rst
13
README.rst
|
@ -5,6 +5,9 @@ This repository contains the code of an experiment, in order to understand
|
|||
how hard would it be (if possible) to have an application that can monitor
|
||||
the access of other apps to the clipboard on Linux machines.
|
||||
|
||||
This app can also ask the user for permission before providing the clipboard
|
||||
contents.
|
||||
|
||||
At the moment it only supports desktop environments that use X.
|
||||
|
||||
To learn more please read the
|
||||
|
@ -13,19 +16,13 @@ To learn more please read the
|
|||
Installation
|
||||
------------
|
||||
|
||||
To build and run this demo app, you will need to have the following libraries
|
||||
on your system:
|
||||
|
||||
* ``libdbus-1-dev``
|
||||
* ``libglib2.0-dev``
|
||||
|
||||
You will also need to have `Poetry <http://www.python.org/>`_ in order to be
|
||||
To build and run this demo app, you will need to have `Poetry <http://www.python.org/>`_ in order to be
|
||||
able to execute the following commands:
|
||||
|
||||
.. code-block::
|
||||
|
||||
$ poetry install
|
||||
$ poetry run watcher
|
||||
$ poetry run watcher --help
|
||||
|
||||
Contributions
|
||||
-------------
|
||||
|
|
|
@ -43,6 +43,12 @@ def main() -> None:
|
|||
"Monitors the access of other processes to the clipboard contents."
|
||||
)
|
||||
parser.add_argument("-l", "--loglevel", help="Choose the log level")
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--permission",
|
||||
action="store_true",
|
||||
help="Ask for permission before sending clipboard data",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.loglevel and args.loglevel in ["DEBUG", "INFO", "WARNING", "ERROR"]:
|
||||
set_logger_settings(args.loglevel)
|
||||
|
@ -63,7 +69,9 @@ def main() -> None:
|
|||
job_queue = Queue()
|
||||
# Thread 1
|
||||
event_worker = Thread(
|
||||
target=process_event_loop, args=(disp, window, job_queue, cb_data), daemon=True
|
||||
target=process_event_loop,
|
||||
args=(disp, window, job_queue, cb_data, args.permission),
|
||||
daemon=True,
|
||||
)
|
||||
# Thread 2
|
||||
notif_worker = Thread(
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
from tkinter.messagebox import askokcancel
|
||||
|
||||
|
||||
def ask_for_permission(window_name, pid, path) -> bool:
|
||||
details = f"The process {pid} ({path}) with the window named '{window_name}' wants to access your clipboard data."
|
||||
return askokcancel("Clipboard-Watcher", details)
|
|
@ -1,16 +1,12 @@
|
|||
import dbus
|
||||
from desktop_notifier import DesktopNotifier
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("ClipboardWatcher")
|
||||
notifier = DesktopNotifier(app_name="Clipboard-Watcher", app_icon=None)
|
||||
|
||||
|
||||
def display_desktop_notification(title: str, details: str = "", icon: str = "") -> None:
|
||||
interface = "org.freedesktop.Notifications"
|
||||
path = "/org/freedesktop/Notifications"
|
||||
notification = dbus.Interface(
|
||||
dbus.SessionBus().get_object(interface, path), interface
|
||||
)
|
||||
def display_desktop_notification(title: str, details: str = "") -> None:
|
||||
try:
|
||||
notification.Notify("Clipboard-Watcher", 0, icon, title, details, [], [], 7000)
|
||||
notifier.send_sync(title=title, message=details)
|
||||
except Exception:
|
||||
logger.error("Unable to publish notification due to dbus error")
|
||||
|
|
|
@ -13,14 +13,18 @@ The current functionality contains:
|
|||
import logging
|
||||
from typing import List, Optional, Tuple
|
||||
from queue import Queue
|
||||
from collections import namedtuple
|
||||
|
||||
from Xlib import X, Xatom
|
||||
from Xlib.ext.res import query_client_ids, LocalClientPIDMask
|
||||
from Xlib.protocol import event
|
||||
|
||||
from clipboard_watcher.dialog import ask_for_permission
|
||||
|
||||
from .process_info import ProcessInfo
|
||||
|
||||
logger = logging.getLogger("ClipboardWatcher")
|
||||
FakeData = namedtuple("FakeData", ["primary", "clipboard"])
|
||||
|
||||
|
||||
def get_selection_targets(disp, win, selection: str) -> List[str]:
|
||||
|
@ -214,7 +218,7 @@ def process_selection_clear_event(d, cb_data, e) -> None:
|
|||
logger.warning("Owner again")
|
||||
|
||||
|
||||
def process_event_loop(d, w, q: Queue, cb_data) -> None:
|
||||
def process_event_loop(d, w, q: Queue, cb_data, permission=False) -> None:
|
||||
while True:
|
||||
e = d.next_event()
|
||||
if (
|
||||
|
@ -237,7 +241,15 @@ def process_event_loop(d, w, q: Queue, cb_data) -> None:
|
|||
# due to the risk of the requestor no longer be running afterwards
|
||||
proc_info = ProcessInfo.collect(req_pid) if req_pid else None
|
||||
|
||||
process_selection_request_event(d, cb_data, e)
|
||||
if permission and d.get_atom_name(e.target) != "TARGETS":
|
||||
if ask_for_permission(req_name, proc_info.pid, proc_info.path):
|
||||
data = cb_data
|
||||
else:
|
||||
data = FakeData({}, {})
|
||||
else:
|
||||
data = cb_data
|
||||
|
||||
process_selection_request_event(d, data, e)
|
||||
if d.get_atom_name(e.target) != "TARGETS":
|
||||
q.put(
|
||||
{
|
||||
|
|
|
@ -66,12 +66,30 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "dbus-python"
|
||||
version = "1.2.18"
|
||||
description = "Python bindings for libdbus"
|
||||
name = "dbus-next"
|
||||
version = "0.2.3"
|
||||
description = "A zero-dependency DBus library for Python with asyncio support"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=3.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "desktop-notifier"
|
||||
version = "3.4.0"
|
||||
description = "Python library for cross-platform desktop notifications"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
dbus-next = {version = "*", markers = "sys_platform == \"linux\""}
|
||||
packaging = "*"
|
||||
rubicon-objc = {version = "*", markers = "sys_platform == \"darwin\""}
|
||||
winsdk = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["black", "bump2version", "flake8", "mypy", "pre-commit", "pytest", "pytest-cov"]
|
||||
docs = ["sphinx", "m2r2", "sphinx-autoapi", "sphinx-rtd-theme"]
|
||||
|
||||
[[package]]
|
||||
name = "more-itertools"
|
||||
|
@ -93,7 +111,7 @@ python-versions = "*"
|
|||
name = "packaging"
|
||||
version = "21.3"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
|
@ -154,7 +172,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|||
name = "pyparsing"
|
||||
version = "3.0.6"
|
||||
description = "Python parsing module"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
|
@ -194,6 +212,14 @@ python-versions = "*"
|
|||
[package.dependencies]
|
||||
six = ">=1.10.0"
|
||||
|
||||
[[package]]
|
||||
name = "rubicon-objc"
|
||||
version = "0.4.2"
|
||||
description = "A bridge between an Objective C runtime environment and Python."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
|
@ -226,10 +252,18 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "winsdk"
|
||||
version = "1.0.0b6"
|
||||
description = "Python bindings for the Windows SDK"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = ">=3.9,<3.11"
|
||||
content-hash = "5de6bab639719a04f7cf4b791199fabcec0f9c75e6bb12ce6039a299313a9f3c"
|
||||
content-hash = "fc4b2112388189c48f90187d3601f965cb6c402781eed13c0ccc4614cefc0bdd"
|
||||
|
||||
[metadata.files]
|
||||
atomicwrites = [
|
||||
|
@ -252,8 +286,13 @@ colorama = [
|
|||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||
]
|
||||
dbus-python = [
|
||||
{file = "dbus-python-1.2.18.tar.gz", hash = "sha256:92bdd1e68b45596c833307a5ff4b217ee6929a1502f5341bae28fd120acf7260"},
|
||||
dbus-next = [
|
||||
{file = "dbus_next-0.2.3-py3-none-any.whl", hash = "sha256:58948f9aff9db08316734c0be2a120f6dc502124d9642f55e90ac82ffb16a18b"},
|
||||
{file = "dbus_next-0.2.3.tar.gz", hash = "sha256:f4eae26909332ada528c0a3549dda8d4f088f9b365153952a408e28023a626a5"},
|
||||
]
|
||||
desktop-notifier = [
|
||||
{file = "desktop-notifier-3.4.0.tar.gz", hash = "sha256:92b10dfe97ea5599adbe2c03520cae5a4343017c3bb1f1dc2256c17b224608a9"},
|
||||
{file = "desktop_notifier-3.4.0-py3-none-any.whl", hash = "sha256:f7151171ef78b9c46bb3509eb80c95b5108d7b482e4c0215cbc44fe35a61a4f3"},
|
||||
]
|
||||
more-itertools = [
|
||||
{file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"},
|
||||
|
@ -329,6 +368,10 @@ python-xlib = [
|
|||
{file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"},
|
||||
{file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"},
|
||||
]
|
||||
rubicon-objc = [
|
||||
{file = "rubicon-objc-0.4.2.tar.gz", hash = "sha256:6fbc8e12bd66c84427cfb95634c4bd10ade356ae2b2ae0d2b51dcbf5810d2602"},
|
||||
{file = "rubicon_objc-0.4.2-py3-none-any.whl", hash = "sha256:c8780b9d6c3c906642080a9f8b710f5823a498c17e9bbea7f70781ea6ede7962"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
|
@ -345,3 +388,14 @@ wcwidth = [
|
|||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||
]
|
||||
winsdk = [
|
||||
{file = "winsdk-1.0.0b6-cp310-cp310-win32.whl", hash = "sha256:3589f0535d159b6e64f25e9688adc7896acc01bfa0f0f1e3d08dfb3ed5809104"},
|
||||
{file = "winsdk-1.0.0b6-cp310-cp310-win_amd64.whl", hash = "sha256:c0352706fa68cd28064f82b934ec709494e8f32efda23bf8f92e452ec2543df8"},
|
||||
{file = "winsdk-1.0.0b6-cp37-cp37m-win32.whl", hash = "sha256:daef8d49c653a516430ac681f92ff28f96b2ff2f11fa805f8b2073b1a5864f2f"},
|
||||
{file = "winsdk-1.0.0b6-cp37-cp37m-win_amd64.whl", hash = "sha256:20e384b50dbc2bd360dbfd33804be78679337ed4a31afb376abb9ddfae453010"},
|
||||
{file = "winsdk-1.0.0b6-cp38-cp38-win32.whl", hash = "sha256:410c2323af51c8e11c41bc88e431b9dc22b6aa9a1f36d45ad758170cc9fed098"},
|
||||
{file = "winsdk-1.0.0b6-cp38-cp38-win_amd64.whl", hash = "sha256:493eeb3807d2a50c4c203420a4cea55a83a675b53ab1309f3be3fda09ff143fe"},
|
||||
{file = "winsdk-1.0.0b6-cp39-cp39-win32.whl", hash = "sha256:01886275aae8842135c0029e4c249b88356f6df95b1ff522ba2ac01647c01e4b"},
|
||||
{file = "winsdk-1.0.0b6-cp39-cp39-win_amd64.whl", hash = "sha256:22048f379d46232961b1d98fed467a8603f6f9c138750a1e310f12063d340207"},
|
||||
{file = "winsdk-1.0.0b6.tar.gz", hash = "sha256:c72248967311145d6544744d98aaf413161f24ec6e0849c3bfa86565ab2100cd"},
|
||||
]
|
||||
|
|
|
@ -7,8 +7,8 @@ authors = ["Gonçalo Valério <gon@ovalerio.net>"]
|
|||
[tool.poetry.dependencies]
|
||||
python = ">=3.9,<3.11"
|
||||
python-xlib = "^0.31"
|
||||
dbus-python = "^1.2.18"
|
||||
psutil = "^5.9.0"
|
||||
desktop-notifier = "^3.4.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^5.2"
|
||||
|
|
Loading…
Reference in New Issue