├── .gitignore
├── org.perezdecastro.Alexandria.gresources.xml
├── org.perezdecastro.Alexandria.desktop
├── gtk
└── menus.ui
├── Makefile
├── README.md
└── alexandria
/.gitignore:
--------------------------------------------------------------------------------
1 | org.perezdecastro.Alexandria.gresource
2 |
--------------------------------------------------------------------------------
/org.perezdecastro.Alexandria.gresources.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | gtk/menus.ui
5 |
6 |
7 |
--------------------------------------------------------------------------------
/org.perezdecastro.Alexandria.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Hidden=false
3 | Name=Alexandria
4 | Exec=alexandria
5 | TryExec=alexandria
6 | Icon=accesories-dictionary
7 | Comment=Read book in ePub format
8 | Type=Application
9 | NoDisplay=false
10 | StartupNotify=true
11 | Categories=GNOME;GTK;Office
12 | Terminal=false
13 | X-GNOME-UsesNotifications=false
14 |
--------------------------------------------------------------------------------
/gtk/menus.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ifeq ($(strip $(APP_ID)),)
2 | APP_ID := org.perezdecastro.Alexandria
3 | endif
4 |
5 | all: $(APP_ID).gresource
6 |
7 | RESOURCE_FILES := $(wildcard gtk/*.ui)
8 |
9 | $(APP_ID).gresource: $(APP_ID).gresources.xml $(RESOURCE_FILES)
10 | glib-compile-resources --target=$@ $<
11 |
12 | run: $(APP_ID).gresource
13 | __ALEXANDRIA_DEVELOPMENT=1 ./alexandria $(FILE)
14 |
15 | .PHONY: run
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Alexandria
2 | ==========
3 |
4 | A dumb EPub book viewer.
5 |
6 | Needs [libgepub](https://github.com/danigm/libgepub) and
7 | [PyGI](https://wiki.gnome.org/Projects/PyGObject).
8 |
9 |
10 | Motivation
11 | ----------
12 |
13 | I wanted a simple reader which would not ask me to install ~20 dependencies
14 | (like GNOME Documents), and that would not use an obscenely outdated WebKitGTK+
15 | release (like PPub). I use this to preview books in my computer, but in the end
16 | I do my reading using an e-reader, and that's why Alexandria is quite dumb: it
17 | has only the minimum needed for what I do and not look out of place in GNOME.
18 | That's about it.
19 |
--------------------------------------------------------------------------------
/alexandria:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # vim:fenc=utf-8
4 | #
5 | # Copyright © 2017 Adrian Perez
6 | #
7 | # Distributed under terms of the GPLv3 license.
8 |
9 | import gi
10 | gi.require_versions(dict(WebKit2='4.0', Gepub='0.4', Gtk='3.0', GLib='2.0'))
11 | from gi.repository import Gepub, Gtk, GObject, Gio
12 |
13 | def _find_resources_path(program_path):
14 | from os import path as P, environ
15 | devel = environ.get("__ALEXANDRIA_DEVELOPMENT")
16 | if devel and devel.strip():
17 | # Use the directory where the executable is located, most likely
18 | # a checkout of the Git repository.
19 | path = P.dirname(program_path)
20 | else:
21 | # Use an installed location: binary is in /bin/alexandria,
22 | # and resources in /share/alexandria/*
23 | path = P.join(P.dirname(P.dirname(program_path)), "share", "alexandria")
24 | return P.abspath(P.join(path, "org.perezdecastro.Alexandria.gresource"))
25 |
26 |
27 | class memoized_function(object):
28 | __slots__ = ("value", "get_value")
29 | INVALID = object()
30 |
31 | def __init__(self, f):
32 | self.value = self.INVALID
33 | self.get_value = f
34 |
35 | def __call__(self, *arg, **kw):
36 | if self.value is self.INVALID:
37 | self.value = self.get_value(*arg, **kw)
38 | return self.value
39 |
40 |
41 | def memoized_property(fget, doc=None):
42 | return property(memoized_function(fget), doc=doc)
43 |
44 |
45 | class AppWindow(Gtk.ApplicationWindow):
46 | def __init__(self, application, document):
47 | self._document = document
48 | Gtk.ApplicationWindow.__init__(self,
49 | application=application,
50 | icon_name="accessories-dictionary",
51 | role="main-window")
52 | self.set_titlebar(self._headerbar)
53 | self._headerbar.set_subtitle(document.get_metadata("title"))
54 | view = Gepub.Widget()
55 | view.set_doc(document)
56 | self.add(view)
57 |
58 | @memoized_property
59 | def _headerbar(self):
60 | header = Gtk.HeaderBar()
61 | header.set_title("Alexandria")
62 | prev_button = Gtk.Button.new_from_icon_name("go-previous-symbolic",
63 | Gtk.IconSize.BUTTON)
64 | prev_button.connect("clicked", self.__on_prev)
65 | next_button = Gtk.Button.new_from_icon_name("go-next-symbolic",
66 | Gtk.IconSize.BUTTON)
67 | next_button.connect("clicked", self.__on_next)
68 | header.pack_start(prev_button)
69 | header.pack_start(next_button)
70 | header.set_show_close_button(True)
71 | header.show_all()
72 | return header
73 |
74 | def __on_prev(self, button):
75 | self._document.go_prev()
76 |
77 | def __on_next(self, button):
78 | self._document.go_next()
79 |
80 |
81 | class App(Gtk.Application):
82 | def __init__(self):
83 | Gtk.Application.__init__(self,
84 | application_id="org.perezdecastro.Alexandria",
85 | flags=Gio.ApplicationFlags.HANDLES_OPEN)
86 | self.connect("startup", self.__on_startup)
87 | self.connect("open", self.__on_open)
88 |
89 | def __action(self, name, callback):
90 | action = Gio.SimpleAction.new(name)
91 | action.connect("activate", callback)
92 | self.add_action(action)
93 |
94 | def __on_startup(self, app):
95 | gtk_settings = Gtk.Settings.get_default()
96 | gtk_settings.set_property("gtk-dialogs-use-header", True)
97 | self.__action("quit", lambda *arg: self.quit())
98 |
99 | def __on_open(self, app, files, n_files, hint):
100 | for f in files:
101 | if f.query_exists(None):
102 | window = AppWindow(self, Gepub.Doc.new(f.get_path()))
103 | self.add_window(window)
104 | window.show_all()
105 | window.present()
106 |
107 |
108 | if __name__ == "__main__":
109 | import signal, sys
110 | signal.signal(signal.SIGINT, signal.SIG_DFL)
111 | Gio.Resource.load(_find_resources_path(__file__))._register()
112 | sys.exit(App().run(sys.argv))
113 |
--------------------------------------------------------------------------------