#!/usr/bin/env python
# -*- coding: UTF8 -*-
#***************************************************************************
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU General Public License (GPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* 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 Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library 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 *
#* *
#***************************************************************************
'''
Fluxtwitter 0.2 - 19.11.2009
author: Yorik van Have
url: http://yorik.orgfree.com
A very simple app to connect to your twitter account, fetch your friends
timeline and display it in a pidgin-like window.
History:
0.1 - 12.08.2009 - First version
0.2 - 19.11.2009 - Added fluxbox pseudo-transparency support
'''
import os, gtk, gobject, twitter, urllib, re, subprocess, time
# defaults
USERNAME = "user" # username
PASSWORD = "pass" # password
DISPLAYTWEETS = 8 # number of tweets displayed
INTERVAL = 120 # update interval in seconds
BROWSER = "x-www-browser" # default browser
COMPOSITE = False
TRANSPARENCY = 50
TOOLBARHEIGHT = 19
fluxicon=[
"16 16 17 1",
" c None",
". c #060911",
"+ c #011136",
"@ c #022274",
"# c #002CA7",
"$ c #1E2F58",
"% c #313434",
"& c #3D360B",
"* c #0045DE",
"= c #505461",
"- c #7A6700",
"; c #8B8B8B",
"> c #B89F00",
", c #B6B8B5",
"' c #F9D900",
") c #DCDCD7",
"! c #FBFDFA",
"..++++.. ",
" @#######@+ ",
" @#@$$$@##@$. ",
" @@=)!);@=));. ",
" @=!!!!!,;)!!=. ",
" @,!!!!%%%%!!;+ ",
" @,!!!)....,!;@ ",
"+#;!!!!....)!$#+",
"+#$!!!!;.+$;%@@.",
" ##=)!!!=$&->>>&",
" @*#$;;&>''''''&",
" +**#.-''''''''&",
" @**#=->''''''&",
" @*#@#@&>'''> ",
" .@#***$-'>& ",
" +++ && "
]
fluxiconalt=[
"16 16 6 1",
" c None",
". c #FF8700",
"+ c #FF8800",
"@ c #FF8900",
"# c #FF8A00",
"$ c #FF8B00",
" @#+@@++ ",
" # @#+ ",
" + # ++ ",
" @ +# @ @ #@ ",
" # + # # ",
" ## @$#@ @+ ",
" @# @@++ #+ ",
" @@ @#@+ +# ",
" # $@@@@@#@ ",
" @ + +##@$ # ",
" @ @@+@@ +",
" # +. ",
" @ @@+ # ",
" @ @#+++$ @ ",
" @@ @@ @ ",
" ###@ @ "
]
iconnew = [
"16 16 17 1",
" c None",
". c #080A02",
"+ c #102E06",
"@ c #174A00",
"# c #444A3A",
"$ c #545200",
"% c #2C7200",
"& c #72756B",
"* c #7C7B7D",
"= c #62A200",
"- c #8A8C89",
"; c #B69B00",
"> c #AFAFB0",
", c #93E100",
"' c #C4C3C5",
") c #FCDB00",
"! c #F7F9F6",
" .++++++. ",
" @%%%%%%%@. ",
" +%@++@%%@+. ",
" @@>!!!#@'!'. ",
" %*!!!!!*!!!*. ",
" .@'!!!'.*.!!'+ ",
" .@!!!!-...-!'% ",
" .%>!!!>...>!#,.",
" .=#!!!!#.%@@.",
" .==*!!!'+$$;))$",
" ===+$))))))$",
" +,,%$))))))))$",
" =,,%$;)))))).",
" =,%$=$;))); ",
" .=,,,=$)). ",
" .$+ .+ "
]
iconnewalt = [
"16 16 4 1",
" c None",
". c #FF9900",
"+ c #FF9A00",
"@ c #FF9B00",
" ",
" +++++++.+ ",
" +++.++++++ ",
" ++ .+@ + ",
" ++ . . ",
" @+ + . ",
" ++ +.++ ++ ",
" +. @+@. +@ ",
" ++ .++ +++ ",
" .++ +++++++ ",
" @++.+ ++++++++ ",
" +++++++++++++ ",
" +++++++++++@ ",
" .++++++++++@ ",
" +++++++++ ",
" . + "
]
class TwitterStatusIcon(gtk.StatusIcon):
def __init__(self):
gtk.StatusIcon.__init__(self)
menu = '''
'''
actions = [
('Menu', None, 'Menu'),
('Update', gtk.STOCK_REFRESH, '_Update now', None, 'Update', self.update),
('Settings', gtk.STOCK_PREFERENCES, '_Settings...', None, 'Settings', self.config),
('About', gtk.STOCK_ABOUT, '_About...', None, 'About Fluxtwitter', self.about),
('Close', gtk.STOCK_CLOSE, '_Close', None, 'Close', self.close)]
ag = gtk.ActionGroup('Actions')
ag.add_actions(actions)
self.manager = gtk.UIManager()
self.manager.insert_action_group(ag, 0)
self.manager.add_ui_from_string(menu)
self.menu = self.manager.get_widget('/Twitter/Menu/About').props.parent
self.icon = gtk.gdk.pixbuf_new_from_xpm_data(fluxicon)
self.iconnew = gtk.gdk.pixbuf_new_from_xpm_data(iconnew)
self.set_from_pixbuf(self.icon)
self.getconfig()
self.set_tooltip(self.username + "'s timeline")
self.set_visible(True)
self.isTweet = False
self.connect('popup-menu', self.popup_menu)
self.connect('activate', self.showtimeline)
self.api = twitter.Api(username=self.username,password=self.password)
self.lastid = 0
self.createdialog()
self.timeout = gobject.timeout_add(self.interval*1000,self.update)
self.update()
def createdialog(self):
self.tweetdialog = gtk.Window()
self.tweetdialog.connect("destroy",self.showtimeline)
self.tweetdialog.connect("delete-event",self.showtimeline)
self.tweetdialog.connect('configure-event', self.updateBackground)
self.tweetdialog.set_title(self.username)
self.tweetdialog.set_icon(self.icon)
self.tweetdialog.set_border_width(5)
self.tweetdialog.set_size_request(280, 500)
self.layout = gtk.ScrolledWindow()
self.layout.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC)
self.vbox = gtk.VBox()
self.layout.add_with_viewport(self.vbox)
self.tweetdialog.add(self.layout)
self.layout.get_child().set_shadow_type(gtk.SHADOW_NONE)
self.layout.connect('scroll-child', self.updateBackground)
self.table = gtk.Table(self.displaytweets,2)
self.table.set_row_spacings(10)
self.vbox.pack_start(self.table)
self.labels = []
self.icons = []
self.users = []
for i in range(self.displaytweets):
label = gtk.Label()
label.set_line_wrap(True)
label.set_width_chars(25)
label.set_selectable(True)
label.connect("activate-link",self.clicked)
self.table.attach(label,1,2,i,i+1)
self.labels.append(label)
icon = gtk.Image()
button = gtk.Button()
button.set_relief(gtk.RELIEF_NONE)
button.set_image(icon)
button.set_name(str(i))
button.connect("clicked",self.clicked)
button.set_focus_on_click(False)
self.table.attach(button,0,1,i,i+1)
self.icons.append(icon)
self.users.append('')
self.updateBackground()
def update(self, data=None):
print "updating ",self.timeout,time.strftime('%X %x %Z')
try:
statuses = self.api.GetFriendsTimeline(count=self.displaytweets)
except:
print "Error: Couldn't connect to Twitter server."
return True
if statuses[0].id != self.lastid:
self.set_from_pixbuf(self.iconnew)
self.lastid = statuses[0].id
for i in range(len(statuses)):
print i
iconurl = statuses[i].user.GetProfileImageUrl()
iconfile=urllib.urlopen(iconurl)
pbl = gtk.gdk.PixbufLoader()
pbl.write(iconfile.read())
pb = pbl.get_pixbuf()
pbl.close()
if not pb:
pb = self.icon
pb = pb.scale_simple(48,48,gtk.gdk.INTERP_BILINEAR)
self.icons[i].set_from_pixbuf(pb)
self.icons[i].set_tooltip_text(statuses[i].user.name)
pbl.close()
print "from ",statuses[i].user.name," : ",statuses[i].text," ",iconurl
tweettext = statuses[i].text.replace('ç','c')
tweettext = tweettext.replace('ã','a')
tweettext = tweettext.replace("'","")
tweettext = tweettext.replace("&","&")
pat1 = re.compile(r"(^|[\n ])(([\w]+?://[\w\#$%&~.\-;:=,?@\[\]+]*)(/[\w\#$%&~/.\-;:=,?@\[\]+]*)?)", re.IGNORECASE | re.DOTALL)
pat2 = re.compile(r"(^|[\n ])(@([\w\#$%&~.\-;:=,?@\[\]+]*)(/[\w\#$%&~/.\-;:=,?@\[\]+]*)?)", re.IGNORECASE | re.DOTALL)
pat3 = re.compile(r"(^|[\n ])(#([\w\#$%&~.\-;:=,?@\[\]+]*)(/[\w\#$%&~/.\-;:=,?@\[\]+]*)?)", re.IGNORECASE | re.DOTALL)
tweettext = pat1.sub(r'\1\3', tweettext)
tweettext = pat2.sub(r'\1\2', tweettext)
tweettext = pat3.sub(r'\1\2', tweettext)
self.labels[i].set_markup(tweettext)
self.users[i] = statuses[i].user.screen_name
return True
def getconfig(self):
configfile = os.path.expanduser('~') + os.sep + '.fluxtwitterrc'
if os.path.isfile(configfile):
file = open(configfile)
for line in file:
if not("#" in line):
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
if key == "username": self.username = value
elif key == "password": self.password = value
elif key == "displaytweets": self.displaytweets = int(value)
elif key == "browser": self.browser = value
elif key == "interval": self.interval = int(value)
elif key == "composite": self.composite = bool(value)
elif key == "transparency": self.transparency = int(value)
elif key == "toolbarheight": self.toolbarheight = int(value)
file.close()
else:
self.username = USERNAME
self.password = PASSWORD
self.displaytweets = DISPLAYTWEETS
self.browser = BROWSER
self.interval = INTERVAL
self.composite = COMPOSITE
self.transparency = TRANSPARENCY
self.toolbarheight = TOOLBARHEIGHT
print "Creating config file..."
self.writeconfig()
def writeconfig(self):
configfile = os.path.expanduser('~') + os.sep + '.fluxtwitterrc'
file = open(configfile,'wb')
file.write('# Fluxtwitter configuration file\n')
file.write('# This is your twitter username\n')
file.write('username = ' + self.username + '\n')
file.write('# This is your twitter password\n')
file.write('password = ' + self.password + '\n')
file.write('# Number of tweets displayed (default 8)\n')
file.write('displaytweets = ' + str(self.displaytweets) + '\n')
file.write('# Browser command to open links (default x-www-browser) \n')
file.write('browser = ' + self.browser + '\n')
file.write('# Interval in seconds between twitter updates (default 120)\n')
file.write('interval = ' + str(self.interval) + '\n')
file.write('# Do we use pseudo-transparency?\n')
file.write('composite = '+ str(self.composite) + '\n')
file.write('# Amount of image fading in percent\n')
file.write('transparency = '+ str(self.transparency) + '\n')
file.write('# Window titlebar height correction in pixels\n')
file.write('toolbarheight = ' + str(self.toolbarheight) + '\n')
file.close()
def config(self,data):
dialog = gtk.Dialog()
dialog.set_title('Fluxtwitter settings')
table = gtk.Table(8,2)
c1 = gtk.Entry()
c1.set_text(self.username)
c1.set_tooltip_text('Your twitter username')
c2 = gtk.Entry()
c2.set_text(self.password)
c2.set_visibility(False)
c2.set_tooltip_text('Your twitter password')
c3 = gtk.Entry()
c3.set_text(str(self.displaytweets))
c3.set_tooltip_text('Number of tweets to display')
c4 = gtk.Entry()
c4.set_text(self.browser)
c4.set_tooltip_text('Web browser to open links in')
c5 = gtk.Entry()
c5.set_text(str(self.interval))
c5.set_tooltip_text('Interval in seconds between updates')
c6 = gtk.ToggleButton()
c6.set_active(self.composite)
c6.set_tooltip_text('Check this to use fluxbox pseudo-transparency')
c7 = gtk.Entry()
c7.set_text(str(self.transparency))
c7.set_tooltip_text('Fading level in percents')
c8 = gtk.Entry()
c8.set_text(str(self.toolbarheight))
c8.set_tooltip_text('Vertical correction in pixels')
table.attach(gtk.Label('Username '),0,1,0,1)
table.attach(c1,1,2,0,1)
table.attach(gtk.Label('Password '),0,1,1,2)
table.attach(c2,1,2,1,2)
table.attach(gtk.Label('Nr of tweets '),0,1,2,3)
table.attach(c3,1,2,2,3)
table.attach(gtk.Label('Web browser '),0,1,3,4)
table.attach(c4,1,2,3,4)
table.attach(gtk.Label('Interval '),0,1,4,5)
table.attach(c5,1,2,4,5)
table.attach(gtk.Label('Pseudo-transparency '),0,1,5,6)
table.attach(c6,1,2,5,6)
table.attach(gtk.Label('Fading '),0,1,6,7)
table.attach(c7,1,2,6,7)
table.attach(gtk.Label('Vertical correction '),0,1,7,8)
table.attach(c8,1,2,7,8)
dialog.vbox.pack_start(table)
dialog.show_all()
cancel_button = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
ok_button = dialog.add_button(gtk.STOCK_OK,gtk.RESPONSE_OK)
ok_button.grab_default()
resp = dialog.run()
if resp == gtk.RESPONSE_OK:
self.username = c1.get_text()
self.password = c2.get_text()
self.displaytweets = int(c3.get_text())
self.browser = c4.get_text()
self.interval = int(c5.get_text())
self.composite = c6.is_active()
self.transparency = int(c7.get_text())
self.toolbarheight = int(c8.get_text())
self.writeconfig()
dialog.destroy()
def updateBackground(self,args=None,stuff=None):
if self.composite:
x,y = self.tweetdialog.get_position()
w,h = self.tweetdialog.get_size()
bgfile = os.path.expanduser('~') + os.sep + '.fluxbox/lastwallpaper'
if os.path.isfile(bgfile):
wpfile = open(bgfile)
pb=gtk.gdk.pixbuf_new_from_file(wpfile.read().split('|')[1])
wpfile.close()
crop = gtk.gdk.Pixbuf( gtk.gdk.COLORSPACE_RGB, False, 8, w, h )
pb.copy_area(x, y+self.toolbarheight, w, h, crop, 0, 0)
mask = crop.copy()
mask.fill(0x00000000)
opacity = int((self.transparency/100)*255)
mask.composite(crop, 0, 0, w, h, 0, 0, 1, 1, gtk.gdk.INTERP_BILINEAR, 127)
pm,m = crop.render_pixmap_and_mask(255)
style = self.tweetdialog.get_style().copy()
style.bg_pixmap[gtk.STATE_NORMAL] = pm
self.tweetdialog.set_style(style)
self.layout.get_child().set_style(style)
def clicked(self,data,url=None):
if data.name:
url="http://www.twitter.com/"+self.users[int(data.name)]
self.tweetdialog.hide()
self.isTweet = False
# subprocess.Popen([self.browser,url],shell=False)
def close(self, data):
gobject.source_remove(self.timeout)
gtk.main_quit()
def popup_menu(self, status, button, time):
self.menu.popup(None, None, None, button, time)
def about(self, data):
dialog = gtk.AboutDialog()
dialog.set_name('Fluxtwitter')
dialog.set_version('0.2')
dialog.set_comments('A system tray icon displaying twitter feed')
dialog.set_website('http://yorik.uncreated.net')
dialog.run()
dialog.destroy()
def showtimeline(self,data,event=None):
if self.isTweet:
self.tweetdialog.hide()
self.isTweet = False
else:
self.isTweet = True
self.tweetdialog.show_all()
self.set_from_pixbuf(self.icon)
return True
if __name__ == '__main__':
TwitterStatusIcon()
gtk.main()
note1 = '''
Since 2.18, GTK+ supports markup for clickable hyperlinks in addition to regular Pango markup. The markup for links is borrowed from HTML, using the a with href and title attributes. GTK+ renders links similar to the way they appear in web browsers, with colored, underlined text. The title attribute is displayed as a tooltip on the link. An example looks like this:
gtk_label_set_markup (label, "Go to the GTK+ website for more...");
It is possible to implement custom handling for links and their tooltips with the "activate-link" signal and the gtk_label_get_current_uri() function. '''