#!/usr/bin/env ruby
# systray code based on http://neugierig.org/software/misc/gmail-notifier

require 'gtk2'
require 'gtktrayicon'
require 'net/http'

require 'open-uri'
require 'uri'
require 'fileutils.rb'
require 'yaml'

class RouterConfig

	def user
		@user
	end
	def user=(usr)

		@user=usr
	end

	def url
		@url
	end
	def url=(ur)

		@url=ur
	end
	def pass
		@pass
	end
	def pass=(pw)

		@pass=pw
	end

end

class ConnectedStationsMessageBox < Gtk::Dialog
	def initialize (what)

		super()
		if (what == nil) then what="" end

		set_modal(true)
		#TODO: dynamic sizing from text
		set_default_size(200,150)

		set_title('Connected Stations')

		label = Gtk::Label.new(nil, nil)

		label.set_markup(what)
		vbox.add(label)

		ok = Gtk::Button.new('OK')
		action_area.add(ok)

		ok.signal_connect('clicked') do
			destroy
		end

	    show_all
	end

end

class ConfigureRouterMessageBox < Gtk::Dialog
	def initialize (routerConfig)

		super()
		set_modal(true)
		set_default_size(200,100)

		set_title('Router Configuration')

		lblUrl = Gtk::Label.new("URL", nil)

		lblUser = Gtk::Label.new("Username", nil)
		lblPass = Gtk::Label.new("Password", nil)

		entryUrl = Gtk::Entry.new
		entryUser = Gtk::Entry.new
		entryPass = Gtk::Entry.new

		entryPass.set_visibility(false)

		urlHbox = Gtk::HBox.new(true,nil)
		userHbox = Gtk::HBox.new(true,nil)

		passHbox = Gtk::HBox.new(true,nil)

		urlHbox.add(lblUrl)

		urlHbox.add(entryUrl);

		userHbox.add(lblUser)
		userHbox.add(entryUser);

		passHbox.add(lblPass)
		passHbox.add(entryPass);

		vbox.add(urlHbox)

		vbox.add(userHbox)
		vbox.add(passHbox)

		btOk = Gtk::Button.new('Save')
		btCancel = Gtk::Button.new('Cancel')

		action_area.add(btOk)
		action_area.add(btCancel)

		if (routerConfig != nil) then

			if (routerConfig.user != nil) then entryUser.text = routerConfig.user end

			if (routerConfig.pass != nil) then entryPass.text = routerConfig.pass end

			if (routerConfig.url != nil) then entryUrl.text = routerConfig.url end

		end

		btOk.signal_connect('clicked') do
			routerConfig.user=entryUser.text
			routerConfig.pass=entryPass.text
			routerConfig.url=entryUrl.text
			response(Gtk::Dialog::RESPONSE_ACCEPT)

		end

		btCancel.signal_connect('clicked') do
			response(Gtk::Dialog::RESPONSE_CANCEL)

		end

		show_all
	end
end

