2 """ Minibook: the Facebook(TM) status updater
3 (C) 2009 Gergely Imreh <imrehg@gmail.com>
14 from facebook
import Facebook
16 print "Pyfacebook is not available, cannot run."
21 gobject
.threads_init()
22 gtk
.gdk
.threads_init()
23 gtk
.gdk
.threads_enter()
27 spelling_support
= True
29 spelling_support
= False
33 (ID
, STATUS
, DATETIME
, REPLIES
, LIKES
) = range(5)
36 #-------------------------------------------------
37 # From http://edsiper.linuxchile.cl/blog/?p=152
38 # to mitigate TreeView + threads problems
39 #-------------------------------------------------
41 class _IdleObject(gobject
.GObject
):
43 Override gobject.GObject to always emit signals in the main thread
44 by emmitting on an idle handler
48 gobject
.GObject
.__init
__(self
)
50 def emit(self
, *args
):
51 gobject
.idle_add(gobject
.GObject
.emit
, self
, *args
)
54 #-------------------------------------------------
56 #-------------------------------------------------
58 class _WorkerThread(threading
.Thread
, _IdleObject
):
59 """A single working thread."""
63 gobject
.SIGNAL_RUN_LAST
,
65 (gobject
.TYPE_PYOBJECT
, )),
67 gobject
.SIGNAL_RUN_LAST
,
69 (gobject
.TYPE_PYOBJECT
, ))}
71 def __init__(self
, function
, *args
, **kwargs
):
72 threading
.Thread
.__init
__(self
)
73 _IdleObject
.__init
__(self
)
74 self
._function
= function
80 print('Thread %s calling %s', self
.name
, str(self
._function
))
86 result
= self
._function
(*args
, **kwargs
)
87 except Exception, exc
: # Catch ALL exceptions
88 # TODO: Check if this catch all warnins too!
89 print('Exception %s' % str(exc
))
90 self
.emit("exception", exc
)
93 print('Thread %s completed', self
.name
)
95 self
.emit("completed", result
)
99 class _ThreadManager(object):
100 """Manages the threads."""
102 def __init__(self
, max_threads
=2):
103 """Start the thread pool. The number of threads in the pool is defined
104 by `pool_size`, defaults to 2."""
105 self
._max
_threads
= max_threads
106 self
._thread
_pool
= []
112 def _remove_thread(self
, widget
, arg
=None):
113 """Called when the thread completes. We remove it from the thread list
114 (dictionary, actually) and start the next thread (if there is one)."""
116 # not actually a widget. It's the object that emitted the signal, in
117 # this case, the _WorkerThread object.
118 thread_id
= widget
.name
120 print('Thread %s completed, %d threads in the queue' % (thread_id
,
121 len(self
._thread
_pool
)))
123 self
._running
.remove(thread_id
)
125 if self
._thread
_pool
:
126 if len(self
._running
) < self
._max
_threads
:
127 next
= self
._thread
_pool
.pop()
128 print('Dequeuing thread %s', next
.name
)
129 self
._running
.append(next
.name
)
134 def add_work(self
, complete_cb
, exception_cb
, func
, *args
, **kwargs
):
135 """Add a work to the thread list."""
137 thread
= _WorkerThread(func
, *args
, **kwargs
)
138 thread_id
= '%s' % (self
._thread
_id
)
140 thread
.connect('completed', complete_cb
)
141 thread
.connect('completed', self
._remove
_thread
)
142 thread
.connect('exception', exception_cb
)
143 thread
.setName(thread_id
)
145 if len(self
._running
) < self
._max
_threads
:
146 self
._running
.append(thread_id
)
149 running_names
= ', '.join(self
._running
)
150 print('Threads %s running, adding %s to the queue',
151 running_names
, thread_id
)
152 self
._thread
_pool
.append(thread
)
159 """The main application interface"""
161 def enter_callback(self
, widget
, entry
):
162 entry_text
= entry
.get_buffer().get_text()
163 print "Entry contents: %s\n" % entry_text
165 def sendupdate(self
):
166 textfield
= self
.entry
.get_buffer()
167 start
= textfield
.get_start_iter()
168 end
= textfield
.get_end_iter()
169 entry_text
= textfield
.get_text(start
, end
)
171 print "Sent entry contents: %s\n" % entry_text
172 self
._facebook
.status
.set([entry_text
], [self
._facebook
.uid
])
174 textfield
.set_text("")
176 def getupdates(self
):
177 list = self
._facebook
.status
.get([self
._facebook
.uid
], [10])
181 status_list
.append((status
['status_id'],
186 for data
in status_list
:
187 self
.liststore
.append(data
)
189 def post_updates(self
, widget
, results
):
190 print("Update result: %s" % (str(results
)))
193 def except_updates(self
, widget
, exception
):
194 print("Update exception: %s" % (str(exception
)))
196 def count(self
, text
):
197 start
= text
.get_start_iter()
198 end
= text
.get_end_iter()
199 thetext
= text
.get_text(start
, end
)
200 self
.count_label
.set_text('(%d)' % (160 - len(thetext
)))
203 def __init__(self
, facebook
):
204 global spelling_support
206 # create a new window
207 self
.window
= gtk
.Window(gtk
.WINDOW_TOPLEVEL
)
208 self
.window
.set_size_request(400, 250)
209 self
.window
.set_title("Minibook")
210 self
.window
.connect("delete_event", lambda w
, e
: gtk
.main_quit())
212 vbox
= gtk
.VBox(False, 0)
213 self
.window
.add(vbox
)
217 self
.statuslist_window
= gtk
.ScrolledWindow()
218 self
.statuslist_window
.set_policy(gtk
.POLICY_NEVER
, gtk
.POLICY_ALWAYS
)
219 self
.statuslist_window
.add(self
.treeview
)
221 self
.statuslist_window
.show()
222 vbox
.add(self
.statuslist_window
)
224 hbox
= gtk
.HBox(False, 0)
226 label
= gtk
.Label("What's on your mind?")
227 hbox
.pack_start(label
, True, True, 0)
229 self
.count_label
= gtk
.Label("(160)")
230 hbox
.pack_start(self
.count_label
, True, True, 0)
231 self
.count_label
.show()
235 self
.entry
= gtk
.TextView()
236 text
= self
.entry
.get_buffer()
237 text
.connect('changed', self
.count
)
238 vbox
.pack_start(self
.entry
, True, True, 0)
241 hbox
= gtk
.HBox(False, 0)
245 button
= gtk
.Button(stock
=gtk
.STOCK_CLOSE
)
246 button
.connect("clicked", lambda w
: gtk
.main_quit())
247 hbox
.pack_start(button
, True, True, 0)
248 button
.set_flags(gtk
.CAN_DEFAULT
)
249 button
.grab_default()
252 button
= gtk
.Button(stock
=gtk
.STOCK_ADD
)
253 button
.connect("clicked", lambda w
: self
.sendupdate())
254 hbox
.pack_start(button
, True, True, 0)
255 button
.set_flags(gtk
.CAN_DEFAULT
)
256 button
.grab_default()
261 spelling
= gtkspell
.Spell(self
.entry
, 'en')
263 spelling_support
= False
266 self
._facebook
= facebook
268 self
._app
_icon
= 'minibook.png'
269 self
._systray
= gtk
.StatusIcon()
270 self
._systray
.set_from_file(self
._app
_icon
)
271 self
._systray
.set_tooltip('%s\n' \
272 'Left-click: toggle window hiding' % (APPNAME
))
273 self
._systray
.connect('activate', self
.systray_click
)
274 self
._systray
.set_visible(True)
276 self
._threads
= _ThreadManager()
278 self
.userinfo
= self
._facebook
.users
.getInfo([self
._facebook
.uid
], \
280 self
._threads
.add_work(self
.post_updates
,
284 def systray_click(self
, widget
, user_param
=None):
285 if self
.window
.get_property('visible'):
288 self
.window
.deiconify()
289 self
.window
.present()
291 def create_grid(self
):
292 self
.liststore
= gtk
.ListStore(gobject
.TYPE_STRING
,
297 self
.treeview
= gtk
.TreeView(self
.liststore
)
298 self
.treeview
.set_property('headers-visible', False)
299 self
.treeview
.set_rules_hint(True)
301 self
.status_renderer
= gtk
.CellRendererText()
302 #~ self.status_renderer.set_property('wrap-mode', gtk.WRAP_WORD)
303 self
.status_renderer
.set_property('wrap-width', 350)
304 self
.status_renderer
.set_property('width', 10)
306 self
.status_column
= gtk
.TreeViewColumn('Message', \
307 self
.status_renderer
, text
=1)
308 self
.status_column
.set_cell_data_func(self
.status_renderer
, \
310 self
.treeview
.append_column(self
.status_column
)
311 self
.treeview
.set_resize_mode(gtk
.RESIZE_IMMEDIATE
)
313 def status_format(self
, column
, cell
, store
, position
):
314 status
= store
.get_value(position
, Columns
.STATUS
)
315 name
= self
.userinfo
['name']
318 cell
.set_property('markup', markup
)
324 gtk
.gdk
.threads_leave()
327 if __name__
== "__main__":
329 config_file
= open("config", "r")
330 api_key
= config_file
.readline()[:-1]
331 secret_key
= config_file
.readline()[:-1]
333 exit('Error while loading config file: %s' % (str(e
)))
334 facebook
= Facebook(api_key
, secret_key
)
335 facebook
.auth
.createToken()
338 # Delay dialog to allow for login in browser
339 dia
= gtk
.Dialog('minibook: login',
342 gtk
.DIALOG_DESTROY_WITH_PARENT | \
343 gtk
.DIALOG_NO_SEPARATOR
,
344 ("Logged In", gtk
.RESPONSE_OK
, gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CLOSE
))
345 label
= gtk
.Label("Click after logging in to Facebook in your browser:")
346 dia
.vbox
.pack_start(label
, True, True, 10)
350 if result
== gtk
.RESPONSE_CLOSE
:
355 facebook
.auth
.getSession()
356 print 'Session Key: ', facebook
.session_key
357 print 'Your UID: ', facebook
.uid