Include more information about the requesting process in the notification

pull/4/head
Gonçalo Valério 10 months ago
parent 7aa95fded6
commit 048d4e448b
Signed by: dethos
GPG Key ID: DF557F2BDCC2445E
  1. 14
      clipboard_watcher/app.py
  2. 2
      clipboard_watcher/models.py
  3. 52
      clipboard_watcher/process_info.py
  4. 37
      clipboard_watcher/xoperations.py
  5. 47
      poetry.lock
  6. 1
      pyproject.toml

@ -1,3 +1,7 @@
"""Main script to execute the application
This module provides the entrypoint to CLI app/script
"""
import logging
import sys
from argparse import ArgumentParser
@ -22,9 +26,15 @@ def set_logger_settings(level_name: str) -> None:
def process_notifications(q: Queue):
while True:
req = q.get(block=True)
window_info = f"Window info: {req['window_name']} (id: {req['id']})"
if process := req.get("process"):
process_info = f"Process info: {process.path} (pid: {process.pid})"
else:
process_info = "Process info: Unknown"
display_desktop_notification(
f"New access to {req['selection']}({req['target']}) detected.",
f"Window: {req['window_name']} (id: {req['id']})\nPossible PID: {req['pid']} | Extra Info: {req['extra']}",
f"Access to Clipboard ({req['selection']}) detected.",
f"{window_info}\n{process_info}",
)

@ -1,3 +1,5 @@
"""Structures to store the existing clipboard data"""
from dataclasses import dataclass
from typing import Optional, Any, Dict, List
import logging

@ -0,0 +1,52 @@
"""Process Info Module
This module provides the tools to fetch all the necessary information
about running processes.
It is used to populate the information displayed to the user about who
is accessing the clipboard information.
"""
from __future__ import annotations
from datetime import datetime
from typing import Optional
from dataclasses import dataclass
from pathlib import Path
import psutil
@dataclass()
class ProcessInfo:
pid: int
name: Optional[str]
path: Optional[Path]
parent: int
user: str
started_at: Optional[datetime]
@classmethod
def collect(cls, pid: int) -> ProcessInfo:
p = psutil.Process(pid)
details = p.as_dict(
attrs=["ppid", "exe", "create_time", "name", "username"], ad_value=None
)
if timestamp := details.get("create_time"):
date = datetime.fromtimestamp(timestamp)
else:
date = None
if exe := details.get("exe"):
path = Path(exe)
else:
path = None
return cls(
pid,
details["name"],
path,
details["ppid"],
details["username"],
date,
)

