Merge lp:~injaon/indicator-sysmonitor/trunk into lp:indicator-sysmonitor/trunk
- trunk
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 20 |
Proposed branch: | lp:~injaon/indicator-sysmonitor/trunk |
Merge into: | lp:indicator-sysmonitor/trunk |
Diff against target: |
1392 lines (+788/-428) 3 files modified
indicator-sysmonitor (+646/-428) lang/es/es.po (+72/-0) messages.pot (+70/-0) |
To merge this branch: | bzr merge lp:~injaon/indicator-sysmonitor/trunk |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alex Eftimie | Needs Fixing | ||
Review via email: mp+125394@code.launchpad.net |
Commit message
Description of the change
Alex Eftimie (alexeftimie) wrote : | # |
Alex Eftimie (alexeftimie) wrote : | # |
Please bump the version to 0.5
Gabriel Lopez (injaon) wrote : | # |
I don't know how to do it :(
2012/9/24 Alex Eftimie <email address hidden>:
> Review: Needs Fixing
>
> Please bump the version to 0.5
> --
> https:/
> You are the owner of lp:~pipodecnt/indicator-sysmonitor/trunk.
Alex Eftimie (alexeftimie) wrote : | # |
Just change the variable VERSION value :)
- 31. By Gabriel Lopez <email address hidden>
-
Change VERSION variable.
Gabriel Lopez (injaon) wrote : | # |
> Just change the variable VERSION value :)
LOL. Tha hard work is done :P
Alex - WarumLinuxBesserIst - (wlbi) wrote : | # |
This indicator is so great, it's my favourite one to see network speed.
In Ubuntu 15.04 I can't add the network activity sensor in teh "Advanced" settings.
If I like to save with the network sensor, there is the error: " {bat\d*} sensor not supported."
Is there a special package to add to have this?
Preview Diff
1 | === modified file 'indicator-sysmonitor' |
2 | --- indicator-sysmonitor 2012-04-22 06:30:20 +0000 |
3 | +++ indicator-sysmonitor 2012-09-26 17:56:20 +0000 |
4 | @@ -7,39 +7,184 @@ |
5 | # Homepage: http://launchpad.net/indicator-sysmonitor |
6 | # License: GPL v3 |
7 | # |
8 | +from gettext import gettext as _ |
9 | +from gettext import textdomain, bindtextdomain |
10 | +textdomain("indicator-sysmonitor") |
11 | +bindtextdomain("indicator-sysmonitor", "./lang") |
12 | + |
13 | import sys |
14 | import os |
15 | import shutil |
16 | import json |
17 | -import string |
18 | import time |
19 | -from copy import deepcopy |
20 | from threading import Thread, Event |
21 | import subprocess |
22 | import psutil as ps |
23 | - |
24 | -import logging |
25 | -logging.basicConfig(file=sys.stderr,level=logging.INFO) |
26 | - |
27 | +import re |
28 | import gobject |
29 | import gtk |
30 | +gtk.gdk.threads_init() |
31 | import appindicator |
32 | - |
33 | -VERSION='0.4.1~unreleased' |
34 | - |
35 | -gtk.gdk.threads_init() |
36 | +import logging |
37 | +logging.basicConfig(file=sys.stderr, level=logging.INFO) |
38 | + |
39 | +B_UNITS = ['', 'KB', 'MB', 'GB', 'TB'] |
40 | +VERSION = '0.5.0~unreleased' |
41 | +HELP_MSG = """<span underline="single" size="x-large">{title}</span> |
42 | + |
43 | +{introduction} |
44 | + |
45 | +{basic} |
46 | +• cpu: {cpu_desc} |
47 | +• mem: {mem_desc} |
48 | +• bat<i>%d</i>: {bat_desc} |
49 | +• net: {net_desc} |
50 | + |
51 | +{compose} |
52 | +• fs//<i>mount-point</i> : {fs_desc} |
53 | + |
54 | +<big>{example}</big> |
55 | +CPU {{cpu}} | MEM {{mem}} | root {{fs///}} |
56 | +""".format( |
57 | + title=_("Help Page"), |
58 | + introduction=_("The sensors are the names of the devices you want to retrieve information from. They must be placed between brackets."), |
59 | + basic=_("The basics are:"), |
60 | + cpu_desc=_("It shows the average of CPU usage."), |
61 | + mem_desc=_("It shows the physical memory in use."), |
62 | + bat_desc=_("It shows the available battery which id is %d."), |
63 | + net_desc=_("It shows the amount of data you are downloading and uploading through your network."), |
64 | + compose=_("Also there are the following sensors that are composed with two parts divided by two slashes."), |
65 | + fs_desc=_("Show available space in the file system."), |
66 | + example=_("Example:")) |
67 | + |
68 | + |
69 | +class ISMError(Exception): |
70 | + """General exception.""" |
71 | + def __init__(self, msg): |
72 | + Exception.__init__(self, msg) |
73 | + |
74 | + |
75 | +def raise_dialog(parent, flags, type_, buttons, msg, title): |
76 | + """It raise a dialog. It a blocking function.""" |
77 | + dialog = gtk.MessageDialog( |
78 | + parent, flags, type_, buttons, msg) |
79 | + dialog.set_title(title) |
80 | + dialog.run() |
81 | + dialog.destroy() |
82 | + |
83 | +supported_sensors = re.compile("mem|cpu|net|bat\d+|fs//.+?") |
84 | +settings = { |
85 | + 'custom_text': 'cpu: {cpu} mem: {mem}', |
86 | + 'interval': 2, |
87 | + 'on_startup': False, |
88 | + 'sensors': { |
89 | + # 'name' => (desc, cmd) |
90 | + 'cpu': (_('Average CPU usage'), True), |
91 | + 'mem': (_('Physical memory in use.'), True), |
92 | + 'net': (_('Network activity.'), True), |
93 | + 'bat\d+': (_('Network activity.'), True), |
94 | + 'fs//.+': (_('Available space in file system.'), True) |
95 | + } |
96 | + } |
97 | + |
98 | + |
99 | +class Sensor(object): |
100 | + """Singleton""" |
101 | + _instance = None |
102 | + |
103 | + def __init__(self): |
104 | + """It must not be called. Use Sensor.get_instace() |
105 | + to retrieve an instance of this class.""" |
106 | + if Sensor._instance is not None: |
107 | + raise Exception("Sensor class can not be instanceted twice.") |
108 | + else: |
109 | + Sensor._instance = self |
110 | + self.update_regex() |
111 | + |
112 | + @classmethod |
113 | + def get_instance(cls): |
114 | + """Returns the unique instance of Sensor.""" |
115 | + if Sensor._instance is None: |
116 | + Sensor._instance = Sensor() |
117 | + |
118 | + return Sensor._instance |
119 | + |
120 | + @staticmethod |
121 | + def exists(name): |
122 | + """Checks if the sensor name exists""" |
123 | + return bool(supported_sensors.match(name)) |
124 | + |
125 | + def add(self, name, desc, cmd): |
126 | + """Adds a custom sensors.""" |
127 | + if Sensor.exists(name): |
128 | + raise ISMError(_("Sensor name already in use.")) |
129 | + |
130 | + settings["sensors"][name] = (desc, cmd) |
131 | + self.update_regex() |
132 | + |
133 | + @staticmethod |
134 | + def update_regex(names=None): |
135 | + if names is None: |
136 | + names = settings["sensors"].keys() |
137 | + |
138 | + reg = '|'.join(names) |
139 | + global supported_sensors |
140 | + supported_sensors = re.compile("{}".format(reg)) |
141 | + |
142 | + def delete(self, name): |
143 | + """Deletes a custom sensors.""" |
144 | + sensors = settings['sensors'] |
145 | + names = sensors.keys() |
146 | + if name not in names: |
147 | + raise ISMError(_("Sensor is not defined.")) |
148 | + |
149 | + _desc, default = sensors[name] |
150 | + if default is True: |
151 | + raise ISMError(_("Can not delete default sensors.")) |
152 | + |
153 | + del sensors[name] |
154 | + self.update_regex() |
155 | + |
156 | + def edit(self, name, newname, desc, cmd): |
157 | + """Edits a custom sensors.""" |
158 | + try: |
159 | + sensors = settings['sensors'] |
160 | + _desc, default = sensors[name] |
161 | + |
162 | + except KeyError: |
163 | + raise ISMError(_("Sensor does not exists.")) |
164 | + |
165 | + if default is True: |
166 | + raise ISMError(_("Can not edit default sensors.")) |
167 | + if newname != name: |
168 | + if newname in sensors.keys(): |
169 | + raise ISMError(_("Sensor name already in use.")) |
170 | + |
171 | + sensors[newname] = (desc, cmd) |
172 | + del sensors[name] |
173 | + settings["custom_text"] = settings["custom_text"].replace( |
174 | + name, newname) |
175 | + self.update_regex() |
176 | + |
177 | |
178 | class StatusFetcher(Thread): |
179 | + """It recollects the info about the sensors.""" |
180 | + digit_regex = re.compile(r'''\d+''') |
181 | + |
182 | def __init__(self, parent): |
183 | Thread.__init__(self) |
184 | - self.parent = parent |
185 | + self._parent = parent |
186 | self.last = ps.cpu_times() |
187 | |
188 | def _fetch_cpu(self): |
189 | last = self.last |
190 | current = ps.cpu_times() |
191 | |
192 | - total_time_passed = sum([v-last.__dict__[k] if not isinstance(v,list) else 0 for k,v in current.__dict__.iteritems()]) |
193 | + total_time_passed = sum( |
194 | + [v - last.__dict__[k] |
195 | + if not isinstance(v, list) |
196 | + else 0 |
197 | + for k, v in current.__dict__.iteritems()]) |
198 | |
199 | sys_time = current.system - last.system |
200 | usr_time = current.user - last.user |
201 | @@ -54,545 +199,618 @@ |
202 | return 0 |
203 | |
204 | def _fetch_mem(self): |
205 | - total_mem = subprocess.Popen("free -b | grep Mem | tr -s ' ' | cut -d\ -f 2", |
206 | - stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
207 | - free_mem = subprocess.Popen("free -b | grep Mem | tr -s ' ' | cut -d\ -f 4-", |
208 | - stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
209 | - free_mem = sum([int(i) for i in free_mem.split()]) |
210 | - return 100 - 100 * float(free_mem) / float(total_mem) |
211 | + """It gets the total memory info and return the used in percentaje.""" |
212 | + with open('/proc/meminfo') as meminfo: |
213 | + total = StatusFetcher.digit_regex.findall(meminfo.readline()).pop() |
214 | + free = StatusFetcher.digit_regex.findall(meminfo.readline()).pop() |
215 | + meminfo.readline() |
216 | + cached = StatusFetcher.digit_regex.findall(meminfo.readline()).pop() |
217 | + free = int(free) + int(cached) |
218 | + return 100 - 100 * free / float(total) |
219 | |
220 | def _fetch_bat(self, bat_id): |
221 | - current_bat = subprocess.Popen("grep 'remaining capacity:' /proc/acpi/battery/BAT%s/state |awk '{print $3}' |grep [0-9] || echo 10" % bat_id, |
222 | + current_bat = subprocess.Popen( |
223 | + "grep 'remaining capacity:' /proc/acpi/battery/BAT%s/state |awk '{print $3}' |grep [0-9] || echo 10" % bat_id, |
224 | stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
225 | #total_bat = subprocess.Popen("cat /proc/acpi/battery/BAT0/info |grep -i 'design capacity:' |awk '{print $3}'", |
226 | - total_bat = subprocess.Popen("grep 'last full capacity:' /proc/acpi/battery/BAT%s/info |awk '{print $4}' |grep [0-9] || echo 65130" % bat_id, |
227 | + total_bat = subprocess.Popen( |
228 | + "grep 'last full capacity:' /proc/acpi/battery/BAT%s/info | awk '{print $4}' |grep [0-9] || echo 65130" % bat_id, |
229 | stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
230 | return 100 * float(current_bat) / float(total_bat) |
231 | |
232 | def _fetch_net(self): |
233 | - total_net = subprocess.Popen("ifstat -a -n -q -S -T 0.5 1 | tail -1 | awk '{ printf(\"↓%dkB/s ↑%dkB/s\", $6, $7) }'", stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
234 | + """It use the command "ifstat" to know the total net usage""" |
235 | + total_net = subprocess.Popen( |
236 | + "ifstat -a -n -q -S -T 0.5 1 | tail -1 | awk '{ printf(\"↓%dkB/s ↑%dkB/s\", $6, $7) }'", |
237 | + stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
238 | return total_net |
239 | |
240 | - def _fetch_sensor(self, sensor_name): |
241 | - sensor_data = sensor_name.split('//') |
242 | - |
243 | - if (len(sensor_data) != 2): |
244 | - return 'N/A' |
245 | - |
246 | - sensor_item = sensor_data[1].replace('-', '.') |
247 | - postfix = '' |
248 | - if sensor_data[0] == 'hddtemp': |
249 | - sensor_cmd = 'netcat localhost 7634' |
250 | - netcat_value = subprocess.Popen(sensor_cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
251 | - sensor_value = '' |
252 | - |
253 | - for hd_data in self.parse_hddtemp_entries(netcat_value): |
254 | - if hd_data[0] == sensor_item: |
255 | - sensor_value = hd_data[2] + '°' + hd_data[3] |
256 | - else: |
257 | - sensor_cmd = 'sensors -A ' + sensor_data[0] + " | grep -i '" + sensor_item + "' | cut -f 2 -d ':' | awk '{print $1}'" |
258 | - if sensor_data[0] == 'nvidia': |
259 | - if sensor_item == 'gputemp': |
260 | - sensor_cmd = 'nvidia-settings -q [gpu:0]/GPUCoreTemp | grep "Attribute" | sed -e "s/.*: //g" -e "s/\.//g"' |
261 | - postfix = '°C' |
262 | - elif sensor_item == 'fanspeed': |
263 | - sensor_cmd = 'nvidia-settings -q [fan:0]/GPUCurrentFanSpeed | grep "Attribute" | sed -e "s/.*: //g" -e "s/\.//g"' |
264 | - postfix = ' RPM' |
265 | - else: |
266 | - sensor_cmd = None |
267 | - elif sensor_data[0] == 'ati': |
268 | - if sensor_item == 'gputemp': |
269 | - sensor_cmd = 'aticonfig --adapter=0 --od-gettemperature | grep "Sensor 0:" | awk ' + "'{ printf(\"%d\", $5) }'" |
270 | - postfix = '°C' |
271 | - |
272 | - sensor_value = subprocess.Popen(sensor_cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
273 | - |
274 | - if len(sensor_value): |
275 | - sensor_value = self._clean_value(sensor_value) # Cleaning the + prefix |
276 | - return sensor_value + postfix |
277 | - else: |
278 | - if sensor_cmd == None: |
279 | - logging.info('Sensor not supported: ' + sensor_name) |
280 | - else: |
281 | - logging.info('Sensor command failed:\n' + sensor_cmd) |
282 | - return 'N/A' |
283 | - |
284 | - def _clean_value(self, value): |
285 | - if value.find('+') == 0: |
286 | - return value[1:] |
287 | - return value |
288 | - |
289 | - def _get_sensors_to_fetch(self): |
290 | - fmt = string.Formatter() |
291 | - tokens = [] |
292 | - for token in fmt.parse(self.parent.custom_text): |
293 | - tokens.append(str(token[1])) |
294 | - |
295 | - return tokens |
296 | - |
297 | - def _fetch(self): |
298 | + def fetch(self): |
299 | + """Return a dict whose element are the sensors |
300 | + and their values""" |
301 | res = {} |
302 | - for sensor in self._get_sensors_to_fetch(): |
303 | + for sensor in Preferences.sensors_regex.findall(settings["custom_text"]): |
304 | + sensor = sensor[1:-1] |
305 | + if not Sensor.exists(sensor): |
306 | + continue |
307 | if sensor == 'cpu': |
308 | - res['cpu'] = '%02.0f%%' % self._fetch_cpu() |
309 | + res['cpu'] = "{:02.0f}%".format(self._fetch_cpu()) |
310 | elif sensor == 'mem': |
311 | - res['mem'] = '%02.0f%%' % self._fetch_mem() |
312 | + res['mem'] = '{:02.0f}%'.format(self._fetch_mem()) |
313 | + elif sensor == 'net': |
314 | + res['net'] = self._fetch_net() |
315 | + |
316 | elif sensor.startswith('bat'): |
317 | - bat_id = sensor[3:] |
318 | + bat_id = sensor[4:] |
319 | try: |
320 | bat_id = int(bat_id) |
321 | except: |
322 | bat_id = 0 |
323 | - res[sensor] = '%02.0f%%' % self._fetch_bat(bat_id) |
324 | - elif sensor == 'net': |
325 | - res['net'] = self._fetch_net() |
326 | - else: |
327 | - res[sensor] = '%s' % self._fetch_sensor(sensor) |
328 | + res[sensor] = '{:02.0f}%'.format(self._fetch_bat(bat_id)) |
329 | + |
330 | + elif sensor.startswith('fs//'): |
331 | + parts = sensor.split('//') |
332 | + res[sensor] = self._fetch_fs(parts[1]) |
333 | + |
334 | + else: # custom sensor |
335 | + res[sensor] = self._exec(settings["sensors"][sensor][1]) |
336 | |
337 | return res |
338 | |
339 | - def _fetch_user(self, command): |
340 | + def _exec(self, command): |
341 | + """Execute a custom command.""" |
342 | try: |
343 | - output = subprocess.Popen(command, stdout=subprocess.PIPE, |
344 | - shell=True).communicate()[0].strip() |
345 | - if output == '': |
346 | - output = '(no output)' |
347 | + output = subprocess.Popen(command, stdout=subprocess.PIPE, |
348 | + shell=True).communicate()[0].strip() |
349 | except: |
350 | - output = 'error' |
351 | - logging.error('Error running: '+command) |
352 | - return output |
353 | - |
354 | - def parse_hddtemp_entries(self, netcat_value): |
355 | - hddtemp_entries = [] |
356 | - |
357 | - if len(netcat_value): |
358 | - for hd_row in netcat_value.strip('|').split('||'): |
359 | - hddtemp_entries.append(hd_row.split('|')) |
360 | - |
361 | - return hddtemp_entries |
362 | + output = _("Error") |
363 | + logging.error(_("Error running: {}").format(command)) |
364 | + |
365 | + return output if output else _("(no output)") |
366 | + |
367 | + def _fetch_fs(self, mount_point): |
368 | + """It returns the amount of bytes available in the fs in |
369 | + a human-readble format.""" |
370 | + if not os.access(mount_point, os.F_OK): |
371 | + return None |
372 | + |
373 | + stat = os.statvfs(mount_point) |
374 | + bytes_ = stat.f_bavail * stat.f_frsize |
375 | + |
376 | + for unit in B_UNITS: |
377 | + if bytes_ < 1024: |
378 | + return "{} {}".format(bytes_, unit) |
379 | + bytes_ /= 1024 |
380 | |
381 | def run(self): |
382 | - while(self.parent.alive.isSet()): |
383 | - if self.parent.mode_user: |
384 | - output = self._fetch_user(self.parent.user_command) |
385 | - self.parent.update_text(output, output) |
386 | - else: |
387 | - data = self._fetch() |
388 | - self.parent.update(data) |
389 | - time.sleep(self.parent.interval) |
390 | - |
391 | -class SensorsListModel: |
392 | + """It is the main loop.""" |
393 | + while self._parent.alive.isSet(): |
394 | + data = self.fetch() |
395 | + self._parent.update(data) |
396 | + time.sleep(settings["interval"]) |
397 | + |
398 | + |
399 | +class SensorsListModel(object): |
400 | + """A TreeView showing the available sensors. It allows to |
401 | + add/edit/delete custom sensors.""" |
402 | + |
403 | def __init__(self, parent): |
404 | self.ind_parent = parent |
405 | - self.tree_store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) |
406 | - |
407 | - # lib sensors |
408 | - for sensor_data in subprocess.Popen('sensors', stdout=subprocess.PIPE, shell=True).communicate()[0].split('\n\n'): |
409 | - sensor_name = None |
410 | - skip_line = False |
411 | - for line in sensor_data.split('\n'): |
412 | - if len(line): |
413 | - if skip_line: |
414 | - skip_line = False |
415 | - else: |
416 | - if sensor_name == None: |
417 | - logging.info("New sensor found: " + line) |
418 | - sensor_name = line |
419 | - parent = self.tree_store.append(None, (sensor_name, None, False)) |
420 | - skip_line = True |
421 | - else: |
422 | - logging.info("Sensor entry: " + line) |
423 | - self.tree_store.append(parent, (line, self.generate_sensor_item_name(sensor_name, line), False)) |
424 | - |
425 | - # hddtemp |
426 | - hddtemp = subprocess.Popen('netcat localhost 7634', stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
427 | - if len(hddtemp): |
428 | - sensor_name = 'hddtemp' |
429 | - logging.info("New sensor found: " + sensor_name) |
430 | - parent = self.tree_store.append(None, (sensor_name, None, False)) |
431 | - for hd_data in self.ind_parent.ind_parent.fetch.parse_hddtemp_entries(hddtemp): |
432 | - logging.info("Sensor entry: " + hd_data[0]) |
433 | - self.tree_store.append(parent, (hd_data[0] + ' - ' + hd_data[1] + ' - ' + hd_data[2] + '°' + hd_data[3], self.generate_sensor_item_name(sensor_name, hd_data[0]), False)) |
434 | - |
435 | - # nvidia GPU |
436 | - nvidia_model = subprocess.Popen("lspci | grep nVidia | sed -e 's/.*\[//g' -e 's/\].*//g'", stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
437 | - if len(nvidia_model): |
438 | - sensor_name = 'nvidia' |
439 | - logging.info("New sensor found: " + sensor_name) |
440 | - parent = self.tree_store.append(None, (sensor_name, None, False)) |
441 | - self.tree_store.append(parent, (nvidia_model + ' - Temperature', self.generate_sensor_item_name(sensor_name, 'gputemp'), False)) |
442 | - self.tree_store.append(parent, (nvidia_model + ' - Fan speed', self.generate_sensor_item_name(sensor_name, 'fanspeed'), False)) |
443 | - |
444 | - # ati GPU |
445 | - ati_model = subprocess.Popen('lspci | grep \"ATI Radeon HD"' + " | sed -e 's/.*\[//g' -e 's/\].*//g'", stdout=subprocess.PIPE, shell=True).communicate()[0].strip() |
446 | - if len(ati_model): |
447 | - sensor_name = 'ati' |
448 | - logging.info("New sensor found: " + sensor_name) |
449 | - parent = self.tree_store.append(None, (sensor_name, None, False)) |
450 | - self.tree_store.append(parent, (ati_model + ' - Temperature', self.generate_sensor_item_name(sensor_name, 'gputemp'), False)) |
451 | + self._list_store = gtk.ListStore(str, str) |
452 | + self._tree_view = gtk.TreeView(self._list_store) |
453 | + |
454 | + sensors = settings['sensors'] |
455 | + for name in sensors.keys(): |
456 | + self._list_store.append([name, sensors[name][0]]) |
457 | |
458 | def get_view(self): |
459 | - self.view = gtk.HBox() |
460 | - |
461 | - self.vb = gtk.VBox(False, 3) |
462 | - l = gtk.Label('Sensors:') |
463 | - l.set_alignment(0, 0.5) |
464 | - self.vb.pack_start(l, expand=False, fill=False) |
465 | - |
466 | - self.tree_view = gtk.TreeView(self.tree_store) |
467 | - |
468 | - # setup the text cell renderer |
469 | - self.renderer = gtk.CellRendererText() |
470 | - self.renderer.set_property('editable', False) |
471 | - |
472 | - # sensor name render |
473 | - self.renderer1 = gtk.CellRendererText() |
474 | - self.renderer1.set_property('editable', False) |
475 | - |
476 | - # quick add checkbox render |
477 | - self.renderer2 = gtk.CellRendererToggle() |
478 | - self.renderer2.set_property('activatable', True) |
479 | - self.renderer2.connect('toggled', self.quick_add_cb_toggled, self.tree_store) |
480 | - |
481 | + """It's call from Preference. It create the view and return it""" |
482 | + vbox = gtk.VBox(False, 3) |
483 | # create columns |
484 | - self.column0 = gtk.TreeViewColumn('Sensor', self.renderer, text=0) |
485 | - self.column1 = gtk.TreeViewColumn('Identifier', self.renderer1, text=1) |
486 | - self.column2 = gtk.TreeViewColumn('', self.renderer2, active=2) |
487 | - self.tree_view.append_column(self.column0) |
488 | - self.tree_view.append_column(self.column1) |
489 | - self.tree_view.append_column(self.column2) |
490 | - |
491 | - self.tree_view.expand_all() |
492 | + renderer = gtk.CellRendererText() |
493 | + renderer.set_property('editable', False) |
494 | + column = gtk.TreeViewColumn(_('Sensor'), renderer, text=0) |
495 | + self._tree_view.append_column(column) |
496 | + |
497 | + renderer = gtk.CellRendererText() |
498 | + renderer.set_property('editable', False) |
499 | + column = gtk.TreeViewColumn(_('Description'), renderer, text=1) |
500 | + self._tree_view.append_column(column) |
501 | + |
502 | + self._tree_view.expand_all() |
503 | sw = gtk.ScrolledWindow() |
504 | - sw.add_with_viewport(self.tree_view) |
505 | - self.vb.pack_start(sw, fill=True, expand=True) |
506 | - |
507 | - self.add_bt = gtk.Button('Add selected sensors') |
508 | - self.add_bt.connect('clicked', self.update_custom_text) |
509 | - self.vb.pack_end(self.add_bt, fill=False, expand=False) |
510 | - |
511 | - self.view.add(self.vb) |
512 | - self.view.show() |
513 | - |
514 | - return self.view |
515 | - |
516 | - def quick_add_cb_toggled(self, cell, path, tree_store): |
517 | - tree_store[path][2] = not tree_store[path][2] |
518 | - iter = tree_store.iter_children(tree_store.get_iter(path)) |
519 | - while iter: |
520 | - tree_store.set_value(iter, 2, tree_store[path][2]) |
521 | - iter = tree_store.iter_next(iter) |
522 | - |
523 | - def generate_sensor_item_name(self, sensor_name, sensor_item_label): |
524 | - return sensor_name + '//' + sensor_item_label.split(':')[0].lower().replace('.', '-') |
525 | - |
526 | - def update_custom_text(self, event=None): |
527 | - iter = self.tree_store.get_iter_root() |
528 | - |
529 | - while iter: |
530 | - iter_children = self.tree_store.iter_children(iter) |
531 | - while iter_children: |
532 | - if self.tree_store.get_value(iter_children, 2): |
533 | - current_text = self.ind_parent.custom_entry.get_text() |
534 | - to_add_value = '{' + self.tree_store.get_value(iter_children, 1) + '}' |
535 | - if string.find(current_text, to_add_value) == -1: |
536 | - self.ind_parent.custom_entry.set_text(current_text + ' ' + to_add_value) |
537 | - iter_children = self.tree_store.iter_next(iter_children) |
538 | - |
539 | - iter = self.tree_store.iter_next(iter) |
540 | - |
541 | -class Preferences(gtk.Window): |
542 | - AUTOSTART_PATH = os.getenv("HOME") + '/.config/autostart/indicator-sysmonitor.desktop' |
543 | + sw.add_with_viewport(self._tree_view) |
544 | + vbox.pack_start(sw, fill=True, expand=True) |
545 | + |
546 | + # add buttons |
547 | + hbox = gtk.HBox() |
548 | + new_button = gtk.Button(stock=gtk.STOCK_NEW) |
549 | + new_button.connect('clicked', self._on_edit_sensor) |
550 | + # hbox.pack_start(new_button, fill=False, expand=False) |
551 | + hbox.pack_start(new_button, fill=False, expand=False) |
552 | + |
553 | + edit_button = gtk.Button(stock=gtk.STOCK_EDIT) |
554 | + edit_button.connect('clicked', self._on_edit_sensor, False) |
555 | + hbox.pack_start(edit_button, fill=False, expand=False) |
556 | + |
557 | + del_button = gtk.Button(stock=gtk.STOCK_DELETE) |
558 | + del_button.connect('clicked', self._on_del_sensor) |
559 | + hbox.pack_start(del_button, fill=False, expand=False) |
560 | + |
561 | + add_button = gtk.Button(stock=gtk.STOCK_ADD) |
562 | + add_button.connect('clicked', self._on_add_sensor) |
563 | + hbox.pack_end(add_button, fill=False, expand=False) |
564 | + vbox.pack_end(hbox, fill=False, expand=False) |
565 | + |
566 | + frame = gtk.Frame(_('Sensors')) |
567 | + frame.add(vbox) |
568 | + return frame |
569 | + |
570 | + def _get_selected_row(self): |
571 | + """Returns an iter for the selected rows in the view or None.""" |
572 | + model, pathlist = self._tree_view.get_selection().get_selected_rows() |
573 | + if len(pathlist): |
574 | + path = pathlist.pop() |
575 | + return model.get_iter(path) |
576 | + return None |
577 | + |
578 | + def _on_add_sensor(self, evnt=None, data=None): |
579 | + tree_iter = self._get_selected_row() |
580 | + if tree_iter is None: |
581 | + return |
582 | + |
583 | + sensor = self._list_store.get_value(tree_iter, 0) |
584 | + self.ind_parent.custom_entry.insert_text( |
585 | + "{{{}}}".format(sensor), -1) |
586 | + |
587 | + def _on_edit_sensor(self, evnt=None, blank=True): |
588 | + """Raises a dialog with a form to add/edit a sensor""" |
589 | + name = desc = cmd = "" |
590 | + tree_iter = None |
591 | + if not blank: |
592 | + # edit, so get the info from the selected row |
593 | + tree_iter = self._get_selected_row() |
594 | + if tree_iter is None: |
595 | + return |
596 | + |
597 | + name = self._list_store.get_value(tree_iter, 0) |
598 | + desc = self._list_store.get_value(tree_iter, 1) |
599 | + cmd = settings["sensors"][name][1] |
600 | + |
601 | + if cmd is True: # default sensor |
602 | + raise_dialog( |
603 | + self.ind_parent, |
604 | + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, |
605 | + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, |
606 | + _("Can not edit defualt sensors."), _("Error")) |
607 | + return |
608 | + |
609 | + dialog = gtk.Dialog(_("Edit Sensor"), self.ind_parent, |
610 | + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, |
611 | + (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, |
612 | + gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) |
613 | + vbox = dialog.get_content_area() |
614 | + |
615 | + hbox = gtk.HBox() |
616 | + label = gtk.Label(_("Sensor")) |
617 | + sensor_entry = gtk.Entry() |
618 | + sensor_entry.set_text(name) |
619 | + hbox.pack_start(label) |
620 | + hbox.pack_end(sensor_entry) |
621 | + vbox.pack_start(hbox) |
622 | + |
623 | + hbox = gtk.HBox() |
624 | + label = gtk.Label(_("Description")) |
625 | + desc_entry = gtk.Entry() |
626 | + desc_entry.set_text(desc) |
627 | + hbox.pack_start(label) |
628 | + hbox.pack_end(desc_entry) |
629 | + vbox.pack_start(hbox) |
630 | + |
631 | + hbox = gtk.HBox() |
632 | + label = gtk.Label(_("Command")) |
633 | + cmd_entry = gtk.Entry() |
634 | + |
635 | + cmd_entry.set_text(cmd) |
636 | + hbox.pack_start(label) |
637 | + hbox.pack_end(cmd_entry) |
638 | + vbox.pack_end(hbox) |
639 | + |
640 | + dialog.show_all() |
641 | + response = dialog.run() |
642 | + |
643 | + if response == gtk.RESPONSE_ACCEPT: |
644 | + try: |
645 | + newname, desc, cmd = str(sensor_entry.get_text()), \ |
646 | + str(desc_entry.get_text()), str(cmd_entry.get_text()) |
647 | + |
648 | + if blank: |
649 | + Sensor.get_instance().add(newname, desc, cmd) |
650 | + else: |
651 | + Sensor.get_instance().edit(name, newname, desc, cmd) |
652 | + self._list_store.remove(tree_iter) |
653 | + |
654 | + self._list_store.append([newname, desc]) |
655 | + ctext = self.ind_parent.custom_entry.get_text() |
656 | + self.ind_parent.custom_entry.set_text( |
657 | + ctext.replace(name, newname)) |
658 | + |
659 | + except ISMError as ex: |
660 | + raise_dialog( |
661 | + self.ind_parent, |
662 | + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, |
663 | + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, |
664 | + ex.message, _("Error")) |
665 | + |
666 | + dialog.destroy() |
667 | + |
668 | + def _on_del_sensor(self, evnt=None, data=None): |
669 | + """Remove a custom sensor.""" |
670 | + tree_iter = self._get_selected_row() |
671 | + if tree_iter is None: |
672 | + return |
673 | + |
674 | + name = self._list_store.get_value(tree_iter, 0) |
675 | + try: |
676 | + Sensor.get_instance().delete(name) |
677 | + self._list_store.remove(tree_iter) |
678 | + ctext = self.ind_parent.custom_entry.get_text() |
679 | + self.ind_parent.custom_entry.set_text( |
680 | + ctext.replace("{{{}}}".format(name), "")) |
681 | + |
682 | + except ISMError as ex: |
683 | + raise_dialog( |
684 | + self.ind_parent, |
685 | + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, |
686 | + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, |
687 | + ex.message, _("Error")) |
688 | + |
689 | + |
690 | +class Preferences(gtk.Dialog): |
691 | + """It define the the Preferences Dialog and its operations.""" |
692 | + AUTOSTART_PATH = '{}/.config/autostart/indicator-sysmonitor.desktop'.format( |
693 | + os.getenv("HOME")) |
694 | DESKTOP_PATH = '/usr/share/applications/indicator-sysmonitor.desktop' |
695 | + sensors_regex = re.compile("{.+?}") |
696 | |
697 | def __init__(self, parent): |
698 | - gtk.Window.__init__(self) |
699 | + """It creates the widget of the dialogs""" |
700 | + gtk.Dialog.__init__(self) |
701 | self.ind_parent = parent |
702 | - self.connect('delete-event', self.on_destroy) |
703 | + self.connect('delete-event', self.on_cancel) |
704 | + self.set_title(_('Preferences')) |
705 | + self.resize(400, 350) |
706 | + self.set_position(gtk.WIN_POS_CENTER_ALWAYS) |
707 | + self._create_content() |
708 | + self.set_data() |
709 | + self.show_all() |
710 | |
711 | + def _create_content(self): |
712 | + """It creates the content for this dialog.""" |
713 | notebook = gtk.Notebook() |
714 | notebook.set_border_width(4) |
715 | |
716 | - vb = gtk.VBox(spacing=3) |
717 | - hb = gtk.HBox() |
718 | - hb.set_border_width(4) |
719 | - l = gtk.Label('Run on startup:') |
720 | - l.set_alignment(0, 0.5) |
721 | - hb.pack_start(l) |
722 | + # General page of the notebook {{{ |
723 | + vbox = gtk.VBox(spacing=3) |
724 | + hbox = gtk.HBox() |
725 | + |
726 | + hbox.set_border_width(4) |
727 | + label = gtk.Label(_('Run on startup:')) |
728 | + label.set_alignment(0, 0.5) |
729 | + hbox.pack_start(label) |
730 | self.autostart_check = gtk.CheckButton() |
731 | self.autostart_check.set_active(self.get_autostart()) |
732 | - hb.pack_end(self.autostart_check, expand=False, fill=False) |
733 | - vb.pack_start(hb, expand=False, fill=False) |
734 | + hbox.pack_end(self.autostart_check, expand=False, fill=False) |
735 | + vbox.pack_start(hbox, expand=False, fill=False) |
736 | |
737 | - hb = gtk.HBox() |
738 | - l = gtk.Label('This is indicator-sysmonitor version: %s' % VERSION) |
739 | + hbox = gtk.HBox() |
740 | + l = gtk.Label( |
741 | + _('This is indicator-sysmonitor version: {}').format(VERSION)) |
742 | l.set_alignment(0.5, 0.5) |
743 | - hb.pack_start(l) |
744 | - vb.pack_end(hb) |
745 | - |
746 | - notebook.append_page(vb, gtk.Label('General')) |
747 | + hbox.pack_start(l) |
748 | + vbox.pack_end(hbox) |
749 | + notebook.append_page(vbox, gtk.Label(_('General'))) |
750 | + # }}} |
751 | |
752 | - vb = gtk.VBox(spacing=3) |
753 | - hb = gtk.VBox() |
754 | - self.custom_radio = gtk.RadioButton(label='Customize output:') |
755 | - hb.pack_start(self.custom_radio) |
756 | + # Advanced page in notebook {{{ |
757 | + vbox = gtk.VBox() # main box |
758 | + label = gtk.Label(_('Customize output:')) |
759 | + label.set_alignment(0, 0) |
760 | + vbox.pack_start(label, expand=False, fill=False) |
761 | self.custom_entry = gtk.Entry() |
762 | - hb.pack_end(self.custom_entry) |
763 | - vb.pack_start(hb, expand=False, fill=False) |
764 | - |
765 | - hb = gtk.VBox() |
766 | - self.user_radio = gtk.RadioButton(group=self.custom_radio, label='Use this command:') |
767 | - hb.pack_start(self.user_radio) |
768 | - self.user_entry = gtk.Entry(max=100) |
769 | - #info = gtk.Label('Use this option to specify a program to be run every time the indicator refreshes') |
770 | - #info.set_line_wrap(True) |
771 | - #hb.pack_end(info) |
772 | - hb.pack_end(self.user_entry) |
773 | - vb.pack_start(hb, expand=False, fill=False) |
774 | - |
775 | - hb = gtk.HBox() |
776 | - l = gtk.Label('Update interval:') |
777 | - l.set_alignment(0, 0.5) |
778 | - hb.pack_start(l) |
779 | + vbox.pack_start(self.custom_entry, expand=False, fill=False) |
780 | + |
781 | + hbox = gtk.HBox() |
782 | + label = gtk.Label(_('Update interval:')) |
783 | + label.set_alignment(0, 0) |
784 | + hbox.pack_start(label) |
785 | self.interval_entry = gtk.Entry(max=4) |
786 | - self.interval_entry.set_width_chars(3) |
787 | - hb.pack_end(self.interval_entry, expand=False, fill=False) |
788 | - vb.pack_start(hb, expand=False, fill=False) |
789 | - |
790 | - notebook.append_page(vb, gtk.Label('Advanced')) |
791 | - |
792 | - if not parent.sensors_disabled: |
793 | - sensors_list = SensorsListModel(self) |
794 | - if sensors_list.tree_store.get_iter_root() != None: |
795 | - vb.pack_start(sensors_list.get_view()) |
796 | - |
797 | - # footer |
798 | - vb = gtk.VBox() |
799 | - vb.pack_start(notebook) |
800 | + self.interval_entry.set_width_chars(5) |
801 | + |
802 | + hbox.pack_end(self.interval_entry, expand=False, fill=False) |
803 | + vbox.pack_start(hbox, expand=False, fill=False) |
804 | + |
805 | + sensors_list = SensorsListModel(self) |
806 | + vbox.pack_start(sensors_list.get_view()) |
807 | + notebook.append_page(vbox, gtk.Label(_('Advanced'))) |
808 | + # }}} |
809 | + |
810 | + # footer {{{ |
811 | + vbox = self.get_content_area() |
812 | + vbox.pack_start(notebook) |
813 | buttons = gtk.HButtonBox() |
814 | buttons.set_layout(gtk.BUTTONBOX_EDGE) |
815 | - test = gtk.Button('Test') |
816 | + test = gtk.Button(_('Test')) |
817 | test.connect('clicked', self.update_parent) |
818 | buttons.pack_start(test) |
819 | + # TODO: add an info message on hover |
820 | + |
821 | cancel = gtk.Button(stock=gtk.STOCK_CANCEL) |
822 | - cancel.connect('clicked', self.on_destroy) |
823 | + cancel.connect('clicked', self.on_cancel) |
824 | buttons.pack_end(cancel) |
825 | + |
826 | close = gtk.Button(stock=gtk.STOCK_SAVE) |
827 | close.connect('clicked', self.on_save) |
828 | buttons.pack_end(close) |
829 | - vb.pack_end(buttons, expand=False) |
830 | - |
831 | - self.add(vb) |
832 | - self.set_title('Preferences') |
833 | - self.resize(400, 350) |
834 | - |
835 | - def run(self): |
836 | - self.set_position(gtk.WIN_POS_CENTER_ALWAYS) |
837 | - self.show_all() |
838 | - self.set_data() |
839 | - gtk.main() |
840 | - |
841 | - def on_destroy(self, event=None, data=None): |
842 | - self.hide() |
843 | - gtk.main_quit() |
844 | - return False |
845 | - |
846 | - def on_save(self, event=None, data=None): |
847 | - self.update_parent() |
848 | + vbox.pack_end(buttons, expand=False) |
849 | + # }}} |
850 | + |
851 | + def on_save(self, evnt=None, data=None): |
852 | + """The action of the save button.""" |
853 | + try: |
854 | + self.update_parent() |
855 | + except Exception as ex: |
856 | + error_dialog = gtk.MessageDialog( |
857 | + None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, |
858 | + gtk.BUTTONS_CLOSE, ex.message) |
859 | + error_dialog.set_title("Error") |
860 | + error_dialog.run() |
861 | + error_dialog.destroy() |
862 | + return False |
863 | + |
864 | self.ind_parent.save_settings() |
865 | self.update_autostart() |
866 | - self.on_destroy() |
867 | + self.destroy() |
868 | |
869 | - def on_cancel(self, event=None, data=None): |
870 | + def on_cancel(self, evnt=None, data=None): |
871 | + """The action of the cancel button.""" |
872 | self.ind_parent.load_settings() |
873 | - self.on_destroy() |
874 | + self.destroy() |
875 | + |
876 | + def update_parent(self, evnt=None, data=None): |
877 | + """It gets the config info from the widgets and sets them to the vars. |
878 | + It does NOT update the config file.""" |
879 | + custom_text = self.custom_entry.get_text() |
880 | + |
881 | + # check if the sensers are supported |
882 | + sensors = Preferences.sensors_regex.findall(custom_text) |
883 | + for sensor in sensors: |
884 | + sensor = sensor[1:-1] |
885 | + if not Sensor.exists(sensor): |
886 | + raise ISMError(_("{} sensor not supported.").format(sensor)) |
887 | + |
888 | + try: |
889 | + interval = float(self.interval_entry.get_text()) |
890 | + if interval <= 0: |
891 | + raise ISMError(_("Interval value is not valid.")) |
892 | + |
893 | + except ValueError, ex: |
894 | + raise ISMError(_("Interval value is not valid.")) |
895 | + |
896 | + settings["custom_text"] = custom_text |
897 | + settings["interval"] = interval |
898 | + # TODO: on_startup |
899 | + self.ind_parent.update_indicator_guide() |
900 | |
901 | def set_data(self): |
902 | - self.custom_entry.set_text(self.ind_parent.custom_text) |
903 | - self.user_entry.set_text(self.ind_parent.user_command) |
904 | - self.interval_entry.set_text(str(self.ind_parent.interval)) |
905 | - if self.ind_parent.mode_user: |
906 | - self.user_radio.set_active(True) |
907 | - else: |
908 | - self.custom_radio.set_active(True) |
909 | - |
910 | - def update_parent(self, event=None): |
911 | - custom_text = self.custom_entry.get_text() |
912 | - user_text = self.user_entry.get_text() |
913 | - try: interval = int(self.interval_entry.get_text()); assert interval > 0 |
914 | - except: interval = self.ind_parent.interval |
915 | - mode_user = [r for r in self.custom_radio.get_group() if r.get_active()][0] |
916 | - self.ind_parent.custom_text = custom_text |
917 | - self.ind_parent.user_command = user_text |
918 | - self.ind_parent.mode_user = (mode_user == self.user_radio) |
919 | - self.ind_parent.interval = interval |
920 | - self.ind_parent.force_update() |
921 | + """It sets the widgets with the config data.""" |
922 | + self.custom_entry.set_text(settings["custom_text"]) |
923 | + self.interval_entry.set_text(str(settings["interval"])) |
924 | |
925 | def update_autostart(self): |
926 | autostart = self.autostart_check.get_active() |
927 | if not autostart: |
928 | try: |
929 | os.remove(Preferences.AUTOSTART_PATH) |
930 | - except: pass |
931 | + except: |
932 | + pass |
933 | else: |
934 | try: |
935 | - shutil.copy(Preferences.DESKTOP_PATH, Preferences.AUTOSTART_PATH) |
936 | + shutil.copy(Preferences.DESKTOP_PATH, |
937 | + Preferences.AUTOSTART_PATH) |
938 | except Exception as e: |
939 | logging.exception(e) |
940 | |
941 | def get_autostart(self): |
942 | return os.path.exists(Preferences.AUTOSTART_PATH) |
943 | |
944 | -class IndicatorSysmonitor: |
945 | + |
946 | +class IndicatorSysmonitor(object): |
947 | SETTINGS_FILE = os.getenv("HOME") + '/.indicator-sysmonitor.json' |
948 | SENSORS_DISABLED = False |
949 | |
950 | def __init__(self): |
951 | - self.preferences_dialog = None |
952 | - self.custom_text = 'cpu: {cpu} mem: {mem}' |
953 | - self.user_command = '' |
954 | - self.last_data, self.last_text, self.last_guide = {}, '', '' |
955 | - self.mode_user = False |
956 | - self.sensors_disabled = IndicatorSysmonitor.SENSORS_DISABLED |
957 | - self.interval = 2 |
958 | + self._preferences_dialog = None |
959 | + self._help_dialog = None |
960 | + self._fetcher = StatusFetcher(self) |
961 | + self.alive = Event() |
962 | |
963 | - self.ind = appindicator.Indicator ("indicator-sysmonitor", |
964 | + self.ind = appindicator.Indicator("indicator-sysmonitor", |
965 | "sysmonitor", |
966 | appindicator.CATEGORY_SYSTEM_SERVICES) |
967 | - self.ind.set_status (appindicator.STATUS_ACTIVE) |
968 | + self.ind.set_status(appindicator.STATUS_ACTIVE) |
969 | self.ind.set_label("Init...") |
970 | - self.menu = gtk.Menu() |
971 | - |
972 | - full_sysmon = gtk.MenuItem('System Monitor') |
973 | + |
974 | + self._create_menu() |
975 | + self.load_settings() |
976 | + self.alive.set() |
977 | + self._fetcher.start() |
978 | + logging.info("Fetcher started") |
979 | + |
980 | + def _create_menu(self): |
981 | + """Creates the main menu and shows it.""" |
982 | + # create menu {{{ |
983 | + menu = gtk.Menu() |
984 | + # add System Monitor menu item |
985 | + full_sysmon = gtk.MenuItem(_('System Monitor')) |
986 | full_sysmon.connect('activate', self.on_full_sysmon_activated) |
987 | - self.menu.add(full_sysmon) |
988 | - |
989 | - self.menu.add(gtk.SeparatorMenuItem()) |
990 | - |
991 | - pref_menu = gtk.MenuItem('Preferences') |
992 | + menu.add(full_sysmon) |
993 | + menu.add(gtk.SeparatorMenuItem()) |
994 | + |
995 | + # add preferences menu item |
996 | + pref_menu = gtk.ImageMenuItem(stock_id=gtk.STOCK_PREFERENCES) |
997 | pref_menu.connect('activate', self.on_preferences_activated) |
998 | - self.menu.add(pref_menu) |
999 | - |
1000 | + menu.add(pref_menu) |
1001 | + |
1002 | + # add help menu item |
1003 | + help_menu = gtk.ImageMenuItem(stock_id=gtk.STOCK_HELP) |
1004 | + help_menu.connect('activate', self._on_help) |
1005 | + menu.add(help_menu) |
1006 | + |
1007 | + #add preference menu item |
1008 | exit_menu = gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT) |
1009 | exit_menu.connect('activate', self.on_exit) |
1010 | - self.menu.add(exit_menu) |
1011 | - |
1012 | - self.menu.show_all() |
1013 | - |
1014 | - self.ind.set_menu(self.menu) |
1015 | - |
1016 | + menu.add(exit_menu) |
1017 | + |
1018 | + menu.show_all() |
1019 | + self.ind.set_menu(menu) |
1020 | logging.info("Menu shown") |
1021 | - |
1022 | - self.load_settings() |
1023 | - |
1024 | - self.alive = Event() |
1025 | - self.alive.set() |
1026 | - self.fetch = StatusFetcher(self) |
1027 | - self.fetch.start() |
1028 | - logging.info("Fetcher started") |
1029 | + # }}} menu done! |
1030 | + |
1031 | + def update_indicator_guide(self): |
1032 | + """Updates the label guide from appindicator.""" |
1033 | + data = self._fetcher.fetch() |
1034 | + for key in data: |
1035 | + if key.startswith('fs'): |
1036 | + data[key] = '000gB' |
1037 | + break |
1038 | + |
1039 | + data['mem'] = data['cpu'] = data['bat'] = '000%' |
1040 | + data['net'] = '↓666kB/s ↑666kB/s' |
1041 | + |
1042 | + guide = settings['custom_text'].format(**data) |
1043 | + self.ind.set_property("label-guide", guide) |
1044 | |
1045 | def update(self, data): |
1046 | + """It updates the appindicator text with the the values |
1047 | + from data""" |
1048 | try: |
1049 | - label = self.custom_text.format(**data) |
1050 | - cdata = deepcopy(data) |
1051 | - cdata['mem'] = cdata['cpu'] = cdata['bat'] = '000%' |
1052 | - cdata['net'] = '' |
1053 | - guide = self.custom_text.format(**cdata) |
1054 | - except KeyError as e: |
1055 | - logging.exception(e) |
1056 | - logging.info('not found in dataset') |
1057 | - return |
1058 | - except: |
1059 | - label = 'Unknown error' |
1060 | - if not label: |
1061 | - label = '(no output)' |
1062 | - self.last_data = data |
1063 | - self.last_guide = guide |
1064 | - self.update_text(label, guide) |
1065 | - |
1066 | - def update_text(self, text, guide): |
1067 | - self.last_text = text |
1068 | - self.last_guide = guide |
1069 | - self.ind.set_label(text, guide) |
1070 | - |
1071 | - def force_update(self): |
1072 | - if self.mode_user: |
1073 | - self.update_text(self.last_text, self.last_guide) |
1074 | - else: |
1075 | - self.update(self.last_data) |
1076 | - |
1077 | - def on_exit(self, event=None): |
1078 | - logging.info("Terminated") |
1079 | - self.alive.clear() |
1080 | - try: gtk.main_quit() |
1081 | - except RuntimeError: pass |
1082 | + label = settings["custom_text"].format(**data) if len(data)\ |
1083 | + else _("(no output)") |
1084 | + |
1085 | + except KeyError as ex: |
1086 | + label = _("Invalid Sensor: {}").format(ex.message) |
1087 | + except Exception as ex: |
1088 | + logging.exception(ex) |
1089 | + label = _("Unknown error: ").format(ex.message) |
1090 | + |
1091 | + self.ind.set_label(label) |
1092 | |
1093 | def load_settings(self): |
1094 | + """It gets the settings from the config file and |
1095 | + sets them to the correct vars""" |
1096 | try: |
1097 | with open(IndicatorSysmonitor.SETTINGS_FILE, 'r') as f: |
1098 | - settings = json.load(f) |
1099 | - self.mode_user = settings['mode_user'] |
1100 | - self.custom_text = settings['custom_text'] |
1101 | - self.user_command = settings['user_command'] |
1102 | - self.interval = settings['interval'] |
1103 | - self.sensors_disabled = settings.get('sensors_disabled', IndicatorSysmonitor.SENSORS_DISABLED) |
1104 | + cfg = json.load(f) |
1105 | + |
1106 | + if cfg['custom_text'] is not None: |
1107 | + settings['custom_text'] = cfg['custom_text'] |
1108 | + if cfg['interval'] is not None: |
1109 | + settings['interval'] = cfg['interval'] |
1110 | + if cfg['on_startup'] is not None: |
1111 | + settings['on_startup'] = cfg['on_startup'] |
1112 | + if cfg['sensors'] is not None: |
1113 | + settings['sensors'] = cfg['sensors'] |
1114 | + |
1115 | + Sensor.update_regex() |
1116 | + self.update_indicator_guide() |
1117 | + |
1118 | except Exception as e: |
1119 | logging.exception(e) |
1120 | logging.error('Reading settings failed') |
1121 | |
1122 | - def save_settings(self): |
1123 | + @staticmethod |
1124 | + def save_settings(): |
1125 | + """It stores the current settings to the config file.""" |
1126 | # TODO: use gsettings |
1127 | - settings = {'mode_user': self.mode_user, |
1128 | - 'custom_text': self.custom_text, |
1129 | - 'user_command': self.user_command, |
1130 | - 'interval': self.interval, |
1131 | - 'sensors_disabled': self.sensors_disabled} |
1132 | try: |
1133 | - with open(IndicatorSysmonitor.SETTINGS_FILE, 'w') as f: |
1134 | - f.write(json.dumps(settings)) |
1135 | + with open(IndicatorSysmonitor.SETTINGS_FILE, 'w') as f: |
1136 | + f.write(json.dumps(settings)) |
1137 | + |
1138 | except Exception as e: |
1139 | logging.exception(e) |
1140 | logging.error('Writting settings failed') |
1141 | |
1142 | + # actions raised from menu |
1143 | def on_preferences_activated(self, event=None): |
1144 | - self.preferences_dialog = Preferences(self) |
1145 | - self.preferences_dialog.run() |
1146 | + """Raises the preferences dialog. If it's already open, it's |
1147 | + focused""" |
1148 | + if self._preferences_dialog is not None: |
1149 | + self._preferences_dialog.present() |
1150 | + return |
1151 | + |
1152 | + self._preferences_dialog = Preferences(self) |
1153 | + self._preferences_dialog.run() |
1154 | + self._preferences_dialog = None |
1155 | |
1156 | def on_full_sysmon_activated(self, event=None): |
1157 | os.system('gnome-system-monitor &') |
1158 | |
1159 | + def on_exit(self, event=None, data=None): |
1160 | + """Action call when the main programs is closed.""" |
1161 | + # close the open dialogs |
1162 | + if self._help_dialog is not None: |
1163 | + self._help_dialog.destroy() |
1164 | + |
1165 | + if self._preferences_dialog is not None: |
1166 | + self._preferences_dialog.destroy() |
1167 | + |
1168 | + logging.info("Terminated") |
1169 | + self.alive.clear() |
1170 | + try: |
1171 | + gtk.main_quit() |
1172 | + except RuntimeError: |
1173 | + pass |
1174 | + |
1175 | + def _on_help(self, event=None, data=None): |
1176 | + """Raise a dialog with info about the app.""" |
1177 | + if self._help_dialog is not None: |
1178 | + self._help_dialog.present() |
1179 | + return |
1180 | + |
1181 | + self._help_dialog = gtk.MessageDialog( |
1182 | + None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, |
1183 | + gtk.BUTTONS_OK, None) |
1184 | + |
1185 | + self._help_dialog.set_title(_("Help")) |
1186 | + self._help_dialog.set_markup(HELP_MSG) |
1187 | + self._help_dialog.run() |
1188 | + self._help_dialog.destroy() |
1189 | + self._help_dialog = None |
1190 | + |
1191 | + |
1192 | from optparse import OptionParser |
1193 | |
1194 | if __name__ == "__main__": |
1195 | - parser = OptionParser("usage: %prog [options]", version="%prog "+VERSION) |
1196 | + parser = OptionParser("usage: %prog [options]", version="%prog " + VERSION) |
1197 | parser.add_option("--config", "", default=None, |
1198 | - help="use custom config file") |
1199 | - parser.add_option("--disable-sensors", action="store_true", |
1200 | - help="disable sensors", default=False) |
1201 | - parser.add_option("--enable-sensors", action="store_true", |
1202 | - help="re-enable sensors", default=False) |
1203 | + help=_("Use custom config file.")) |
1204 | |
1205 | (options, args) = parser.parse_args() |
1206 | |
1207 | if options.config: |
1208 | if not os.path.exists(options.config): |
1209 | - print options.config, "does not exist!" |
1210 | + logging.error(_("{} does not exist!").format(options.config)) |
1211 | sys.exit(-1) |
1212 | + logging.info(_("Using config file: {}").format(options.config)) |
1213 | IndicatorSysmonitor.SETTINGS_FILE = options.config |
1214 | |
1215 | + if not os.path.exists(IndicatorSysmonitor.SETTINGS_FILE): |
1216 | + IndicatorSysmonitor.save_settings() |
1217 | + |
1218 | # setup an instance with config |
1219 | - i = IndicatorSysmonitor() |
1220 | - if options.disable_sensors: |
1221 | - i.sensors_disabled = True |
1222 | - i.save_settings() |
1223 | - logging.info("Sensors disabled") |
1224 | - |
1225 | - if options.enable_sensors: |
1226 | - i.sensors_disabled = False |
1227 | - i.save_settings() |
1228 | - logging.info("Sensors enabled") |
1229 | - |
1230 | + app = IndicatorSysmonitor() |
1231 | try: |
1232 | gtk.main() |
1233 | except KeyboardInterrupt: |
1234 | - i.on_exit() |
1235 | + app.on_exit() |
1236 | |
1237 | === added directory 'lang' |
1238 | === added directory 'lang/es' |
1239 | === added directory 'lang/es/LC_MESSAGES' |
1240 | === added file 'lang/es/LC_MESSAGES/indicator-sysmonitor.mo' |
1241 | Binary files lang/es/LC_MESSAGES/indicator-sysmonitor.mo 1970-01-01 00:00:00 +0000 and lang/es/LC_MESSAGES/indicator-sysmonitor.mo 2012-09-26 17:56:20 +0000 differ |
1242 | === added file 'lang/es/es.po' |
1243 | --- lang/es/es.po 1970-01-01 00:00:00 +0000 |
1244 | +++ lang/es/es.po 2012-09-26 17:56:20 +0000 |
1245 | @@ -0,0 +1,72 @@ |
1246 | +# Spanish translations for PACKAGE package |
1247 | +# Traducciones al español para el paquete PACKAGE. |
1248 | +# Copyright (C) 2012 THE PACKAGE'S COPYRIGHT HOLDER |
1249 | +# This file is distributed under the same license as the PACKAGE package. |
1250 | +# Gabriel Lopez <pipodecnt@gmail.com>, 2012. |
1251 | +# |
1252 | +msgid "" |
1253 | +msgstr "" |
1254 | +"Project-Id-Version: PACKAGE VERSION\n" |
1255 | +"Report-Msgid-Bugs-To: \n" |
1256 | +"POT-Creation-Date: 2012-09-01 17:24-0300\n" |
1257 | +"PO-Revision-Date: 2012-09-01 17:30-0300\n" |
1258 | +"Last-Translator: Gabriel Lopez <pipodecnt@gmail.com>\n" |
1259 | +"Language-Team: Argentinian\n" |
1260 | +"Language: es_AR\n" |
1261 | +"MIME-Version: 1.0\n" |
1262 | +"Content-Type: text/plain; charset=UTF-8\n" |
1263 | +"Content-Transfer-Encoding: 8bit\n" |
1264 | +"Plural-Forms: nplurals=2; plural=(n != 1);\n" |
1265 | + |
1266 | +#: indicator-sysmonitor:381 |
1267 | +msgid "Preferences" |
1268 | +msgstr "Preferencias" |
1269 | + |
1270 | +#: indicator-sysmonitor:397 |
1271 | +msgid "Run on startup:" |
1272 | +msgstr "Ejecutar en el inicio:" |
1273 | + |
1274 | +#: indicator-sysmonitor:407 |
1275 | +msgid "This is indicator-sysmonitor version: {}" |
1276 | +msgstr "Esta es la versión de indicator-sysmonitor: {}" |
1277 | + |
1278 | +#: indicator-sysmonitor:411 |
1279 | +msgid "General" |
1280 | +msgstr "General" |
1281 | + |
1282 | +#: indicator-sysmonitor:416 |
1283 | +msgid "Customize output:" |
1284 | +msgstr "Salida personalizada:" |
1285 | + |
1286 | +#: indicator-sysmonitor:424 |
1287 | +msgid "Use this command:" |
1288 | +msgstr "Usar este comando:" |
1289 | + |
1290 | +#: indicator-sysmonitor:431 |
1291 | +msgid "Update interval:" |
1292 | +msgstr "Intervalo de actualización:" |
1293 | + |
1294 | +#: indicator-sysmonitor:439 |
1295 | +msgid "Advanced" |
1296 | +msgstr "Avanzado" |
1297 | + |
1298 | +#: indicator-sysmonitor:452 |
1299 | +msgid "Test" |
1300 | +msgstr "Prueba" |
1301 | + |
1302 | +#: indicator-sysmonitor:499 |
1303 | +msgid "{} sensor not supported." |
1304 | +msgstr "{} sensor no soportado." |
1305 | + |
1306 | +#: indicator-sysmonitor:506 |
1307 | +msgid "Interval value is not valid." |
1308 | +msgstr "Valor del intervalo no válido." |
1309 | + |
1310 | +#: indicator-sysmonitor:582 |
1311 | +msgid "System Monitor" |
1312 | +msgstr "Monitor del Sistema" |
1313 | + |
1314 | +#: indicator-sysmonitor:712 |
1315 | +msgid "Help" |
1316 | +msgstr "Ayuda" |
1317 | + |
1318 | |
1319 | === added file 'messages.pot' |
1320 | --- messages.pot 1970-01-01 00:00:00 +0000 |
1321 | +++ messages.pot 2012-09-26 17:56:20 +0000 |
1322 | @@ -0,0 +1,70 @@ |
1323 | +# SOME DESCRIPTIVE TITLE. |
1324 | +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER |
1325 | +# This file is distributed under the same license as the PACKAGE package. |
1326 | +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. |
1327 | +# |
1328 | +#, fuzzy |
1329 | +msgid "" |
1330 | +msgstr "" |
1331 | +"Project-Id-Version: PACKAGE VERSION\n" |
1332 | +"Report-Msgid-Bugs-To: \n" |
1333 | +"POT-Creation-Date: 2012-09-01 17:24-0300\n" |
1334 | +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
1335 | +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
1336 | +"Language-Team: LANGUAGE <LL@li.org>\n" |
1337 | +"Language: \n" |
1338 | +"MIME-Version: 1.0\n" |
1339 | +"Content-Type: text/plain; charset=CHARSET\n" |
1340 | +"Content-Transfer-Encoding: 8bit\n" |
1341 | + |
1342 | +#: indicator-sysmonitor:381 |
1343 | +msgid "Preferences" |
1344 | +msgstr "" |
1345 | + |
1346 | +#: indicator-sysmonitor:397 |
1347 | +msgid "Run on startup:" |
1348 | +msgstr "" |
1349 | + |
1350 | +#: indicator-sysmonitor:407 |
1351 | +msgid "This is indicator-sysmonitor version: {}" |
1352 | +msgstr "" |
1353 | + |
1354 | +#: indicator-sysmonitor:411 |
1355 | +msgid "General" |
1356 | +msgstr "" |
1357 | + |
1358 | +#: indicator-sysmonitor:416 |
1359 | +msgid "Customize output:" |
1360 | +msgstr "" |
1361 | + |
1362 | +#: indicator-sysmonitor:424 |
1363 | +msgid "Use this command:" |
1364 | +msgstr "" |
1365 | + |
1366 | +#: indicator-sysmonitor:431 |
1367 | +msgid "Update interval:" |
1368 | +msgstr "" |
1369 | + |
1370 | +#: indicator-sysmonitor:439 |
1371 | +msgid "Advanced" |
1372 | +msgstr "" |
1373 | + |
1374 | +#: indicator-sysmonitor:452 |
1375 | +msgid "Test" |
1376 | +msgstr "" |
1377 | + |
1378 | +#: indicator-sysmonitor:499 |
1379 | +msgid "{} sensor not supported." |
1380 | +msgstr "" |
1381 | + |
1382 | +#: indicator-sysmonitor:506 |
1383 | +msgid "Interval value is not valid." |
1384 | +msgstr "" |
1385 | + |
1386 | +#: indicator-sysmonitor:582 |
1387 | +msgid "System Monitor" |
1388 | +msgstr "" |
1389 | + |
1390 | +#: indicator-sysmonitor:712 |
1391 | +msgid "Help" |
1392 | +msgstr "" |
This is basically a complete rewrite. I approve the changes, and coding style, generally.