#!/usr/bin/env python

# NVidiaScreenlet 
# 
# 	Copyright 2008 Rene Jansen <tequilasunrise@planet.nl>
#
# 	This file is part of nVidiaScreenlet.
#
#    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 3 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, see <http://www.gnu.org/licenses/>.
#
#	And I personally would like an e-mail if someone would be using the screenlet
#	code or the screenlet itself in other projects. I'm curious in nature ;)
#
# Requirements:
# - Nvidia proprietary driver
# - nvidia-settings package
#
# Info:
# - A multipurpose nVidia GPU screenlet using the nvidia-settings package
# 
# Features:
# - Viewing the GPU type via the nvidia-settings package
# - Viewing the amount of memory of the GPU in MB via the nvidia-settings package
# - Viewing the current driver version via the nvidia-settings package
# - Viewing the current resolution via the nvidia-settings package or xrandr if CRT is used
# - Viewing the current refreshrate via the nvidia-settings package
# - Viewing the current GPU clock frequency via the nvidia-settings package
# - Viewing the current Memory clock frequency via the nvidia-settings package
# - Viewing the current GPU temperature via the nvidia-settings package (Range from 0 to 100 degrees celcius and Fahrenheit equivalent)
# - Middle mouse button on the Nvidia logo to run the nvidia-settings package
#
# TODO
# - Multi GPU support (I don't have a Multi GPU PC. Feel free to add)
# - GPU fanspeed monitor (nvidia-settings doesn't have a read-out for this)
# - Boolean options to turn views on/off
# - CPU optimization (lowering CPU usage)
#
#=========================================
# ChangeLog: <version number> <dd-mm-yyyy>
#
# v0.5 [05-06-2008]
# - Fixed nvidia-settings recognition bug (On a small amount of computers)
#
# v0.4 [04-06-2008]
# - Fixed resolution bug
#
# v0.3 [03-06-2008]
# - Major code cleaning
# - Added choice of interval in seconds (range from 1 to 60)
# - Added choice of font size (range from 4 to 10)
# - Added choice of indentation to position the data values (range from 0 to 2)
# - Added choice of foreground, text and textshadow colors
# - Added choice of temperature units (used formula from <http://www.csgnetwork.com/tempconvjava.html>)
#
# v0.2 [25-5-2008]
# - Fixed notation bug
# - Major code cleaning
#
# v0.1 [24-5-2008]
# - First public release.

import gtk, screenlets
from screenlets.options import IntOption, FloatOption, BoolOption, StringOption
from screenlets.options import ColorOption, FontOption, ListOption
import cairo, pango, gobject, math, sys, commands, os
#use gettext for translation
import gettext

_ = screenlets.utils.get_translator(__file__)

def tdoc(obj):
	obj.__doc__ = _(obj.__doc__)
	return obj

