forked from koreader/koreader
-
Notifications
You must be signed in to change notification settings - Fork 0
/
reader.lua
executable file
·355 lines (319 loc) · 12 KB
/
reader.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
#!./luajit
io.stdout:write([[
---------------------------------------------
launching...
_ _____ ____ _
| |/ / _ \| _ \ ___ __ _ __| | ___ _ __
| ' / | | | |_) / _ \/ _` |/ _` |/ _ \ '__|
| . \ |_| | _ < __/ (_| | (_| | __/ |
|_|\_\___/|_| \_\___|\__,_|\__,_|\___|_|
It's a scroll... It's a codex... It's KOReader!
[*] Current time: ]], os.date("%x-%X"), "\n")
io.stdout:flush()
-- Load default settings
require("defaults")
local DataStorage = require("datastorage")
pcall(dofile, DataStorage:getDataDir() .. "/defaults.persistent.lua")
-- Set up Lua and ffi search paths
require("setupkoenv")
io.stdout:write(" [*] Version: ", require("version"):getCurrentRevision(), "\n\n")
io.stdout:flush()
-- Read settings and check for language override
-- Has to be done before requiring other files because
-- they might call gettext on load
G_reader_settings = require("luasettings"):open(
DataStorage:getDataDir().."/settings.reader.lua")
local lang_locale = G_reader_settings:readSetting("language")
-- Allow quick switching to Arabic for testing RTL/UI mirroring
if os.getenv("KO_RTL") then lang_locale = "ar_AA" end
local _ = require("gettext")
if lang_locale then
_.changeLang(lang_locale)
end
-- Make the C blitter optional (ffi/blitbuffer.lua will check that env var)
local ffi = require("ffi")
local dummy = require("ffi/posix_h")
local C = ffi.C
if G_reader_settings:isTrue("dev_no_c_blitter") then
if ffi.os == "Windows" then
C._putenv("KO_NO_CBB=true")
else
C.setenv("KO_NO_CBB", "true", 1)
end
else
if ffi.os == "Windows" then
C._putenv("KO_NO_CBB=false")
else
C.unsetenv("KO_NO_CBB")
end
end
-- Should check DEBUG option in arg and turn on DEBUG before loading other
-- modules, otherwise DEBUG in some modules may not be printed.
local dbg = require("dbg")
if G_reader_settings:isTrue("debug") then dbg:turnOn() end
if G_reader_settings:isTrue("debug") and G_reader_settings:isTrue("debug_verbose") then dbg:setVerbose(true) end
-- Option parsing:
local longopts = {
debug = "d",
profile = "p",
help = "h",
}
local function showusage()
print("usage: ./reader.lua [OPTION] ... path")
print("Read all the books on your E-Ink reader")
print("")
print("-d start in debug mode")
print("-v debug in verbose mode")
print("-p enable Lua code profiling")
print("-h show this usage help")
print("")
print("If you give the name of a directory instead of a file path, a file")
print("chooser will show up and let you select a file")
print("")
print("If you don't pass any path, the last viewed document will be opened")
print("")
print("This software is licensed under the AGPLv3.")
print("See http://github.com/koreader/koreader for more info.")
end
local Profiler = nil
local ARGV = arg
local argidx = 1
while argidx <= #ARGV do
local arg = ARGV[argidx]
argidx = argidx + 1
if arg == "--" then break end
-- parse longopts
if arg:sub(1,2) == "--" then
local opt = longopts[arg:sub(3)]
if opt ~= nil then arg = "-"..opt end
end
-- code for each option
if arg == "-h" then
return showusage()
elseif arg == "-d" then
dbg:turnOn()
elseif arg == "-v" then
dbg:setVerbose(true)
elseif arg == "-p" then
Profiler = require("jit.p")
Profiler.start("la")
else
-- not a recognized option, should be a filename
argidx = argidx - 1
break
end
end
-- Setup device
local Device = require("device")
-- DPI
local dpi_override = G_reader_settings:readSetting("screen_dpi")
if dpi_override ~= nil then
Device:setScreenDPI(dpi_override)
end
-- Night mode
if G_reader_settings:isTrue("night_mode") then
Device.screen:toggleNightMode()
end
-- Dithering
if Device:hasEinkScreen() then
Device.screen:setupDithering()
if Device.screen.hw_dithering and G_reader_settings:isTrue("dev_no_hw_dither") then
Device.screen:toggleHWDithering(false)
end
if Device.screen.sw_dithering and G_reader_settings:isTrue("dev_no_sw_dither") then
Device.screen:toggleSWDithering(false)
end
-- NOTE: If device can HW dither (i.e., after setupDithering(), hw_dithering is true, but sw_dithering is false),
-- but HW dither is explicitly disabled, and SW dither enabled, don't leave SW dither disabled (i.e., re-enable sw_dithering)!
if Device:canHWDither() and G_reader_settings:isTrue("dev_no_hw_dither") and G_reader_settings:nilOrFalse("dev_no_sw_dither") then
Device.screen:toggleSWDithering(true)
end
end
-- Handle global settings migration
local SettingsMigration = require("ui/data/settings_migration")
SettingsMigration:migrateSettings(G_reader_settings)
-- Document renderers canvas
local CanvasContext = require("document/canvascontext")
CanvasContext:init(Device)
-- Touch screen (this may display some widget, on first install on Kobo Touch,
-- so have it done after CanvasContext:init() but before Bidi.setup() to not
-- have mirroring mess x/y probing).
if Device:needsTouchScreenProbe() then
Device:touchScreenProbe()
end
-- UI mirroring for RTL languages, and text shaping configuration
local Bidi = require("ui/bidi")
Bidi.setup(lang_locale)
-- Avoid loading UIManager and widgets before here, as they may
-- cache Bidi mirroring settings. Check that with:
-- for name, _ in pairs(package.loaded) do print(name) end
-- User fonts override
local fontmap = G_reader_settings:readSetting("fontmap")
if fontmap ~= nil then
local Font = require("ui/font")
for k, v in pairs(fontmap) do
Font.fontmap[k] = v
end
end
local UIManager = require("ui/uimanager")
-- Inform once about color rendering on newly supported devices
-- (there are some android devices that may not have a color screen,
-- and we are not (yet?) able to guess that fact)
if Device:hasColorScreen() and not G_reader_settings:has("color_rendering") then
-- enable it to prevent further display of this message
G_reader_settings:saveSetting("color_rendering", true)
local InfoMessage = require("ui/widget/infomessage")
UIManager:show(InfoMessage:new{
text = _("Documents will be rendered in color on this device.\nIf your device is grayscale, you can disable color rendering in the screen sub-menu for reduced memory usage."),
})
end
-- Conversely, if color is enabled on a Grayscale screen (e.g., after importing settings from a color device), warn that it'll break stuff and adversely affect performance.
if G_reader_settings:isTrue("color_rendering") and not Device:hasColorScreen() then
local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{
text = _("Color rendering is mistakenly enabled on your grayscale device.\nThis will subtly break some features, and adversely affect performance."),
cancel_text = _("Ignore"),
cancel_callback = function()
return
end,
ok_text = _("Disable"),
ok_callback = function()
local Event = require("ui/event")
G_reader_settings:delSetting("color_rendering")
CanvasContext:setColorRenderingEnabled(false)
UIManager:broadcastEvent(Event:new("ColorRenderingUpdate"))
end,
})
end
-- Helpers
local lfs = require("libs/libkoreader-lfs")
local function retryLastFile()
local ConfirmBox = require("ui/widget/confirmbox")
return ConfirmBox:new{
text = _("Cannot open last file.\nThis could be because it was deleted or because external storage is still being mounted.\nDo you want to retry?"),
ok_callback = function()
local last_file = G_reader_settings:readSetting("lastfile")
if lfs.attributes(last_file, "mode") == "file" then
local ReaderUI = require("apps/reader/readerui")
UIManager:nextTick(function()
ReaderUI:showReader(last_file)
end)
else
UIManager:show(retryLastFile())
end
end,
}
end
local function getPathFromURI(str)
local hexToChar = function(x)
return string.char(tonumber(x, 16))
end
local unescape = function(url)
return url:gsub("%%(%x%x)", hexToChar)
end
local prefix = "file://"
if str:sub(1, #prefix) ~= prefix then
return str
end
return unescape(str):sub(#prefix+1)
end
-- Get which file to start with
local last_file = G_reader_settings:readSetting("lastfile")
local start_with = G_reader_settings:readSetting("start_with")
local open_last = start_with == "last"
if open_last and last_file and lfs.attributes(last_file, "mode") ~= "file" then
UIManager:show(retryLastFile())
last_file = nil
else
local QuickStart = require("ui/quickstart")
if not QuickStart:isShown() then
open_last = true
last_file = QuickStart:getQuickStart()
end
end
-- Start app
local exit_code
if ARGV[argidx] and ARGV[argidx] ~= "" then
local file
local sanitized_path = getPathFromURI(ARGV[argidx])
if lfs.attributes(sanitized_path, "mode") == "file" then
file = sanitized_path
elseif open_last and last_file then
file = last_file
end
-- if file is given in command line argument or open last document is set
-- true, the given file or the last file is opened in the reader
if file and file ~= "" then
local ReaderUI = require("apps/reader/readerui")
UIManager:nextTick(function()
ReaderUI:showReader(file)
end)
-- we assume a directory is given in command line argument
-- the filemanger will show the files in that path
else
local FileManager = require("apps/filemanager/filemanager")
local home_dir =
G_reader_settings:readSetting("home_dir") or ARGV[argidx]
UIManager:nextTick(function()
FileManager:setRotationMode(true)
FileManager:showFiles(home_dir)
end)
-- always open history on top of filemanager so closing history
-- doesn't result in exit
if start_with == "history" then
local FileManagerHistory = require("apps/filemanager/filemanagerhistory")
UIManager:nextTick(function()
FileManagerHistory:onShowHist(last_file)
end)
elseif start_with == "favorites" then
local FileManagerCollection = require("apps/filemanager/filemanagercollection")
UIManager:nextTick(function()
FileManagerCollection:new{
ui = FileManager.instance,
}:onShowColl("favorites")
end)
elseif start_with == "folder_shortcuts" then
local FileManagerShortcuts = require("apps/filemanager/filemanagershortcuts")
UIManager:nextTick(function()
FileManagerShortcuts:new{
ui = FileManager.instance,
}:onShowFolderShortcutsDialog()
end)
end
end
exit_code = UIManager:run()
elseif last_file then
local ReaderUI = require("apps/reader/readerui")
UIManager:nextTick(function()
ReaderUI:showReader(last_file)
end)
exit_code = UIManager:run()
else
return showusage()
end
-- Exit
local function exitReader()
-- Exit code can be shoddy on some platforms due to broken library dtors calling _exit(0) from os.exit(N)
local ko_exit = os.getenv("KO_EXIT_CODE")
if ko_exit then
local fo = io.open(ko_exit, "w+")
fo:write(tostring(exit_code))
fo:close()
end
local ReaderActivityIndicator =
require("apps/reader/modules/readeractivityindicator")
-- Save any device settings before closing G_reader_settings
Device:saveSettings()
G_reader_settings:close()
-- Close lipc handles
ReaderActivityIndicator:coda()
-- shutdown hardware abstraction
Device:exit()
if Profiler then Profiler.stop() end
if type(exit_code) == "number" then
os.exit(exit_code)
else
os.exit(0)
end
end
exitReader()