A monitor for my health.
[busymon] / src / busymon.c
1 /*
2  * Copyright (C) 2003-2008 Adam Wendt (the X11 code, although heavily re-arranged now)
3  * Copyright (C) 2019 Michael Zucchi
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <stdio.h>
20 #include <X11/Xlib.h>
21 #include <X11/extensions/scrnsaver.h>
22 #include <sys/time.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <sys/types.h>
26 #include <signal.h>
27 #include <sys/wait.h>
28
29 #define POLL_RATE 1
30 // idle time to reset status / afk
31 #define IDLE_TIME (60*2)
32 // max busy time at once
33 #define BUSY_TIME (60*55)
34 // If any poll interval spans this time, probably suspended so ignore everything
35 #define SUSPEND_DETECT (POLL_RATE + 10)
36
37 enum {
38         STATE_IDLE,
39         STATE_BUSY
40 };
41
42 static char *alert_cmd;
43
44 static int idle_time(void) {
45         int res = 0;
46         Display *display;
47
48         if (display = XOpenDisplay(NULL)) {
49                 XScreenSaverInfo *info;
50
51                 if (info = XScreenSaverAllocInfo()) {
52                         XScreenSaverQueryInfo(display, DefaultRootWindow(display), info);
53                         res = (info->idle + 1000) / 1000;
54                         XFree(info);
55                 }
56                 XCloseDisplay(display);
57         }
58
59         return res;
60 }
61
62 static void take_break(void) {
63         static pid_t pid = 0;
64
65         if (alert_cmd) {
66                 if (pid != 0) {
67                         pid_t res;
68                         int wstatus;
69
70                         res = waitpid(pid, &wstatus, WNOHANG);
71                         if (res == 0) {
72                                 printf("alert still running\n");
73                                 return;
74                         }
75
76                 }
77                 printf("start alert\n");
78                 pid = fork();
79                 if (pid == 0) {
80                         char * args[] = { alert_cmd, NULL };
81
82                         execv(alert_cmd, args);
83                 }
84         }
85 }
86
87 static time_t get_now() {
88         struct timeval tv;
89
90         gettimeofday(&tv, NULL);
91
92         return tv.tv_sec;
93 }
94
95 /**
96  * Usage: idlemon alert-cmd
97  *
98  * Runs alert-cmd if you've been too active on the computer.
99  */
100 int main(int argc, char **argv) {
101         int state = STATE_BUSY;
102         int lastidle = idle_time();
103         time_t busy_start = get_now();
104         time_t lastnow = get_now();
105
106         if (argc > 1)
107                 alert_cmd = argv[1];
108
109         while (1) {
110                 int idle = idle_time();
111                 int now = get_now();
112
113                 if ((now - lastnow) > SUSPEND_DETECT) {
114                         printf("awoken from suspend?\n");
115                         busy_start = now;
116                         state = STATE_BUSY;
117                         lastidle = idle;
118                         lastnow = now;
119                 } else {
120                         switch (state) {
121                         case STATE_IDLE:
122                                 if (idle < lastidle) {
123                                         busy_start = now - idle;
124
125                                         printf("was idle ~ %d\n", lastidle - idle);
126
127                                         state = STATE_BUSY;
128                                 } else {
129                                         printf("idle: %3d:%02d\n", idle / 60, idle % 60);
130                                 }
131                                 break;
132                         case STATE_BUSY:
133                                 if (idle > IDLE_TIME) {
134                                         printf("was busy ~ %zd\n", now - busy_start - idle);
135
136                                         state = STATE_IDLE;
137                                 } else {
138                                         time_t busy = now - busy_start - idle;
139
140                                         if (busy > BUSY_TIME) {
141                                                 take_break();
142                                         }
143                                         printf("busy: %3zd:%02zd\n", busy / 60, busy % 60);
144                                 }
145                                 break;
146                         }
147                 }
148                 lastidle = idle;
149                 lastnow = now;
150
151                 sleep(POLL_RATE);
152         }
153
154         return 0;
155 }