Include more information about the requesting process in the notification
This commit is contained in:
parent
7aa95fded6
commit
048d4e448b
|
@ -1,3 +1,7 @@
|
||||||
|
"""Main script to execute the application
|
||||||
|
|
||||||
|
This module provides the entrypoint to CLI app/script
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
@ -22,9 +26,15 @@ def set_logger_settings(level_name: str) -> None:
|
||||||
def process_notifications(q: Queue):
|
def process_notifications(q: Queue):
|
||||||
while True:
|
while True:
|
||||||
req = q.get(block=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(
|
display_desktop_notification(
|
||||||
f"New access to {req['selection']}({req['target']}) detected.",
|
f"Access to Clipboard ({req['selection']}) detected.",
|
||||||
f"Window: {req['window_name']} (id: {req['id']})\nPossible PID: {req['pid']} | Extra Info: {req['extra']}",
|
f"{window_info}\n{process_info}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"""Structures to store the existing clipboard data"""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, Any, Dict, List
|
from typing import Optional, Any, Dict, List
|
||||||
import logging
|
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
|
import logging
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
@ -6,6 +18,8 @@ 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 .process_info import ProcessInfo
|
||||||
|
|
||||||
logger = logging.getLogger("ClipboardWatcher")
|
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)
|
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"""
|
"""Handle the selection's data, when it is provided in chunks"""
|
||||||
w.change_attributes(event_mask=X.PropertyChangeMask)
|
w.change_attributes(event_mask=X.PropertyChangeMask)
|
||||||
data = None
|
data = None
|
||||||
|
@ -99,7 +113,7 @@ def _handle_incr(d, w, data_atom):
|
||||||
data += r.value
|
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())
|
logger.debug("Selection %s request from %s", e.selection, e.requestor.get_wm_name())
|
||||||
client = e.requestor
|
client = e.requestor
|
||||||
targets_atom = d.get_atom("TARGETS")
|
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))
|
logger.warning("New content on %s, assuming ownership", d.get_atom_name(e.atom))
|
||||||
if e.atom == d.get_atom("PRIMARY"):
|
if e.atom == d.get_atom("PRIMARY"):
|
||||||
cb_data.refresh_primary()
|
cb_data.refresh_primary()
|
||||||
|
@ -200,7 +214,7 @@ def process_selection_clear_event(d, cb_data, e):
|
||||||
logger.warning("Owner again")
|
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:
|
while True:
|
||||||
e = d.next_event()
|
e = d.next_event()
|
||||||
if (
|
if (
|
||||||
|
@ -210,25 +224,28 @@ def process_event_loop(d, w, q: Queue, cb_data):
|
||||||
):
|
):
|
||||||
req_id = e.requestor.id
|
req_id = e.requestor.id
|
||||||
req_name = e.requestor.get_wm_name()
|
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(
|
req_pid = e.requestor.get_property(
|
||||||
d.get_atom("_NET_WM_PID"), d.get_atom("CARDINAL"), 0, 1024
|
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:
|
if client_id.spec.mask & LocalClientPIDMask:
|
||||||
req_pid = client_id.value[0]
|
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)
|
process_selection_request_event(d, cb_data, e)
|
||||||
if d.get_atom_name(e.target) != "TARGETS":
|
if d.get_atom_name(e.target) != "TARGETS":
|
||||||
q.put(
|
q.put(
|
||||||
{
|
{
|
||||||
"id": req_id,
|
"id": req_id,
|
||||||
"window_name": req_name,
|
"window_name": req_name,
|
||||||
"pid": req_pid,
|
"process": proc_info,
|
||||||
"target": d.get_atom_name(e.target),
|
"target": d.get_atom_name(e.target),
|
||||||
"selection": d.get_atom_name(e.selection),
|
"selection": d.get_atom_name(e.selection),
|
||||||
"extra": extra,
|
|
||||||
},
|
},
|
||||||
block=False,
|
block=False,
|
||||||
)
|
)
|
||||||
|
|
|
@ -131,6 +131,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["pre-commit", "tox"]
|
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]]
|
[[package]]
|
||||||
name = "py"
|
name = "py"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
@ -218,7 +229,7 @@ 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 = "6231f2ce7abc15e8e1fb0d98ea4705c356e6322e24ab4f9cfae2a223813c7bbb"
|
content-hash = "5de6bab639719a04f7cf4b791199fabcec0f9c75e6bb12ce6039a299313a9f3c"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
atomicwrites = [
|
atomicwrites = [
|
||||||
|
@ -268,6 +279,40 @@ pluggy = [
|
||||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
{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 = [
|
py = [
|
||||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||||
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
{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 = ">=3.9,<3.11"
|
||||||
python-xlib = "^0.31"
|
python-xlib = "^0.31"
|
||||||
dbus-python = "^1.2.18"
|
dbus-python = "^1.2.18"
|
||||||
|
psutil = "^5.9.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pytest = "^5.2"
|
pytest = "^5.2"
|
||||||
|
|
Loading…
Reference in New Issue