-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbashets.lua
547 lines (462 loc) · 15.6 KB
/
bashets.lua
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
------------------------------------------------------------------------
-- Bashets - use your shellscript's output in Awesome3 widgets
--
-- @author Anton Lobov <[email protected]>
-- @copyright 2010 Anton Lobov
-- @license GPLv3
-- @release 0.6.2 for Awesome-git
-----------------------------------------------------------------------
-- Grab only needed enviroment
local awful = require("awful")
local string = string
local io = io
local table = table
local pairs = pairs
local timer = timer
local type = type
local image = image
local capi = {oocairo = oocairo, timer = timer, dbus = dbus}
local tonumber = tonumber
local print = print
local error = error
--- Bashets module
module("bashets")
-- Default paths
local script_path = "/usr/share/awesome/bashets/"
local tmp_folder = "/tmp/"
-- Utility functions table
local util = {}
-- Timer data
local timerdata = {}
local timers = {}
-- External mode data
local ewidgets = {}
-- Some default values
local defaults = {}
defaults.update_time = 1
defaults.file_update_time = 2
defaults.format_string = "$1"
defaults.separator = " "
defaults.updater = "runner.sh"
-- State variable
local is_running = false
-- # Utility functions
--- Split string by separator into table
-- @param str String to split
-- @param sep Separator to use
function util.split(str, sep)
if sep == nil then
return {str}
end
local parts = {} --parts array
local first = 1
local ostart, oend = string.find(str, sep, first, true) --regexp disabled search
while ostart do
local part = string.sub(str, first, ostart - 1)
table.insert(parts, part)
first = oend + 1
ostart, oend = string.find(str, sep, first, true)
end
local part = string.sub(str, first)
table.insert(parts, part)
return parts
end
function util.tmpkey(script)
-- Replace all slashes with empty string so that /home/user1/script.sh
-- and /home/user2/script.sh will have different temporary files
local tmpname = string.gsub(script, '/', '')
-- Replace all spaces with dots so that "script.sh arg1"
-- and "script.sh arg2" will have different temporary files
tmpname = string.gsub(tmpname, '%s+', '.')
return tmpname
end
function util.tmpname(script)
-- Generated script-parameter unique temporary file path
local file = tmp_folder .. util.tmpkey(script) .. '.bashets.out'
return file
end
function util.fullpath(script)
if string.find(script, '^/') == nil then
script = script_path .. script
end
return script
end
--- Execute a command and write it's output to temporary file
-- @param script Script to execute
-- @param file File for script output
function util.execfile(script, file)
-- Spawn command and redirect it's output to file
awful.util.spawn_with_shell(script .. " > " .. file)
end
function util.readstring(str, sep)
if sep == nil then
return str
else
parts = util.split(str, sep);
return parts
end
end
--- Read temporary file to a table or string
-- @param file File to be read
-- @param israw If true, return raw string, not table
function util.readfile(file, sep)
local fh = io.input(file)
local str = fh:read("*all");
io.close(fh)
return util.readstring(str, sep)
end
--- Read script output to a table or string
-- @param script Script to execute
-- @param israw If true, return raw string, not table
function util.readshell(script, sep)
local str = awful.util.pread(script)
return util.readstring(str, sep)
end
--- Format script output with user defined format string
-- @param output sep-separated string of values
-- @param format Format string
-- @param sep Separator of values in string
function util.format(parts, format, escape)
-- For each part with number "k" replace corresponding "$k" variable in format string
for k,part in pairs(parts) do
local part = string.gsub(part, "%%", "%1%1") --percent fix for next gsub (bug found in Wicked)
if escape then
part = awful.util.escape(part) --escape XML entities for correct Pango markup
end
format = string.gsub(format, "$" .. k, part)
end
return format
end
--- Add function to corresponding timer object (Awesome >= 3.4 timer API)
-- @param updtime Update time for widget, also dispatch time for timer
-- @param func Function to dispatch
function util.add_to_timings(updtime, func, force_new)
local found = false
-- Search for an existing timer at the same period
for k,tmr in pairs(timerdata) do
if tmr[1] == updtime and (force_new == nil or force_new == false) then
table.insert(timerdata[k][2], func)
found = true
end
end
-- Add a new timer for period if not found
if not found then
table.insert(timerdata, {updtime, {func}})
end
end
--- Create timer table to define timers for multiple widget updates
function util.create_timers_table()
-- Parse table with timer data
for _,tmr in pairs(timerdata) do
-- Create timer for the period
local t
if capi.timer ~= nil then
t = capi.timer {timeout = tmr[1]}
else
t = timer {timeout = tmr[1]}
end
-- Function to call all dispatched functions
local f = function()
for _, func in pairs(tmr[2]) do
func()
end
end
if t.add_signal ~= nil then
t:add_signal("timeout", f)
else
t:connect_signal("timeout", f)
end
table.insert(timers, t)
end
end
function util.get_widget_meta(widget)
if widget.type == "imagebox" then --imagebox (old API)
return {wtype = "imagebox", update = function (v) widget["image"] = image(v) end}
elseif widget.set_image ~= nil then --imagebox (new API)
return {wtype = "imagebox", update = function (v) widget:set_image(v) end}
elseif widget.type == "textbox" then --textbox (old API)
return {wtype = "textbox", update = function (v) widget["text"] = v end}
elseif widget.set_markup ~= nil then
return {wtype = "textbox", update = function (v) widget:set_markup(v) end} --textbox (new API)
elseif widget.set_value ~= nil then --progressbar (new API)
return {wtype = "progressbar", update = function (v) widget:set_value(tonumber(v)) end}
elseif widget.add_value ~= nil then --graph (new API)
return {wtype = "graph", update = function (v) widget:add_value(tonumber(v)) end}
else
return {wtype = "unknown", update = function (v) error("bashets: unknown widget type to deal with") end}
end
end
function util.update_widget_field(widget, valuess)
local wid = util.get_widget_meta(widget)
wid.update(valuess)
-- print(widget.type)
-- if widget.type == "imagebox" then --imagebox (old API)
-- widget["image"] = image(valuess)
-- elseif widget.set_image ~= nil then --imagebox (new API)
-- --widget["image"] = capi.oocairo.image_surface_create_from_png(valuess)
-- --widget:set_image(capi.oocairo.image_surface_create_from_png(valuess))
-- widget:set_image(valuess)
-- elseif widget.type == "textbox" then --textbox (old API)
-- widget["text"] = valuess
-- elseif widget.set_markup ~= nil then --textbox (new API)
-- widget:set_markup(valuess)
-- --elseif widget["widget"] ~= nil then
-- elseif widget.set_value ~= nil then --progressbar
-- widget:set_value(tonumber(valuess))
-- elseif widget.add_value ~= nil then --graph
-- widget:add_value(tonumber(valuess))
-- end
-- --end
end
--- Update widget from values
function util.update_widget(widget, values, format)
if widget ~= nil then
local w = util.get_widget_meta(widget)
if type(values) == "table" then
util.update_widget_field(widget, util.format(values, format, w.wtype == "textbox"))
else
if (w.wtype == "textbox") then
util.update_widget_field(widget, awful.util.escape(values))
else
util.update_widget_field(widget, values)
end
end
end
end
-- # Setter functions
--- Set path for scripts
-- @param path Path to set
function set_script_path(path)
script_path = path
end
--- Set path for temporary files
-- @param path Path to set
function set_temporary_path(path)
tmp_folder = path
end
--- Set default values
-- @param defs Table with defaults
function set_defaults(defs)
if type(defs) == "table" then
if defs.update_time ~= nil then
defaults.update_time = defs.update_time
end
if defs.file_update_time ~= nil then
defaults.file_update_time = defs.file_update_time
end
if defs.format_string ~= nil then
defaults.format_string = defs.format_string
end
if defs.updater ~= nil then
defaults.updater = defs.updater
end
defaults.separator = defs.separator --now could be nil
end
end
-- # Acting functions
--- Start widget updates
function start()
-- Create timers table if not initialized or empty
if (not timers) or table.maxn(timers) == 0 then
util.create_timers_table()
end
-- Start all timers
for _, tmr in pairs(timers) do
tmr:start()
end
-- Kill all externals (if some were here from previous launch)
--awful.util.spawn_with_shell('killall ' .. defaults.updater)
-- Run all externals
for _, wgt in pairs(ewidgets) do
awful.util.spawn_with_shell(wgt.cmd)
end
is_running = true
end
--- Stop widget updates
function stop()
-- Stop all timers
for _, tmr in pairs(timers) do
tmr:stop()
end
-- Kill all externals
awful.util.spawn_with_shell('killall ' .. defaults.updater)
is_running = false
end
--- Check whether updates are running
function get_running()
return is_running
end
--- Toggle updates
function toggle()
if is_running then
start()
else
stop()
end
end
--- Shedule function for timed execution
-- @param func Function to run
-- @param updatime Update time (optional)
function schedule(func, updtime)
updtime = updtime or defaults.update_time
if func ~= nil then
util.add_to_timings(updtime, func)
end
end
-- # Widget registration functions
--- General widget-callback registration function. For internal use, but if you need it, feel free to call it :)
-- @param data_provider Function that returns table of values
-- @param widget Widget to update, if null, nothing is updated
-- @param callback Callback to call after the update of values, if null, nothing is called
-- @param format Format string for widget //N.B.: it is not checked for null
function schedule_w(data_provider, widget, callback, format, updtime)
if callback ~= nil and type(callback) == "function" then
if widget ~= nil then
schedule(function()
local data = data_provider()
util.update_widget(widget, data, format)
callback(data)
end, updtime)
else
schedule(function()
local data = data_provider()
callback(data)
end, updtime)
end
elseif widget ~= nil then
schedule(function()
local data = data_provider()
util.update_widget(widget, data, format)
end, updtime)
end
end
--- External script registration. Script will pass data by calling external_w through dbus
function schedule_e(script, widget, callback, format, updtime, sep)
local ascript = util.fullpath(script)
local key = util.tmpkey(ascript)
ewidgets[key] = {}
ewidgets[key].widget = widget
ewidgets[key].callback = callback
ewidgets[key].format = format
ewidgets[key].separator = sep
ewidgets[key].cmd = util.fullpath(defaults.updater) .. " \"" .. ascript .. "\" " .. key .. " " ..updtime
--print(ewidgets[key].cmd)
-- awful.util.spawn_with_shell()
end
function external_w(raw_data, key)
local callback = ewidgets[key].callback or nil
local widget = ewidgets[key].widget or nil
local format = ewidgets[key].format or defaults.format_string
local data = util.readstring(raw_data, ewidgets[key].separator)
if callback ~= nil and type(callback) == "function" then
if widget ~= nil then
util.update_widget(widget, data, format)
callback(data)
else
callback(data)
end
elseif widget ~= nil then
util.update_widget(widget, data, format)
end
end
function register_d(bus, iface, widget, callback, format)
if capi.dbus then
bus = bus or "session"
capi.dbus.add_match(bus, "interface='" .. iface .. "'")
capi.dbus.connect_signal(iface, function (...)
local argums = {...}
local data = {}
for _,v in pairs(argums) do
if type(v) == "table" then
for _,iv in pairs(v) do
table.insert(data, iv)
end
else
table.insert(data, v)
end
end
if callback ~= nil and type(callback) == "function" then
if widget ~= nil then
util.update_widget(widget, data, format)
callback(data)
else
callback(data)
end
elseif widget ~= nil then
util.update_widget(widget, data, format)
end
end)
else
error("bashets: You are trying to employ dbus, but no dbus api detected in your build of awesome")
end
end
--- Register script for text widget
-- @param object Object to be a data provider (function or string with a path to file/shellscript)
-- @param options Options table with following content (all fields are optional):
--
-- widget Widget to update
-- callback Function to execute after the update (should have a single parameter which represents table with data)
-- NOTE: you can use the widget without a callback, the callback without a widget, or both
--
-- update_time Widget update time (in seconds)
-- separator Output separator (string, could be null)
-- format User-defined format string (any valid Pango markup with variables)
--
-- async Perform script execution through a temporary file (true/false)
-- file_update_time Temporary file content update time (in seconds)
--
-- read_file Read file instead of executing it
function register(object, options)
-- Set optional variables
options = options or {}
local updtime = options.update_time or defaults.update_time
local fltime = options.file_update_time or defaults.file_update_time
local format = options.format or defaults.format_string
local sep = options.separator or defaults.separator
local callback = options.callback
local async = options.async or false
local isdbus = options.dbus or false
local busname = options.busname or "session"
local readfile = options.read_file or false
local widget = options.widget
local external = options.external or false
if type(object) == "function" then --We have a function as a data provider
-- Do it first time
local data = func()
util.update_widget(widget, data, format)
-- Schedule it for timed execution
schedule_w(func, widget, callback, format, updtime)
elseif readfile then --We have a text file as a data provider
-- Do it first time
local data = util.readfile(object, sep)
util.update_widget(widget, data, format)
-- Schedule it for timed execution
schedule_w(function() return util.readfile(object, sep) end, widget, callback, format, updtime)
elseif async then --Script could hang Awesome, we need to run it
local script = util.fullpath(object) --through a temporary file
local tmpfile = util.tmpname(script)
-- Create temporary file if not exists
fl = io.open(tmpfile, "w")
io.close(fl)
-- Do it first time
util.execfile(script, tmpfile)
local data = util.readfile(tmpfile)
util.update_widget(widget, data, format)
-- Schedule it for timed execution
schedule(function() util.execfile(script, tmpfile) end, fltime)
schedule_w(function() return util.readfile(tmpfile, sep) end, widget, callback, format, updtime)
elseif external then -- Script provides external data through dbus
local script = util.fullpath(object)
schedule_e(script, widget, callback, format, updtime, sep)
elseif isdbus then -- Data is fetched through a message bus
register_d(busname, object, widget, callback, format)
--print("registered with bus name \"" .. busname .. "\" and object \"" .. object .. "\"")
else -- Fast script that can be read through pread
local script = util.fullpath(object)
-- Do it first time
local data = util.readshell(script)
util.update_widget(widget, data, format)
-- Schedule it for timed execution
schedule_w(function() return util.readshell(script, sep) end, widget, callback, format, updtime)
end
end