-
Notifications
You must be signed in to change notification settings - Fork 115
/
attention.py
executable file
·169 lines (132 loc) · 5.26 KB
/
attention.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/usr/bin/env python3
"""Attention vs Distraction:
keep a record of workspace switching and keyboard use.
Inspired by Gloria Mark's work discussed ont he Hidden Brain podcast episode
https://hiddenbrain.org/podcast/finding-focus/
"""
from os.path import expanduser, join, exists
DATADIR = join(expanduser("~/Data/attention"))
import time
import subprocess
SLEEPSECS = 30
def wakeup():
idlesec = getIdleSec()
workspaces, active = get_workspaces()
datafile = join(DATADIR, time.strftime("%Y-%m-%d") + ".csv")
if not exists(datafile):
with open(datafile, 'w') as ofp:
print("time,idlesec,desktopnum,desktopname", file=ofp)
with open(datafile, 'a') as ofp:
print("%s,%d,%d,%s" % (time.strftime("%Y-%m-%d %H:%M:%S"),
idlesec, active, workspaces[active]),
file=ofp)
# wmctrl -d can show which desktop is active
# wmctrl -l -G shows windows on all desktops
def get_workspaces():
"""Use wmctrl to get a list of all virtual desktops.
Return a dict of workspaces, and the number of the active one.
"""
workspaces = {}
active = None
proc = subprocess.run(["wmctrl", "-d"], capture_output=True)
for line in proc.stdout.splitlines():
words = line.split()
num = int(words[0])
# Just in case it's possible to have a space in the workspace name
workspaces[num] = b' '.join(words[9:]).decode()
if words[1] == b'*':
active = num
return workspaces, active
# May eventually want to look at active windows, see if we're
# googling in firefox, etc.
# def get_windows():
# """Use wmctrl to figure out which desktop we're on"""
# proc = subprocess.run(["wmctrl", "-l", "-G"], capture_output=True)
# for line in proc.stdout.splitlines():
# if not line.endswith(b'irefox'):
# continue
###################################################################
# Discussion of methods of getting idle time:
# Getting idle time is harder. For an example of how frustrating it can be,
# see discussion at
# https://github.com/Zygo/xscreensaver/blob/0c43268adc2e7a932ca5427db7b18d37ef53f7ad/driver/xscreensaver.c#L119
# and continue reading at line 1848.
# Installing xprintidle would work; xprintidle itself uses the
# Xlib scrnsaver extension, which in Python is from Xlib.ext import screensaver
# but attempting to screensaver.QueryInfo(dpy, root) fails with
# Missing required argument opcode. No opcode is needed in the C code),
# so I can only guess that this is a bug in the Python port of the extension.
# But the best method I've found that doesn't require installing any
# additional programs is this one, from gajim
# https://dev.gajim.org/gajim/gajim/-/blob/master/gajim/common/idle.py
# which is adapted here:
import ctypes
import ctypes.util
class XScreenSaverInfo(ctypes.Structure):
_fields_ = [
('window', ctypes.c_ulong),
('state', ctypes.c_int),
('kind', ctypes.c_int),
('til_or_since', ctypes.c_ulong),
('idle', ctypes.c_ulong),
('eventMask', ctypes.c_ulong)
]
XScreenSaverInfo_p = ctypes.POINTER(XScreenSaverInfo)
display_p = ctypes.c_void_p
xid = ctypes.c_ulong
c_int_p = ctypes.POINTER(ctypes.c_int)
try:
libX11path = ctypes.util.find_library('X11')
if libX11path == None:
raise OSError('libX11 could not be found.')
libX11 = ctypes.cdll.LoadLibrary(libX11path)
libX11.XOpenDisplay.restype = display_p
libX11.XOpenDisplay.argtypes = ctypes.c_char_p,
libX11.XDefaultRootWindow.restype = xid
libX11.XDefaultRootWindow.argtypes = display_p,
libXsspath = ctypes.util.find_library('Xss')
if libXsspath == None:
raise OSError('libXss could not be found.')
libXss = ctypes.cdll.LoadLibrary(libXsspath)
libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p
libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p
libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p)
dpy_p = libX11.XOpenDisplay(None)
if dpy_p == None:
raise OSError('Could not open X Display.')
_event_basep = ctypes.c_int()
_error_basep = ctypes.c_int()
if libXss.XScreenSaverQueryExtension(dpy_p, ctypes.byref(_event_basep),
ctypes.byref(_error_basep)) == 0:
raise OSError('XScreenSaver Extension not available on display.')
xss_info_p = libXss.XScreenSaverAllocInfo()
if xss_info_p == None:
raise OSError('XScreenSaverAllocInfo: Out of Memory.')
rootwindow = libX11.XDefaultRootWindow(dpy_p)
xss_available = True
except OSError:
# Logging?
xss_available = False
def getIdleSec():
global xss_available
"""
Return the idle time in seconds
"""
if not xss_available:
return -1
if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0:
return -1
else:
return int(xss_info_p.contents.idle) / 1000
def close_xss():
global xss_available
if xss_available:
libX11.XFree(xss_info_p)
libX11.XCloseDisplay(dpy_p)
xss_available = False
# end idle time code
###################################################################
if __name__ == '__main__':
while True:
wakeup()
time.sleep(SLEEPSECS)