i3 - improved tiling WM


Added support for i3status to display file contents

Patch status: rejected

Patch by EscapedNull

To apply this patch, use:
curl http://cr.i3wm.org/patch/393/raw.patch | git am

b/i3status.c

17
@@ -302,6 +302,15 @@ int main(int argc, char *argv[]) {
18
                 CFG_END()
19
         };
20
 
21
+        cfg_opt_t file_opts[] = {
22
+                CFG_STR("format", "%contents (%mtime_diff)", CFGF_NONE),
23
+                CFG_INT("line", 1, CFGF_NONE),
24
+                CFG_INT("skip", 0, CFGF_NONE),
25
+                CFG_INT("limit", 4096, CFGF_NONE),
26
+                CFG_STR("mtime_format", "%H:%M:%S", CFGF_NONE),
27
+                CFG_END()
28
+        };
29
+
30
         cfg_opt_t opts[] = {
31
                 CFG_STR_LIST("order", "{}", CFGF_NONE),
32
                 CFG_SEC("general", general_opts, CFGF_NONE),
33
@@ -319,6 +328,7 @@ int main(int argc, char *argv[]) {
34
                 CFG_SEC("ddate", ddate_opts, CFGF_NONE),
35
                 CFG_SEC("load", load_opts, CFGF_NONE),
36
                 CFG_SEC("cpu_usage", usage_opts, CFGF_NONE),
37
+                CFG_SEC("file", file_opts, CFGF_TITLE | CFGF_MULTI),
38
                 CFG_CUSTOM_COLOR_OPTS,
39
                 CFG_END()
40
         };
41
@@ -547,6 +557,12 @@ int main(int argc, char *argv[]) {
42
                                 print_cpu_usage(json_gen, buffer, cfg_getstr(sec, "format"));
43
                                 SEC_CLOSE_MAP;
44
                         }
45
+
46
+                        CASE_SEC_TITLE("file") {
47
+                                SEC_OPEN_MAP("file");
48
+                                print_file(json_gen, buffer, title, cfg_getstr(sec, "format"), cfg_getint(sec, "line"), cfg_getint(sec, "skip"), cfg_getint(sec, "limit"), cfg_getstr(sec, "mtime_format"));
49
+                                SEC_CLOSE_MAP;
50
+                        }
51
                 }
52
                 if (output_format == O_I3BAR) {
53
                         yajl_gen_array_close(json_gen);

b/include/i3status.h

58
@@ -158,6 +158,7 @@ void print_cpu_usage(yajl_gen json_gen, char *buffer, const char *format);
59
 void print_eth_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down);
60
 void print_load(yajl_gen json_gen, char *buffer, const char *format, const float max_threshold);
61
 void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char *fmt_muted, const char *device, const char *mixer, int mixer_idx);
62
+void print_file(yajl_gen json_gen, char *buffer, const char *title, const char *format, const int line, const int skip, const int limit, const char *mtime_format);
63
 bool process_runs(const char *path);
64
 
65
 /* socket file descriptor for general purposes */

b/man/i3status.man

70
@@ -376,6 +376,49 @@ volume master {
71
 }
72
 -------------------------------------------------------------
73
 
74
+=== File
75
+
76
+Prints a file's contents at the specified line, and/or its modification time.
77
+
78
++format+ specifies how the file should be displayed. It has the following variables:
79
+
80
++%contents+ displays the file's contents according to +line+, +skip+, and +limit+.
81
+
82
++%mtime+ shows the file's modification time according to +mtime_format+. By default,
83
+"%H:%M:%S" is used.
84
+
85
++%mtime_diff+ expands to the file's age in a coarse, concise, human-readable format.
86
+For example, "1y" or "28m". Only the largest unit of time is displayed.
87
+
88
++mtime_format+ defines which units of time should be displayed as specified by
89
+strftime(3).
90
+
91
++line+ specifies which line (delimited by "\r", "\n", or "\r\n") should be displayed.
92
+Negative values for +line+ will be counted from the bottom. For example, +line = -1+
93
+behaves similarly to tail(1). If the line does not exist, +%contents+ will be blank.
94
+Note that many plain text files contain a trailing newline, in which case you should
95
+subtract one from the desired +line+ when using a negative value.
96
+
97
+i3status removes +skip+ bytes from the beginning of the specified line before
98
+displaying it. Similarly, i3status will not display more than +limit+ bytes
99
+after +skip+.
100
+
101
+*Example order*: +file /var/log/Xorg.0.log+
102
+
103
+*Example format*: +(%mtime_diff) %contents+
104
+*Example format_mtime*: +%a %Y/%m/%d %H:%M:%S+
105
+
106
+*Example configuration*:
107
+-------------------------------------------------------------
108
+file /var/log/Xorg.0.log {
109
+	format = "[%mtime] %contents (%mtime_diff ago)"
110
+	mtime_format = "%a %H:%M:%S"
111
+	line = -2
112
+	skip = 10
113
+	limit = 20
114
+}
115
+-------------------------------------------------------------
116
+
117
 == Using i3status with dzen2
118
 
119
 After installing dzen2, you can directly use it with i3status. Just ensure that

b/src/print_file.c

125
@@ -0,0 +1,129 @@
126
+#include <stdio.h>
127
+#include <string.h>
128
+#include <sys/stat.h>
129
+#include <fcntl.h>
130
+#include <time.h>
131
+#include <yajl/yajl_gen.h>
132
+#include <yajl/yajl_version.h>
133
+#include <sys/stat.h>
134
+#include "i3status.h"
135
+
136
+bool file_line(char *buffer, const char *title, const int line, const int skip, const int limit);
137
+void format(char *buffer, const char *fmt, const char *title, const struct stat *st);
138
+void format_mtime_diff(char *buffer, const time_t end, const time_t beginning);
139
+
140
+/**Prints the contents of a file, up to 4096 bytes, according to the configuration.
141
+ * @param title - the title of the "file" directive (the path of the file)
142
+ * @param fmt - the output format string specified by the config file. 
143
+ * @param line - the line of the file to display. Negative values start at the bottom of the file.*/
144
+void print_file(yajl_gen json_gen, char *buffer, const char *title, const char *format, const int line, const int skip, const int limit, const char *mtime_format) {
145
+	char *outwalk = buffer;
146
+	const char *walk = format;
147
+	for(; *walk != '\0'; walk++) {
148
+		if(*walk == '%') {
149
+			if(BEGINS_WITH(walk+1, "mtime")) {
150
+				struct stat st;
151
+				int err = stat(title, &st);
152
+				if(err) {
153
+					perror(title);
154
+					return;
155
+				} else if(BEGINS_WITH(walk+1, "mtime_diff")) {
156
+					char buf[1024];
157
+					format_mtime_diff(buf, time(NULL), st.st_mtime);
158
+					outwalk += sprintf(outwalk, buf);
159
+				} else {
160
+					char mtime_buf[1024]; //holds the nicely formatted output text from format_mtime
161
+					struct tm *lmtime = localtime(&(st.st_mtime));
162
+					strftime(mtime_buf, 1024, mtime_format, lmtime);
163
+					outwalk += sprintf(outwalk, mtime_buf);
164
+				}
165
+			} else if(BEGINS_WITH(walk+1, "contents")) {
166
+				char fbuf[4096];
167
+				file_line(fbuf, title, line, skip, limit);
168
+				outwalk += sprintf(outwalk, fbuf);
169
+			}
170
+		}
171
+	}
172
+        OUTPUT_FULL_TEXT(buffer);
173
+}
174
+
175
+bool file_line(char *buffer, const char *title, const int line, const int skip, const int limit) {
176
+	char rb[4096]; //read buffer (next 4096 byte block of data)
177
+	int last_read; //number of bytes returned by the last read(3)
178
+	int cline = 0; //current line
179
+	int i = 0; //position in the read buffer
180
+	int j = 0; //position in the line buffer
181
+	int k = 0; //bytes read since last new line
182
+
183
+	int fd = open(title, O_RDONLY);
184
+	if(fd == -1) {
185
+		perror(title);
186
+		return false;
187
+	}
188
+
189
+	if(line < 0) {
190
+		/* Seek to the end of the file.
191
+ 		 * Read backwards, decrementing cline every time we hit a newline.
192
+ 		 * When cline == line, seek to the newline+1 and let the next code block do the work. */
193
+		int pos = lseek(fd, 0, SEEK_END);
194
+		int last_pos = pos;
195
+		while(cline != line) {
196
+			pos = lseek(fd, pos > 4096 ? pos-4096 : 0, SEEK_SET);
197
+			if(last_pos == pos) { //we have hit the beginning of the file
198
+				cline--;
199
+				pos = lseek(fd, 0, SEEK_SET);
200
+				fprintf(stderr, "%s: Hit top of file searching for line %d.", title, line);
201
+				break;
202
+			}
203
+			last_read = read(fd, rb, last_pos-pos);
204
+			if(last_read < 0) {
205
+				perror(title);
206
+				return false;
207
+			}
208
+			for(i = last_read; i > 0 && cline != line; i--) {
209
+				if(rb[i-1] == '\r' && rb[i] == '\n') {
210
+					cline--;
211
+					i--; //skip the \n
212
+				} else if(rb[i] == '\r' || rb[i] == '\n') cline--;
213
+				if(cline == line) pos = lseek(fd, pos+i+1, SEEK_SET);
214
+			}
215
+			last_pos = pos;
216
+		}
217
+	}
218
+
219
+	do {
220
+		last_read = read(fd, rb, 4096);
221
+		if(last_read < 0) {
222
+			perror(title);
223
+			return false;
224
+		}
225
+		for(i = 0; i < last_read && cline <= line; i++, k++) {
226
+			if(rb[i] == '\r' && rb[i+1] == '\n') {
227
+				cline++;
228
+				i++; //skip the \n
229
+				k  = j = 0;
230
+			} else if(rb[i] == '\r' || rb[i] == '\n') {
231
+				cline++;
232
+				k = j = 0;
233
+			} else if(cline == line && k >= skip && j < limit) buffer[j++] = rb[i];
234
+		}
235
+	} while(last_read == 4096 && cline <= line);
236
+	return true;
237
+}
238
+
239
+void format_mtime_diff(char *buffer, const time_t end, const time_t beginning) {
240
+	time_t diff = difftime(end, beginning);
241
+	time_t years = diff / (60*60*24*365);
242
+	time_t months = diff / (60*60*24*30);
243
+	time_t days = diff / (60*60*24);
244
+	time_t hours = diff / (60*60);
245
+	time_t minutes = diff / 60;
246
+	time_t seconds = diff;
247
+	if(years >= 1) sprintf(buffer, "%dy", (int) years);
248
+	else if(months >= 1) sprintf(buffer, "%dmo", (int) months);
249
+	else if(days >= 1) sprintf(buffer, "%dd", (int) days);
250
+	else if(hours >= 1) sprintf(buffer, "%dh", (int) hours);
251
+	else if(minutes >= 1) sprintf(buffer, "%dm", (int) minutes);
252
+	else if(seconds >= 1) sprintf(buffer, "%ds", (int) seconds);
253
+
254
+}