#!/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. '''