This repository was archived by the owner on May 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 84
Expand file tree
/
Copy pathdata.go
More file actions
239 lines (210 loc) · 6.63 KB
/
data.go
File metadata and controls
239 lines (210 loc) · 6.63 KB
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package thyme
import (
"bytes"
"fmt"
"log"
"strings"
"time"
)
// trackers is the list of Tracker constructors that are available on this system. Tracker implementations should call
// the RegisterTracker function to make themselves available.
var trackers = make(map[string]func() Tracker)
// RegisterTracker makes a Tracker constructor available to clients of this package.
func RegisterTracker(name string, t func() Tracker) {
if _, exists := trackers[name]; exists {
log.Fatalf("a tracker already exists with the name %s", name)
}
trackers[name] = t
}
// NewTracker returns a new Tracker instance whose type is `name`.
func NewTracker(name string) Tracker {
if _, exists := trackers[name]; !exists {
log.Fatalf("no Tracker constructor has been registered with name %s", name)
}
return trackers[name]()
}
// Tracker tracks application usage. An implementation that satisfies
// this interface is required for each OS windowing system Thyme
// supports.
type Tracker interface {
// Snap returns a Snapshot reflecting the currently in-use windows
// at the current time.
Snap() (*Snapshot, error)
// Deps returns a string listing the dependencies that still need
// to be installed with instructions for how to install them.
Deps() string
}
// Stream represents all the sampling data gathered by Thyme.
type Stream struct {
// Snapshots is a list of window snapshots ordered by time.
Snapshots []*Snapshot
}
// Print returns a pretty-printed representation of the snapshot.
func (s Stream) Print() string {
var b bytes.Buffer
for _, snap := range s.Snapshots {
fmt.Fprintf(&b, "%s", snap.Print())
}
return string(b.Bytes())
}
// Snapshot represents the current state of all in-use application
// windows at a moment in time.
type Snapshot struct {
Time time.Time
Windows []*Window
Active int64
Visible []int64
}
// Print returns a pretty-printed representation of the snapshot.
func (s Snapshot) Print() string {
var b bytes.Buffer
var active *Window
visible := make([]*Window, 0, len(s.Windows))
other := make([]*Window, 0, len(s.Windows))
s_Windows:
for _, w := range s.Windows {
if w.ID == s.Active {
active = w
continue s_Windows
}
for _, v := range s.Visible {
if w.ID == v {
visible = append(visible, w)
continue s_Windows
}
}
other = append(other, w)
}
fmt.Fprintf(&b, "%s\n", s.Time.Format("Mon Jan 2 15:04:05 -0700 MST 2006"))
if active != nil {
fmt.Fprintf(&b, "\tActive: %s\n", active.Info().Print())
}
if len(visible) > 0 {
fmt.Fprintf(&b, "\tVisible: ")
for _, v := range visible {
fmt.Fprintf(&b, "%s, ", v.Info().Print())
}
fmt.Fprintf(&b, "\n")
}
if len(other) > 0 {
fmt.Fprintf(&b, "\tOther: ")
for _, w := range other {
fmt.Fprintf(&b, "%s, ", w.Info().Print())
}
fmt.Fprintf(&b, "\n")
}
return string(b.Bytes())
}
// Window represents an application window.
type Window struct {
// ID is the numerical identifier of the window.
ID int64
// Desktop is the numerical identifier of the desktop the
// window belongs to. Equal to -1 if the window is sticky.
Desktop int64
// Name is the display name of the window (typically what the
// windowing system shows in the top bar of the window).
Name string
}
// systemNames is a set of excluded window names that are known to
// be used by system windows that aren't visible to the user.
var systemNames = map[string]struct{}{
"XdndCollectionWindowImp": {},
"unity-launcher": {},
"unity-panel": {},
"unity-dash": {},
"Hud": {},
"Desktop": {},
}
// IsSystem returns true if the window is a system window (like
// "unity-panel" and thus shouldn't be considered an application
// visible to the end-users)
func (w *Window) IsSystem() bool {
if _, is := systemNames[w.Name]; is {
return true
}
return false
}
// IsSticky returns true if the window is a sticky window (i.e.
// present on all desktops)
func (w *Window) IsSticky() bool {
return w.Desktop == -1
}
// IsOnDesktop returns true if the window is present on the specified
// desktop
func (w *Window) IsOnDesktop(desktop int64) bool {
return w.IsSticky() || w.Desktop == desktop
}
const (
defaultWindowTitleSeparator = " - "
microsoftEdgeWindowTitleSeparator = "\u200e- "
)
// Info returns more structured metadata about a window. The metadata
// is extracted using heuristics.
//
// Assumptions:
// 1) Most windows use " - " to separate their window names from their content
// 2) Most windows use the " - " with the application name at the end.
// 3) The few programs that reverse this convention only reverse it.
func (w *Window) Info() *Winfo {
// Special Cases
fields := strings.Split(w.Name, defaultWindowTitleSeparator)
if len(fields) > 1 {
last := strings.TrimSpace(fields[len(fields)-1])
if last == "Google Chrome" {
return &Winfo{
App: "Google Chrome",
SubApp: strings.TrimSpace(fields[len(fields)-2]),
Title: strings.Join(fields[0:len(fields)-2], defaultWindowTitleSeparator),
}
}
}
if strings.Contains(w.Name, microsoftEdgeWindowTitleSeparator) {
// App Name Last
beforeSep := strings.LastIndex(w.Name, microsoftEdgeWindowTitleSeparator)
afterSep := beforeSep + len(microsoftEdgeWindowTitleSeparator)
return &Winfo{
App: strings.TrimSpace(w.Name[afterSep:]),
Title: strings.TrimSpace(w.Name[:beforeSep]),
}
}
// Normal Cases
if beforeSep := strings.Index(w.Name, defaultWindowTitleSeparator); beforeSep > -1 {
// App Name First
if w.Name[:beforeSep] == "Slack" {
afterSep := beforeSep + len(defaultWindowTitleSeparator)
return &Winfo{
App: strings.TrimSpace(w.Name[:beforeSep]),
Title: strings.TrimSpace(w.Name[afterSep:]),
}
}
// App Name Last
beforeSep := strings.LastIndex(w.Name, defaultWindowTitleSeparator)
afterSep := beforeSep + len(defaultWindowTitleSeparator)
return &Winfo{
App: strings.TrimSpace(w.Name[afterSep:]),
Title: strings.TrimSpace(w.Name[:beforeSep]),
}
}
// No Application name separator
return &Winfo{
Title: w.Name,
}
}
// Winfo is structured metadata info about a window.
type Winfo struct {
// App is the application that controls the window.
App string
// SubApp is the sub-application that controls the window. An
// example is a web app (e.g., Sourcegraph) that runs
// inside a Chrome tab. In this case, the App field would be
// "Google Chrome" and the SubApp field would be "Sourcegraph".
SubApp string
// Title is the title of the window after the App and SubApp name
// have been stripped.
Title string
}
// Print returns a pretty-printed representation of the snapshot.
func (w Winfo) Print() string {
return fmt.Sprintf("[%s|%s|%s]", w.App, w.SubApp, w.Title)
}