Compare commits

..

2 Commits

Author SHA1 Message Date
e6f4038fb5 Add readme 2013-12-16 00:59:28 -07:00
6fc219338f Adding gtk tests 2013-12-16 00:58:13 -07:00
11 changed files with 150 additions and 614 deletions

56
.gitignore vendored
View File

@ -1,56 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
*.pot
# Sphinx documentation
docs/_build/
#deleted files
*~
# Temporary graph file
graph.png

View File

@ -1,15 +0,0 @@
This package 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 package 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, see <http://www.gnu.org/licenses/>
.
On Debian systems, the complete text of the GNU General
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".

View File

@ -1,26 +0,0 @@
![Screenshot](https://github.com/zyphlar/pinger/raw/master/pinger.png)
Pinger.py
=========
A ping tool that sits in your system tray
---------
- Saves your sanity when the wifi sucks
- Doesn't clutter up your screen with ping windows
- Lets you know when pings fail instead of silently failing
- **Currently for Ubuntu only**
- (Requires Python, GTK, and AppIndicator3)
- Startup Automatically option creates a `~/.config/autostart/pinger.desktop` file
- **Contributions welcome to expand to other OSes!**
**Usage (in Ubuntu):**
Open the Terminal program and enter the following commands:
cd ~
git clone https://github.com/zyphlar/pinger.git
python pinger/pinger.py &
Pinger should open in your system tray. To set Pinger to start automatically (in Ubuntu) click it and choose Start Automatically.
Report bugs or feature requests at https://github.com/zyphlar/pinger/issues

4
README.txt Normal file
View File

@ -0,0 +1,4 @@
Pinger.py -- A ping tool that sits in your system tray
+ Saves your sanity when the wifi sucks
+ Doesn't clutter up your screen with ping windows

View File

@ -1,5 +0,0 @@
- local caching
- remote submission
- centralized reporting
- packaging/distribution
- cross-platform gui

BIN
activity.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

114
appindicator.py Normal file
View File

@ -0,0 +1,114 @@
#!/usr/bin/env python
#
# Copyright 2009-2012 Canonical Ltd.
#
# Authors: Neil Jagdish Patel <neil.patel@canonical.com>
# Jono Bacon <jono@ubuntu.com>
# David Planella <david.planella@ubuntu.com>
#
# 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/>
#
from gi.repository import Gtk
# Timer
from gi.repository import GObject as gobject
# Pinging
import subprocess
# Regex
import re
# Vars
host = "www.google.com"
class HelloWorld:
def ping(self, widget=None, data=None):
ping = subprocess.Popen(
["ping", "-c", "1", host],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
out, error = ping.communicate()
if error:
label = "!! FAIL !!"
else:
m = re.search('time=(.*) ms', out)
label = m.group(1)+" ms"
#ind.set_label (label, "100.0 ms")
status.set_title(label)
#self.ping_menu_item.set_label(out)
gobject.timeout_add_seconds(self.timeout, self.ping)
def destroy(self, widget, data=None):
print "destroy signal occurred"
Gtk.main_quit()
def __init__(self):
# register a periodic timer
self.counter = 0
self.timeout = 1
gobject.timeout_add_seconds(self.timeout, self.ping)
def menuitem_response(w, buf):
print buf
def create_menu_item(menu, text, callback):
menu_items = Gtk.MenuItem(text)
menu.append(menu_items)
menu_items.connect("activate", callback, text)
# show the items
menu_items.show()
return menu_items
if __name__ == "__main__":
status = Gtk.StatusIcon()
status.set_title("0.0 ms")
status.set_from_stock(Gtk.STOCK_HOME)
status.connect("activate",Gtk.Window.present)
#ind = appindicator.Indicator.new (
# "pinger",
# "", #indicator-messages
# appindicator.IndicatorCategory.COMMUNICATIONS)
#ind.set_status (appindicator.IndicatorStatus.ACTIVE)
#ind.set_attention_icon ("indicator-messages-new")
#ind.set_label ("0.0 ms", "100.0 ms")
# create a menu
menu = Gtk.Menu()
# and the app
hello = HelloWorld()
# create menu items
#hello.ping_menu_item = create_menu_item(menu, "Ping", hello.ping)
create_menu_item(menu, "Exit", hello.destroy)
# Add the menu to our statusbar
#ind.set_menu(menu)
# Runtime loop
Gtk.main()

32
new_gtk.py Normal file
View File

@ -0,0 +1,32 @@
import gtk
class Main(gtk.Window):
def __init__(self):
super(Main, self).__init__()
self.connect('delete-event', self.on_delete_event)
self.set_title("Virtual Machine Monitor")
self.set_position(gtk.WIN_POS_CENTER)
self.set_default_size(640,600)
self.set_geometry_hints(min_width=640, min_height=600)
self.set_icon_from_file("activity.png")
#menubar = self.add_menubar()
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size("activity.png",25,25)
statusicon = gtk.StatusIcon()
statusicon.set_title("0.0 ms")
statusicon = gtk.status_icon_new_from_pixbuf(pixbuf)
statusicon.connect("activate",self.tray_activate)
self.show_all()
def on_delete_event(self, widget, event):
self.hide()
return True
def tray_activate(self, widget):
self.present()
if __name__ == "__main__":
Main()
gtk.main()

View File

@ -1,146 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Pinger.py -- A ping tool that sits in your system tray
# Copyright 2013 Will Bradley
#
# Contributors: Will Bradley <bradley.will@gmail.com>
# AltF4 <altf4@phx2600.org>
#
# 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/>
#
#
# Dependencies
#
# System Tray Icon
# Timer
# Date
import datetime
# Pinging
import subprocess
# Regex
import re
# Ctrl-c
import signal
# File paths
import os
# OS naming
import platform
# Argument parsing
import argparse
# For exit
import sys
# For graphing
# For IP addresses
import socket, struct
# For API
import json
#
# Main Class
#
class Pinger:
def ping(self, target, log=None, widget=None, data=None):
pingResult = Util.doPing(target)
ssidResult = Util.getSsid()
print json.dumps({
'target': target,
'ssid': ssidResult,
'loss': float(pingResult['loss']),
'rtt_avg': float(pingResult['rtt_avg']),
})
def __init__(self):
# Print welcome message
print "Starting Pinger..."
self.ping("4.2.2.2")
# Print started message
print "Finished."
#
# Utility class for platform-agnosticism and reusability
#
class Util:
@staticmethod
def getSsid():
if platform.system() == "Linux":
ssid = subprocess.Popen(["nmcli","-t","-f","active,ssid","dev","wifi"],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
) # | egrep '^yes(.*)' | cut -d\' -f2
out, error = ssid.communicate()
m = re.search(r'yes:(.*)', out)
result = m.group(1)
elif platform.system() == "Darwin":
ssid = subprocess.Popen(["/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport","-I"],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
out, error = ssid.communicate()
result = out
return result
@staticmethod
def doPing(target):
if platform.system() == "Linux":
ping = subprocess.Popen(
["ping", "-c", "5", target],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
pingOut, pingError = ping.communicate()
# Doing two regexes because Python doesn't like doing both in one
lossResult = re.search(r' ([\d\.]+)% packet loss', pingOut, re.MULTILINE)
rttAvgResult = re.search(r' = [\d\.]+/([\d\.]+)/[\d\.]+/[\d\.]+', pingOut, re.MULTILINE)
if pingError or lossResult is None or rttAvgResult is None:
output = {'loss':-1, 'rtt_avg':-1};
else:
output = {'loss':lossResult.group(1), 'rtt_avg':rttAvgResult.group(1)};
elif platform.system() == "Darwin": # Macintosh
ping = subprocess.Popen(
["ping", "-c", "5", target],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
pingOut, pingError = ping.communicate()
# print out
lossResult = re.search(r' ([\d\.]+)% packet loss', pingOut, re.MULTILINE)
rttAvgResult = re.search(r' = [\d\.]+/([\d\.]+)/[\d\.]+/[\d\.]+', pingOut, re.MULTILINE)
if pingError or lossResult is None or rttAvgResult is None:
output = {'loss':-1, 'rtt_avg':-1};
else:
output = {'loss':lossResult.group(1), 'rtt_avg':rttAvgResult.group(1)};
return output
#
# Runtime
#
if __name__ == "__main__":
pinger = Pinger()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

366
pinger.py
View File

@ -1,366 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Pinger.py -- A ping tool that sits in your system tray
# Copyright 2013 Will Bradley
#
# Contributors: Will Bradley <bradley.will@gmail.com>
# AltF4 <altf4@phx2600.org>
#
# 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/>
#
#
# Dependencies
#
# System Tray Icon
from gi.repository import Gtk
from gi.repository import AppIndicator3 as appindicator
# Timer
from gi.repository import GObject as gobject
import datetime
# Pinging
import subprocess
# Regex
import re
# Ctrl-c
import signal
# File paths
import os
# Argument parsing
import argparse
# For exit
import sys
# For graphing
import cairo
# For IP addresses
import socket, struct
# Vars
startup_active_label = "✓ Start Automatically"
startup_inactive_label = "Start Automatically"
pause_label = "Pause"
play_label = "Resume"
home_path = os.path.expanduser("~")
startup_path = home_path+'/.config/autostart/pinger.desktop'
startup_dir = home_path+'/.config/autostart/'
parser = argparse.ArgumentParser()
parser.add_argument("-t", "--target", help="Target to PING against. (IP / Hostname / Domain name). Defaults to 8.8.8.8")
parser.add_argument("-f", "--freq", help="Timeout between pings, in seconds. Defaults to 5")
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.")
args = parser.parse_args()
ubuntu_mono_dark_rgba = [0xdf, 0xd8, 0xc8, 0xff]
ubuntu_mono_light_rgba = [0x3a, 0x39, 0x35, 0xff]
black = [0, 0, 0, 0xff]
red = [0xdf, 0x38, 0x2c, 0xff]
orange = [0xdd, 0x48, 0x14, 0xff]
yellow = [0xef, 0xb7, 0x3e, 0xff]
white = [0xff, 0xff, 0xff, 0xff]
dark_bg = [0, 0, 0, 0x3f]
light_bg = [0xff, 0xff, 0xff, 0x3f]
#accumulate the arguments for use later
arguments = " "
for arg in sys.argv[1:]:
arguments += arg + " "
# User-editable variables
if args.target:
default_host = args.target
else:
print "Using default Internet target of 8.8.8.8"
default_host = "8.8.8.8" # IP or hostname of WAN
default_router = "192.168.1.1" # IP or hostname of router
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:
ping_log_max_size = 40
if args.color == "light":
graph_color = ubuntu_mono_light_rgba
danger_color = red
warning_color = yellow
graph_highlight = ubuntu_mono_dark_rgba
graph_background = light_bg
else:
graph_color = ubuntu_mono_dark_rgba
danger_color = red
warning_color = yellow
graph_highlight = ubuntu_mono_light_rgba
graph_background = dark_bg
#
# Main Class
#
class Pinger:
host = None
router = None
host_log = []
router_log = []
paused = False
autostart = False
icon_height = 22
def ping(self, target, log, widget=None, data=None):
if not self.paused:
#print "Pinging "+str(target)
ping = subprocess.Popen(
["ping", "-c", "1", target],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
out, error = ping.communicate()
m = re.search('time=(.*) ms', out)
if error or m == None:
label = "PING FAIL"
self.log_ping(log, -1)
else:
latency = "%.2f" % float(m.group(1))
label = latency+" ms"
self.log_ping(log, latency)
#self.ind.set_label(label, "100.0 ms")
def ping_both(self):
self.ping(self.host, self.host_log)
self.ping(self.router, self.router_log)
self.update_log_menu()
# If we have 5 router failures, try getting the default GW again
if (self.routerLastUpdated != None
and (datetime.datetime.now()-self.routerLastUpdated).seconds > 60):
if (len(self.router_log) > 5
and self.router_log[-1] == -1
and self.router_log[-2] == -1
and self.router_log[-3] == -1):
new_router = self.get_default_gateway_linux()
# Only update router if it's not blank
if new_router != None:
self.router = new_router
print "Updated router target to "+str(self.router)
self.routerLastUpdated = datetime.datetime.now()
gobject.timeout_add_seconds(self.timeout, self.ping_both)
def log_ping(self, log, value):
log.append(float(value))
# limit the size of the log
if len(log) >= ping_log_max_size:
# remove the earliest ping, not the latest
log.pop(0)
def create_menu_item(self, text, callback):
menu_item = Gtk.MenuItem(text)
self.menu.append(menu_item)
if callback:
menu_item.connect("activate", callback, text)
menu_item.show()
return menu_item
def destroy(self, widget, data=None):
print "Quitting..."
Gtk.main_quit()
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
self.startup_menu.set_label(startup_active_label)
else:
os.remove(startup_path)
self.autostart = False
self.startup_menu.set_label(startup_inactive_label)
def toggle_pause(self, widget, data=None):
if self.paused:
self.paused = False
self.pause_menu.set_label(pause_label)
else:
self.paused = True
self.pause_menu.set_label(play_label)
def update_log_menu(self):
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 )
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:
self.draw_log(ctx, self.router_log, (self.icon_height/2))
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):
max_ping = 100
elif(max(log) > 1000):
max_ping = 1000
else:
max_ping = max(log)
for index, ping in enumerate(log):
if float(ping) == -1: # Ping error
# Draw full-height error bar
self.draw_rect( ctx, [index,(self.icon_height/2)+yOffset], [1,(-self.icon_height/2)-1], danger_color )
else:
# draw normal bar
bar_height = -int(self.scale(ping, (0,max_ping), (0,(self.icon_height/2)-1)))
if bar_height > -1:
bar_height = -1
if ping > 100:
color = warning_color
else:
color = graph_color
self.draw_rect( ctx, [index,self.icon_height/2+yOffset], [1,bar_height], color )
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.
"""
scale = ((val - src[0]) / (src[1]-src[0])) * (dst[1]-dst[0]) + dst[0]
return scale
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))))
def __init__(self):
# Handle ctrl-c
signal.signal(signal.SIGINT, self.destroy)
# Print welcome message
print "Starting Pinger..."
# Create systray icon
self.ind = appindicator.Indicator.new (
"pinger",
"", # no icon
appindicator.IndicatorCategory.SYSTEM_SERVICES)
self.ind.set_status (appindicator.IndicatorStatus.ACTIVE)
#self.ind.set_label ("Pinger Loading...", "Pinger Loading...")
self.ind.set_icon_theme_path("/tmp")
# create a menu
self.menu = Gtk.Menu()
# with ping numbers
self.ping_menu = self.create_menu_item("", None)
self.router_menu = self.create_menu_item("", None)
# with pause option
self.pause_menu = self.create_menu_item(pause_label, self.toggle_pause)
# with autostart option
# first, check current autostart state by checking existance of .desktop file
if os.path.exists(startup_path):
self.autostart = True
self.startup_menu = self.create_menu_item(startup_active_label, self.toggle_autostart)
else:
self.autostart = False
self.startup_menu = self.create_menu_item(startup_inactive_label, self.toggle_autostart)
# and exit option
self.create_menu_item("Exit", self.destroy)
self.ind.set_menu(self.menu)
# load host/router vars
self.host = default_host
# set router / gateway dynamically
self.router = self.get_default_gateway_linux()
if self.router == None:
self.router = default_router
print "Set router target to "+str(self.router)
self.routerLastUpdated = datetime.datetime.now()
# start the ping process
self.counter = 0
self.timeout = ping_frequency
self.ping_both()
# Print started message
print "Started."
# Begin runtime loop
Gtk.main()
#
# Runtime
#
if __name__ == "__main__":
pinger = Pinger()