]> git.decadent.org.uk Git - ion3.git/blob - mod_statusbar/mod_statusbar.lua
c37008295ffb9ffade7e83a813c2bf0d5abaa292
[ion3.git] / mod_statusbar / mod_statusbar.lua
1 --
2 -- ion/mod_statusbar/mod_statusbar.lua
3 -- 
4 -- Copyright (c) Tuomo Valkonen 2004-2008.
5 --
6 -- See the included file LICENSE for details.
7 --
8
9 -- This is a slight abuse of the package.loaded variable perhaps, but
10 -- library-like packages should handle checking if they're loaded instead of
11 -- confusing the user with require/include differences.
12 if package.loaded["mod_statusbar"] then return end
13
14 if not ioncore.load_module("mod_statusbar") then
15     return
16 end
17
18 local mod_statusbar=_G["mod_statusbar"]
19 assert(mod_statusbar)
20
21
22 -- Meter list {{{
23
24 local meters={}
25
26 --DOC
27 -- Inform of a value.
28 function mod_statusbar.inform(name, value)
29     meters[name]=value
30 end
31
32 -- }}}
33
34
35 -- Template processing {{{
36
37 local function process_template(template, meter_f, text_f, stretch_f)
38     local st, en, b, c, r, p, tmp
39     
40     while template~="" do
41         -- Find '%something'
42         st, en, b, r=string.find(template, '^(.-)%%(.*)')
43     
44         if not b then
45             -- Not found
46             text_f(template)
47             break
48         else
49             if b~="" then
50                 -- Add preciding text as normal text element
51                 text_f(b)
52             end
53             template=r
54
55             -- Check for '% ' and '%%'
56             st, en, c, r=string.find(template, '^([ %%])(.*)')
57
58             if c then
59                 if c==' ' then
60                     stretch_f(c)
61                 else
62                     text_f(c)
63                 end
64                 template=r
65             else 
66                 -- Extract [alignment][zero padding]<meter name>
67                 local pat='([<|>]?)(0*[0-9]*)([a-zA-Z0-9_]+)'
68                 -- First within {...}
69                 st, en, c, p, b, r=string.find(template, '^{'..pat..'}(.*)')
70                 if not st then
71                     -- And then without
72                     st, en, c, p, b, r=string.find(template, '^'..pat..'(.*)')
73                 end
74                 if b then
75                     meter_f(b, c, tonumber(p))
76                     template=r
77                 end
78             end
79         end
80     end
81 end
82
83
84
85 function mod_statusbar.template_to_table(template)
86     local res={}
87     local m=meters --set_date(stng, meters)
88     local aligns={["<"]=0, ["|"]=1, [">"]=2}
89     
90     process_template(template,
91                      -- meter
92                      function(s, c, p)
93                          if s=="filler" then
94                              table.insert(res, {type=4})
95                          elseif (string.find(s, "^systray$") or
96                                  string.find(s, "^systray_")) then
97                              table.insert(res, {
98                                  type=5,
99                                  meter=s,
100                                  align=aligns[c],
101                              })
102                          else
103                              table.insert(res, {
104                                  type=2,
105                                  meter=s,
106                                  align=aligns[c],
107                                  tmpl=meters[s.."_template"],
108                                  zeropad=p,
109                              })
110                          end
111                      end,
112                      -- text
113                      function(t)
114                          table.insert(res, {
115                              type=1,
116                              text=t,
117                          })
118                      end,
119                      -- stretch
120                      function(t)
121                          table.insert(res, {
122                              type=3,
123                              text=t,
124                          })
125                      end)
126     return res
127 end
128
129
130 mod_statusbar._set_template_parser(mod_statusbar.template_to_table)
131
132
133 -- }}}
134
135 -- Update {{{
136
137 --DOC
138 -- Update statusbar contents. To be called after series
139 -- of \fnref{mod_statusbar.inform} calls.
140 function mod_statusbar.update(update_templates)
141     for _, sb in pairs(mod_statusbar.statusbars()) do
142         if update_templates then
143             local t=sb:get_template_table()
144             for _, v in pairs(t) do
145                 if v.meter then
146                     v.tmpl=meters[v.meter.."_template"]
147                 end
148             end
149             sb:set_template_table(t)
150         end
151         sb:update(meters)
152     end
153 end
154
155 -- }}}
156
157
158 -- ion-statusd support {{{
159
160 local statusd_pid=0
161
162 function mod_statusbar.rcv_statusd(str)
163     local data=""
164     local updatenw=false
165     local updated=false
166
167     local function doline(i)
168         if i=="." then
169             mod_statusbar.update(updatenw)
170             updated=true
171         else
172             local _, _, m, v=string.find(i, "^([^:]+):%s*(.*)")
173             if m and v then
174                 mod_statusbar.inform(m, v)
175                 updatenw=updatenw or string.find(m, "_template")
176             end
177         end
178         return ""
179     end
180     
181     while str do
182         updated=false
183         data=string.gsub(data..str, "([^\n]*)\n", doline)
184         str=coroutine.yield(updated)
185     end
186     
187     ioncore.warn(TR("ion-statusd quit."))
188     statusd_pid=0
189     meters={}
190     mod_statusbar.update(updatenw)
191 end
192
193
194 function mod_statusbar.get_modules()
195     local mods={}
196     local specials={["filler"]=true, ["systray"]=true}
197     
198     for _, sb in pairs(mod_statusbar.statusbars()) do
199         for _, item in pairs(sb:get_template_table()) do
200             if item.type==2 and not specials[item.meter] then
201                 local _, _, m=string.find(item.meter, "^([^_]*)");
202                 if m and m~="" then
203                     mods[m]=true
204                 end
205             end
206         end
207     end
208     
209     return mods
210 end
211
212
213 function mod_statusbar.cfg_statusd(cfg)
214     if date_format_backcompat_kludge then
215         if not cfg.date then
216             cfg=table.copy(cfg, false)
217             cfg.date={date_format=date_format_backcompat_kludge}
218         elseif not cfg.date.date_format then
219             cfg=table.copy(cfg, true)
220             cfg.date.date_format=date_format_backcompat_kludge
221         end
222     end
223
224     --TODO: don't construct file name twice.
225     ioncore.write_savefile("cfg_statusd", cfg)
226     return ioncore.get_savefile("cfg_statusd")
227 end
228
229
230 function mod_statusbar.rcv_statusd_err(str)
231     if str then
232         io.stderr:write(str)
233     end
234 end
235
236 --DOC
237 -- Load modules and launch \file{ion-statusd} with configuration 
238 -- table \var{cfg}. The options for each \file{ion-statusd} monitor
239 -- script should be contained in the corresponding sub-table of \var{cfg}.
240 function mod_statusbar.launch_statusd(cfg)
241     if statusd_pid>0 then
242         return
243     end
244     
245     -- Launch tried, don't do it automatically after reading
246     -- configuration.
247     mod_statusbar.no_autolaunch=true
248     
249     local mods=mod_statusbar.get_modules()
250     
251     -- Load modules
252     for m in pairs(mods) do
253         if dopath("statusbar_"..m, true) then
254             mods[m]=nil
255         end
256     end
257
258     -- Lookup ion-statusd
259     local statusd_script="ion-statusd"
260     local statusd=ioncore.lookup_script(statusd_script)
261     if not statusd then
262         ioncore.warn(TR("Could not find %s", statusd_script))
263         return
264     end
265
266     local statusd_errors
267     local function initrcverr(str)
268         statusd_errors=(statusd_errors or "")..str
269     end
270
271     local cfg=mod_statusbar.cfg_statusd(cfg or {})
272     local params=""
273     table.foreach(mods, function(k) params=params.." -m "..k end)
274     local cmd=statusd.." -c "..cfg..params
275     
276     local rcv=coroutine.wrap(mod_statusbar.rcv_statusd)
277     local rcverr=mod_statusbar.rcv_statusd_err
278     
279     statusd_pid=mod_statusbar._launch_statusd(cmd, 
280                                               rcv, initrcverr, 
281                                               rcv, rcverr)
282
283     if statusd_errors then
284         warn(TR("Errors starting ion-statusd:\n")..statusd_errors)
285     end
286
287     if statusd_pid<=0 then
288         warn(TR("Failed to start ion-statusd."))
289     end                                              
290 end
291
292 --}}}
293
294
295 -- Initialisation and default settings {{{
296
297 --DOC
298 -- Create a statusbar. The possible parameters in the
299 -- table \var{param} are:
300 --
301 -- \begin{tabularx}{\linewidth}{llX}
302 --   Variable & Type & Description \\
303 --   \var{template} & string & The template; see
304 --                             Section \ref{sec:statusbar}. \\
305 --   \var{pos} & string & Position: \codestr{tl}, \codestr{tr}, 
306 --                        \codestr{bl} or \codestr{br}
307 --                        (for the obvious combinations of 
308 --                        top/left/bottom/right). \\
309 --   \var{screen} & integer & Screen number to create the statusbar on. \\
310 --   \var{fullsize} & boolean & If set, the statusbar will waste
311 --                              space instead of adapting to layout. \\
312 --   \var{systray} & boolaen & Swallow (KDE protocol) systray icons. \\
313 -- \end{tabularx}
314 --
315 function mod_statusbar.create(param)
316     local scr=ioncore.find_screen_id(param.screen or 0)
317     if not scr then
318         error(TR("Screen not found."))
319     end
320     
321     if not param.force then
322         local stdisp=scr:get_stdisp()
323         if stdisp and stdisp.reg then
324             error(TR("Screen already has an stdisp. Refusing to create a "..
325                      "statusbar."))
326         end
327     end
328     
329     local sb=scr:set_stdisp({
330         type="WStatusBar", 
331         pos=(param.pos or "bl"),
332         fullsize=param.fullsize,
333         name="*statusbar*",
334         template=param.template,
335         template_table=param.template_table,
336         systray=param.systray,
337     })
338     
339     if not sb then
340         error(TR("Failed to create statusbar."))
341     end
342     
343     return sb
344 end
345
346 -- }}}
347
348
349 -- Mark ourselves loaded.
350 package.loaded["mod_statusbar"]=true
351
352
353 -- Load user configuration file
354 dopath('cfg_statusbar', true)
355
356 -- Launch statusd if the user didn't launch it.
357 if not mod_statusbar.no_autolaunch then
358     mod_statusbar.launch_statusd()
359 end