2
# -.- coding: utf-8 -.-
6
# Copyright © 2009 Seif Lotfy <seif@lotfy.com>
7
# Copyright © 2009 Siegfried Gevatter <siegfried@gevatter.com>
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation, either version 3 of the License, or
12
# (at your option) any later version.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU Lesser General Public License for more details.
19
# You should have received a copy of the GNU Lesser General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
34
from docky.docky import DockyItem, DockySink
35
from zeitgeist.client import ZeitgeistClient
36
from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, StorageState
37
from signal import signal, SIGTERM
39
except ImportError, e:
43
CLIENT = ZeitgeistClient()
44
version = [int(x) for x in CLIENT.get_version()]
45
MIN_VERSION = [0, 3, 1, 0]
46
if version < MIN_VERSION:
47
print "PLEASE USE ZEITGEIST 0.3.1 or above"
50
except RuntimeError, e:
51
print "Unable to connect to Zeitgeist, won't send events. Reason: '%s'" %e
55
"http://zeitgeist-project.com/schema/1.0/core#VisitEvent":"OPENED",
56
"http://zeitgeist-project.com/schema/1.0/core#ModifyEvent":"SAVED",
57
"http://zeitgeist-project.com/schema/1.0/core#CreateEvent":"CREATED"
62
A program lauching utility which handles opening a URI or executing a
63
program or .desktop launcher, handling variable expansion in the Exec
66
Adds the launched URI or launcher to the ~/.recently-used log. Sets a
67
DESKTOP_STARTUP_ID environment variable containing useful information such
68
as the URI which caused the program execution and a timestamp.
70
See the startup notification spec for more information on
75
self.recent_model = None
77
def _get_recent_model(self):
78
# FIXME: This avoids import cycles
79
if not self.recent_model:
80
import zeitgeist_recent
81
self.recent_model = zeitgeist_recent.recent_model
82
return self.recent_model
84
def launch_uri(self, uri, mimetype = None):
85
assert uri, "Must specify URI to launch"
91
os.environ['zeitgeist_LAUNCHER'] = uri
92
os.environ['DESKTOP_STARTUP_ID'] = self.make_startup_id(uri)
93
os.spawnlp(os.P_NOWAIT, "gnome-open", "gnome-open", uri)
98
mimetype = "application/octet-stream"
100
# Use XDG to lookup mime type based on file name.
101
# gtk_recent_manager_add_full requires it.
103
mimetype = xdg.Mime.get_type_by_name(uri)
105
mimetype = str(mimetype)
107
except (ImportError, NameError):
108
# No mimetype found for URI: %s
112
def get_local_path(self, uri):
113
scheme, path = urllib.splittype(uri)
116
elif scheme == "file":
117
path = urllib.url2pathname(path)
118
if path[:3] == "///":
123
def launch_command_with_uris(self, command, uri_list, launcher_uri = None):
124
if command.rfind("%U") > -1:
127
uri_str = uri_str + " " + uri
128
return self.launch_command(command.replace("%U", uri_str), launcher_uri)
129
elif command.rfind("%F") > -1:
132
uri = self.get_local_path(self, uri)
134
file_str = file_str + " " + uri
136
# Command does not support non-file URLs
138
return self.launch_command(command.replace("%F", file_str), launcher_uri)
139
elif command.rfind("%u") > -1:
142
startup_ids.append(self.launch_command(command.replace("%u", uri), launcher_uri))
144
return self.launch_command(command.replace("%u", ""), launcher_uri)
146
elif command.rfind("%f") > -1:
149
uri = self.get_local_path(self, uri)
151
startup_ids.append(self.launch_command(command.replace("%f", uri),
154
#print " !!! Command does not support non-file URLs: ", command
157
return self.launch_command(command.replace("%f", ""), launcher_uri)
160
return self.launch_command(command, launcher_uri)
162
def make_startup_id(self, key, ev_time = None):
164
ev_time = gtk.get_current_event_time()
166
return "zeitgeist_TIME%d" % ev_time
168
return "zeitgeist:%s_TIME%d" % (key, ev_time)
170
def parse_startup_id(self, id):
171
if id and id.startswith("zeitgeist:"):
173
uri = id[len("zeitgeist:"):id.rfind("_TIME")]
174
timestamp = id[id.rfind("_TIME") + len("_TIME"):]
175
return (uri, timestamp)
180
def launch_command(self, command, launcher_uri = None):
181
startup_id = self.make_startup_id(launcher_uri)
184
# Inside forked child
186
os.environ['DESKTOP_STARTUP_ID'] = startup_id
188
os.environ['zeitgeist_LAUNCHER'] = launcher_uri
194
return (child, startup_id)
198
Icon lookup swiss-army knife (from menutreemodel.py)
204
def load_icon_from_path(self, icon_path, icon_size=None):
207
icon = gtk.gdk.pixbuf_new_from_file_at_size(icon_path,
208
int(icon_size), int(icon_size))
211
return gtk.gdk.pixbuf_new_from_file(icon_path)
216
def load_icon_from_data_dirs(self, icon_value, icon_size=None):
218
if os.environ.has_key("XDG_DATA_DIRS"):
219
data_dirs = os.environ["XDG_DATA_DIRS"]
221
data_dirs = "/usr/local/share/:/usr/share/"
223
for data_dir in data_dirs.split(":"):
224
retval = self.load_icon_from_path(os.path.join(data_dir, "pixmaps", icon_value),
229
retval = self.load_icon_from_path(os.path.join(data_dir, "icons", icon_value),
235
def transparentize(self, pixbuf, percent):
236
pixbuf = pixbuf.add_alpha(False, '0', '0', '0')
237
for row in pixbuf.get_pixels_array():
239
pix[3] = min(int(pix[3]), 255 - (percent * 0.01 * 255))
242
def greyscale(self, pixbuf):
243
pixbuf = pixbuf.copy()
244
for row in pixbuf.get_pixels_array():
246
pix[0] = pix[1] = pix[2] = (int(pix[0]) + int(pix[1]) + int(pix[2])) / 3
249
def load_icon(self, icon_value, icon_size, force_size=True, cache=True):
250
if not self.icon_dict.has_key(str(icon_value)+str(icon_size)) or not cache:
252
if isinstance(icon_value, gtk.gdk.Pixbuf):
254
elif os.path.isabs(icon_value):
255
icon = self.load_icon_from_path(icon_value, icon_size)
258
icon_name = os.path.basename(icon_value)
260
icon_name = icon_value
262
if icon_name.endswith(".png"):
263
icon_name = icon_name[:-len(".png")]
264
elif icon_name.endswith(".xpm"):
265
icon_name = icon_name[:-len(".xpm")]
266
elif icon_name.endswith(".svg"):
267
icon_name = icon_name[:-len(".svg")]
270
info = icon_theme.lookup_icon(icon_name, icon_size, gtk.ICON_LOOKUP_USE_BUILTIN)
272
if icon_name.startswith("gtk-"):
273
icon = info.load_icon()
274
elif info.get_filename():
275
icon = self.load_icon_from_path(info.get_filename(), icon_size)
277
icon = self.load_icon_from_data_dirs(icon_value, icon_size)
280
self.icon_dict[str(icon_value)+str(icon_size)] = icon
283
self.icon_dict[str(icon_value)+str(icon_size)] = None
286
return self.icon_dict[str(icon_value)+str(icon_size)]
288
def load_image(self, icon_value, icon_size, force_size=True):
289
pixbuf = self.load_icon(icon_value, icon_size, force_size)
291
img.set_from_pixbuf(pixbuf)
295
def make_icon_frame(self, thumb, icon_size = None, blend = False):
298
mythumb = gtk.gdk.Pixbuf(thumb.get_colorspace(),
300
thumb.get_bits_per_sample(),
303
mythumb.fill(0x00000080) # black, 50% transparent
305
thumb.composite(mythumb, 0.5, 0.5,
306
thumb.get_width(), thumb.get_height(),
309
gtk.gdk.INTERP_NEAREST,
311
thumb.copy_area(border, border,
312
thumb.get_width() - (border * 2), thumb.get_height() - (border * 2),
323
def get_icon(self, subject, icon_size, timestamp = 0):
326
if not self.icon_dict.get(uri+str(icon_size)):
327
cached_icon = self._lookup_or_make_thumb(uri, subject.mimetype,
328
icon_size, timestamp)
329
self.icon_dict[uri+str(icon_size)] = cached_icon
331
return self.icon_dict[uri+str(icon_size)]
333
def _lookup_or_make_thumb(self, uri, mimetype, icon_size, timestamp):
334
icon_name, icon_type = \
335
gnome.ui.icon_lookup(icon_theme, thumb_factory, uri, mimetype, 0)
337
if icon_type == gnome.ui.ICON_LOOKUP_RESULT_FLAGS_THUMBNAIL or \
338
thumb_factory.has_valid_failed_thumbnail(uri,int(timestamp)):
339
# Use existing thumbnail
340
thumb = icon_factory.load_icon(icon_name, icon_size)
341
elif self._is_local_uri(uri):
342
# Generate a thumbnail for local files only
343
thumb = thumb_factory.generate_thumbnail(uri, mimetype)
344
thumb_factory.save_thumbnail(thumb, uri, timestamp)
347
thumb = icon_factory.make_icon_frame(thumb, icon_size)
353
return icon_factory.load_icon(icon_name, icon_size)
355
def _is_local_uri(self, uri):
356
# NOTE: gnomevfs.URI.is_local seems to hang for some URIs (e.g. ssh
357
# or http). So look in a list of local schemes which comes
358
# directly from gnome_vfs_uri_is_local_scheme.
359
scheme, path = urllib.splittype(self.get_uri() or "")
360
return not scheme or scheme in ("file", "help", "ghelp", "gnome-help", "trash",
361
"man", "info", "hardware", "search", "pipe",
364
class CellRendererPixbuf(gtk.CellRendererPixbuf):
367
'toggled': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
368
(gobject.TYPE_STRING,))
371
gtk.CellRendererPixbuf.__init__(self)
372
self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
374
def do_activate(self, event, widget, path, background_area, cell_area, flags):
375
model = widget.get_model()
376
self.emit("toggled",path)
378
icon_theme = gtk.icon_theme_get_default()
379
icon_factory = IconFactory()
380
thumbnailer = Thumbnailer()
381
thumb_factory = gnome.ui.ThumbnailFactory("normal")
382
launcher = LaunchManager()
385
from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, StorageState
387
class Window(gtk.Window):
388
def __init__(self, CLIENT):
389
gtk.Window.__init__(self)
390
self.view = DataIconView()
391
scroll = gtk.ScrolledWindow()
392
scroll.add_with_viewport(self.view)
394
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
396
self.set_size_request(600,480)
399
def load_events(self, start=0, end=0, actor=None):
400
self.set_title("Journal for "+actor)
402
return not uri.startswith("file://") or os.path.exists(urllib.unquote(str(uri[7:])))
404
def _handle_find_events(ids):
405
self._zg.get_events(ids, _handle_get_events)
407
def _handle_get_events(events):
410
for subject in event.subjects:
411
if exists(subject.uri):
412
self.view.append_object(event)
415
event.set_actor(actor)
416
self._zg.find_events_for_templates([event],_handle_get_events, [start, end], StorageState.Any, 0, 4)
419
class DataIconView(gtk.TreeView):
421
gtk.TreeView.__init__(self)
422
self.store = gtk.TreeStore(gtk.gdk.Pixbuf,
426
gobject.TYPE_PYOBJECT
430
icon_cell = gtk.CellRendererPixbuf()
431
icon_cell.set_property("yalign", 0.5)
432
icon_column = gtk.TreeViewColumn("Icon",icon_cell,pixbuf=0)
434
time_cell = gtk.CellRendererText()
435
time_column = gtk.TreeViewColumn("Time", time_cell, markup=1)
437
interp_cell = gtk.CellRendererText()
438
interp_column = gtk.TreeViewColumn("Interpretation", interp_cell, markup=2)
440
subject_cell = gtk.CellRendererText()
441
subject_column = gtk.TreeViewColumn("Subject", subject_cell, markup=3)
443
self.append_column(time_column)
444
self.append_column(icon_column)
445
self.append_column(subject_column)
446
self.append_column(interp_column)
447
self.set_model(self.store)
449
self.set_headers_visible(False)
450
self.iter = {"uri":None, "iter":None}
455
def append_object(self, event):
457
icon = thumbnailer.get_icon(event.subjects[0], 32)
458
date = int(event.timestamp)/1000
459
date = datetime.datetime.fromtimestamp(date).strftime("%H:%M:%S %A, %D")
462
if self.iter["uri"] == event.subjects[0].uri:
463
self.store.append(self.iter["iter"],
466
INTERPRETATION[event.interpretation],
467
event.subjects[0].text,
471
self.iter["uri"] = event.subjects[0].uri
472
self.iter["iter"] = self.store.append(None, [icon,
474
INTERPRETATION[event.interpretation],
475
event.subjects[0].text,
478
class DockyJournalItem(DockyItem):
479
def __init__(self, path):
480
DockyItem.__init__(self, path)
481
menu_id = self.iface.AddMenuItem("Journal", "document-open-recent", "")
482
self.id_map[menu_id] = "Journal"
484
if self.iface.GetOwnsUri():
485
self.uri = self.iface.GetUri()
487
self.uri = self.iface.GetDesktopFile()
489
def menu_pressed(self, menu_id):
490
if self.id_map[menu_id] == "Journal":
491
window = Window(CLIENT)
492
window.load_events(0, time.time() * 1000, self.uri)
494
class DockyJournalSink(DockySink):
495
def item_path_found(self, pathtoitem, item):
496
if item.GetOwnsUri() or item.GetOwnsDesktopFile():
497
self.items[pathtoitem] = DockyJournalItem(pathtoitem)
499
dockysink = DockyJournalSink()
504
if __name__ == "__main__":
505
mainloop = gobject.MainLoop(is_running=True)
507
atexit.register (cleanup)
509
signal(SIGTERM, lambda signum, stack_frame: exit(1))
511
while mainloop.is_running():