@ -1,3 +1,15 @@
"""X Operations Module
This module abstracts some of the functionality of Xlib into
easy to use functions.
The current functionality contains:
* Querying the existing clipboard targets
* Fetching existing clipboard data
* Processing requests for clipboard data
* Handling the loss of ownership on any clipboard data
"""
import logging
from typing import List, Optional, Tuple
from queue import Queue
@ -6,6 +18,8 @@ from Xlib import X, Xatom
from Xlib.ext.res import query_client_ids, LocalClientPIDMask
from Xlib.protocol import event
from .process_info import ProcessInfo
logger = logging.getLogger("ClipboardWatcher")
@ -67,7 +81,7 @@ def get_selection_data(disp, win, selection: str, target: str) -> Optional[Tuple
return (data, r.format, r.property_type)
def _handle_incr(d, w, data_atom):
def _handle_incr(d, w, data_atom) -> bytes:
"""Handle the selection's data, when it is provided in chunks"""
w.change_attributes(event_mask=X.PropertyChangeMask)
data = None
@ -99,7 +113,7 @@ def _handle_incr(d, w, data_atom):
data += r.value
def process_selection_request_event(d, cb_data, e):
def process_selection_request_event(d, cb_data, e) -> None:
logger.debug("Selection %s request from %s", e.selection, e.requestor.get_wm_name())
client = e.requestor
targets_atom = d.get_atom("TARGETS")
@ -188,7 +202,7 @@ def process_selection_request_event(d, cb_data, e):
)
def process_selection_clear_event(d, cb_data, e):
def process_selection_clear_event(d, cb_data, e) -> None:
logger.warning("New content on %s, assuming ownership", d.get_atom_name(e.atom))
if e.atom == d.get_atom("PRIMARY"):
cb_data.refresh_primary()
@ -200,7 +214,7 @@ def process_selection_clear_event(d, cb_data, e):
logger.warning("Owner again")
def process_event_loop(d, w, q: Queue, cb_data):
def process_event_loop(d, w, q: Queue, cb_data) -> None:
while True:
e = d.next_event()
if (
@ -210,25 +224,28 @@ def process_event_loop(d, w, q: Queue, cb_data):
):
req_id = e.requestor.id
req_name = e.requestor.get_wm_name()
client_ids = query_client_ids(d, [(e.requestor.id, LocalClientPIDMask)])
client_id = client_ids.ids[0]
req_pid = e.requestor.get_property(
d.get_atom("_NET_WM_PID"), d.get_atom("CARDINAL"), 0, 1024
)
extra = d.get_atom_name(e.property)
client_ids = query_client_ids(d, [(e.requestor.id, LocalClientPIDMask)])
client_id = client_ids.ids[0]
if client_id.spec.mask & LocalClientPIDMask:
req_pid = client_id.value[0]
print(req_pid)
# We must collect this information before processing the request
# 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 d.get_atom_name(e.target) != "TARGETS":
q.put(
{
"id": req_id,
"window_name": req_name,
"pid": req_pid,
"process": proc_info,
"target": d.get_atom_name(e.target),
"selection": d.get_atom_name(e.selection),
"extra": extra,
},
block=False,
)

47
poetry.lock generated

@ -131,6 +131,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.extras]
dev = ["pre-commit", "tox"]
[[package]]
name = "psutil"
version = "5.9.0"
description = "Cross-platform lib for process and system monitoring in Python."
category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.extras]
test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"]
[[package]]
name = "py"
version = "1.11.0"
@ -218,7 +229,7 @@ python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = ">=3.9,<3.11"
content-hash = "6231f2ce7abc15e8e1fb0d98ea4705c356e6322e24ab4f9cfae2a223813c7bbb"
content-hash = "5de6bab639719a04f7cf4b791199fabcec0f9c75e6bb12ce6039a299313a9f3c"
[metadata.files]
atomicwrites = [
@ -268,6 +279,40 @@ pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
]
psutil = [
{file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"},
{file = "psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618"},
{file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2"},
{file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd"},
{file = "psutil-5.9.0-cp27-none-win32.whl", hash = "sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3"},
{file = "psutil-5.9.0-cp27-none-win_amd64.whl", hash = "sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c"},
{file = "psutil-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492"},
{file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3"},
{file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2"},
{file = "psutil-5.9.0-cp310-cp310-win32.whl", hash = "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d"},
{file = "psutil-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b"},
{file = "psutil-5.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56"},
{file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203"},
{file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d"},
{file = "psutil-5.9.0-cp36-cp36m-win32.whl", hash = "sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64"},
{file = "psutil-5.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94"},
{file = "psutil-5.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0"},
{file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce"},
{file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5"},
{file = "psutil-5.9.0-cp37-cp37m-win32.whl", hash = "sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9"},
{file = "psutil-5.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4"},
{file = "psutil-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2"},
{file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d"},
{file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a"},
{file = "psutil-5.9.0-cp38-cp38-win32.whl", hash = "sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666"},
{file = "psutil-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841"},
{file = "psutil-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf"},
{file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07"},
{file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d"},
{file = "psutil-5.9.0-cp39-cp39-win32.whl", hash = "sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845"},
{file = "psutil-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3"},
{file = "psutil-5.9.0.tar.gz", hash = "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25"},
]
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},

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

Loading…
Cancel
Save