diff -Nru indicator-sysmonitor-0.4.1~quantal/build/indicator-sysmonitor indicator-sysmonitor-0.4.2/build/indicator-sysmonitor --- indicator-sysmonitor-0.4.1~quantal/build/indicator-sysmonitor 2012-06-30 10:20:19.000000000 +0000 +++ indicator-sysmonitor-0.4.2/build/indicator-sysmonitor 2012-11-27 16:22:28.000000000 +0000 @@ -7,39 +7,211 @@ # Homepage: http://launchpad.net/indicator-sysmonitor # License: GPL v3 # +from gettext import gettext as _ +from gettext import textdomain, bindtextdomain +textdomain("indicator-sysmonitor") +bindtextdomain("indicator-sysmonitor", "./lang") + import sys import os import shutil import json -import string import time -from copy import deepcopy from threading import Thread, Event import subprocess import psutil as ps - -import logging -logging.basicConfig(file=sys.stderr,level=logging.INFO) - -import gobject +import re import gtk +gtk.gdk.threads_init() import appindicator +import logging +logging.basicConfig(file=sys.stderr, level=logging.INFO) -VERSION='0.4.1~unreleased' +B_UNITS = ['', 'KB', 'MB', 'GB', 'TB'] +VERSION = '0.5.1~unreleased' +HELP_MSG = """{title} + +{introduction} + +{basic} +• cpu: {cpu_desc} +• mem: {mem_desc} +• bat%d: {bat_desc} +• net: {net_desc} + +{compose} +• fs//mount-point : {fs_desc} + +{example} +CPU {{cpu}} | MEM {{mem}} | root {{fs///}} +""".format( + title=_("Help Page"), + introduction=_("The sensors are the names of the devices you want to \ + retrieve information from. They must be placed between brackets."), + basic=_("The basics are:"), + cpu_desc=_("It shows the average of CPU usage."), + mem_desc=_("It shows the physical memory in use."), + bat_desc=_("It shows the available battery which id is %d."), + net_desc=_("It shows the amount of data you are downloading and uploading \ + through your network."), + compose=_("Also there are the following sensors that are composed with \ + two parts divided by two slashes."), + fs_desc=_("Show available space in the file system."), + example=_("Example:")) + +supported_sensors = re.compile("\A(mem|swap|cpu\d*|net|bat\d*|fs//.+)\Z") +settings = { + 'custom_text': 'cpu: {cpu} mem: {mem}', + 'interval': 2, + 'on_startup': False, + 'sensors': { + # 'name' => (desc, cmd) + 'cpu\d*': (_('Average CPU usage'), True), + 'mem': (_('Physical memory in use.'), True), + 'net': (_('Network activity.'), True), + 'bat\d*': (_('Network activity.'), True), + 'fs//.+': (_('Available space in file system.'), True), + "swap": (_("Average swap usage"), True) + + } + } + + +class ISMError(Exception): + """General exception.""" + def __init__(self, msg): + Exception.__init__(self, msg) + + +def raise_dialog(parent, flags, type_, buttons, msg, title): + """It raise a dialog. It a blocking function.""" + dialog = gtk.MessageDialog( + parent, flags, type_, buttons, msg) + dialog.set_title(title) + dialog.run() + dialog.destroy() + + +class Sensor(object): + """Singleton""" + _instance = None + bat = re.compile("\Abat\d*\Z") + cpus = re.compile("\Acpu\d+\Z") + + def __init__(self): + """It must not be called. Use Sensor.get_instace() + to retrieve an instance of this class.""" + if Sensor._instance is not None: + raise Exception("Sensor class can not be instanceted twice.") + else: + Sensor._instance = self + self.update_regex() + + @staticmethod + def update_regex(names=None): + if names is None: + names = settings["sensors"].keys() + + reg = '|'.join(names) + reg = "\A({})\Z".format(reg) + global supported_sensors + supported_sensors = re.compile("{}".format(reg)) + + @classmethod + def get_instance(cls): + """Returns the unique instance of Sensor.""" + if Sensor._instance is None: + Sensor._instance = Sensor() + + return Sensor._instance + + @staticmethod + def exists(name): + """Checks if the sensor name exists""" + return bool(supported_sensors.match(name)) + + @staticmethod + def check(sensor): + if sensor.startswith("fs//"): + path = sensor.split("//")[1] + if not os.path.exists(path): + raise ISMError(_("Path: {} doesn't exists.").format(path)) + + elif Sensor.cpus.match(sensor): + nber = int(sensor[3:]) + if nber >= ps.NUM_CPUS: + raise ISMError(_("Invalid number of CPU.")) + + elif Sensor.bat.match(sensor): + bat_id = int(sensor[3:]) if len(sensor) > 3 else 0 + if bat_id >= len(os.listdir("/proc/acpi/battery/")): + raise ISMError(_("Invalid number of Batterry.")) + + def add(self, name, desc, cmd): + """Adds a custom sensors.""" + if Sensor.exists(name): + raise ISMError(_("Sensor name already in use.")) + + settings["sensors"][name] = (desc, cmd) + self.update_regex() + + def delete(self, name): + """Deletes a custom sensors.""" + sensors = settings['sensors'] + names = sensors.keys() + if name not in names: + raise ISMError(_("Sensor is not defined.")) + + _desc, default = sensors[name] + if default is True: + raise ISMError(_("Can not delete default sensors.")) + + del sensors[name] + self.update_regex() + + def edit(self, name, newname, desc, cmd): + """Edits a custom sensors.""" + try: + sensors = settings['sensors'] + _desc, default = sensors[name] + + except KeyError: + raise ISMError(_("Sensor does not exists.")) + + if default is True: + raise ISMError(_("Can not edit default sensors.")) + if newname != name: + if newname in sensors.keys(): + raise ISMError(_("Sensor name already in use.")) + + sensors[newname] = (desc, cmd) + del sensors[name] + settings["custom_text"] = settings["custom_text"].replace( + name, newname) + self.update_regex() -gtk.gdk.threads_init() class StatusFetcher(Thread): + """It recollects the info about the sensors.""" + digit_regex = re.compile(r'''\d+''') + def __init__(self, parent): Thread.__init__(self) - self.parent = parent + self._parent = parent self.last = ps.cpu_times() - def _fetch_cpu(self): + def _fetch_cpu(self, percpu=False): + if percpu: + return ps.cpu_percent(interval=0, percpu=True) + last = self.last current = ps.cpu_times() - total_time_passed = sum([v-last.__dict__[k] if not isinstance(v,list) else 0 for k,v in current.__dict__.iteritems()]) + total_time_passed = sum( + [v - last.__dict__[k] + if not isinstance(v, list) + else 0 + for k, v in current.__dict__.iteritems()]) sys_time = current.system - last.system usr_time = current.user - last.user @@ -53,546 +225,659 @@ else: return 0 - def _fetch_mem(self): - total_mem = subprocess.Popen("free -b | grep Mem | tr -s ' ' | cut -d\ -f 2", - stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - free_mem = subprocess.Popen("free -b | grep Mem | tr -s ' ' | cut -d\ -f 4-", - stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - free_mem = sum([int(i) for i in free_mem.split()]) - return 100 - 100 * float(free_mem) / float(total_mem) + def _fetch_swap(self): + """Return the swap usage in percent""" + usage = 0 + total = 0 + try: + with open("/proc/swaps") as swaps: + swaps.readline() + for line in swaps.readlines(): + dummy, dummy, total_, usage_, dummy = line.split() + total += int(total_) + usage += int(usage_) - def _fetch_bat(self, bat_id): - current_bat = subprocess.Popen("grep 'remaining capacity:' /proc/acpi/battery/BAT%s/state |awk '{print $3}' |grep [0-9] || echo 10" % bat_id, - stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - #total_bat = subprocess.Popen("cat /proc/acpi/battery/BAT0/info |grep -i 'design capacity:' |awk '{print $3}'", - total_bat = subprocess.Popen("grep 'last full capacity:' /proc/acpi/battery/BAT%s/info |awk '{print $4}' |grep [0-9] || echo 65130" % bat_id, - stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - return 100 * float(current_bat) / float(total_bat) + return usage * 100 / total - def _fetch_net(self): - 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() - return total_net + except IOError: + return "N/A" - def _fetch_sensor(self, sensor_name): - sensor_data = sensor_name.split('//') + def _fetch_mem(self): + """It gets the total memory info and return the used in percent.""" + with open('/proc/meminfo') as meminfo: + total = StatusFetcher.digit_regex.findall(meminfo.readline()).pop() + free = StatusFetcher.digit_regex.findall(meminfo.readline()).pop() + meminfo.readline() + cached = StatusFetcher.digit_regex.findall( + meminfo.readline()).pop() + free = int(free) + int(cached) + return 100 - 100 * free / float(total) - if (len(sensor_data) != 2): - return 'N/A' + def _fetch_bat(self, batid): + """Fetch the the amount of remaining battery""" + try: + with open("/proc/acpi/battery/BAT{}/state".format(batid)) as state: + while True: + line = state.readline() + if "remaining capacity:" in line: + remaining = int(line.split()[2]) + break + + with open("/proc/acpi/battery/BAT{}/info".format(batid)) as info: + while True: + line = info.readline() + if "last full capacity" in line: + capacity = int(line.split()[3]) + break - sensor_item = sensor_data[1].replace('-', '.') - postfix = '' - if sensor_data[0] == 'hddtemp': - sensor_cmd = 'netcat localhost 7634' - netcat_value = subprocess.Popen(sensor_cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - sensor_value = '' - - for hd_data in self.parse_hddtemp_entries(netcat_value): - if hd_data[0] == sensor_item: - sensor_value = hd_data[2] + '°' + hd_data[3] - else: - sensor_cmd = 'sensors -A ' + sensor_data[0] + " | grep -i '" + sensor_item + "' | cut -f 2 -d ':' | awk '{print $1}'" - if sensor_data[0] == 'nvidia': - if sensor_item == 'gputemp': - sensor_cmd = 'nvidia-settings -q [gpu:0]/GPUCoreTemp | grep "Attribute" | sed -e "s/.*: //g" -e "s/\.//g"' - postfix = '°C' - elif sensor_item == 'fanspeed': - sensor_cmd = 'nvidia-settings -q [fan:0]/GPUCurrentFanSpeed | grep "Attribute" | sed -e "s/.*: //g" -e "s/\.//g"' - postfix = ' RPM' - else: - sensor_cmd = None - elif sensor_data[0] == 'ati': - if sensor_item == 'gputemp': - sensor_cmd = 'aticonfig --adapter=0 --od-gettemperature | grep "Sensor 0:" | awk ' + "'{ printf(\"%d\", $5) }'" - postfix = '°C' - - sensor_value = subprocess.Popen(sensor_cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - - if len(sensor_value): - sensor_value = self._clean_value(sensor_value) # Cleaning the + prefix - return sensor_value + postfix - else: - if sensor_cmd == None: - logging.info('Sensor not supported: ' + sensor_name) - else: - logging.info('Sensor command failed:\n' + sensor_cmd) - return 'N/A' - - def _clean_value(self, value): - if value.find('+') == 0: - return value[1:] - return value - - def _get_sensors_to_fetch(self): - fmt = string.Formatter() - tokens = [] - for token in fmt.parse(self.parent.custom_text): - tokens.append(str(token[1])) + except IOError: + return "N/A" + + return remaining * 100.0 / capacity - return tokens + def _fetch_net(self): + """It use the command "ifstat" to know the total net usage""" + 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() + return total_net - def _fetch(self): + def fetch(self): + """Return a dict whose element are the sensors + and their values""" res = {} - for sensor in self._get_sensors_to_fetch(): + cpus = None + for sensor in Preferences.sensors_regex.findall( + settings["custom_text"]): + sensor = sensor[1:-1] if sensor == 'cpu': - res['cpu'] = '%02.0f%%' % self._fetch_cpu() + res['cpu'] = "{:02.0f}%".format(self._fetch_cpu()) + elif Sensor.cpus.match(sensor): + if cpus is None: + cpus = self._fetch_cpu(percpu=True) + res[sensor] = "{:02.0f}%".format(cpus[int(sensor[3:])]) + elif sensor == 'mem': - res['mem'] = '%02.0f%%' % self._fetch_mem() - elif sensor.startswith('bat'): - bat_id = sensor[3:] - try: - bat_id = int(bat_id) - except: - bat_id = 0 - res[sensor] = '%02.0f%%' % self._fetch_bat(bat_id) + res['mem'] = '{:02.0f}%'.format(self._fetch_mem()) elif sensor == 'net': res['net'] = self._fetch_net() - else: - res[sensor] = '%s' % self._fetch_sensor(sensor) + + elif Sensor.bat.match(sensor): + bat_id = int(sensor[3:]) if len(sensor) > 3 else 0 + res[sensor] = '{:02.0f}%'.format(self._fetch_bat(bat_id)) + + elif sensor.startswith('fs//'): + parts = sensor.split('//') + res[sensor] = self._fetch_fs(parts[1]) + + elif sensor == "swap": + res[sensor] = '{:02.0f}%'.format(self._fetch_swap()) + + else: # custom sensor + res[sensor] = self._exec(settings["sensors"][sensor][1]) return res - def _fetch_user(self, command): + def _exec(self, command): + """Execute a custom command.""" try: - output = subprocess.Popen(command, stdout=subprocess.PIPE, - shell=True).communicate()[0].strip() - if output == '': - output = '(no output)' + output = subprocess.Popen(command, stdout=subprocess.PIPE, + shell=True).communicate()[0].strip() except: - output = 'error' - logging.error('Error running: '+command) - return output - - def parse_hddtemp_entries(self, netcat_value): - hddtemp_entries = [] - - if len(netcat_value): - for hd_row in netcat_value.strip('|').split('||'): - hddtemp_entries.append(hd_row.split('|')) + output = _("Error") + logging.error(_("Error running: {}").format(command)) + + return output if output else _("(no output)") - return hddtemp_entries + def _fetch_fs(self, mount_point): + """It returns the amount of bytes available in the fs in + a human-readble format.""" + if not os.access(mount_point, os.F_OK): + return None + + stat = os.statvfs(mount_point) + bytes_ = stat.f_bavail * stat.f_frsize + + for unit in B_UNITS: + if bytes_ < 1024: + return "{} {}".format(bytes_, unit) + bytes_ /= 1024 def run(self): - while(self.parent.alive.isSet()): - if self.parent.mode_user: - output = self._fetch_user(self.parent.user_command) - self.parent.update_text(output, output) - else: - data = self._fetch() - self.parent.update(data) - time.sleep(self.parent.interval) + """It is the main loop.""" + while self._parent.alive.isSet(): + data = self.fetch() + self._parent.update(data) + time.sleep(settings["interval"]) + + +class SensorsListModel(object): + """A TreeView showing the available sensors. It allows to + add/edit/delete custom sensors.""" -class SensorsListModel: def __init__(self, parent): self.ind_parent = parent - self.tree_store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) + self._list_store = gtk.ListStore(str, str) + self._tree_view = gtk.TreeView(self._list_store) - # lib sensors - for sensor_data in subprocess.Popen('sensors', stdout=subprocess.PIPE, shell=True).communicate()[0].split('\n\n'): - sensor_name = None - skip_line = False - for line in sensor_data.split('\n'): - if len(line): - if skip_line: - skip_line = False - else: - if sensor_name == None: - logging.info("New sensor found: " + line) - sensor_name = line - parent = self.tree_store.append(None, (sensor_name, None, False)) - skip_line = True - else: - logging.info("Sensor entry: " + line) - self.tree_store.append(parent, (line, self.generate_sensor_item_name(sensor_name, line), False)) - - # hddtemp - hddtemp = subprocess.Popen('netcat localhost 7634', stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - if len(hddtemp): - sensor_name = 'hddtemp' - logging.info("New sensor found: " + sensor_name) - parent = self.tree_store.append(None, (sensor_name, None, False)) - for hd_data in self.ind_parent.ind_parent.fetch.parse_hddtemp_entries(hddtemp): - logging.info("Sensor entry: " + hd_data[0]) - 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)) - - # nvidia GPU - nvidia_model = subprocess.Popen("lspci | grep nVidia | sed -e 's/.*\[//g' -e 's/\].*//g'", stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - if len(nvidia_model): - sensor_name = 'nvidia' - logging.info("New sensor found: " + sensor_name) - parent = self.tree_store.append(None, (sensor_name, None, False)) - self.tree_store.append(parent, (nvidia_model + ' - Temperature', self.generate_sensor_item_name(sensor_name, 'gputemp'), False)) - self.tree_store.append(parent, (nvidia_model + ' - Fan speed', self.generate_sensor_item_name(sensor_name, 'fanspeed'), False)) - - # ati GPU - ati_model = subprocess.Popen('lspci | grep \"ATI Radeon HD"' + " | sed -e 's/.*\[//g' -e 's/\].*//g'", stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - if len(ati_model): - sensor_name = 'ati' - logging.info("New sensor found: " + sensor_name) - parent = self.tree_store.append(None, (sensor_name, None, False)) - self.tree_store.append(parent, (ati_model + ' - Temperature', self.generate_sensor_item_name(sensor_name, 'gputemp'), False)) + sensors = settings['sensors'] + for name in sensors.keys(): + self._list_store.append([name, sensors[name][0]]) def get_view(self): - self.view = gtk.HBox() - - self.vb = gtk.VBox(False, 3) - l = gtk.Label('Sensors:') - l.set_alignment(0, 0.5) - self.vb.pack_start(l, expand=False, fill=False) - - self.tree_view = gtk.TreeView(self.tree_store) - - # setup the text cell renderer - self.renderer = gtk.CellRendererText() - self.renderer.set_property('editable', False) - - # sensor name render - self.renderer1 = gtk.CellRendererText() - self.renderer1.set_property('editable', False) - - # quick add checkbox render - self.renderer2 = gtk.CellRendererToggle() - self.renderer2.set_property('activatable', True) - self.renderer2.connect('toggled', self.quick_add_cb_toggled, self.tree_store) - + """It's call from Preference. It create the view and return it""" + vbox = gtk.VBox(False, 3) # create columns - self.column0 = gtk.TreeViewColumn('Sensor', self.renderer, text=0) - self.column1 = gtk.TreeViewColumn('Identifier', self.renderer1, text=1) - self.column2 = gtk.TreeViewColumn('', self.renderer2, active=2) - self.tree_view.append_column(self.column0) - self.tree_view.append_column(self.column1) - self.tree_view.append_column(self.column2) + renderer = gtk.CellRendererText() + renderer.set_property('editable', False) + column = gtk.TreeViewColumn(_('Sensor'), renderer, text=0) + self._tree_view.append_column(column) + + renderer = gtk.CellRendererText() + renderer.set_property('editable', False) + column = gtk.TreeViewColumn(_('Description'), renderer, text=1) + self._tree_view.append_column(column) - self.tree_view.expand_all() + self._tree_view.expand_all() sw = gtk.ScrolledWindow() - sw.add_with_viewport(self.tree_view) - self.vb.pack_start(sw, fill=True, expand=True) + sw.add_with_viewport(self._tree_view) + vbox.pack_start(sw, fill=True, expand=True) - self.add_bt = gtk.Button('Add selected sensors') - self.add_bt.connect('clicked', self.update_custom_text) - self.vb.pack_end(self.add_bt, fill=False, expand=False) - - self.view.add(self.vb) - self.view.show() - - return self.view - - def quick_add_cb_toggled(self, cell, path, tree_store): - tree_store[path][2] = not tree_store[path][2] - iter = tree_store.iter_children(tree_store.get_iter(path)) - while iter: - tree_store.set_value(iter, 2, tree_store[path][2]) - iter = tree_store.iter_next(iter) - - def generate_sensor_item_name(self, sensor_name, sensor_item_label): - return sensor_name + '//' + sensor_item_label.split(':')[0].lower().replace('.', '-') - - def update_custom_text(self, event=None): - iter = self.tree_store.get_iter_root() - - while iter: - iter_children = self.tree_store.iter_children(iter) - while iter_children: - if self.tree_store.get_value(iter_children, 2): - current_text = self.ind_parent.custom_entry.get_text() - to_add_value = '{' + self.tree_store.get_value(iter_children, 1) + '}' - if string.find(current_text, to_add_value) == -1: - self.ind_parent.custom_entry.set_text(current_text + ' ' + to_add_value) - iter_children = self.tree_store.iter_next(iter_children) + # add buttons + hbox = gtk.HBox() + new_button = gtk.Button(stock=gtk.STOCK_NEW) + new_button.connect('clicked', self._on_edit_sensor) + # hbox.pack_start(new_button, fill=False, expand=False) + hbox.pack_start(new_button, fill=False, expand=False) + + edit_button = gtk.Button(stock=gtk.STOCK_EDIT) + edit_button.connect('clicked', self._on_edit_sensor, False) + hbox.pack_start(edit_button, fill=False, expand=False) + + del_button = gtk.Button(stock=gtk.STOCK_DELETE) + del_button.connect('clicked', self._on_del_sensor) + hbox.pack_start(del_button, fill=False, expand=False) + + add_button = gtk.Button(stock=gtk.STOCK_ADD) + add_button.connect('clicked', self._on_add_sensor) + hbox.pack_end(add_button, fill=False, expand=False) + vbox.pack_end(hbox, fill=False, expand=False) + + frame = gtk.Frame(_('Sensors')) + frame.add(vbox) + return frame + + def _get_selected_row(self): + """Returns an iter for the selected rows in the view or None.""" + model, pathlist = self._tree_view.get_selection().get_selected_rows() + if len(pathlist): + path = pathlist.pop() + return model.get_iter(path) + return None + + def _on_add_sensor(self, evnt=None, data=None): + tree_iter = self._get_selected_row() + if tree_iter is None: + return - iter = self.tree_store.iter_next(iter) + sensor = self._list_store.get_value(tree_iter, 0) + self.ind_parent.custom_entry.insert_text( + "{{{}}}".format(sensor), -1) + + def _on_edit_sensor(self, evnt=None, blank=True): + """Raises a dialog with a form to add/edit a sensor""" + name = desc = cmd = "" + tree_iter = None + if not blank: + # edit, so get the info from the selected row + tree_iter = self._get_selected_row() + if tree_iter is None: + return + + name = self._list_store.get_value(tree_iter, 0) + desc = self._list_store.get_value(tree_iter, 1) + cmd = settings["sensors"][name][1] + + if cmd is True: # default sensor + raise_dialog( + self.ind_parent, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, + _("Can not edit defualt sensors."), _("Error")) + return + + dialog = gtk.Dialog(_("Edit Sensor"), self.ind_parent, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, + gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + vbox = dialog.get_content_area() + + hbox = gtk.HBox() + label = gtk.Label(_("Sensor")) + sensor_entry = gtk.Entry() + sensor_entry.set_text(name) + hbox.pack_start(label) + hbox.pack_end(sensor_entry) + vbox.pack_start(hbox) + + hbox = gtk.HBox() + label = gtk.Label(_("Description")) + desc_entry = gtk.Entry() + desc_entry.set_text(desc) + hbox.pack_start(label) + hbox.pack_end(desc_entry) + vbox.pack_start(hbox) + + hbox = gtk.HBox() + label = gtk.Label(_("Command")) + cmd_entry = gtk.Entry() + + cmd_entry.set_text(cmd) + hbox.pack_start(label) + hbox.pack_end(cmd_entry) + vbox.pack_end(hbox) -class Preferences(gtk.Window): - AUTOSTART_PATH = os.getenv("HOME") + '/.config/autostart/indicator-sysmonitor.desktop' + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_ACCEPT: + try: + newname, desc, cmd = str(sensor_entry.get_text()), \ + str(desc_entry.get_text()), str(cmd_entry.get_text()) + + if blank: + Sensor.get_instance().add(newname, desc, cmd) + else: + Sensor.get_instance().edit(name, newname, desc, cmd) + self._list_store.remove(tree_iter) + + self._list_store.append([newname, desc]) + ctext = self.ind_parent.custom_entry.get_text() + self.ind_parent.custom_entry.set_text( + ctext.replace(name, newname)) + + except ISMError as ex: + raise_dialog( + self.ind_parent, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, + ex.message, _("Error")) + + dialog.destroy() + + def _on_del_sensor(self, evnt=None, data=None): + """Remove a custom sensor.""" + tree_iter = self._get_selected_row() + if tree_iter is None: + return + + name = self._list_store.get_value(tree_iter, 0) + try: + Sensor.get_instance().delete(name) + self._list_store.remove(tree_iter) + ctext = self.ind_parent.custom_entry.get_text() + self.ind_parent.custom_entry.set_text( + ctext.replace("{{{}}}".format(name), "")) + + except ISMError as ex: + raise_dialog( + self.ind_parent, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, + ex.message, _("Error")) + + +class Preferences(gtk.Dialog): + """It define the the Preferences Dialog and its operations.""" + AUTOSTART_PATH = '{}/.config/autostart/indicator-sysmonitor.desktop'\ + .format(os.getenv("HOME")) DESKTOP_PATH = '/usr/share/applications/indicator-sysmonitor.desktop' + sensors_regex = re.compile("{.+?}") def __init__(self, parent): - gtk.Window.__init__(self) + """It creates the widget of the dialogs""" + gtk.Dialog.__init__(self) self.ind_parent = parent - self.connect('delete-event', self.on_destroy) + self.custom_entry = None + self.interval_entry = None + self._create_content() + self.set_data() + self.show_all() + def _create_content(self): + """It creates the content for this dialog.""" + self.connect('delete-event', self.on_cancel) + self.set_title(_('Preferences')) + self.resize(400, 350) + self.set_position(gtk.WIN_POS_CENTER_ALWAYS) notebook = gtk.Notebook() notebook.set_border_width(4) - vb = gtk.VBox(spacing=3) - hb = gtk.HBox() - hb.set_border_width(4) - l = gtk.Label('Run on startup:') - l.set_alignment(0, 0.5) - hb.pack_start(l) + # General page of the notebook {{{ + vbox = gtk.VBox(spacing=3) + hbox = gtk.HBox() + + hbox.set_border_width(4) + label = gtk.Label(_('Run on startup:')) + label.set_alignment(0, 0.5) + hbox.pack_start(label) self.autostart_check = gtk.CheckButton() self.autostart_check.set_active(self.get_autostart()) - hb.pack_end(self.autostart_check, expand=False, fill=False) - vb.pack_start(hb, expand=False, fill=False) + hbox.pack_end(self.autostart_check, expand=False, fill=False) + vbox.pack_start(hbox, expand=False, fill=False) - hb = gtk.HBox() - l = gtk.Label('This is indicator-sysmonitor version: %s' % VERSION) - l.set_alignment(0.5, 0.5) - hb.pack_start(l) - vb.pack_end(hb) - - notebook.append_page(vb, gtk.Label('General')) - - vb = gtk.VBox(spacing=3) - hb = gtk.VBox() - self.custom_radio = gtk.RadioButton(label='Customize output:') - hb.pack_start(self.custom_radio) + hbox = gtk.HBox() + label = gtk.Label( + _('This is indicator-sysmonitor version: {}').format(VERSION)) + label.set_alignment(0.5, 0.5) + hbox.pack_start(label) + vbox.pack_end(hbox) + notebook.append_page(vbox, gtk.Label(_('General'))) + # }}} + + # Advanced page in notebook {{{ + vbox = gtk.VBox() # main box + label = gtk.Label(_('Customize output:')) + label.set_alignment(0, 0) + vbox.pack_start(label, expand=False, fill=False) self.custom_entry = gtk.Entry() - hb.pack_end(self.custom_entry) - vb.pack_start(hb, expand=False, fill=False) + vbox.pack_start(self.custom_entry, expand=False, fill=False) - hb = gtk.VBox() - self.user_radio = gtk.RadioButton(group=self.custom_radio, label='Use this command:') - hb.pack_start(self.user_radio) - self.user_entry = gtk.Entry(max=100) - #info = gtk.Label('Use this option to specify a program to be run every time the indicator refreshes') - #info.set_line_wrap(True) - #hb.pack_end(info) - hb.pack_end(self.user_entry) - vb.pack_start(hb, expand=False, fill=False) - - hb = gtk.HBox() - l = gtk.Label('Update interval:') - l.set_alignment(0, 0.5) - hb.pack_start(l) + hbox = gtk.HBox() + label = gtk.Label(_('Update interval:')) + label.set_alignment(0, 0) + hbox.pack_start(label) self.interval_entry = gtk.Entry(max=4) - self.interval_entry.set_width_chars(3) - hb.pack_end(self.interval_entry, expand=False, fill=False) - vb.pack_start(hb, expand=False, fill=False) - - notebook.append_page(vb, gtk.Label('Advanced')) - - if not parent.sensors_disabled: - sensors_list = SensorsListModel(self) - if sensors_list.tree_store.get_iter_root() != None: - vb.pack_start(sensors_list.get_view()) - - # footer - vb = gtk.VBox() - vb.pack_start(notebook) + self.interval_entry.set_width_chars(5) + + hbox.pack_end(self.interval_entry, expand=False, fill=False) + vbox.pack_start(hbox, expand=False, fill=False) + + sensors_list = SensorsListModel(self) + vbox.pack_start(sensors_list.get_view()) + notebook.append_page(vbox, gtk.Label(_('Advanced'))) + # }}} + + # footer {{{ + vbox = self.get_content_area() + vbox.pack_start(notebook) buttons = gtk.HButtonBox() buttons.set_layout(gtk.BUTTONBOX_EDGE) - test = gtk.Button('Test') + test = gtk.Button(_('Test')) test.connect('clicked', self.update_parent) buttons.pack_start(test) + # TODO: add an info message on hover + cancel = gtk.Button(stock=gtk.STOCK_CANCEL) - cancel.connect('clicked', self.on_destroy) + cancel.connect('clicked', self.on_cancel) buttons.pack_end(cancel) + close = gtk.Button(stock=gtk.STOCK_SAVE) close.connect('clicked', self.on_save) buttons.pack_end(close) - vb.pack_end(buttons, expand=False) - - self.add(vb) - self.set_title('Preferences') - self.resize(400, 350) + vbox.pack_end(buttons, expand=False) + # }}} - def run(self): - self.set_position(gtk.WIN_POS_CENTER_ALWAYS) - self.show_all() - self.set_data() - gtk.main() - - def on_destroy(self, event=None, data=None): - self.hide() - gtk.main_quit() - return False + def on_save(self, evnt=None, data=None): + """The action of the save button.""" + try: + self.update_parent() + except Exception as ex: + error_dialog = gtk.MessageDialog( + None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, ex.message) + error_dialog.set_title("Error") + error_dialog.run() + error_dialog.destroy() + return False - def on_save(self, event=None, data=None): - self.update_parent() self.ind_parent.save_settings() self.update_autostart() - self.on_destroy() + self.destroy() - def on_cancel(self, event=None, data=None): + def on_cancel(self, evnt=None, data=None): + """The action of the cancel button.""" self.ind_parent.load_settings() - self.on_destroy() + self.destroy() - def set_data(self): - self.custom_entry.set_text(self.ind_parent.custom_text) - self.user_entry.set_text(self.ind_parent.user_command) - self.interval_entry.set_text(str(self.ind_parent.interval)) - if self.ind_parent.mode_user: - self.user_radio.set_active(True) - else: - self.custom_radio.set_active(True) - - def update_parent(self, event=None): + def update_parent(self, evnt=None, data=None): + """It gets the config info from the widgets and sets them to the vars. + It does NOT update the config file.""" custom_text = self.custom_entry.get_text() - user_text = self.user_entry.get_text() - try: interval = int(self.interval_entry.get_text()); assert interval > 0 - except: interval = self.ind_parent.interval - mode_user = [r for r in self.custom_radio.get_group() if r.get_active()][0] - self.ind_parent.custom_text = custom_text - self.ind_parent.user_command = user_text - self.ind_parent.mode_user = (mode_user == self.user_radio) - self.ind_parent.interval = interval - self.ind_parent.force_update() + + # check if the sensers are supported + sensors = Preferences.sensors_regex.findall(custom_text) + for sensor in sensors: + sensor = sensor[1:-1] + if not Sensor.exists(sensor): + raise ISMError(_("{{{}}} sensor not supported."). + format(sensor)) + # Check if the sensor is well-formed + Sensor.check(sensor) + + try: + interval = float(self.interval_entry.get_text()) + if interval <= 0: + raise ISMError(_("Interval value is not valid.")) + + except ValueError: + raise ISMError(_("Interval value is not valid.")) + + settings["custom_text"] = custom_text + settings["interval"] = interval + # TODO: on_startup + self.ind_parent.update_indicator_guide() + + def set_data(self): + """It sets the widgets with the config data.""" + self.custom_entry.set_text(settings["custom_text"]) + self.interval_entry.set_text(str(settings["interval"])) def update_autostart(self): autostart = self.autostart_check.get_active() if not autostart: try: os.remove(Preferences.AUTOSTART_PATH) - except: pass + except: + pass else: try: - shutil.copy(Preferences.DESKTOP_PATH, Preferences.AUTOSTART_PATH) - except Exception as e: - logging.exception(e) + shutil.copy(Preferences.DESKTOP_PATH, + Preferences.AUTOSTART_PATH) + except Exception, ex: + logging.exception(ex) def get_autostart(self): return os.path.exists(Preferences.AUTOSTART_PATH) -class IndicatorSysmonitor: + +class IndicatorSysmonitor(object): SETTINGS_FILE = os.getenv("HOME") + '/.indicator-sysmonitor.json' SENSORS_DISABLED = False def __init__(self): - self.preferences_dialog = None - self.custom_text = 'cpu: {cpu} mem: {mem}' - self.user_command = '' - self.last_data, self.last_text, self.last_guide = {}, '', '' - self.mode_user = False - self.sensors_disabled = IndicatorSysmonitor.SENSORS_DISABLED - self.interval = 2 + self._preferences_dialog = None + self._help_dialog = None + self._fetcher = StatusFetcher(self) + self.alive = Event() - self.ind = appindicator.Indicator ("indicator-sysmonitor", + self.ind = appindicator.Indicator("indicator-sysmonitor", "sysmonitor", appindicator.CATEGORY_SYSTEM_SERVICES) - self.ind.set_status (appindicator.STATUS_ACTIVE) + self.ind.set_status(appindicator.STATUS_ACTIVE) self.ind.set_label("Init...") - self.menu = gtk.Menu() - - full_sysmon = gtk.MenuItem('System Monitor') - full_sysmon.connect('activate', self.on_full_sysmon_activated) - self.menu.add(full_sysmon) - self.menu.add(gtk.SeparatorMenuItem()) + self._create_menu() + self.load_settings() + self.alive.set() + self._fetcher.start() + logging.info("Fetcher started") - pref_menu = gtk.MenuItem('Preferences') + def _create_menu(self): + """Creates the main menu and shows it.""" + # create menu {{{ + menu = gtk.Menu() + # add System Monitor menu item + full_sysmon = gtk.MenuItem(_('System Monitor')) + full_sysmon.connect('activate', self.on_full_sysmon_activated) + menu.add(full_sysmon) + menu.add(gtk.SeparatorMenuItem()) + + # add preferences menu item + pref_menu = gtk.ImageMenuItem(stock_id=gtk.STOCK_PREFERENCES) pref_menu.connect('activate', self.on_preferences_activated) - self.menu.add(pref_menu) + menu.add(pref_menu) + + # add help menu item + help_menu = gtk.ImageMenuItem(stock_id=gtk.STOCK_HELP) + help_menu.connect('activate', self._on_help) + menu.add(help_menu) + #add preference menu item exit_menu = gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT) exit_menu.connect('activate', self.on_exit) - self.menu.add(exit_menu) - - self.menu.show_all() - - self.ind.set_menu(self.menu) + menu.add(exit_menu) + menu.show_all() + self.ind.set_menu(menu) logging.info("Menu shown") + # }}} menu done! - self.load_settings() + def update_indicator_guide(self): + """Updates the label guide from appindicator.""" + data = self._fetcher.fetch() + for key in data: + if key.startswith('fs'): + data[key] = '000gB' + break - self.alive = Event() - self.alive.set() - self.fetch = StatusFetcher(self) - self.fetch.start() - logging.info("Fetcher started") + data['mem'] = data['cpu'] = data['bat'] = '000%' + data['net'] = '↓666kB/s ↑666kB/s' + + guide = settings['custom_text'].format(**data) + self.ind.set_property("label-guide", guide) def update(self, data): + """It updates the appindicator text with the the values + from data""" try: - label = self.custom_text.format(**data) - cdata = deepcopy(data) - cdata['mem'] = cdata['cpu'] = cdata['bat'] = '000%' - cdata['net'] = '' - guide = self.custom_text.format(**cdata) - except KeyError as e: - logging.exception(e) - logging.info('not found in dataset') - return - except: - label = 'Unknown error' - if not label: - label = '(no output)' - self.last_data = data - self.last_guide = guide - self.update_text(label, guide) - - def update_text(self, text, guide): - self.last_text = text - self.last_guide = guide - self.ind.set_label(text, guide) - - def force_update(self): - if self.mode_user: - self.update_text(self.last_text, self.last_guide) - else: - self.update(self.last_data) + label = settings["custom_text"].format(**data) if len(data)\ + else _("(no output)") - def on_exit(self, event=None): - logging.info("Terminated") - self.alive.clear() - try: gtk.main_quit() - except RuntimeError: pass + except KeyError as ex: + label = _("Invalid Sensor: {}").format(ex.message) + except Exception as ex: + logging.exception(ex) + label = _("Unknown error: ").format(ex.message) + + self.ind.set_label(label) def load_settings(self): + """It gets the settings from the config file and + sets them to the correct vars""" try: with open(IndicatorSysmonitor.SETTINGS_FILE, 'r') as f: - settings = json.load(f) - self.mode_user = settings['mode_user'] - self.custom_text = settings['custom_text'] - self.user_command = settings['user_command'] - self.interval = settings['interval'] - self.sensors_disabled = settings.get('sensors_disabled', IndicatorSysmonitor.SENSORS_DISABLED) - except Exception as e: - logging.exception(e) + cfg = json.load(f) + + if cfg['custom_text'] is not None: + settings['custom_text'] = cfg['custom_text'] + if cfg['interval'] is not None: + settings['interval'] = cfg['interval'] + if cfg['on_startup'] is not None: + settings['on_startup'] = cfg['on_startup'] + if cfg['sensors'] is not None: + settings['sensors'] = cfg['sensors'] + + Sensor.update_regex() + self.update_indicator_guide() + + except Exception as ex: + logging.exception(ex) logging.error('Reading settings failed') - def save_settings(self): + @staticmethod + def save_settings(): + """It stores the current settings to the config file.""" # TODO: use gsettings - settings = {'mode_user': self.mode_user, - 'custom_text': self.custom_text, - 'user_command': self.user_command, - 'interval': self.interval, - 'sensors_disabled': self.sensors_disabled} - try: - with open(IndicatorSysmonitor.SETTINGS_FILE, 'w') as f: - f.write(json.dumps(settings)) - except Exception as e: - logging.exception(e) + try: + with open(IndicatorSysmonitor.SETTINGS_FILE, 'w') as f: + f.write(json.dumps(settings)) + + except Exception as ex: + logging.exception(ex) logging.error('Writting settings failed') + # actions raised from menu def on_preferences_activated(self, event=None): - self.preferences_dialog = Preferences(self) - self.preferences_dialog.run() + """Raises the preferences dialog. If it's already open, it's + focused""" + if self._preferences_dialog is not None: + self._preferences_dialog.present() + return + + self._preferences_dialog = Preferences(self) + self._preferences_dialog.run() + self._preferences_dialog = None def on_full_sysmon_activated(self, event=None): os.system('gnome-system-monitor &') -from optparse import OptionParser + def on_exit(self, event=None, data=None): + """Action call when the main programs is closed.""" + # close the open dialogs + if self._help_dialog is not None: + self._help_dialog.destroy() + + if self._preferences_dialog is not None: + self._preferences_dialog.destroy() + + logging.info("Terminated") + self.alive.clear() + try: + gtk.main_quit() + except RuntimeError: + pass + + def _on_help(self, event=None, data=None): + """Raise a dialog with info about the app.""" + if self._help_dialog is not None: + self._help_dialog.present() + return + + self._help_dialog = gtk.MessageDialog( + None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, + gtk.BUTTONS_OK, None) + + self._help_dialog.set_title(_("Help")) + self._help_dialog.set_markup(HELP_MSG) + self._help_dialog.run() + self._help_dialog.destroy() + self._help_dialog = None + + +from optparse import OptionParser # TODO: optparse is deprecated if __name__ == "__main__": - parser = OptionParser("usage: %prog [options]", version="%prog "+VERSION) + parser = OptionParser("usage: %prog [options]", version="%prog " + VERSION) parser.add_option("--config", "", default=None, - help="use custom config file") - parser.add_option("--disable-sensors", action="store_true", - help="disable sensors", default=False) - parser.add_option("--enable-sensors", action="store_true", - help="re-enable sensors", default=False) + help=_("Use custom config file.")) (options, args) = parser.parse_args() if options.config: if not os.path.exists(options.config): - print options.config, "does not exist!" + logging.error(_("{} does not exist!").format(options.config)) sys.exit(-1) + logging.info(_("Using config file: {}").format(options.config)) IndicatorSysmonitor.SETTINGS_FILE = options.config - # setup an instance with config - i = IndicatorSysmonitor() - if options.disable_sensors: - i.sensors_disabled = True - i.save_settings() - logging.info("Sensors disabled") - - if options.enable_sensors: - i.sensors_disabled = False - i.save_settings() - logging.info("Sensors enabled") + if not os.path.exists(IndicatorSysmonitor.SETTINGS_FILE): + IndicatorSysmonitor.save_settings() + # setup an instance with config + app = IndicatorSysmonitor() try: gtk.main() except KeyboardInterrupt: - i.on_exit() + app.on_exit() diff -Nru indicator-sysmonitor-0.4.1~quantal/debian/changelog indicator-sysmonitor-0.4.2/debian/changelog --- indicator-sysmonitor-0.4.1~quantal/debian/changelog 2012-11-07 18:21:10.000000000 +0000 +++ indicator-sysmonitor-0.4.2/debian/changelog 2012-11-27 16:24:08.000000000 +0000 @@ -1,9 +1,16 @@ -indicator-sysmonitor (0.4.1~quantal) quantal; urgency=low +indicator-sysmonitor (0.4.2) quantal; urgency=low + + * bump version for quantal + * refactor from Gabriel + + -- Alex Eftimie (alexef) Tue, 27 Nov 2012 16:23:57 +0300 + +indicator-sysmonitor (0.4.1) precise; urgency=low * bump version for precise * minor changes - -- Umair Riaz Wed, 07 Nov 2012 20:20:37 +0200 + -- Alex Eftimie (alexef) Sat, 30 Jun 2012 10:20:57 +0300 indicator-sysmonitor (0.3.9~oneiric0) oneiric; urgency=low diff -Nru indicator-sysmonitor-0.4.1~quantal/debian/control indicator-sysmonitor-0.4.2/debian/control --- indicator-sysmonitor-0.4.1~quantal/debian/control 2012-11-07 18:21:20.000000000 +0000 +++ indicator-sysmonitor-0.4.2/debian/control 2012-11-27 16:24:08.000000000 +0000 @@ -1,11 +1,11 @@ Source: indicator-sysmonitor Section: gnome Priority: extra -Maintainer: Umair Riaz , Ubuntu MOTU Team +Maintainer: Ubuntu MOTU Team XSBC-Original-Maintainer: Alex Eftimie Build-Depends: debhelper (>= 7), cdbs Build-Depends-Indep: python, python-central -Standards-Version: 3.9.3 +Standards-Version: 3.9.1 XS-Python-Version: all Package: indicator-sysmonitor diff -Nru indicator-sysmonitor-0.4.1~quantal/debian/copyright indicator-sysmonitor-0.4.2/debian/copyright --- indicator-sysmonitor-0.4.1~quantal/debian/copyright 2012-11-07 18:21:30.000000000 +0000 +++ indicator-sysmonitor-0.4.2/debian/copyright 2012-11-27 16:24:08.000000000 +0000 @@ -1,5 +1,5 @@ -This package was debianized by Umair Riaz - http://www.NoobsLab.com - +This package was debianized by Alex Eftimie on +Fri Mar 25 14:39:23 EET 2011 Upstream Author(s): diff -Nru indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor indicator-sysmonitor-0.4.2/indicator-sysmonitor --- indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor 2012-06-30 10:25:50.000000000 +0000 +++ indicator-sysmonitor-0.4.2/indicator-sysmonitor 2012-11-27 16:24:08.000000000 +0000 @@ -7,39 +7,211 @@ # Homepage: http://launchpad.net/indicator-sysmonitor # License: GPL v3 # +from gettext import gettext as _ +from gettext import textdomain, bindtextdomain +textdomain("indicator-sysmonitor") +bindtextdomain("indicator-sysmonitor", "./lang") + import sys import os import shutil import json -import string import time -from copy import deepcopy from threading import Thread, Event import subprocess import psutil as ps - -import logging -logging.basicConfig(file=sys.stderr,level=logging.INFO) - -import gobject +import re import gtk +gtk.gdk.threads_init() import appindicator +import logging +logging.basicConfig(file=sys.stderr, level=logging.INFO) -VERSION='0.4.1~unreleased' +B_UNITS = ['', 'KB', 'MB', 'GB', 'TB'] +VERSION = '0.5.1~unreleased' +HELP_MSG = """{title} + +{introduction} + +{basic} +• cpu: {cpu_desc} +• mem: {mem_desc} +• bat%d: {bat_desc} +• net: {net_desc} + +{compose} +• fs//mount-point : {fs_desc} + +{example} +CPU {{cpu}} | MEM {{mem}} | root {{fs///}} +""".format( + title=_("Help Page"), + introduction=_("The sensors are the names of the devices you want to \ + retrieve information from. They must be placed between brackets."), + basic=_("The basics are:"), + cpu_desc=_("It shows the average of CPU usage."), + mem_desc=_("It shows the physical memory in use."), + bat_desc=_("It shows the available battery which id is %d."), + net_desc=_("It shows the amount of data you are downloading and uploading \ + through your network."), + compose=_("Also there are the following sensors that are composed with \ + two parts divided by two slashes."), + fs_desc=_("Show available space in the file system."), + example=_("Example:")) + +supported_sensors = re.compile("\A(mem|swap|cpu\d*|net|bat\d*|fs//.+)\Z") +settings = { + 'custom_text': 'cpu: {cpu} mem: {mem}', + 'interval': 2, + 'on_startup': False, + 'sensors': { + # 'name' => (desc, cmd) + 'cpu\d*': (_('Average CPU usage'), True), + 'mem': (_('Physical memory in use.'), True), + 'net': (_('Network activity.'), True), + 'bat\d*': (_('Network activity.'), True), + 'fs//.+': (_('Available space in file system.'), True), + "swap": (_("Average swap usage"), True) + + } + } + + +class ISMError(Exception): + """General exception.""" + def __init__(self, msg): + Exception.__init__(self, msg) + + +def raise_dialog(parent, flags, type_, buttons, msg, title): + """It raise a dialog. It a blocking function.""" + dialog = gtk.MessageDialog( + parent, flags, type_, buttons, msg) + dialog.set_title(title) + dialog.run() + dialog.destroy() + + +class Sensor(object): + """Singleton""" + _instance = None + bat = re.compile("\Abat\d*\Z") + cpus = re.compile("\Acpu\d+\Z") + + def __init__(self): + """It must not be called. Use Sensor.get_instace() + to retrieve an instance of this class.""" + if Sensor._instance is not None: + raise Exception("Sensor class can not be instanceted twice.") + else: + Sensor._instance = self + self.update_regex() + + @staticmethod + def update_regex(names=None): + if names is None: + names = settings["sensors"].keys() + + reg = '|'.join(names) + reg = "\A({})\Z".format(reg) + global supported_sensors + supported_sensors = re.compile("{}".format(reg)) + + @classmethod + def get_instance(cls): + """Returns the unique instance of Sensor.""" + if Sensor._instance is None: + Sensor._instance = Sensor() + + return Sensor._instance + + @staticmethod + def exists(name): + """Checks if the sensor name exists""" + return bool(supported_sensors.match(name)) + + @staticmethod + def check(sensor): + if sensor.startswith("fs//"): + path = sensor.split("//")[1] + if not os.path.exists(path): + raise ISMError(_("Path: {} doesn't exists.").format(path)) + + elif Sensor.cpus.match(sensor): + nber = int(sensor[3:]) + if nber >= ps.NUM_CPUS: + raise ISMError(_("Invalid number of CPU.")) + + elif Sensor.bat.match(sensor): + bat_id = int(sensor[3:]) if len(sensor) > 3 else 0 + if bat_id >= len(os.listdir("/proc/acpi/battery/")): + raise ISMError(_("Invalid number of Batterry.")) + + def add(self, name, desc, cmd): + """Adds a custom sensors.""" + if Sensor.exists(name): + raise ISMError(_("Sensor name already in use.")) + + settings["sensors"][name] = (desc, cmd) + self.update_regex() + + def delete(self, name): + """Deletes a custom sensors.""" + sensors = settings['sensors'] + names = sensors.keys() + if name not in names: + raise ISMError(_("Sensor is not defined.")) + + _desc, default = sensors[name] + if default is True: + raise ISMError(_("Can not delete default sensors.")) + + del sensors[name] + self.update_regex() + + def edit(self, name, newname, desc, cmd): + """Edits a custom sensors.""" + try: + sensors = settings['sensors'] + _desc, default = sensors[name] + + except KeyError: + raise ISMError(_("Sensor does not exists.")) + + if default is True: + raise ISMError(_("Can not edit default sensors.")) + if newname != name: + if newname in sensors.keys(): + raise ISMError(_("Sensor name already in use.")) + + sensors[newname] = (desc, cmd) + del sensors[name] + settings["custom_text"] = settings["custom_text"].replace( + name, newname) + self.update_regex() -gtk.gdk.threads_init() class StatusFetcher(Thread): + """It recollects the info about the sensors.""" + digit_regex = re.compile(r'''\d+''') + def __init__(self, parent): Thread.__init__(self) - self.parent = parent + self._parent = parent self.last = ps.cpu_times() - def _fetch_cpu(self): + def _fetch_cpu(self, percpu=False): + if percpu: + return ps.cpu_percent(interval=0, percpu=True) + last = self.last current = ps.cpu_times() - total_time_passed = sum([v-last.__dict__[k] if not isinstance(v,list) else 0 for k,v in current.__dict__.iteritems()]) + total_time_passed = sum( + [v - last.__dict__[k] + if not isinstance(v, list) + else 0 + for k, v in current.__dict__.iteritems()]) sys_time = current.system - last.system usr_time = current.user - last.user @@ -53,546 +225,659 @@ else: return 0 - def _fetch_mem(self): - total_mem = subprocess.Popen("free -b | grep Mem | tr -s ' ' | cut -d\ -f 2", - stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - free_mem = subprocess.Popen("free -b | grep Mem | tr -s ' ' | cut -d\ -f 4-", - stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - free_mem = sum([int(i) for i in free_mem.split()]) - return 100 - 100 * float(free_mem) / float(total_mem) + def _fetch_swap(self): + """Return the swap usage in percent""" + usage = 0 + total = 0 + try: + with open("/proc/swaps") as swaps: + swaps.readline() + for line in swaps.readlines(): + dummy, dummy, total_, usage_, dummy = line.split() + total += int(total_) + usage += int(usage_) - def _fetch_bat(self, bat_id): - current_bat = subprocess.Popen("grep 'remaining capacity:' /proc/acpi/battery/BAT%s/state |awk '{print $3}' |grep [0-9] || echo 10" % bat_id, - stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - #total_bat = subprocess.Popen("cat /proc/acpi/battery/BAT0/info |grep -i 'design capacity:' |awk '{print $3}'", - total_bat = subprocess.Popen("grep 'last full capacity:' /proc/acpi/battery/BAT%s/info |awk '{print $4}' |grep [0-9] || echo 65130" % bat_id, - stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - return 100 * float(current_bat) / float(total_bat) + return usage * 100 / total - def _fetch_net(self): - 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() - return total_net + except IOError: + return "N/A" - def _fetch_sensor(self, sensor_name): - sensor_data = sensor_name.split('//') + def _fetch_mem(self): + """It gets the total memory info and return the used in percent.""" + with open('/proc/meminfo') as meminfo: + total = StatusFetcher.digit_regex.findall(meminfo.readline()).pop() + free = StatusFetcher.digit_regex.findall(meminfo.readline()).pop() + meminfo.readline() + cached = StatusFetcher.digit_regex.findall( + meminfo.readline()).pop() + free = int(free) + int(cached) + return 100 - 100 * free / float(total) - if (len(sensor_data) != 2): - return 'N/A' + def _fetch_bat(self, batid): + """Fetch the the amount of remaining battery""" + try: + with open("/proc/acpi/battery/BAT{}/state".format(batid)) as state: + while True: + line = state.readline() + if "remaining capacity:" in line: + remaining = int(line.split()[2]) + break + + with open("/proc/acpi/battery/BAT{}/info".format(batid)) as info: + while True: + line = info.readline() + if "last full capacity" in line: + capacity = int(line.split()[3]) + break - sensor_item = sensor_data[1].replace('-', '.') - postfix = '' - if sensor_data[0] == 'hddtemp': - sensor_cmd = 'netcat localhost 7634' - netcat_value = subprocess.Popen(sensor_cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - sensor_value = '' - - for hd_data in self.parse_hddtemp_entries(netcat_value): - if hd_data[0] == sensor_item: - sensor_value = hd_data[2] + '°' + hd_data[3] - else: - sensor_cmd = 'sensors -A ' + sensor_data[0] + " | grep -i '" + sensor_item + "' | cut -f 2 -d ':' | awk '{print $1}'" - if sensor_data[0] == 'nvidia': - if sensor_item == 'gputemp': - sensor_cmd = 'nvidia-settings -q [gpu:0]/GPUCoreTemp | grep "Attribute" | sed -e "s/.*: //g" -e "s/\.//g"' - postfix = '°C' - elif sensor_item == 'fanspeed': - sensor_cmd = 'nvidia-settings -q [fan:0]/GPUCurrentFanSpeed | grep "Attribute" | sed -e "s/.*: //g" -e "s/\.//g"' - postfix = ' RPM' - else: - sensor_cmd = None - elif sensor_data[0] == 'ati': - if sensor_item == 'gputemp': - sensor_cmd = 'aticonfig --adapter=0 --od-gettemperature | grep "Sensor 0:" | awk ' + "'{ printf(\"%d\", $5) }'" - postfix = '°C' - - sensor_value = subprocess.Popen(sensor_cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - - if len(sensor_value): - sensor_value = self._clean_value(sensor_value) # Cleaning the + prefix - return sensor_value + postfix - else: - if sensor_cmd == None: - logging.info('Sensor not supported: ' + sensor_name) - else: - logging.info('Sensor command failed:\n' + sensor_cmd) - return 'N/A' - - def _clean_value(self, value): - if value.find('+') == 0: - return value[1:] - return value - - def _get_sensors_to_fetch(self): - fmt = string.Formatter() - tokens = [] - for token in fmt.parse(self.parent.custom_text): - tokens.append(str(token[1])) + except IOError: + return "N/A" + + return remaining * 100.0 / capacity - return tokens + def _fetch_net(self): + """It use the command "ifstat" to know the total net usage""" + 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() + return total_net - def _fetch(self): + def fetch(self): + """Return a dict whose element are the sensors + and their values""" res = {} - for sensor in self._get_sensors_to_fetch(): + cpus = None + for sensor in Preferences.sensors_regex.findall( + settings["custom_text"]): + sensor = sensor[1:-1] if sensor == 'cpu': - res['cpu'] = '%02.0f%%' % self._fetch_cpu() + res['cpu'] = "{:02.0f}%".format(self._fetch_cpu()) + elif Sensor.cpus.match(sensor): + if cpus is None: + cpus = self._fetch_cpu(percpu=True) + res[sensor] = "{:02.0f}%".format(cpus[int(sensor[3:])]) + elif sensor == 'mem': - res['mem'] = '%02.0f%%' % self._fetch_mem() - elif sensor.startswith('bat'): - bat_id = sensor[3:] - try: - bat_id = int(bat_id) - except: - bat_id = 0 - res[sensor] = '%02.0f%%' % self._fetch_bat(bat_id) + res['mem'] = '{:02.0f}%'.format(self._fetch_mem()) elif sensor == 'net': res['net'] = self._fetch_net() - else: - res[sensor] = '%s' % self._fetch_sensor(sensor) + + elif Sensor.bat.match(sensor): + bat_id = int(sensor[3:]) if len(sensor) > 3 else 0 + res[sensor] = '{:02.0f}%'.format(self._fetch_bat(bat_id)) + + elif sensor.startswith('fs//'): + parts = sensor.split('//') + res[sensor] = self._fetch_fs(parts[1]) + + elif sensor == "swap": + res[sensor] = '{:02.0f}%'.format(self._fetch_swap()) + + else: # custom sensor + res[sensor] = self._exec(settings["sensors"][sensor][1]) return res - def _fetch_user(self, command): + def _exec(self, command): + """Execute a custom command.""" try: - output = subprocess.Popen(command, stdout=subprocess.PIPE, - shell=True).communicate()[0].strip() - if output == '': - output = '(no output)' + output = subprocess.Popen(command, stdout=subprocess.PIPE, + shell=True).communicate()[0].strip() except: - output = 'error' - logging.error('Error running: '+command) - return output - - def parse_hddtemp_entries(self, netcat_value): - hddtemp_entries = [] - - if len(netcat_value): - for hd_row in netcat_value.strip('|').split('||'): - hddtemp_entries.append(hd_row.split('|')) + output = _("Error") + logging.error(_("Error running: {}").format(command)) + + return output if output else _("(no output)") - return hddtemp_entries + def _fetch_fs(self, mount_point): + """It returns the amount of bytes available in the fs in + a human-readble format.""" + if not os.access(mount_point, os.F_OK): + return None + + stat = os.statvfs(mount_point) + bytes_ = stat.f_bavail * stat.f_frsize + + for unit in B_UNITS: + if bytes_ < 1024: + return "{} {}".format(bytes_, unit) + bytes_ /= 1024 def run(self): - while(self.parent.alive.isSet()): - if self.parent.mode_user: - output = self._fetch_user(self.parent.user_command) - self.parent.update_text(output, output) - else: - data = self._fetch() - self.parent.update(data) - time.sleep(self.parent.interval) + """It is the main loop.""" + while self._parent.alive.isSet(): + data = self.fetch() + self._parent.update(data) + time.sleep(settings["interval"]) + + +class SensorsListModel(object): + """A TreeView showing the available sensors. It allows to + add/edit/delete custom sensors.""" -class SensorsListModel: def __init__(self, parent): self.ind_parent = parent - self.tree_store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) + self._list_store = gtk.ListStore(str, str) + self._tree_view = gtk.TreeView(self._list_store) - # lib sensors - for sensor_data in subprocess.Popen('sensors', stdout=subprocess.PIPE, shell=True).communicate()[0].split('\n\n'): - sensor_name = None - skip_line = False - for line in sensor_data.split('\n'): - if len(line): - if skip_line: - skip_line = False - else: - if sensor_name == None: - logging.info("New sensor found: " + line) - sensor_name = line - parent = self.tree_store.append(None, (sensor_name, None, False)) - skip_line = True - else: - logging.info("Sensor entry: " + line) - self.tree_store.append(parent, (line, self.generate_sensor_item_name(sensor_name, line), False)) - - # hddtemp - hddtemp = subprocess.Popen('netcat localhost 7634', stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - if len(hddtemp): - sensor_name = 'hddtemp' - logging.info("New sensor found: " + sensor_name) - parent = self.tree_store.append(None, (sensor_name, None, False)) - for hd_data in self.ind_parent.ind_parent.fetch.parse_hddtemp_entries(hddtemp): - logging.info("Sensor entry: " + hd_data[0]) - 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)) - - # nvidia GPU - nvidia_model = subprocess.Popen("lspci | grep nVidia | sed -e 's/.*\[//g' -e 's/\].*//g'", stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - if len(nvidia_model): - sensor_name = 'nvidia' - logging.info("New sensor found: " + sensor_name) - parent = self.tree_store.append(None, (sensor_name, None, False)) - self.tree_store.append(parent, (nvidia_model + ' - Temperature', self.generate_sensor_item_name(sensor_name, 'gputemp'), False)) - self.tree_store.append(parent, (nvidia_model + ' - Fan speed', self.generate_sensor_item_name(sensor_name, 'fanspeed'), False)) - - # ati GPU - ati_model = subprocess.Popen('lspci | grep \"ATI Radeon HD"' + " | sed -e 's/.*\[//g' -e 's/\].*//g'", stdout=subprocess.PIPE, shell=True).communicate()[0].strip() - if len(ati_model): - sensor_name = 'ati' - logging.info("New sensor found: " + sensor_name) - parent = self.tree_store.append(None, (sensor_name, None, False)) - self.tree_store.append(parent, (ati_model + ' - Temperature', self.generate_sensor_item_name(sensor_name, 'gputemp'), False)) + sensors = settings['sensors'] + for name in sensors.keys(): + self._list_store.append([name, sensors[name][0]]) def get_view(self): - self.view = gtk.HBox() - - self.vb = gtk.VBox(False, 3) - l = gtk.Label('Sensors:') - l.set_alignment(0, 0.5) - self.vb.pack_start(l, expand=False, fill=False) - - self.tree_view = gtk.TreeView(self.tree_store) - - # setup the text cell renderer - self.renderer = gtk.CellRendererText() - self.renderer.set_property('editable', False) - - # sensor name render - self.renderer1 = gtk.CellRendererText() - self.renderer1.set_property('editable', False) - - # quick add checkbox render - self.renderer2 = gtk.CellRendererToggle() - self.renderer2.set_property('activatable', True) - self.renderer2.connect('toggled', self.quick_add_cb_toggled, self.tree_store) - + """It's call from Preference. It create the view and return it""" + vbox = gtk.VBox(False, 3) # create columns - self.column0 = gtk.TreeViewColumn('Sensor', self.renderer, text=0) - self.column1 = gtk.TreeViewColumn('Identifier', self.renderer1, text=1) - self.column2 = gtk.TreeViewColumn('', self.renderer2, active=2) - self.tree_view.append_column(self.column0) - self.tree_view.append_column(self.column1) - self.tree_view.append_column(self.column2) + renderer = gtk.CellRendererText() + renderer.set_property('editable', False) + column = gtk.TreeViewColumn(_('Sensor'), renderer, text=0) + self._tree_view.append_column(column) + + renderer = gtk.CellRendererText() + renderer.set_property('editable', False) + column = gtk.TreeViewColumn(_('Description'), renderer, text=1) + self._tree_view.append_column(column) - self.tree_view.expand_all() + self._tree_view.expand_all() sw = gtk.ScrolledWindow() - sw.add_with_viewport(self.tree_view) - self.vb.pack_start(sw, fill=True, expand=True) + sw.add_with_viewport(self._tree_view) + vbox.pack_start(sw, fill=True, expand=True) - self.add_bt = gtk.Button('Add selected sensors') - self.add_bt.connect('clicked', self.update_custom_text) - self.vb.pack_end(self.add_bt, fill=False, expand=False) - - self.view.add(self.vb) - self.view.show() - - return self.view - - def quick_add_cb_toggled(self, cell, path, tree_store): - tree_store[path][2] = not tree_store[path][2] - iter = tree_store.iter_children(tree_store.get_iter(path)) - while iter: - tree_store.set_value(iter, 2, tree_store[path][2]) - iter = tree_store.iter_next(iter) - - def generate_sensor_item_name(self, sensor_name, sensor_item_label): - return sensor_name + '//' + sensor_item_label.split(':')[0].lower().replace('.', '-') - - def update_custom_text(self, event=None): - iter = self.tree_store.get_iter_root() - - while iter: - iter_children = self.tree_store.iter_children(iter) - while iter_children: - if self.tree_store.get_value(iter_children, 2): - current_text = self.ind_parent.custom_entry.get_text() - to_add_value = '{' + self.tree_store.get_value(iter_children, 1) + '}' - if string.find(current_text, to_add_value) == -1: - self.ind_parent.custom_entry.set_text(current_text + ' ' + to_add_value) - iter_children = self.tree_store.iter_next(iter_children) + # add buttons + hbox = gtk.HBox() + new_button = gtk.Button(stock=gtk.STOCK_NEW) + new_button.connect('clicked', self._on_edit_sensor) + # hbox.pack_start(new_button, fill=False, expand=False) + hbox.pack_start(new_button, fill=False, expand=False) + + edit_button = gtk.Button(stock=gtk.STOCK_EDIT) + edit_button.connect('clicked', self._on_edit_sensor, False) + hbox.pack_start(edit_button, fill=False, expand=False) + + del_button = gtk.Button(stock=gtk.STOCK_DELETE) + del_button.connect('clicked', self._on_del_sensor) + hbox.pack_start(del_button, fill=False, expand=False) + + add_button = gtk.Button(stock=gtk.STOCK_ADD) + add_button.connect('clicked', self._on_add_sensor) + hbox.pack_end(add_button, fill=False, expand=False) + vbox.pack_end(hbox, fill=False, expand=False) + + frame = gtk.Frame(_('Sensors')) + frame.add(vbox) + return frame + + def _get_selected_row(self): + """Returns an iter for the selected rows in the view or None.""" + model, pathlist = self._tree_view.get_selection().get_selected_rows() + if len(pathlist): + path = pathlist.pop() + return model.get_iter(path) + return None + + def _on_add_sensor(self, evnt=None, data=None): + tree_iter = self._get_selected_row() + if tree_iter is None: + return - iter = self.tree_store.iter_next(iter) + sensor = self._list_store.get_value(tree_iter, 0) + self.ind_parent.custom_entry.insert_text( + "{{{}}}".format(sensor), -1) + + def _on_edit_sensor(self, evnt=None, blank=True): + """Raises a dialog with a form to add/edit a sensor""" + name = desc = cmd = "" + tree_iter = None + if not blank: + # edit, so get the info from the selected row + tree_iter = self._get_selected_row() + if tree_iter is None: + return + + name = self._list_store.get_value(tree_iter, 0) + desc = self._list_store.get_value(tree_iter, 1) + cmd = settings["sensors"][name][1] + + if cmd is True: # default sensor + raise_dialog( + self.ind_parent, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, + _("Can not edit defualt sensors."), _("Error")) + return + + dialog = gtk.Dialog(_("Edit Sensor"), self.ind_parent, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, + gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + vbox = dialog.get_content_area() + + hbox = gtk.HBox() + label = gtk.Label(_("Sensor")) + sensor_entry = gtk.Entry() + sensor_entry.set_text(name) + hbox.pack_start(label) + hbox.pack_end(sensor_entry) + vbox.pack_start(hbox) + + hbox = gtk.HBox() + label = gtk.Label(_("Description")) + desc_entry = gtk.Entry() + desc_entry.set_text(desc) + hbox.pack_start(label) + hbox.pack_end(desc_entry) + vbox.pack_start(hbox) + + hbox = gtk.HBox() + label = gtk.Label(_("Command")) + cmd_entry = gtk.Entry() + + cmd_entry.set_text(cmd) + hbox.pack_start(label) + hbox.pack_end(cmd_entry) + vbox.pack_end(hbox) -class Preferences(gtk.Window): - AUTOSTART_PATH = os.getenv("HOME") + '/.config/autostart/indicator-sysmonitor.desktop' + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_ACCEPT: + try: + newname, desc, cmd = str(sensor_entry.get_text()), \ + str(desc_entry.get_text()), str(cmd_entry.get_text()) + + if blank: + Sensor.get_instance().add(newname, desc, cmd) + else: + Sensor.get_instance().edit(name, newname, desc, cmd) + self._list_store.remove(tree_iter) + + self._list_store.append([newname, desc]) + ctext = self.ind_parent.custom_entry.get_text() + self.ind_parent.custom_entry.set_text( + ctext.replace(name, newname)) + + except ISMError as ex: + raise_dialog( + self.ind_parent, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, + ex.message, _("Error")) + + dialog.destroy() + + def _on_del_sensor(self, evnt=None, data=None): + """Remove a custom sensor.""" + tree_iter = self._get_selected_row() + if tree_iter is None: + return + + name = self._list_store.get_value(tree_iter, 0) + try: + Sensor.get_instance().delete(name) + self._list_store.remove(tree_iter) + ctext = self.ind_parent.custom_entry.get_text() + self.ind_parent.custom_entry.set_text( + ctext.replace("{{{}}}".format(name), "")) + + except ISMError as ex: + raise_dialog( + self.ind_parent, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, + ex.message, _("Error")) + + +class Preferences(gtk.Dialog): + """It define the the Preferences Dialog and its operations.""" + AUTOSTART_PATH = '{}/.config/autostart/indicator-sysmonitor.desktop'\ + .format(os.getenv("HOME")) DESKTOP_PATH = '/usr/share/applications/indicator-sysmonitor.desktop' + sensors_regex = re.compile("{.+?}") def __init__(self, parent): - gtk.Window.__init__(self) + """It creates the widget of the dialogs""" + gtk.Dialog.__init__(self) self.ind_parent = parent - self.connect('delete-event', self.on_destroy) + self.custom_entry = None + self.interval_entry = None + self._create_content() + self.set_data() + self.show_all() + def _create_content(self): + """It creates the content for this dialog.""" + self.connect('delete-event', self.on_cancel) + self.set_title(_('Preferences')) + self.resize(400, 350) + self.set_position(gtk.WIN_POS_CENTER_ALWAYS) notebook = gtk.Notebook() notebook.set_border_width(4) - vb = gtk.VBox(spacing=3) - hb = gtk.HBox() - hb.set_border_width(4) - l = gtk.Label('Run on startup:') - l.set_alignment(0, 0.5) - hb.pack_start(l) + # General page of the notebook {{{ + vbox = gtk.VBox(spacing=3) + hbox = gtk.HBox() + + hbox.set_border_width(4) + label = gtk.Label(_('Run on startup:')) + label.set_alignment(0, 0.5) + hbox.pack_start(label) self.autostart_check = gtk.CheckButton() self.autostart_check.set_active(self.get_autostart()) - hb.pack_end(self.autostart_check, expand=False, fill=False) - vb.pack_start(hb, expand=False, fill=False) + hbox.pack_end(self.autostart_check, expand=False, fill=False) + vbox.pack_start(hbox, expand=False, fill=False) - hb = gtk.HBox() - l = gtk.Label('This is indicator-sysmonitor version: %s' % VERSION) - l.set_alignment(0.5, 0.5) - hb.pack_start(l) - vb.pack_end(hb) - - notebook.append_page(vb, gtk.Label('General')) - - vb = gtk.VBox(spacing=3) - hb = gtk.VBox() - self.custom_radio = gtk.RadioButton(label='Customize output:') - hb.pack_start(self.custom_radio) + hbox = gtk.HBox() + label = gtk.Label( + _('This is indicator-sysmonitor version: {}').format(VERSION)) + label.set_alignment(0.5, 0.5) + hbox.pack_start(label) + vbox.pack_end(hbox) + notebook.append_page(vbox, gtk.Label(_('General'))) + # }}} + + # Advanced page in notebook {{{ + vbox = gtk.VBox() # main box + label = gtk.Label(_('Customize output:')) + label.set_alignment(0, 0) + vbox.pack_start(label, expand=False, fill=False) self.custom_entry = gtk.Entry() - hb.pack_end(self.custom_entry) - vb.pack_start(hb, expand=False, fill=False) + vbox.pack_start(self.custom_entry, expand=False, fill=False) - hb = gtk.VBox() - self.user_radio = gtk.RadioButton(group=self.custom_radio, label='Use this command:') - hb.pack_start(self.user_radio) - self.user_entry = gtk.Entry(max=100) - #info = gtk.Label('Use this option to specify a program to be run every time the indicator refreshes') - #info.set_line_wrap(True) - #hb.pack_end(info) - hb.pack_end(self.user_entry) - vb.pack_start(hb, expand=False, fill=False) - - hb = gtk.HBox() - l = gtk.Label('Update interval:') - l.set_alignment(0, 0.5) - hb.pack_start(l) + hbox = gtk.HBox() + label = gtk.Label(_('Update interval:')) + label.set_alignment(0, 0) + hbox.pack_start(label) self.interval_entry = gtk.Entry(max=4) - self.interval_entry.set_width_chars(3) - hb.pack_end(self.interval_entry, expand=False, fill=False) - vb.pack_start(hb, expand=False, fill=False) - - notebook.append_page(vb, gtk.Label('Advanced')) - - if not parent.sensors_disabled: - sensors_list = SensorsListModel(self) - if sensors_list.tree_store.get_iter_root() != None: - vb.pack_start(sensors_list.get_view()) - - # footer - vb = gtk.VBox() - vb.pack_start(notebook) + self.interval_entry.set_width_chars(5) + + hbox.pack_end(self.interval_entry, expand=False, fill=False) + vbox.pack_start(hbox, expand=False, fill=False) + + sensors_list = SensorsListModel(self) + vbox.pack_start(sensors_list.get_view()) + notebook.append_page(vbox, gtk.Label(_('Advanced'))) + # }}} + + # footer {{{ + vbox = self.get_content_area() + vbox.pack_start(notebook) buttons = gtk.HButtonBox() buttons.set_layout(gtk.BUTTONBOX_EDGE) - test = gtk.Button('Test') + test = gtk.Button(_('Test')) test.connect('clicked', self.update_parent) buttons.pack_start(test) + # TODO: add an info message on hover + cancel = gtk.Button(stock=gtk.STOCK_CANCEL) - cancel.connect('clicked', self.on_destroy) + cancel.connect('clicked', self.on_cancel) buttons.pack_end(cancel) + close = gtk.Button(stock=gtk.STOCK_SAVE) close.connect('clicked', self.on_save) buttons.pack_end(close) - vb.pack_end(buttons, expand=False) - - self.add(vb) - self.set_title('Preferences') - self.resize(400, 350) + vbox.pack_end(buttons, expand=False) + # }}} - def run(self): - self.set_position(gtk.WIN_POS_CENTER_ALWAYS) - self.show_all() - self.set_data() - gtk.main() - - def on_destroy(self, event=None, data=None): - self.hide() - gtk.main_quit() - return False + def on_save(self, evnt=None, data=None): + """The action of the save button.""" + try: + self.update_parent() + except Exception as ex: + error_dialog = gtk.MessageDialog( + None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, ex.message) + error_dialog.set_title("Error") + error_dialog.run() + error_dialog.destroy() + return False - def on_save(self, event=None, data=None): - self.update_parent() self.ind_parent.save_settings() self.update_autostart() - self.on_destroy() + self.destroy() - def on_cancel(self, event=None, data=None): + def on_cancel(self, evnt=None, data=None): + """The action of the cancel button.""" self.ind_parent.load_settings() - self.on_destroy() + self.destroy() - def set_data(self): - self.custom_entry.set_text(self.ind_parent.custom_text) - self.user_entry.set_text(self.ind_parent.user_command) - self.interval_entry.set_text(str(self.ind_parent.interval)) - if self.ind_parent.mode_user: - self.user_radio.set_active(True) - else: - self.custom_radio.set_active(True) - - def update_parent(self, event=None): + def update_parent(self, evnt=None, data=None): + """It gets the config info from the widgets and sets them to the vars. + It does NOT update the config file.""" custom_text = self.custom_entry.get_text() - user_text = self.user_entry.get_text() - try: interval = int(self.interval_entry.get_text()); assert interval > 0 - except: interval = self.ind_parent.interval - mode_user = [r for r in self.custom_radio.get_group() if r.get_active()][0] - self.ind_parent.custom_text = custom_text - self.ind_parent.user_command = user_text - self.ind_parent.mode_user = (mode_user == self.user_radio) - self.ind_parent.interval = interval - self.ind_parent.force_update() + + # check if the sensers are supported + sensors = Preferences.sensors_regex.findall(custom_text) + for sensor in sensors: + sensor = sensor[1:-1] + if not Sensor.exists(sensor): + raise ISMError(_("{{{}}} sensor not supported."). + format(sensor)) + # Check if the sensor is well-formed + Sensor.check(sensor) + + try: + interval = float(self.interval_entry.get_text()) + if interval <= 0: + raise ISMError(_("Interval value is not valid.")) + + except ValueError: + raise ISMError(_("Interval value is not valid.")) + + settings["custom_text"] = custom_text + settings["interval"] = interval + # TODO: on_startup + self.ind_parent.update_indicator_guide() + + def set_data(self): + """It sets the widgets with the config data.""" + self.custom_entry.set_text(settings["custom_text"]) + self.interval_entry.set_text(str(settings["interval"])) def update_autostart(self): autostart = self.autostart_check.get_active() if not autostart: try: os.remove(Preferences.AUTOSTART_PATH) - except: pass + except: + pass else: try: - shutil.copy(Preferences.DESKTOP_PATH, Preferences.AUTOSTART_PATH) - except Exception as e: - logging.exception(e) + shutil.copy(Preferences.DESKTOP_PATH, + Preferences.AUTOSTART_PATH) + except Exception, ex: + logging.exception(ex) def get_autostart(self): return os.path.exists(Preferences.AUTOSTART_PATH) -class IndicatorSysmonitor: + +class IndicatorSysmonitor(object): SETTINGS_FILE = os.getenv("HOME") + '/.indicator-sysmonitor.json' SENSORS_DISABLED = False def __init__(self): - self.preferences_dialog = None - self.custom_text = 'cpu: {cpu} mem: {mem}' - self.user_command = '' - self.last_data, self.last_text, self.last_guide = {}, '', '' - self.mode_user = False - self.sensors_disabled = IndicatorSysmonitor.SENSORS_DISABLED - self.interval = 2 + self._preferences_dialog = None + self._help_dialog = None + self._fetcher = StatusFetcher(self) + self.alive = Event() - self.ind = appindicator.Indicator ("indicator-sysmonitor", + self.ind = appindicator.Indicator("indicator-sysmonitor", "sysmonitor", appindicator.CATEGORY_SYSTEM_SERVICES) - self.ind.set_status (appindicator.STATUS_ACTIVE) + self.ind.set_status(appindicator.STATUS_ACTIVE) self.ind.set_label("Init...") - self.menu = gtk.Menu() - - full_sysmon = gtk.MenuItem('System Monitor') - full_sysmon.connect('activate', self.on_full_sysmon_activated) - self.menu.add(full_sysmon) - self.menu.add(gtk.SeparatorMenuItem()) + self._create_menu() + self.load_settings() + self.alive.set() + self._fetcher.start() + logging.info("Fetcher started") - pref_menu = gtk.MenuItem('Preferences') + def _create_menu(self): + """Creates the main menu and shows it.""" + # create menu {{{ + menu = gtk.Menu() + # add System Monitor menu item + full_sysmon = gtk.MenuItem(_('System Monitor')) + full_sysmon.connect('activate', self.on_full_sysmon_activated) + menu.add(full_sysmon) + menu.add(gtk.SeparatorMenuItem()) + + # add preferences menu item + pref_menu = gtk.ImageMenuItem(stock_id=gtk.STOCK_PREFERENCES) pref_menu.connect('activate', self.on_preferences_activated) - self.menu.add(pref_menu) + menu.add(pref_menu) + + # add help menu item + help_menu = gtk.ImageMenuItem(stock_id=gtk.STOCK_HELP) + help_menu.connect('activate', self._on_help) + menu.add(help_menu) + #add preference menu item exit_menu = gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT) exit_menu.connect('activate', self.on_exit) - self.menu.add(exit_menu) - - self.menu.show_all() - - self.ind.set_menu(self.menu) + menu.add(exit_menu) + menu.show_all() + self.ind.set_menu(menu) logging.info("Menu shown") + # }}} menu done! - self.load_settings() + def update_indicator_guide(self): + """Updates the label guide from appindicator.""" + data = self._fetcher.fetch() + for key in data: + if key.startswith('fs'): + data[key] = '000gB' + break - self.alive = Event() - self.alive.set() - self.fetch = StatusFetcher(self) - self.fetch.start() - logging.info("Fetcher started") + data['mem'] = data['cpu'] = data['bat'] = '000%' + data['net'] = '↓666kB/s ↑666kB/s' + + guide = settings['custom_text'].format(**data) + self.ind.set_property("label-guide", guide) def update(self, data): + """It updates the appindicator text with the the values + from data""" try: - label = self.custom_text.format(**data) - cdata = deepcopy(data) - cdata['mem'] = cdata['cpu'] = cdata['bat'] = '000%' - cdata['net'] = '' - guide = self.custom_text.format(**cdata) - except KeyError as e: - logging.exception(e) - logging.info('not found in dataset') - return - except: - label = 'Unknown error' - if not label: - label = '(no output)' - self.last_data = data - self.last_guide = guide - self.update_text(label, guide) - - def update_text(self, text, guide): - self.last_text = text - self.last_guide = guide - self.ind.set_label(text, guide) - - def force_update(self): - if self.mode_user: - self.update_text(self.last_text, self.last_guide) - else: - self.update(self.last_data) + label = settings["custom_text"].format(**data) if len(data)\ + else _("(no output)") - def on_exit(self, event=None): - logging.info("Terminated") - self.alive.clear() - try: gtk.main_quit() - except RuntimeError: pass + except KeyError as ex: + label = _("Invalid Sensor: {}").format(ex.message) + except Exception as ex: + logging.exception(ex) + label = _("Unknown error: ").format(ex.message) + + self.ind.set_label(label) def load_settings(self): + """It gets the settings from the config file and + sets them to the correct vars""" try: with open(IndicatorSysmonitor.SETTINGS_FILE, 'r') as f: - settings = json.load(f) - self.mode_user = settings['mode_user'] - self.custom_text = settings['custom_text'] - self.user_command = settings['user_command'] - self.interval = settings['interval'] - self.sensors_disabled = settings.get('sensors_disabled', IndicatorSysmonitor.SENSORS_DISABLED) - except Exception as e: - logging.exception(e) + cfg = json.load(f) + + if cfg['custom_text'] is not None: + settings['custom_text'] = cfg['custom_text'] + if cfg['interval'] is not None: + settings['interval'] = cfg['interval'] + if cfg['on_startup'] is not None: + settings['on_startup'] = cfg['on_startup'] + if cfg['sensors'] is not None: + settings['sensors'] = cfg['sensors'] + + Sensor.update_regex() + self.update_indicator_guide() + + except Exception as ex: + logging.exception(ex) logging.error('Reading settings failed') - def save_settings(self): + @staticmethod + def save_settings(): + """It stores the current settings to the config file.""" # TODO: use gsettings - settings = {'mode_user': self.mode_user, - 'custom_text': self.custom_text, - 'user_command': self.user_command, - 'interval': self.interval, - 'sensors_disabled': self.sensors_disabled} - try: - with open(IndicatorSysmonitor.SETTINGS_FILE, 'w') as f: - f.write(json.dumps(settings)) - except Exception as e: - logging.exception(e) + try: + with open(IndicatorSysmonitor.SETTINGS_FILE, 'w') as f: + f.write(json.dumps(settings)) + + except Exception as ex: + logging.exception(ex) logging.error('Writting settings failed') + # actions raised from menu def on_preferences_activated(self, event=None): - self.preferences_dialog = Preferences(self) - self.preferences_dialog.run() + """Raises the preferences dialog. If it's already open, it's + focused""" + if self._preferences_dialog is not None: + self._preferences_dialog.present() + return + + self._preferences_dialog = Preferences(self) + self._preferences_dialog.run() + self._preferences_dialog = None def on_full_sysmon_activated(self, event=None): os.system('gnome-system-monitor &') -from optparse import OptionParser + def on_exit(self, event=None, data=None): + """Action call when the main programs is closed.""" + # close the open dialogs + if self._help_dialog is not None: + self._help_dialog.destroy() + + if self._preferences_dialog is not None: + self._preferences_dialog.destroy() + + logging.info("Terminated") + self.alive.clear() + try: + gtk.main_quit() + except RuntimeError: + pass + + def _on_help(self, event=None, data=None): + """Raise a dialog with info about the app.""" + if self._help_dialog is not None: + self._help_dialog.present() + return + + self._help_dialog = gtk.MessageDialog( + None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, + gtk.BUTTONS_OK, None) + + self._help_dialog.set_title(_("Help")) + self._help_dialog.set_markup(HELP_MSG) + self._help_dialog.run() + self._help_dialog.destroy() + self._help_dialog = None + + +from optparse import OptionParser # TODO: optparse is deprecated if __name__ == "__main__": - parser = OptionParser("usage: %prog [options]", version="%prog "+VERSION) + parser = OptionParser("usage: %prog [options]", version="%prog " + VERSION) parser.add_option("--config", "", default=None, - help="use custom config file") - parser.add_option("--disable-sensors", action="store_true", - help="disable sensors", default=False) - parser.add_option("--enable-sensors", action="store_true", - help="re-enable sensors", default=False) + help=_("Use custom config file.")) (options, args) = parser.parse_args() if options.config: if not os.path.exists(options.config): - print options.config, "does not exist!" + logging.error(_("{} does not exist!").format(options.config)) sys.exit(-1) + logging.info(_("Using config file: {}").format(options.config)) IndicatorSysmonitor.SETTINGS_FILE = options.config - # setup an instance with config - i = IndicatorSysmonitor() - if options.disable_sensors: - i.sensors_disabled = True - i.save_settings() - logging.info("Sensors disabled") - - if options.enable_sensors: - i.sensors_disabled = False - i.save_settings() - logging.info("Sensors enabled") + if not os.path.exists(IndicatorSysmonitor.SETTINGS_FILE): + IndicatorSysmonitor.save_settings() + # setup an instance with config + app = IndicatorSysmonitor() try: gtk.main() except KeyboardInterrupt: - i.on_exit() + app.on_exit() diff -Nru indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor_0.4.1.dsc indicator-sysmonitor-0.4.2/indicator-sysmonitor_0.4.1.dsc --- indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor_0.4.1.dsc 2012-06-30 10:25:50.000000000 +0000 +++ indicator-sysmonitor-0.4.2/indicator-sysmonitor_0.4.1.dsc 2012-11-27 16:24:08.000000000 +0000 @@ -13,22 +13,22 @@ Package-List: indicator-sysmonitor deb gnome extra Checksums-Sha1: - fd5c2dd6cb6e99c5903c8ead3826616a5beffdf0 7453 indicator-sysmonitor_0.4.1.tar.gz + 560bf0c320f72b8a4dbed1733594a68ab1ca4c32 9542 indicator-sysmonitor_0.4.1.tar.gz Checksums-Sha256: - 41e89910d793c63a7219007d053bca90cbb59ea63c62a9d837c0ee705be46d2f 7453 indicator-sysmonitor_0.4.1.tar.gz + a318e9c4fa8bd3bd35929b5f9649cb8692b5e4a6b0ca3133c32aceae5a8a024e 9542 indicator-sysmonitor_0.4.1.tar.gz Files: - 84c3e10a8aafd4abeb004c4c8ea91b27 7453 indicator-sysmonitor_0.4.1.tar.gz + 8776b09a50b753516e308e90de960831 9542 indicator-sysmonitor_0.4.1.tar.gz Original-Maintainer: Alex Eftimie Python-Version: all -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) -iQEcBAEBAgAGBQJP7tLoAAoJEH+e2WxogMg1FfsIAIBoT709BXmK8t3JR6qYBUPE -OtthKrreGDBuJ5afZOST9HFvyUHJ2qO88mOvq0JNhQmN+Ki9SI36tj0t4Fj4BCow -apnV1C9fqu9OQGmhK3fO6k1SPrEGHXkvZEVOUp+y526ky/ypQ+MGF7lyDG0RgO3/ -ZXWGd83qUrfRt2YIdnKNwRcoexfhoXj7EEPwm3BLKQpy3NSFE4WYOMw0abym8LeM -07SdnRO4uwpzzzL9LG0Z2vME6RjskAfh5Kv9gVXJHzR5ZKA+l+4G/6AmzrGlUhgK -rPet45PjyouHrTdFmwnAZGDPq4xIs7bwWubEo3vrBGAABBH+zqsCIIkt6SZjr6U= -=gSwq +iQEcBAEBAgAGBQJQtOjGAAoJEH+e2WxogMg1RHUIAKNIHzd1umj8qZTqVCi5x5/k +q2ry/LxEeulzYH49QcZ0D3vDY6tqFH4F2tDpiXHWnknem1wVLDiUp2R7Ci8ZKKf1 +9aWABg0KKaUsEoUsbySBEanpWh1levZkGM9S4jXQwNWk4eZYsFL7do+2SAjVwtI0 +GsRlqvDJqX9okAT+L5SI2ok+vEEgr5xMLSAD+pTzw6urfzrEVMOPIXUB9qxuXMxE +y6p3gUKK3oZ3YUGRn8b6dhKdN/VcYpoYlLfGmvBoysTGB3XpLvSOBbe1v+pEOo3R +bonLMYKgY+eL7vMmp1ZrfEPlRDOkxOFKLBQ3mDvwpDdJ6IWSLAg8Il1JLPCDEWk= +=bnJE -----END PGP SIGNATURE----- Binary files /tmp/SqGkQjEubw/indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor_0.4.1.tar.gz and /tmp/fg1sh5U9PW/indicator-sysmonitor-0.4.2/indicator-sysmonitor_0.4.1.tar.gz differ Binary files /tmp/SqGkQjEubw/indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor_0.4.1_all.deb and /tmp/fg1sh5U9PW/indicator-sysmonitor-0.4.2/indicator-sysmonitor_0.4.1_all.deb differ diff -Nru indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor_0.4.1_i386.changes indicator-sysmonitor-0.4.2/indicator-sysmonitor_0.4.1_i386.changes --- indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor_0.4.1_i386.changes 2012-06-30 10:25:50.000000000 +0000 +++ indicator-sysmonitor-0.4.2/indicator-sysmonitor_0.4.1_i386.changes 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA1 - -Format: 1.8 -Date: Sat, 30 Jun 2012 10:20:57 +0300 -Source: indicator-sysmonitor -Binary: indicator-sysmonitor -Architecture: source all -Version: 0.4.1 -Distribution: precise -Urgency: low -Maintainer: Ubuntu MOTU Team -Changed-By: Alex Eftimie (alexef) -Description: - indicator-sysmonitor - System Monitor Indicator -Changes: - indicator-sysmonitor (0.4.1) precise; urgency=low - . - * bump version for precise - * minor changes -Checksums-Sha1: - bd7af4dd6abf1574bcd94188de1baf73208e3648 1236 indicator-sysmonitor_0.4.1.dsc - fd5c2dd6cb6e99c5903c8ead3826616a5beffdf0 7453 indicator-sysmonitor_0.4.1.tar.gz - b1905fccb3a836c50efc8f82d5b24ff99d21cfbf 8160 indicator-sysmonitor_0.4.1_all.deb -Checksums-Sha256: - 14f45c3d7cd0846ce82bf99b7b467172ee7f7fcd4867c5a557926de975762cad 1236 indicator-sysmonitor_0.4.1.dsc - 41e89910d793c63a7219007d053bca90cbb59ea63c62a9d837c0ee705be46d2f 7453 indicator-sysmonitor_0.4.1.tar.gz - df6200a4da28c5c64b9e607f90bc99cae2a7ffe9dc2009cd305bb8afda5a2fa0 8160 indicator-sysmonitor_0.4.1_all.deb -Files: - c766489e27010c2818b600a0dc1e112e 1236 gnome extra indicator-sysmonitor_0.4.1.dsc - 84c3e10a8aafd4abeb004c4c8ea91b27 7453 gnome extra indicator-sysmonitor_0.4.1.tar.gz - 510125f05d1998521acd41a3a4144f83 8160 gnome extra indicator-sysmonitor_0.4.1_all.deb -Original-Maintainer: Alex Eftimie - ------BEGIN PGP SIGNATURE----- -Version: GnuPG v1.4.11 (GNU/Linux) - -iQEcBAEBAgAGBQJP7tL6AAoJEH+e2WxogMg1CrMIAKLwmLy8neABAfHu+LZkKizx -9UY8hjOQdx8USKIS7HmNr4dpt53clRC5n9uwgdnbtR9Zs89BTuHevCEDRJdr8neN -pD+FkS68vXa5wKAvslnwQvn/HXlNjx0kA5LhXA6Ch3B9K/+SQ1/FSh16EC46tgHP -ZtXLH3gMXH2vlyuKqwmUFctrb4Ub1W9z7ujuTRPKfuOqiSaaZu/mbsOKQuu1FJJK -ihOMmeG9UKI5uGYQrwqc/FsczQJEebPr9VGM7zjIt9Wy4+xN76FYDceRHKHBztRR -/jPU9hM7CRe7JTUiQd1TEzeLD/HMZ/JJPUjxqxUVK3J3RErSLXJiRDw/ZulhSBM= -=Tx08 ------END PGP SIGNATURE----- diff -Nru indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor_0.4.1_i386.ppa.upload indicator-sysmonitor-0.4.2/indicator-sysmonitor_0.4.1_i386.ppa.upload --- indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor_0.4.1_i386.ppa.upload 2012-06-30 10:25:50.000000000 +0000 +++ indicator-sysmonitor-0.4.2/indicator-sysmonitor_0.4.1_i386.ppa.upload 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -Successfully uploaded indicator-sysmonitor_0.4.1.dsc to ppa.launchpad.net for ppa. -Successfully uploaded indicator-sysmonitor_0.4.1.tar.gz to ppa.launchpad.net for ppa. -Successfully uploaded indicator-sysmonitor_0.4.1_all.deb to ppa.launchpad.net for ppa. -Successfully uploaded indicator-sysmonitor_0.4.1_i386.changes to ppa.launchpad.net for ppa. diff -Nru indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor_0.4.1_source.changes indicator-sysmonitor-0.4.2/indicator-sysmonitor_0.4.1_source.changes --- indicator-sysmonitor-0.4.1~quantal/indicator-sysmonitor_0.4.1_source.changes 1970-01-01 00:00:00.000000000 +0000 +++ indicator-sysmonitor-0.4.2/indicator-sysmonitor_0.4.1_source.changes 2012-11-27 16:24:08.000000000 +0000 @@ -0,0 +1,42 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Format: 1.8 +Date: Sat, 30 Jun 2012 10:20:57 +0300 +Source: indicator-sysmonitor +Binary: indicator-sysmonitor +Architecture: source +Version: 0.4.1 +Distribution: precise +Urgency: low +Maintainer: Ubuntu MOTU Team +Changed-By: Alex Eftimie (alexef) +Description: + indicator-sysmonitor - System Monitor Indicator +Changes: + indicator-sysmonitor (0.4.1) precise; urgency=low + . + * bump version for precise + * minor changes +Checksums-Sha1: + 66cfb798443ec6ec07b491af4e9baf3c7db036f1 1236 indicator-sysmonitor_0.4.1.dsc + 560bf0c320f72b8a4dbed1733594a68ab1ca4c32 9542 indicator-sysmonitor_0.4.1.tar.gz +Checksums-Sha256: + 57b37c098dc09d6003a656f8319b64576c1d7fef579c91a2643c7e75bcec6e48 1236 indicator-sysmonitor_0.4.1.dsc + a318e9c4fa8bd3bd35929b5f9649cb8692b5e4a6b0ca3133c32aceae5a8a024e 9542 indicator-sysmonitor_0.4.1.tar.gz +Files: + 2cba0bdd4235f419a7a074007bf90c69 1236 gnome extra indicator-sysmonitor_0.4.1.dsc + 8776b09a50b753516e308e90de960831 9542 gnome extra indicator-sysmonitor_0.4.1.tar.gz +Original-Maintainer: Alex Eftimie + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.11 (GNU/Linux) + +iQEcBAEBAgAGBQJQtOjiAAoJEH+e2WxogMg1vnkH/26ZTbXG1fszCYgxXpyNz//w +I/SRZHGEZVQDeqgtp298ibBrHg0WI6keZd6II9AcaTP6Sx5eZMzgevrsiHkW47+D +UFEH60gzlCo/BfENLIdlpMuOyxWPHl5R0UjljOxm8rCZK8ywaC7XLRzYOgD6+/ug +lArae8A1sv+AgdEJxJG2l44H7fkv+FtkcDR2t9PlX24pb2jI7F4ikSlTu3/+22K4 +NV7G3uWYtTx2Ci1QoO4sLQIs2lnjFexbox8Q/Pu+FfYgYnhVwJDywsOtPPJm/ruv +A8cjFQz4/prOkAV9yepCfyccnCwxXswpnZ3pMUcFEQ/Q0x2QXy2YN2EjjNuRu+0= +=8M1o +-----END PGP SIGNATURE-----