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 |
+} |