Added permission request dialog functionality

This commit is contained in:
Gonçalo Valério 2022-06-26 21:12:28 +01:00
parent 048d4e448b
commit 8cfab6ea30
Signed by: dethos
GPG Key ID: DF557F2BDCC2445E
7 changed files with 102 additions and 29 deletions

View File

@ -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 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. 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. At the moment it only supports desktop environments that use X.
To learn more please read the To learn more please read the
@ -13,19 +16,13 @@ To learn more please read the
Installation Installation
------------ ------------
To build and run this demo app, you will need to have the following libraries To build and run this demo app, you will need to have `Poetry <http://www.python.org/>`_ in order to be
on your system:
* ``libdbus-1-dev``
* ``libglib2.0-dev``
You will also need to have `Poetry <http://www.python.org/>`_ in order to be
able to execute the following commands: able to execute the following commands:
.. code-block:: .. code-block::
$ poetry install $ poetry install
$ poetry run watcher $ poetry run watcher --help
Contributions Contributions
------------- -------------

View File

@ -43,6 +43,12 @@ def main() -> None:
"Monitors the access of other processes to the clipboard contents." "Monitors the access of other processes to the clipboard contents."
) )
parser.add_argument("-l", "--loglevel", help="Choose the log level") 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() args = parser.parse_args()
if args.loglevel and args.loglevel in ["DEBUG", "INFO", "WARNING", "ERROR"]: if args.loglevel and args.loglevel in ["DEBUG", "INFO", "WARNING", "ERROR"]:
set_logger_settings(args.loglevel) set_logger_settings(args.loglevel)
@ -63,7 +69,9 @@ def main() -> None:
job_queue = Queue() job_queue = Queue()
# Thread 1 # Thread 1
event_worker = Thread( 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 # Thread 2
notif_worker = Thread( notif_worker = Thread(

View File

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

View File

@ -1,16 +1,12 @@
import dbus from desktop_notifier import DesktopNotifier
import logging import logging
logger = logging.getLogger("ClipboardWatcher") logger = logging.getLogger("ClipboardWatcher")
notifier = DesktopNotifier(app_name="Clipboard-Watcher", app_icon=None)
def display_desktop_notification(title: str, details: str = "", icon: str = "") -> None: def display_desktop_notification(title: str, details: str = "") -> None:
interface = "org.freedesktop.Notifications"
path = "/org/freedesktop/Notifications"
notification = dbus.Interface(
dbus.SessionBus().get_object(interface, path), interface
)
try: try:
notification.Notify("Clipboard-Watcher", 0, icon, title, details, [], [], 7000) notifier.send_sync(title=title, message=details)
except Exception: except Exception:
logger.error("Unable to publish notification due to dbus error") logger.error("Unable to publish notification due to dbus error")

View File

@ -13,14 +13,18 @@ The current functionality contains:
import logging import logging
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
from queue import Queue from queue import Queue
from collections import namedtuple
from Xlib import X, Xatom from Xlib import X, Xatom
from Xlib.ext.res import query_client_ids, LocalClientPIDMask from Xlib.ext.res import query_client_ids, LocalClientPIDMask
from Xlib.protocol import event from Xlib.protocol import event
from clipboard_watcher.dialog import ask_for_permission
from .process_info import ProcessInfo from .process_info import ProcessInfo
logger = logging.getLogger("ClipboardWatcher") logger = logging.getLogger("ClipboardWatcher")
FakeData = namedtuple("FakeData", ["primary", "clipboard"])
def get_selection_targets(disp, win, selection: str) -> List[str]: 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") 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: while True:
e = d.next_event() e = d.next_event()
if ( 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 # due to the risk of the requestor no longer be running afterwards
proc_info = ProcessInfo.collect(req_pid) if req_pid else None 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": if d.get_atom_name(e.target) != "TARGETS":
q.put( q.put(
{ {

72
poetry.lock generated
View File

@ -66,12 +66,30 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]] [[package]]
name = "dbus-python" name = "dbus-next"
version = "1.2.18" version = "0.2.3"
description = "Python bindings for libdbus" description = "A zero-dependency DBus library for Python with asyncio support"
category = "main" category = "main"
optional = false 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]] [[package]]
name = "more-itertools" name = "more-itertools"
@ -93,7 +111,7 @@ python-versions = "*"
name = "packaging" name = "packaging"
version = "21.3" version = "21.3"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
category = "dev" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
@ -154,7 +172,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
name = "pyparsing" name = "pyparsing"
version = "3.0.6" version = "3.0.6"
description = "Python parsing module" description = "Python parsing module"
category = "dev" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
@ -194,6 +212,14 @@ python-versions = "*"
[package.dependencies] [package.dependencies]
six = ">=1.10.0" 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]] [[package]]
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"
@ -226,10 +252,18 @@ category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "winsdk"
version = "1.0.0b6"
description = "Python bindings for the Windows SDK"
category = "main"
optional = false
python-versions = "*"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = ">=3.9,<3.11" python-versions = ">=3.9,<3.11"
content-hash = "5de6bab639719a04f7cf4b791199fabcec0f9c75e6bb12ce6039a299313a9f3c" content-hash = "fc4b2112388189c48f90187d3601f965cb6c402781eed13c0ccc4614cefc0bdd"
[metadata.files] [metadata.files]
atomicwrites = [ atomicwrites = [
@ -252,8 +286,13 @@ colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
] ]
dbus-python = [ dbus-next = [
{file = "dbus-python-1.2.18.tar.gz", hash = "sha256:92bdd1e68b45596c833307a5ff4b217ee6929a1502f5341bae28fd120acf7260"}, {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 = [ more-itertools = [
{file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"}, {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.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"},
{file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, {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 = [ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {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-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, {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"},
]

View File

@ -7,8 +7,8 @@ authors = ["Gonçalo Valério <gon@ovalerio.net>"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.9,<3.11" python = ">=3.9,<3.11"
python-xlib = "^0.31" python-xlib = "^0.31"
dbus-python = "^1.2.18"
psutil = "^5.9.0" psutil = "^5.9.0"
desktop-notifier = "^3.4.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^5.2" pytest = "^5.2"