Merge lp:~injaon/indicator-sysmonitor/trunk into lp:indicator-sysmonitor/trunk

Proposed by Gabriel Lopez
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
Reviewer Review Type Date Requested Status
Alex Eftimie Needs Fixing
Review via email: mp+125394@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Alex Eftimie (alexeftimie) wrote :

This is basically a complete rewrite. I approve the changes, and coding style, generally.

Revision history for this message
Alex Eftimie (alexeftimie) wrote :

Please bump the version to 0.5

review: Needs Fixing
Revision history for this message
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://code.launchpad.net/~pipodecnt/indicator-sysmonitor/trunk/+merge/125394
> You are the owner of lp:~pipodecnt/indicator-sysmonitor/trunk.

Revision history for this message
Alex Eftimie (alexeftimie) wrote :

Just change the variable VERSION value :)

31. By Gabriel Lopez <email address hidden>

Change VERSION variable.

Revision history for this message
Gabriel Lopez (injaon) wrote :

> Just change the variable VERSION value :)

LOL. Tha hard work is done :P

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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'
1241Binary 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 ""

Subscribers

People subscribed via source and target branches

to all changes: