2013-12-16 06:26:43 +00:00
|
|
|
#!/usr/bin/env python
|
2013-12-16 11:44:58 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2013-12-16 06:26:43 +00:00
|
|
|
#
|
2013-12-16 09:58:00 +00:00
|
|
|
# Pinger.py -- A ping tool that sits in your system tray
|
|
|
|
# Copyright 2013 Will Bradley
|
2013-12-16 06:26:43 +00:00
|
|
|
#
|
2014-01-08 10:26:53 +00:00
|
|
|
# Contributors: Will Bradley <bradley.will@gmail.com>
|
|
|
|
# AltF4 <altf4@phx2600.org>
|
2013-12-16 06:26:43 +00:00
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
|
|
# under the terms of either or both of the following licenses:
|
|
|
|
#
|
|
|
|
# 1) the GNU Lesser General Public License version 3, as published by the
|
|
|
|
# Free Software Foundation; and/or
|
|
|
|
# 2) the GNU Lesser General Public License version 2.1, as published by
|
|
|
|
# the Free Software Foundation.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful, but
|
|
|
|
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
|
|
|
# MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
|
|
|
|
# PURPOSE. See the applicable version of the GNU Lesser General Public
|
|
|
|
# License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of both the GNU Lesser General Public
|
|
|
|
# License version 3 and version 2.1 along with this program. If not, see
|
|
|
|
# <http://www.gnu.org/licenses/>
|
|
|
|
#
|
|
|
|
|
2013-12-16 10:36:08 +00:00
|
|
|
#
|
|
|
|
# Dependencies
|
|
|
|
#
|
|
|
|
|
|
|
|
# System Tray Icon
|
2013-12-16 06:26:43 +00:00
|
|
|
from gi.repository import Gtk
|
|
|
|
from gi.repository import AppIndicator3 as appindicator
|
|
|
|
# Timer
|
2013-12-16 07:28:10 +00:00
|
|
|
from gi.repository import GObject as gobject
|
2013-12-16 06:26:43 +00:00
|
|
|
# Pinging
|
|
|
|
import subprocess
|
2013-12-16 07:28:10 +00:00
|
|
|
# Regex
|
|
|
|
import re
|
2013-12-16 10:36:08 +00:00
|
|
|
# Ctrl-c
|
|
|
|
import signal
|
2013-12-16 11:44:58 +00:00
|
|
|
# File paths
|
|
|
|
import os
|
2014-01-08 15:09:28 +00:00
|
|
|
# Argument parsing
|
2014-01-05 02:43:13 +00:00
|
|
|
import argparse
|
2014-01-08 15:09:28 +00:00
|
|
|
# For exit
|
2014-01-05 02:43:13 +00:00
|
|
|
import sys
|
2014-01-08 15:09:28 +00:00
|
|
|
# For graphing
|
|
|
|
import cairo
|
2015-05-18 00:57:25 +00:00
|
|
|
# For IP addresses
|
|
|
|
import socket, struct
|
2013-12-16 11:44:58 +00:00
|
|
|
|
|
|
|
# Vars
|
|
|
|
startup_active_label = "✓ Start Automatically"
|
|
|
|
startup_inactive_label = "Start Automatically"
|
2014-01-05 05:00:47 +00:00
|
|
|
pause_label = "Pause"
|
|
|
|
play_label = "Resume"
|
2013-12-16 11:44:58 +00:00
|
|
|
home_path = os.path.expanduser("~")
|
|
|
|
startup_path = home_path+'/.config/autostart/pinger.desktop'
|
2014-01-05 03:13:51 +00:00
|
|
|
startup_dir = home_path+'/.config/autostart/'
|
2013-12-16 06:26:43 +00:00
|
|
|
|
2014-01-05 02:43:13 +00:00
|
|
|
parser = argparse.ArgumentParser()
|
2014-11-13 22:52:54 +00:00
|
|
|
parser.add_argument("-t", "--target", help="Target to PING against. (IP / Hostname / Domain name). Defaults to 8.8.8.8")
|
2014-01-05 02:43:13 +00:00
|
|
|
parser.add_argument("-f", "--freq", help="Timeout between pings, in seconds. Defaults to 5")
|
2014-01-08 15:09:28 +00:00
|
|
|
parser.add_argument("-m", "--maxlog", help="Maximum amount of pings to log. Defaults to 40")
|
|
|
|
parser.add_argument("-c", "--color", help="Color scheme ('dark' or 'light'). Defaults to dark.")
|
2014-01-05 02:43:13 +00:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
2014-01-08 15:09:28 +00:00
|
|
|
ubuntu_mono_dark_rgba = [0xdf, 0xd8, 0xc8, 0xff]
|
|
|
|
ubuntu_mono_light_rgba = [0x3a, 0x39, 0x35, 0xff]
|
|
|
|
black = [0, 0, 0, 0xff]
|
2015-05-17 23:51:02 +00:00
|
|
|
red = [0xdf, 0x38, 0x2c, 0xff]
|
|
|
|
orange = [0xdd, 0x48, 0x14, 0xff]
|
|
|
|
yellow = [0xef, 0xb7, 0x3e, 0xff]
|
2014-01-08 15:09:28 +00:00
|
|
|
white = [0xff, 0xff, 0xff, 0xff]
|
|
|
|
dark_bg = [0, 0, 0, 0x3f]
|
|
|
|
light_bg = [0xff, 0xff, 0xff, 0x3f]
|
|
|
|
|
2014-01-05 02:43:13 +00:00
|
|
|
#accumulate the arguments for use later
|
2014-01-05 03:06:43 +00:00
|
|
|
arguments = " "
|
2014-01-05 02:43:13 +00:00
|
|
|
for arg in sys.argv[1:]:
|
|
|
|
arguments += arg + " "
|
|
|
|
|
|
|
|
# User-editable variables
|
|
|
|
if args.target:
|
|
|
|
host = args.target
|
|
|
|
else:
|
2014-11-13 22:52:54 +00:00
|
|
|
print "Using default target of 8.8.8.8"
|
2015-05-18 00:57:25 +00:00
|
|
|
host = "8.8.8.8" # IP or hostname of WAN
|
|
|
|
|
|
|
|
router = "192.168.1.1" # IP or hostname of router
|
2014-01-05 02:43:13 +00:00
|
|
|
|
|
|
|
if args.freq:
|
|
|
|
try:
|
|
|
|
ping_frequency = int(args.freq)
|
|
|
|
except ValueError:
|
|
|
|
sys.stderr.write("Error parsing argument '--freq'\n")
|
|
|
|
sys.exit(1)
|
|
|
|
else:
|
|
|
|
ping_frequency = 5 # in seconds
|
|
|
|
|
|
|
|
if args.maxlog:
|
|
|
|
try:
|
|
|
|
ping_log_max_size = int(args.maxlog)
|
|
|
|
except ValueError:
|
|
|
|
sys.stderr.write("Error parsing argument '--maxlog'\n")
|
|
|
|
sys.exit(1)
|
|
|
|
else:
|
2014-01-08 15:09:28 +00:00
|
|
|
ping_log_max_size = 40
|
|
|
|
|
|
|
|
if args.color == "light":
|
|
|
|
graph_color = ubuntu_mono_light_rgba
|
2015-05-17 23:51:02 +00:00
|
|
|
danger_color = red
|
|
|
|
warning_color = yellow
|
2014-01-08 15:09:28 +00:00
|
|
|
graph_highlight = ubuntu_mono_dark_rgba
|
|
|
|
graph_background = light_bg
|
|
|
|
else:
|
|
|
|
graph_color = ubuntu_mono_dark_rgba
|
2015-05-17 23:51:02 +00:00
|
|
|
danger_color = red
|
|
|
|
warning_color = yellow
|
2014-01-08 15:09:28 +00:00
|
|
|
graph_highlight = ubuntu_mono_light_rgba
|
|
|
|
graph_background = dark_bg
|
2014-01-05 02:43:13 +00:00
|
|
|
|
2013-12-16 10:36:08 +00:00
|
|
|
#
|
|
|
|
# Main Class
|
|
|
|
#
|
2013-12-16 06:26:43 +00:00
|
|
|
|
2013-12-16 10:36:08 +00:00
|
|
|
class Pinger:
|
2015-05-17 23:51:02 +00:00
|
|
|
host_log = []
|
|
|
|
router_log = []
|
2014-01-05 05:00:47 +00:00
|
|
|
paused = False
|
2014-01-05 05:36:18 +00:00
|
|
|
autostart = False
|
2014-01-08 15:09:28 +00:00
|
|
|
icon_height = 22
|
2013-12-16 06:26:43 +00:00
|
|
|
|
2015-05-17 23:51:02 +00:00
|
|
|
def ping(self, target, log, widget=None, data=None):
|
2014-01-05 05:00:47 +00:00
|
|
|
if not self.paused:
|
|
|
|
ping = subprocess.Popen(
|
2015-05-17 23:51:02 +00:00
|
|
|
["ping", "-c", "1", target],
|
2014-01-05 05:00:47 +00:00
|
|
|
stdout = subprocess.PIPE,
|
|
|
|
stderr = subprocess.PIPE
|
|
|
|
)
|
|
|
|
out, error = ping.communicate()
|
|
|
|
m = re.search('time=(.*) ms', out)
|
|
|
|
if error or m == None:
|
|
|
|
label = "PING FAIL"
|
2015-05-17 23:51:02 +00:00
|
|
|
self.log_ping(log, -1)
|
2014-01-05 05:00:47 +00:00
|
|
|
else:
|
2014-01-05 06:01:06 +00:00
|
|
|
latency = "%.2f" % float(m.group(1))
|
|
|
|
label = latency+" ms"
|
2015-05-17 23:51:02 +00:00
|
|
|
self.log_ping(log, latency)
|
|
|
|
|
2014-01-08 15:09:28 +00:00
|
|
|
#self.ind.set_label(label, "100.0 ms")
|
2013-12-16 06:26:43 +00:00
|
|
|
|
2015-05-17 23:51:02 +00:00
|
|
|
|
|
|
|
def ping_both(self):
|
|
|
|
self.ping(host, self.host_log)
|
|
|
|
self.ping(router, self.router_log)
|
2013-12-17 12:50:58 +00:00
|
|
|
self.update_log_menu()
|
2015-05-17 23:51:02 +00:00
|
|
|
gobject.timeout_add_seconds(self.timeout, self.ping_both)
|
|
|
|
|
|
|
|
def log_ping(self, log, value):
|
|
|
|
log.append(float(value))
|
2013-12-17 12:50:58 +00:00
|
|
|
# limit the size of the log
|
2015-05-17 23:51:02 +00:00
|
|
|
if len(log) >= ping_log_max_size:
|
2013-12-17 12:50:58 +00:00
|
|
|
# remove the earliest ping, not the latest
|
2015-05-17 23:51:02 +00:00
|
|
|
log.pop(0)
|
2013-12-17 12:50:58 +00:00
|
|
|
|
2013-12-16 10:36:08 +00:00
|
|
|
def create_menu_item(self, text, callback):
|
|
|
|
menu_item = Gtk.MenuItem(text)
|
|
|
|
self.menu.append(menu_item)
|
2013-12-17 12:50:58 +00:00
|
|
|
if callback:
|
|
|
|
menu_item.connect("activate", callback, text)
|
2013-12-16 10:36:08 +00:00
|
|
|
menu_item.show()
|
2013-12-16 11:44:58 +00:00
|
|
|
return menu_item
|
2013-12-16 10:36:08 +00:00
|
|
|
|
2013-12-16 06:26:43 +00:00
|
|
|
def destroy(self, widget, data=None):
|
2013-12-16 11:44:58 +00:00
|
|
|
print "Quitting..."
|
2013-12-16 06:26:43 +00:00
|
|
|
Gtk.main_quit()
|
|
|
|
|
2014-01-05 05:36:18 +00:00
|
|
|
def toggle_autostart(self, widget, data=None):
|
|
|
|
if not self.autostart:
|
|
|
|
if not os.path.exists(startup_dir):
|
|
|
|
os.makedirs(startup_dir)
|
|
|
|
with open(startup_path,'w') as f:
|
|
|
|
f.write("[Desktop Entry]\r\n"
|
|
|
|
"Type=Application\r\n"
|
|
|
|
"Exec=python "+os.path.abspath( __file__ )+arguments+"\r\n"
|
|
|
|
"X-GNOME-Autostart-enabled=true\r\n"
|
|
|
|
"Name=Pinger\r\n"
|
|
|
|
"Comment=Pings the internet every few seconds")
|
|
|
|
self.autostart = True
|
2013-12-16 11:44:58 +00:00
|
|
|
self.startup_menu.set_label(startup_active_label)
|
|
|
|
else:
|
2014-01-05 05:36:18 +00:00
|
|
|
os.remove(startup_path)
|
|
|
|
self.autostart = False
|
2013-12-16 11:44:58 +00:00
|
|
|
self.startup_menu.set_label(startup_inactive_label)
|
2014-01-05 05:00:47 +00:00
|
|
|
|
2014-01-05 05:36:18 +00:00
|
|
|
def toggle_pause(self, widget, data=None):
|
2014-01-05 05:00:47 +00:00
|
|
|
if self.paused:
|
2014-01-05 05:36:18 +00:00
|
|
|
self.paused = False
|
2014-01-05 06:48:22 +00:00
|
|
|
self.pause_menu.set_label(pause_label)
|
2014-01-05 05:00:47 +00:00
|
|
|
else:
|
2014-01-05 05:36:18 +00:00
|
|
|
self.paused = True
|
2014-01-05 06:48:22 +00:00
|
|
|
self.pause_menu.set_label(play_label)
|
2013-12-16 11:44:58 +00:00
|
|
|
|
2013-12-17 12:50:58 +00:00
|
|
|
def update_log_menu(self):
|
2014-01-08 15:09:28 +00:00
|
|
|
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, ping_log_max_size, self.icon_height)
|
|
|
|
ctx = cairo.Context(surface)
|
|
|
|
|
|
|
|
# draw semitransparent box
|
|
|
|
self.draw_rect( ctx, [0,0], [ping_log_max_size,self.icon_height], graph_background )
|
|
|
|
|
2015-05-17 23:51:02 +00:00
|
|
|
if len(self.host_log) > 0:
|
|
|
|
self.draw_log(ctx, self.host_log, 0)
|
|
|
|
host_avg = sum(self.host_log)/len(self.host_log)
|
|
|
|
self.ping_menu.set_label("Internet: "+str(int(round(self.host_log[-1])))+" ms "+str(int(round(host_avg)))+" avg")
|
|
|
|
if len(self.router_log) > 0:
|
2015-05-18 04:21:06 +00:00
|
|
|
self.draw_log(ctx, self.router_log, (self.icon_height/2))
|
2015-05-17 23:51:02 +00:00
|
|
|
router_avg = sum(self.router_log)/len(self.router_log)
|
|
|
|
self.router_menu.set_label("Router: "+str(int(round(self.router_log[-1])))+" ms "+str(int(round(router_avg)))+" avg")
|
|
|
|
|
|
|
|
try:
|
|
|
|
os.remove("/tmp/graph.png")
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
surface.write_to_png("/tmp/graph.png")
|
|
|
|
self.ind.set_icon("") # gotta set it to nothing in order to update
|
|
|
|
self.ind.set_icon("graph")
|
|
|
|
|
|
|
|
|
|
|
|
def draw_log(self, ctx, log, yOffset):
|
|
|
|
if(max(log) < 100):
|
2014-01-08 15:09:28 +00:00
|
|
|
max_ping = 100
|
2015-05-17 23:51:02 +00:00
|
|
|
elif(max(log) > 1000):
|
|
|
|
max_ping = 1000
|
2014-01-08 15:09:28 +00:00
|
|
|
else:
|
2015-05-17 23:51:02 +00:00
|
|
|
max_ping = max(log)
|
2014-01-08 15:09:28 +00:00
|
|
|
|
2015-05-17 23:51:02 +00:00
|
|
|
for index, ping in enumerate(log):
|
2014-01-08 15:09:28 +00:00
|
|
|
|
|
|
|
if float(ping) == -1: # Ping error
|
|
|
|
# Draw full-height error bar
|
2015-05-17 23:51:02 +00:00
|
|
|
|
|
|
|
self.draw_rect( ctx, [index,(self.icon_height/2)+yOffset], [1,(-self.icon_height/2)-1], danger_color )
|
2013-12-17 12:50:58 +00:00
|
|
|
else:
|
2014-01-08 15:09:28 +00:00
|
|
|
# draw normal bar
|
2015-05-18 00:57:25 +00:00
|
|
|
bar_height = -int(self.scale(ping, (0,max_ping), (0,(self.icon_height/2)-1)))
|
2014-07-16 20:15:07 +00:00
|
|
|
|
|
|
|
if bar_height > -1:
|
|
|
|
bar_height = -1
|
|
|
|
|
2015-05-17 23:51:02 +00:00
|
|
|
if ping > 100:
|
|
|
|
color = warning_color
|
|
|
|
else:
|
|
|
|
color = graph_color
|
2014-01-08 15:09:28 +00:00
|
|
|
|
2015-05-17 23:51:02 +00:00
|
|
|
self.draw_rect( ctx, [index,self.icon_height/2+yOffset], [1,bar_height], color )
|
|
|
|
|
|
|
|
|
2014-01-08 15:09:28 +00:00
|
|
|
def draw_rect(self, ctx, point, size, rgba):
|
|
|
|
ctx.rectangle( point[0], point[1], size[0], size[1] )
|
|
|
|
ctx.set_source_rgba(rgba[0]/float(255), rgba[1]/float(255), rgba[2]/float(255), rgba[3]/float(255))
|
|
|
|
ctx.fill()
|
|
|
|
|
|
|
|
def scale(self, val, src, dst):
|
|
|
|
"""
|
|
|
|
Scale the given value from the scale of src to the scale of dst.
|
|
|
|
"""
|
2014-07-16 20:15:07 +00:00
|
|
|
scale = ((val - src[0]) / (src[1]-src[0])) * (dst[1]-dst[0]) + dst[0]
|
|
|
|
return scale
|
2014-01-08 15:09:28 +00:00
|
|
|
|
2015-05-18 00:57:25 +00:00
|
|
|
def get_default_gateway_linux(self):
|
|
|
|
# Read the default gateway directly from /proc.
|
|
|
|
with open("/proc/net/route") as fh:
|
|
|
|
for line in fh:
|
|
|
|
fields = line.strip().split()
|
|
|
|
if fields[1] != '00000000' or not int(fields[3], 16) & 2:
|
|
|
|
continue
|
|
|
|
|
|
|
|
return str(socket.inet_ntoa(struct.pack("<L", int(fields[2], 16))))
|
|
|
|
|
2013-12-16 06:26:43 +00:00
|
|
|
def __init__(self):
|
2013-12-16 10:36:08 +00:00
|
|
|
# Handle ctrl-c
|
|
|
|
signal.signal(signal.SIGINT, self.destroy)
|
|
|
|
|
2013-12-16 11:48:05 +00:00
|
|
|
# Print welcome message
|
|
|
|
print "Starting Pinger..."
|
|
|
|
|
2013-12-16 10:36:08 +00:00
|
|
|
# Create systray icon
|
|
|
|
self.ind = appindicator.Indicator.new (
|
|
|
|
"pinger",
|
|
|
|
"", # no icon
|
2014-01-08 15:09:28 +00:00
|
|
|
appindicator.IndicatorCategory.SYSTEM_SERVICES)
|
2013-12-16 10:36:08 +00:00
|
|
|
self.ind.set_status (appindicator.IndicatorStatus.ACTIVE)
|
2014-01-08 15:09:28 +00:00
|
|
|
#self.ind.set_label ("Pinger Loading...", "Pinger Loading...")
|
2014-01-18 08:00:08 +00:00
|
|
|
self.ind.set_icon_theme_path("/tmp")
|
2013-12-16 10:36:08 +00:00
|
|
|
|
|
|
|
# create a menu
|
|
|
|
self.menu = Gtk.Menu()
|
2014-01-09 09:02:26 +00:00
|
|
|
# with ping numbers
|
|
|
|
self.ping_menu = self.create_menu_item("", None)
|
2015-05-17 23:51:02 +00:00
|
|
|
self.router_menu = self.create_menu_item("", None)
|
2014-01-05 05:00:47 +00:00
|
|
|
# with pause option
|
|
|
|
self.pause_menu = self.create_menu_item(pause_label, self.toggle_pause)
|
2013-12-17 12:50:58 +00:00
|
|
|
# with autostart option
|
2014-01-05 05:36:18 +00:00
|
|
|
# first, check current autostart state by checking existance of .desktop file
|
2014-01-08 10:26:53 +00:00
|
|
|
if os.path.exists(startup_path):
|
|
|
|
self.autostart = True
|
|
|
|
self.startup_menu = self.create_menu_item(startup_active_label, self.toggle_autostart)
|
|
|
|
else:
|
2014-01-05 05:36:18 +00:00
|
|
|
self.autostart = False
|
2014-01-08 10:26:53 +00:00
|
|
|
self.startup_menu = self.create_menu_item(startup_inactive_label, self.toggle_autostart)
|
2013-12-17 12:50:58 +00:00
|
|
|
# and exit option
|
2013-12-16 10:36:08 +00:00
|
|
|
self.create_menu_item("Exit", self.destroy)
|
|
|
|
self.ind.set_menu(self.menu)
|
|
|
|
|
2015-05-18 00:57:25 +00:00
|
|
|
# set router / gateway dynamically
|
|
|
|
router = self.get_default_gateway_linux()
|
|
|
|
|
2013-12-16 10:36:08 +00:00
|
|
|
# start the ping process
|
2013-12-16 06:26:43 +00:00
|
|
|
self.counter = 0
|
2013-12-16 10:36:08 +00:00
|
|
|
self.timeout = ping_frequency
|
2015-05-17 23:51:02 +00:00
|
|
|
self.ping_both()
|
2013-12-16 06:26:43 +00:00
|
|
|
|
2013-12-16 11:48:05 +00:00
|
|
|
# Print started message
|
|
|
|
print "Started."
|
|
|
|
|
2013-12-16 10:36:08 +00:00
|
|
|
# Begin runtime loop
|
|
|
|
Gtk.main()
|
2013-12-16 06:26:43 +00:00
|
|
|
|
2013-12-16 10:36:08 +00:00
|
|
|
#
|
|
|
|
# Runtime
|
|
|
|
#
|
2013-12-16 06:38:08 +00:00
|
|
|
|
2013-12-16 06:26:43 +00:00
|
|
|
if __name__ == "__main__":
|
2013-12-16 10:36:08 +00:00
|
|
|
pinger = Pinger()
|