class TrayApplet < Gtk::TrayIcon
	def routerConfig
		@routerConfig

	end

	def routerConfig=(rcf)
		@routerConfig=rcf
	end

	def load_config
		begin
			@routerConfig=YAML::load(File.open(@configFolder+@configFile)) if File.exist?(@configFolder+@configFile)

			if (!@routerConfig) then
				puts "Error loading config file: "+$!
				@routerConfig = RouterConfig.new
				return false

			else
				print "Found #{@configFolder}#{@configFile}.\n"
				@tURL = URI.parse(routerConfig.url)

				return true
			end
		rescue
			puts "Error loading config file: "+$!

			@routerConfig = RouterConfig.new
			return false
		end
	end

	def initialize
		super("Wireless Station monitor")
		@configFolder = ENV['HOME']+"/.wifistat/"

		@configFile = "config.yaml"
		@staticConn=0

		@conf=load_config

		load_images
		if (@conf) then

			@image = Gtk::Image.new(@active)
		else

			@image = Gtk::Image.new(@inactive)
		end

		add(@image)

		show_all

		@tooltips = Gtk::Tooltips.new

		menu = Gtk::Menu.new

		item = Gtk::ImageMenuItem.new('_Show all')

		item.image = Gtk::Image.new(@active)
		item.signal_connect('activate') { show_stations }

		item.show_all
		menu.append(item)

		item = Gtk::ImageMenuItem.new('Configure')

		item.image = Gtk::Image.new(@config)
		item.signal_connect('activate') { configuration }

		item.show_all
		menu.append(item)

		item = Gtk::SeparatorMenuItem.new
		item.show_all
		menu.append(item)

		item = Gtk::ImageMenuItem.new(Gtk::Stock::QUIT)

		item.signal_connect('activate') { Gtk.main_quit }
		item.show; menu.append(item)

		add_events(Gdk::Event::BUTTON_PRESS_MASK)
		add_events(Gdk::Event::BUTTON_RELEASE_MASK)

		signal_connect('button-press-event') do |w, e|
			show_stations if e.button == 1 and e.event_type == Gdk::Event::BUTTON2_PRESS
		end

		signal_connect('button-release-event') do |w, e|
			menu.popup(nil, nil, e.button, e.time) if e.button == 3

		end

		update()
		Gtk::timeout_add(10000) {update; true}

	end

	def load_images

		img_online = "/usr/share/icons/Tango/scalable/status/gnome-dev-wavelan-encrypted.svg"
		img_offline = "/usr/share/icons/Tango/scalable/status/network-offline.svg"

		img_config = "/usr/share/icons/Tango/scalable/apps/config-users.svg"
		@active = Gtk::Image.new(Gdk::Pixbuf.new(img_online, 24, 24)).pixbuf
		@inactive = Gtk::Image.new(Gdk::Pixbuf.new(img_offline, 24, 24)).pixbuf
		@config = Gtk::Image.new(Gdk::Pixbuf.new(img_config, 24, 24)).pixbuf

	end

	def check
		# http request/html parsing to check for new connections
		# TODO: make regex a configurable parameter for multiple routers

		allItem = Array.new
		regex=/writeRow\(\[sid\,\"(.+)\"\,(.+)\,(.+)/

		open(@tURL, :http_basic_authentication=> [@routerConfig.user, @routerConfig.pass]) {|f|

			oi=f.read
			oi.scan(regex)
			tmpHash= {'mac'=>$1,'chan'=>$2}

			allItem << tmpHash
		}
		return allItem

	    rescue
			dialog = Gtk::MessageDialog.new(self,

			Gtk::Dialog::DESTROY_WITH_PARENT,
				Gtk::MessageDialog::WARNING,
				Gtk::MessageDialog::BUTTONS_CLOSE, "Error http request: "+$!)

			dialog.run
			dialog.destroy
	end

	def show_stations
		if (@allStation != nil) then

			stList="<b>MAC</b>\t\t\t\t<b>chan</b>\n"
			@allStation.each {	|st| stList+=st['mac']+"\t\t"+st['chan']+"\n" }

		else
			stList="No data\n"
		end

		dlg = ConnectedStationsMessageBox.new(stList)

	end

	def configuration
		cfg=ConfigureRouterMessageBox.new(routerConfig);
		cfg.run do |response|

			case response
				when Gtk::Dialog::RESPONSE_ACCEPT
					FileUtils.mkdir_p(@configFolder) unless File.directory?(@configFolder)

					File.open(@configFolder+@configFile, 'w') { |f| f.puts routerConfig.to_yaml }

					dlg = ConnectedStationsMessageBox.new("New config for #{routerConfig.url}")
			end
		end

		cfg.destroy
		@conf=true
		@tURL = URI.parse(routerConfig.url)

	end

	def update

		return if (@conf==false)

		@allStation = check()
		return if (@allStation == nil)
		@staticConn=@allStation.size if (@staticConn == 0) 

		dlg = ConnectedStationsMessageBox.new(

			"A new station has connected on \n#{routerConfig.url}"
			) if (@staticConn != @allStation.size)

		if (@allStation == nil) then
			nStat=0

		else
			nStat=@allStation.size
		end

		@image.pixbuf = if nStat > 0 then @active else @inactive end

		tip = if nStat > 0 then "#{nStat} connections" else 'no connection' end

		@tooltips.set_tip(self, tip, nil)
	end

end

# main

Gtk.init
TrayApplet.new
Gtk.main
%d bloggers like this: