Skip to main content

macos/find_processes_using_secure_input

1#!/usr/bin/env python3
2"""
3If macOS detects that you're typing sensitive data (e.g. a password), it prevents
4other apps from reading keystrokes. This is normally a good thing, but it's mildly
5annoying if you use an app like TextExpander, and macOS doesn't notice that it's
6no longer using Secure Input. Then your keyboard shortcuts stop working!
8This script will print a list of processes that have Secure Input enabled, e.g.:
10 The following processes are using Secure Input:
11 113 loginwindow
12 302 Safari
13 519 Firefox
15It relies on two interesting commands:
17 ioreg -a -l -w 0
18 prints the I/O Kit Registry in XML format, which includes information about
19 a process using Secure Input in the kCGSSessionSecureInputPID key
21 ps -c -o command= -p <pid>
22 prints the executable name associated with a process ID (pid), which is
23 used for the output
25See https://alexwlchan.net/2021/secure-input/
27"""
29import plistlib
30import subprocess
33def find_dicts_in_tree(d):
34 """
35 Traverses the values of d, returning everything that looks dict-like.
37 Ideally I'd have a better idea of the structure of the output of ioreg,
38 but I don't care to spend that much time on this problem.
39 """
40 if isinstance(d, dict):
41 yield d
42 for dict_value in d.values():
43 for dv in find_dicts_in_tree(dict_value):
44 yield dv
45 elif isinstance(d, list):
46 for list_item in d:
47 for lv in find_dicts_in_tree(list_item):
48 yield lv
49 else:
50 pass
53def executable_name(pid: int) -> str:
54 """
55 Returns the executable name associated with a given pid.
56 """
57 cmd = [
58 "ps",
59 "-c", # Only show the executable name, not the full command line
60 "-o", "command=", # Only show the command column, no header
61 "-p", str(pid) # Only show the given process ID
62 ]
64 return subprocess.check_output(cmd, text=True).strip()
67if __name__ == "__main__":
68 ioreg_output = subprocess.check_output([
69 "ioreg",
70 "-a", # Archive the output in XML
71 "-l", # Show properties for all displayed objects
72 "-w", "0" # Unlimited line width
73 ])
75 try:
76 plist = plistlib.loads(ioreg_output, fmt=plistlib.FMT_XML)
77 except AttributeError: # Python 2
78 plist = plistlib.readPlistFromString(ioreg_output)
80 process_ids = set()
81 for d in find_dicts_in_tree(plist):
82 if "kCGSSessionSecureInputPID" in d:
83 process_ids.add(d["kCGSSessionSecureInputPID"])
85 sorted_pids = [
86 str(pid)
87 for pid in sorted(int(p) for p in process_ids)
88 ]
90 if sorted_pids:
91 print("The following processes are using Secure Input:")
92 subprocess.check_call([
93 "ps",
94 "-c", # Only show the executable name, not the full command line
95 "-o", "pid=,command=", # Only show the PID/command column, no header
96 "-p", ",".join(sorted_pids) # Only get the selected processes
97 ])
98 else:
99 print("I can’t find any processes using Secure Input!")