#!/usr/bin/env python # vim: ts=4 sts=4 sw=4 ai et # Copyright (C) 2006 Wander Boessenkool # # sphinX is a graphical system-monitor using pycairo # # Authors: Wander Boessenkool # Martin von Weissenberrg # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA # NAME = 'sphinX' VERSION = '0.3' AUTHORS = ['Wander Boessenkool ', 'Martin von Weissenberg 1024 and index < len(sizes) - 1): i = i / 1024.0 index += 1 return '%.2f %s' % (i, sizes[index]) class stats(object): def __init__(self): self.rawcpustats = [0 for i in range(8)] self.cpu = cpustat() self.mem = memstat() self.swap = memstat() self.wifi = wifistat() self.update_all() def update_mem(self): statlines = file('/proc/meminfo', 'r').readlines() meminfo = {} for line in statlines: splitline = line.split() meminfo[splitline[0]] = float(splitline[1]) self.mem.total = meminfo['MemTotal:'] self.mem.free = meminfo['MemFree:'] / self.mem.total self.mem.buffers = meminfo['Buffers:'] / self.mem.total self.mem.cached = meminfo['Cached:'] / self.mem.total self.mem.used = 1.0 \ - self.mem.free \ - self.mem.buffers \ - self.mem.cached self.swap.total = meminfo['SwapTotal:'] if self.swap.total > 0: self.swap.free = meminfo['SwapFree:'] / self.swap.total else: self.swap.free = -1.0 self.swap.used = 1.0 - self.swap.free def update_cpu(self): statlines = file('/proc/stat', 'r').readlines() for line in statlines: splitline = line.split() if splitline[0] == 'cpu': rawcpu = [int(i) for i in splitline[1:]] diff = [rawcpu[i] - self.rawcpustats[i] for i in range(8)] totaldiff = float(sum(diff)) if totaldiff != 0: self.cpu.user = diff[0] / totaldiff self.cpu.nice = diff[1] / totaldiff self.cpu.system = diff[2] / totaldiff self.cpu.idle = diff[3] / totaldiff self.cpu.iowait = diff[4] / totaldiff self.cpu.irq = diff[5] / totaldiff self.cpu.softirq = diff[6] / totaldiff self.rawcpustats = rawcpu try: self.cpu.max_freq = float(file( '/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq', 'r').read()) self.cpu.cur_freq = float(file( '/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq', 'r').read()) self.cpu.scale = self.cpu.cur_freq / self.cpu.max_freq except: self.cpu.max_freq = 1.0 self.cpu.cur_freq = 1.0 self.cpu.scale = 0.0 def update_wifi(self): try: statlines = file('/proc/net/wireless', 'r').readlines() except: statlines = ["", "", "0 0 0"] while len(statlines) < 3: statlines.append("0 0 0") self.wifi.quality = float(statlines[2].split()[2]) / 100.0 def update_all(self): self.update_cpu() self.update_mem() self.update_wifi() class Witsjet(gtk.Window): __gsignals__ = { 'expose-event': 'override', 'screen-changed': 'override' } def __init__(self, stats, last_mouse_pos): gtk.Window.__init__(self) self.gconfclient = gconf.client_get_default() sw = self.gconfclient.get_int('/apps/sphinX/width') if sw is 0: sw = 200 sh = self.gconfclient.get_int('/apps/sphinX/height') if sh is 0: sh = 200 sx = self.gconfclient.get_int('/apps/sphinX/xpos') sy = self.gconfclient.get_int('/apps/sphinX/ypos') self.set_size_request(32, 32) self.set_default_size(sw, sh) self.move(sx, sy) self.stats = stats self.last_mouse_pos = last_mouse_pos self.stick() self.set_keep_below(True) self.set_property('accept-focus', False) self.set_property('skip-pager-hint', True) self.set_property('skip-taskbar-hint', True) self.set_property('resizable', True) self.drawing_stack = [] self.set_app_paintable(True) self.set_decorated(False) self.add_events(gtk.gdk.BUTTON_PRESS_MASK|gtk.gdk.POINTER_MOTION_MASK) self.connect('button-press-event', self.buttonpress) self.connect('configure-event', self.configure_handler) self.do_screen_changed() self.swapgradient = cairo.LinearGradient(0.2, 0.8, 1.0, 0.5) self.swapgradient.add_color_stop_rgba(0.0, 1.0, 0.0, 0.0, 0.6) self.swapgradient.add_color_stop_rgba(1.0, 1.0, 0.0, 0.0, 1.0) self.memgradient = cairo.LinearGradient(0.8, 0.2, 0.0, 0.5) self.memgradient.add_color_stop_rgba(0.0, 0.0, 1.0, 0.0, 0.6) self.memgradient.add_color_stop_rgba(1.0, 0.0, 1.0, 0.0, 1.0) self.scalegradient = cairo.LinearGradient(0.2, 0.8, 1.0, 0.5) self.scalegradient.add_color_stop_rgba(0.0, 0.0, 0.0, 1.0, 0.3) self.scalegradient.add_color_stop_rgba(1.0, 0.0, 0.0, 1.0, 1.0) self.menu = gtk.Menu() about = gtk.Action('About', None, None, gtk.STOCK_ABOUT) about.connect('activate', self.about) aboutmenu = gtk.ImageMenuItem() about.connect_proxy(aboutmenu) self.menu.add(aboutmenu) quit = gtk.Action('Quit', None, None, gtk.STOCK_QUIT) quit.connect('activate', gtk.main_quit) quitmenu = gtk.ImageMenuItem() quit.connect_proxy(quitmenu) self.menu.add(quitmenu) ############ # Notes to mvonweis: # 1. use self.tooltip()... to register a tooltip self.tooltips = gtk.Tooltips() self.tooltips.set_tip(self, 'Starting...') # 2. register a callback on mousemoves, store x+y in global vars self.connect('motion-notify-event', self.motion_callback) # 3. in update_all, update tooltip text based on x+y ############ def motion_callback(self, widget, event): self.last_mouse_pos.x, self.last_mouse_pos.y = event.get_coords() return True def about(self, *args): dialog = gtk.AboutDialog() dialog.set_name('sphinX') dialog.set_logo_icon_name('system') dialog.set_copyright('(C) 2006 Red Hat, Inc.') dialog.set_authors(AUTHORS) dialog.set_version(VERSION) dialog.connect('response', lambda d, r: d.destroy()) dialog.show() def configure_handler(self, window, event): w,h = self.get_size() x, y = self.get_position() self.gconfclient.set_int('/apps/sphinX/width', w) self.gconfclient.set_int('/apps/sphinX/height', h) self.gconfclient.set_int('/apps/sphinX/xpos', x) self.gconfclient.set_int('/apps/sphinX/ypos', y) return False def buttonpress(self, window, event): if event.button == 1: self.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time) return True if event.button == 2: self.begin_resize_drag(gtk.gdk.WINDOW_EDGE_SOUTH_EAST, event.button, int(event.x_root), int(event.y_root), event.time) return True if event.button == 3: self.menu.popup(None, None, None, event.button, event.time) return True def set_tooltip(self): tip = '' if self.last_mouse_pos.x >= 0: (width, height) = self.get_size() a = (self.last_mouse_pos.x-5)/(width-10) b = (self.last_mouse_pos.y-5)/(height-10) #print self.last_mouse_pos.x, self.last_mouse_pos.y, ' --> ', a, b if distsquare(0.5, 0.5, a, b) <= (0.5*0.5): if distsquare(0.5, 0.5, a, b) <= (RADIUS_WIFI*RADIUS_WIFI): if self.stats.wifi.quality == 0: tip = "No WiFi" else: tip = "WiFi quality: %d %%" % (self.stats.wifi.quality*100) else: if b >= 0.5: if distsquare(0.6, 0.5, a, b) <= (0.4*0.4): # swap if self.stats.swap.used >= 0: tip = "Swap total %d MB\nSwap free %d %%" % (self.stats.swap.total/1024, self.stats.swap.free*100) else: tip = "No swap space available" else: # CPU usage i = 100*(self.stats.cpu.irq + self.stats.cpu.softirq + self.stats.cpu.iowait) t = 100*(self.stats.cpu.user + self.stats.cpu.nice + self.stats.cpu.system) + i tip = "User %d %%\nNice %d %%\nSys %d %%\nI/O %d %%\n-----------------\nTOTAL %d %%" % (self.stats.cpu.user*100, self.stats.cpu.nice*100, self.stats.cpu.system*100, i, t) else: if distsquare(0.4, 0.5, a, b) <= (0.4*0.4): # Memory tip = "Free %d %%\nBuffers %d %%\nCached %d %%\nUsed %d %%" % (self.stats.mem.free*100, self.stats.mem.buffers*100, self.stats.mem.cached*100, self.stats.mem.used*100) else: tip = "CPU frequency\nCurrent: %.2f GHz\nMaximum %.2f GHz" % (self.stats.cpu.cur_freq/1000000, self.stats.cpu.max_freq/1000000) self.tooltips.set_tip(self, tip) def do_expose_event(self, event): (width, height) = self.get_size() self.window.begin_paint_rect((0, 0, width, height)) bmp = gtk.gdk.Pixmap(None, width, height, 1) cm = bmp.cairo_create() cm.translate(5, 5) cm.scale(width - 10, height -10) cm.set_source_rgb(0, 0, 0) cm.set_operator(cairo.OPERATOR_DEST_OUT) cm.paint() cm.set_operator(cairo.OPERATOR_OVER) cm.arc(0.5, 0.5, 0.51, 0, 2 * math.pi) cm.fill() if not self.supports_alpha: self.window.shape_combine_mask(bmp, 0, 0) else: self.window.input_shape_combine_mask(bmp, 0, 0) cr = self.window.cairo_create() self.draw(cr, width, height) self.window.end_paint() self.set_tooltip() def draw(self, cr, width, height): if self.supports_alpha: cr.set_source_rgba(1.0, 1.0, 1.0, 0.0) else: cr.set_source_rgb(0.5, 0.5, 0.5) # Draw the background cr.set_operator(cairo.OPERATOR_SOURCE) cr.paint() cr.set_operator(cairo.OPERATOR_OVER) cr.translate(5, 5) cr.scale(width - 10 , height -10) #Inner circle - wifi cr.move_to(0.8, 0.5) cr.arc(0.5, 0.5, RADIUS_WIFI, 0, 2 * math.pi) wifigrad = cairo.RadialGradient(0.5, 0.5, 0.0, 0.5, 0.5, 0.3) wifigrad.add_color_stop_rgba(0.0, 0.0, 0.0, 1.0, 1.0) interval = 0.02 while interval < self.stats.wifi.quality: wifigrad.add_color_stop_rgba(interval, 0.0, 0.0, 1.0, 0.8) interval += 0.04 wifigrad.add_color_stop_rgba(interval, 0.0, 0.0, 1.0, 0.4) interval += 0.04 wifigrad.add_color_stop_rgba(1.0, 0.0, 0.0, 1.0, 0.0) cr.set_source(wifigrad) cr.fill() if self.stats.swap.free >= 0: #Lower small bar - swap cr.move_to(0.2, 0.5) cr.arc_negative(0.6, 0.5, 0.4, math.pi, self.stats.swap.free * math.pi) cr.arc(0.5, 0.5, 0.3, self.stats.swap.free * math.pi, math.pi) cr.close_path() cr.set_source(self.swapgradient) cr.fill() #Lower small outline cr.move_to(0.2, 0.5) cr.arc_negative(0.6, 0.5, 0.4, math.pi, 0) cr.arc(0.5, 0.5, 0.3, 0, math.pi) cr.close_path() cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) cr.set_line_width(0.01) cr.stroke() #Lower large bar - cpu cr.move_to(1.0, 0.5) cr.arc(0.5, 0.5, 0.5, 0, (1.0 - self.stats.cpu.idle) * math.pi) cr.arc_negative(0.6, 0.5, 0.4, (1.0 - self.stats.cpu.idle) * math.pi, 0) cr.close_path() gradient = cairo.LinearGradient(1.0, 1.0, 0.0, 0.5) gradient.add_color_stop_rgba(0.0, 0.0, 1.0, 0.0, 0.6) u = self.stats.cpu.user + self.stats.cpu.nice s = self.stats.cpu.system + u i = self.stats.cpu.irq + self.stats.cpu.softirq + \ self.stats.cpu.iowait + s gradient.add_color_stop_rgba(u, 0.0, 1.0, 0.0, 0.8) gradient.add_color_stop_rgba(s, 1.0, 1.0, 0.0, 0.8) gradient.add_color_stop_rgba(i, 1.0, 0.0, 0.0, 1.0) gradient.add_color_stop_rgba(1.0, 1.0, 0.0, 0.0, 1.0) cr.set_source(gradient) cr.fill() #Lower large outline cr.move_to(1.0, 0.5) cr.arc(0.5, 0.5, 0.5, 0, math.pi) cr.arc_negative(0.6, 0.5, 0.4, math.pi, 0) cr.close_path() cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) cr.set_line_width(0.01) cr.stroke() #Upper small bar - mem cr.move_to(0.8, 0.5) cr.arc_negative(0.4, 0.5, 0.4, 0, -self.stats.mem.used * math.pi) cr.arc(0.5, 0.5, 0.3, -self.stats.mem.used * math.pi, 0) cr.close_path() cr.set_source(self.memgradient) cr.fill() #Upper small outline cr.move_to(0.8, 0.5) cr.arc_negative(0.4, 0.5, 0.4, 0, math.pi) cr.arc(0.5, 0.5, 0.3, math.pi, 0) cr.close_path() cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) cr.set_line_width(0.01) cr.stroke() if self.stats.cpu.scale > 0.0: #Upper large bar - cpu-scale cr.move_to(0.0, 0.5) cr.arc(0.4, 0.5, 0.4, math.pi, math.pi * -(1 - self.stats.cpu.scale)) cr.arc_negative(0.5, 0.5, 0.5, math.pi * -(1 - self.stats.cpu.scale), math.pi) cr.close_path() cr.set_source(self.scalegradient) cr.fill() #Upper large outline cr.move_to(0.0, 0.5) cr.arc(0.4, 0.5, 0.4, math.pi, 0) cr.arc_negative(0.5, 0.5, 0.5, 0, math.pi) cr.close_path() cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) cr.set_line_width(0.01) cr.stroke() #Clock curtime = datetime.now() h = float(curtime.hour) m = float(curtime.minute) s = float(curtime.second) m += s / 60 h += m / 60 h = (h % 12) / 6 * math.pi m = m / 30 * math.pi s = s / 30 * math.pi try: cr.push_group() except: pass cr.set_line_cap(cairo.LINE_CAP_ROUND) cr.move_to(0.5, 0.5) cr.line_to(0.5 + 0.3 * math.sin(h), 0.5 - 0.3 * math.cos(h)) cr.set_source_rgba(0.0, 0.0, 0.0, 0.8) cr.set_line_width(0.075) cr.stroke_preserve() cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) cr.set_line_width(0.05) cr.stroke() cr.move_to(0.5, 0.5) cr.line_to(0.5 + 0.35 * math.sin(m), 0.5 - 0.35 * math.cos(m)) cr.set_source_rgba(0.0, 0.0, 0.0, 0.8) cr.set_line_width(0.065) cr.stroke_preserve() cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) cr.set_line_width(0.04) cr.stroke() cr.move_to(0.5, 0.5) cr.line_to(0.5 + 0.4 * math.sin(s), 0.5 - 0.4 * math.cos(s)) cr.set_source_rgba(0.0, 0.0, 0.0, 0.8) cr.set_line_width(0.055) cr.stroke_preserve() cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) cr.set_line_width(0.03) cr.stroke() cr.move_to(0.55, 0.5) cr.arc(0.5, 0.5, 0.05, 0, 2 * math.pi) cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) cr.fill_preserve() cr.set_line_width(0.015) cr.set_source_rgba(0.0, 0.0, 0.0, 1.0) cr.stroke() try: cr.pop_group_to_source() cr.paint_with_alpha(0.5) except: pass def do_screen_changed(self, old_screen=None): screen = self.get_screen() try: colormap = screen.get_rgba_colormap() if colormap: self.supports_alpha = True else: self.supports_alpha = False except: colormap = screen.get_rgb_colormap() self.supports_alpha = False if colormap: self.set_colormap(colormap) def update(self): self.stats.update_all() self.do_expose_event('update') return True if __name__ == '__main__': mystat = stats() mystat.update_all() mouse_coords = coord() window = Witsjet(mystat, mouse_coords) window.set_title('sphinX') window.connect('delete-event', gtk.main_quit) window.show() gobject.timeout_add(500, window.update) try: gtk.main() except KeyboardInterrupt: pass