@tdoc
class NVidiaScreenlet(screenlets.Screenlet):
	"""A multipurpose nVidia GPU screenlet using the nvidia-settings package."""
	
	# default meta-info for Screenlets
	__name__ = 'nVidiaScreenlet'
	__version__ = '0.5.1'
	__author__ = 'Rene Jansen'
	__desc__ = __doc__

	# internals
	__updateTimer = None
	checkReq = False
	p_layout = None
	textPrefix = [_('GPU:'), _('Ram:'), _('Driver:'), _('Resolution:'), _('Refreshrate:'),
				  _('GPU Clock Frequency:'), _('Ram Clock Frequency:'),
				  _('Run nvidia-settings'), _('GPU Core Temp:')]
	data = [None,None,None,None,None,None,None,0]
	temp = None
	
	# settings
	update_interval = 3 # secs
	showTextShadow = True
	fontSize = 9
	indentData = 2
	indentDataValues = [80,110,140]
	degrees = [_('Celcius'), _('Fahrenheit')]
	degree = _('Celcius')

	# theme
	colorFG = [0,0,0,0.3515]
	colorGraph = [0,1,0,1]
	colorText = [1,1,1,1]
	colorShadow = [0,0,0,1]

	def __init__(self, **keyword_args):
		screenlets.Screenlet.__init__(self, width=275, height=370, uses_theme=True, **keyword_args)
		self.theme_name = "default"
		
		# add default options
		self.add_default_menuitems()

		# add screenlet specific options
		self.add_options_group(_('Nvidia Screenlet Options'), _('Nvidia Screenlet Options'))
		self.add_option(IntOption(_('Nvidia Screenlet Options'), 'update_interval', 
								   self.update_interval, _('Update-Interval'), 
								   _('The interval in seconds for updating the Nvidia Screenlet'),
								   min=1, max=60))
		self.add_option(IntOption(_('Nvidia Screenlet Options'), 'fontSize', 
								   self.fontSize, _('Font Size'), 
								   _('The font size within the Nvidia Screenlet'),
								   min=4, max=10))
		self.add_option(IntOption(_('Nvidia Screenlet Options'), 'indentData',
								   self.indentData, _('Indent Data Values'),
								   _('Set indentation (0 to 2) to position the data values'),
								   min=0, max =2))
		self.add_option(BoolOption(_('Nvidia Screenlet Options'), 'showTextShadow',
								   self.showTextShadow, _('Text Shadow'),
								   _('Render text with a shadow')))
		self.add_option(ColorOption(_('Nvidia Screenlet Options'), 'colorFG', self.colorFG, _('Foreground Color'), ''))
		self.add_option(ColorOption(_('Nvidia Screenlet Options'), 'colorText', self.colorText, _('Text Color'), ''))
		self.add_option(ColorOption(_('Nvidia Screenlet Options'), 'colorShadow', self.colorShadow, _('Text Shadow Color'), ''))
		self.add_option(StringOption(_('Nvidia Screenlet Options'), 'degree', self.degree,
									 _('Temperature Unit'), _('Select Temperature Unit'),
									 choices = [tempItem for tempItem in self.degrees]))

		# set all internal variables
		self.checkRequirement()
		self.data[0] = self.getGpuType()
		self.data[1] = self.getGpuRam()
		self.data[2] = self.getGpuDriver()
		self.data[3] = self.getGpuResolution()
		self.data[4] = self.getGpuRefreshRate()
		self.data[5] = self.getGpuClock()
		self.data[6] = self.getGpuMemClock()
		self.data[7] = self.getGpuTemp()

		self.update_interval = self.update_interval

	def __setattr__(self, name, value):
		# call Screenlet.__setattr__ in baseclass (ESSENTIAL!!!!)
		screenlets.Screenlet.__setattr__(self, name, value)
		# check for this Screenlet's attributes, we are interested in:
		if name == "update_interval":
			self.__dict__['update_interval'] = value
			if self.__updateTimer: gobject.source_remove(self.__updateTimer)
			self.__updateTimer = gobject.timeout_add(value * 1000, self.updateGUI)

	# check if the nvidia-settings package is installed
	def checkRequirement(self):
		if commands.getoutput("nvidia-settings -v | grep 'tool'").lstrip().rstrip() == _("The NVIDIA X Server Settings tool."):
			self.checkReq = True
		else:
			print "Failed to open nvidia-settings. Is it installed?"
			sys.exit()

	# (this is an ugly solution because the name of the computer is different
	# everywhere. For now I don't have and know an alternative)
	# return GPU Type to its caller
	def getGpuType(self):
		if self.checkReq:
			output = commands.getoutput("nvidia-settings -q Gpus | cut -d '(' -f 2 -s")
			return output.replace(")","")
		else:
			return "ERR"

	# return GPU Ram size in MB to its caller
	def getGpuRam(self):
		if self.checkReq:
			output = commands.getoutput("nvidia-settings -q VideoRam -t")
			return str((int(output)/1024)) + " MB"
		else:
			return "ERR"

	# return current GPU Driver version to its caller
	def getGpuDriver(self):
		if self.checkReq:
			output = commands.getoutput("nvidia-settings -q NvidiaDriverVersion -t")
			return output
		else:
			return "ERR"

	# return current screen resolution to its caller
	def getGpuResolution(self):
		if self.checkReq:
			output = commands.getoutput("nvidia-settings -q FrontendResolution -t")
			if output != "":
				return output.replace(",", "x")
			else:
				crt = commands.getoutput("xrandr | grep '*'")[3:12]
				return crt
		else:
			return "ERR"
	
	# return current refreshrate to its caller
	def getGpuRefreshRate(self):
		if self.checkReq:
			output = commands.getoutput("nvidia-settings -q RefreshRate -t")
			return output
		else:
			return "ERR"

	# return current GPU Clock Frequency to its caller
	def getGpuClock(self):
		if self.checkReq:
			output = commands.getoutput("nvidia-settings -q GPUCurrentClockFreqs -t | cut -d ',' -f1")
			return str(output) + " MHz"
		else:
			return "ERR"

	# return current GPU Memory Clock Frequency to its caller
	def getGpuMemClock(self):
		if self.checkReq:
			output = commands.getoutput("nvidia-settings -q GPUCurrentClockFreqs -t | cut -d ',' -f2")
			return str(output) + " MHz"
		else:
			return "ERR"

	# return current GPU Core Temperature to its caller
	def getGpuTemp(self):
		if self.checkReq:
			output = commands.getoutput("nvidia-settings -q GPUCoreTemp -t")
			return output
		else:
			return "ERR"

	def updateGUI(self):
		self.data[0] = self.getGpuType()
		self.data[1] = self.getGpuRam()
		self.data[2] = self.getGpuDriver()
		self.data[3] = self.getGpuResolution()
		self.data[4] = self.getGpuRefreshRate()
		self.data[5] = self.getGpuClock()
		self.data[6] = self.getGpuMemClock()
		self.data[7] = self.getGpuTemp()

		self.redraw_canvas()
		return True

	def on_mouse_down(self, event):
		# filter events
		if event.button == 2:
			os.system("nvidia-settings")			

	def drawText( self, ctx, x, y, text, size, rgba, align=0 ):
		ctx.save()
		if self.p_layout == None: self.p_layout = ctx.create_layout()
		else: ctx.update_layout(self.p_layout)
		p_fdesc = pango.FontDescription()
		p_fdesc.set_family_static("Free Sans")
		p_fdesc.set_size(size*pango.SCALE)
		self.p_layout.set_font_description(p_fdesc)
		self.p_layout.set_markup(text)
		ctx.set_source_rgba(rgba[0], rgba[1], rgba[2], rgba[3])
		textSize = self.p_layout.get_pixel_size()
		self.textWidth = textSize[0]
		if align == 1: x = x - textSize[0]
		elif align == 2: x = x - textSize[0]/2
		ctx.translate(x, y)
		ctx.show_layout(self.p_layout)
		ctx.fill()
		ctx.restore()

	def draw_round_rect( self, ctx, x, y, w, h, r=0, fill=True ):
		""" Draw a pretty rounded rectangle. """
		ctx.move_to( x, y+r )
		ctx.arc( x+r, y+r, r, 3.141, 4.712) #top left
		ctx.line_to( x-r+w, y )
		ctx.arc( x-r+w, y+r, r, 4.712, 0) #top right
		ctx.line_to( x+w, y-r+h )
		ctx.arc( x-r+w, y-r+h, r, 0, 1.570) #bottom right
		ctx.line_to( x+r, y+h )
		ctx.arc( x+r, y-r+h, r, 1.570, 3.141) #bottom left
		ctx.line_to( x, y+r )
		if fill: ctx.fill()
		else: ctx.stroke()

	def on_draw(self, ctx):
		ctx.scale(self.scale, self.scale)
		ctx.set_operator(cairo.OPERATOR_SOURCE)

		#Background
		if self.theme:
			self.theme['background.svg'].render_cairo(ctx)

		#Forground
		x = 0
		y = 0
		for i in range(0, 7):
			ctx.save()
			thisx = 7+(x*33)
			thisy = 55+(y*33)
			ctx.set_source_rgba(self.colorFG[0],self.colorFG[1],self.colorFG[2],self.colorFG[3])
			self.draw_round_rect( ctx, thisx, thisy, 260, 30, 5, True )

			if self.showTextShadow:
				if (i == 5) or (i == 6):
					self.drawText(ctx, thisx+6, thisy+10, self.textPrefix[i], self.fontSize, self.colorShadow, 0 )
					self.drawText(ctx, thisx+141, thisy+10, str(self.data[i]), self.fontSize, self.colorShadow, 0 )
				else:
					self.drawText(ctx, thisx+6, thisy+10, self.textPrefix[i], self.fontSize, self.colorShadow, 0 )
					self.drawText(ctx, thisx+(self.indentDataValues[self.indentData])+1, thisy+10, str(self.data[i]),
								  self.fontSize, self.colorShadow, 0 )
			
			if (i == 5) or (i == 6):
				self.drawText(ctx, thisx+5, thisy+9, self.textPrefix[i], self.fontSize, self.colorText, 0 )			
				self.drawText(ctx, thisx+140, thisy+9, str(self.data[i]), self.fontSize, self.colorText, 0 )
			else:
				self.drawText(ctx, thisx+5, thisy+9, self.textPrefix[i], self.fontSize, self.colorText, 0 )			
				self.drawText(ctx, thisx+(self.indentDataValues[self.indentData]), thisy+9, str(self.data[i]),
							  self.fontSize, self.colorText, 0 )
			ctx.restore()

			y += 1
		
		#Temperature graph
		thisx = 100
		thisy = 55+(y*33)
		ctx.set_source_rgba(self.colorFG[0],self.colorFG[1],self.colorFG[2],self.colorFG[3])
		self.draw_round_rect( ctx, thisx, thisy, 75, 75, 5, True )

		if self.degree == _('Celcius'):
			sign = "C"
			tempValue = self.data[7]
		else:
			sign = "F"
			calc = (float(9.0/5.0))*(int(self.data[7]))+32
			tempValue = (int(calc))

		ctx.save()
		ctx.translate( thisx, thisy )
		ctx.rectangle(0, 75-float(int(self.data[7])/100.0)*75.0, 75, 75)
		ctx.clip()
		self.theme['gpu_fill.svg'].render_cairo(ctx)
		ctx.restore()

		u = u"\N{DEGREE SIGN}"
		ctx.save()
		if self.showTextShadow:
			self.drawText(ctx, thisx+8, thisy+10, self.textPrefix[8], 6, self.colorShadow, 0 )

		
		self.drawText(ctx, thisx+7, thisy+9, self.textPrefix[8], 6, self.colorText, 0 )			
		self.drawText(ctx, thisx+10, thisy+45, str(tempValue) + " " + u.encode("utf-8") + sign, 12, self.colorShadow, 0 )
		ctx.restore()

	def on_draw_shape(self,ctx):
		ctx.scale(self.scale, self.scale)
		ctx.set_operator(cairo.OPERATOR_OVER)
		if self.theme:
			self.theme['background.svg'].render_cairo(ctx)

# If the program is run directly or passed as an argument to the python
# interpreter then create a Screenlet instance and show it
if __name__ == "__main__":
	import screenlets.session
	screenlets.session.create_session(NVidiaScreenlet)
