diff --git a/base b/base index b1eb0b5bc..f52fbec91 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit b1eb0b5bc1e6f49536bce550e93342155494d78d +Subproject commit f52fbec914e54ac24be68161951788e9fb12253c diff --git a/datastorage.lua b/datastorage.lua index c6959fd24..80d1b4d9d 100644 --- a/datastorage.lua +++ b/datastorage.lua @@ -4,8 +4,10 @@ local lfs = require("libs/libkoreader-lfs") local DataStorage = {} +local data_dir function DataStorage:getDataDir() - local data_dir + if data_dir then return data_dir end + if isAndroid then data_dir = "/sdcard/koreader" elseif os.getenv("UBUNTU_APPLICATION_ISOLATION") then @@ -19,6 +21,7 @@ function DataStorage:getDataDir() if lfs.attributes(data_dir, "mode") ~= "directory" then lfs.mkdir(data_dir) end + return data_dir end @@ -26,9 +29,16 @@ function DataStorage:getHistoryDir() return self:getDataDir() .. "/history" end +function DataStorage:getSettingsDir() + return self:getDataDir() .. "/settings" +end + local function initDataDir() local data_dir = DataStorage:getDataDir() - local sub_data_dirs = {"cache", "clipboard", "data", "history", "ota", "screenshots"} + local sub_data_dirs = { + "cache", "clipboard", "data", "history", + "ota", "screenshots", "settings", + } for _, dir in ipairs(sub_data_dirs) do local sub_data_dir = data_dir .. "/" .. dir if lfs.attributes(sub_data_dir, "mode") ~= "directory" then diff --git a/frontend/device/kobo/device.lua b/frontend/device/kobo/device.lua index 8157a4502..a17bd00f7 100644 --- a/frontend/device/kobo/device.lua +++ b/frontend/device/kobo/device.lua @@ -2,19 +2,19 @@ local Generic = require("device/generic/device") local TimeVal = require("ui/timeval") local Geom = require("ui/geometry") local dbg = require("dbg") +local sleep = require("ffi/util").sleep +local _ = require("gettext") local function yes() return true end local function koboEnableWifi(toggle) if toggle == 1 then - os.execute("lsmod | grep -q sdio_wifi_pwr || insmod /drivers/$PLATFORM/wifi/sdio_wifi_pwr.ko") - os.execute("lsmod | grep -q dhd || insmod /drivers/$PLATFORM/wifi/dhd.ko") - os.execute("sleep 2") - os.execute("ifconfig eth0 up") + os.execute("/sbin/lsmod | grep -q sdio_wifi_pwr || /sbin/insmod /drivers/$PLATFORM/wifi/sdio_wifi_pwr.ko") + os.execute("/sbin/lsmod | grep -q dhd || /sbin/insmod /drivers/$PLATFORM/wifi/dhd.ko") + sleep(1) + os.execute("/sbin/ifconfig eth0 up") os.execute("wlarm_le -i eth0 up") - os.execute("pidof wpa_supplicant >/dev/null || cd / && env -u LD_LIBRARY_PATH wpa_supplicant -s -i eth0 -c /etc/wpa_supplicant/wpa_supplicant.conf -C /var/run/wpa_supplicant -B") - os.execute("sleep 1") - os.execute("cd / && env -u LD_LIBRARY_PATH /sbin/udhcpc -S -i eth0 -s /etc/udhcpc.d/default.script -t15 -T10 -A3 -b -q >/dev/null 2>&1 &") + os.execute("pidof wpa_supplicant >/dev/null || env -u LD_LIBRARY_PATH wpa_supplicant -s -ieth0 -O /var/run/wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant.conf -B") else os.execute("killall udhcpc default.script wpa_supplicant 2>/dev/null") os.execute("wlarm_le -i eth0 down") @@ -159,12 +159,28 @@ function Kobo:init() end function Kobo:initNetworkManager(NetworkMgr) - NetworkMgr.turnOffWifi = function() + function NetworkMgr:turnOffWifi(complete_callback) koboEnableWifi(0) + if complete_callback then + complete_callback() + end end - NetworkMgr.turnOnWifi = function() + function NetworkMgr:turnOnWifi(complete_callback) koboEnableWifi(1) + self:showNetworkMenu(complete_callback) + end + + NetworkMgr:setWirelessBackend( + "wpa_supplicant", {ctrl_interface = "/var/run/wpa_supplicant/eth0"}) + + function NetworkMgr:obtainIP() + os.execute("env -u LD_LIBRARY_PATH /sbin/udhcpc -S -i eth0 -s /etc/udhcpc.d/default.script -t15 -T10 -A3 -b -q") + end + + function NetworkMgr:releaseIP() + os.execute("pkill -9 -f '/bin/sh /etc/udhcpc.d/default.script';") + os.execute("/sbin/ifconfig eth0 0.0.0.0") end end diff --git a/frontend/docsettings.lua b/frontend/docsettings.lua index 39c52bd47..098f633b0 100644 --- a/frontend/docsettings.lua +++ b/frontend/docsettings.lua @@ -6,7 +6,6 @@ local purgeDir = require("ffi/util").purgeDir local DocSettings = {} local HISTORY_DIR = DataStorage:getHistoryDir() -local READER_SETTING_FILE = DataStorage:getDataDir() .. "/settings.reader.lua" local function buildCandidate(file_path) if lfs.attributes(file_path, "mode") == "file" then @@ -42,62 +41,55 @@ end function DocSettings:open(docfile) -- TODO(zijiehe): Remove history_path, use only sidecar. - local new = { data = {} } + local new = {} local ok, stored - if docfile == ".reader" then - -- we handle reader setting as special case - new.history_file = READER_SETTING_FILE - ok, stored = pcall(dofile, new.history_file) - else - new.history_file = self:getHistoryPath(docfile) + new.history_file = self:getHistoryPath(docfile) - local sidecar = self:getSidecarDir(docfile) - new.sidecar = sidecar - if lfs.attributes(sidecar, "mode") ~= "directory" then - lfs.mkdir(sidecar) - end - -- If there is a file which has a same name as the sidecar directory, or - -- the file system is read-only, we should not waste time to read it. - if lfs.attributes(sidecar, "mode") == "directory" then - -- New sidecar file name is metadata.{file last suffix}.lua. So we - -- can handle two files with only different suffixes. - new.sidecar_file = sidecar.."/metadata.".. - docfile:match(".*%.(.*)")..".lua" - if docfile:find("/") then - new.legacy_sidecar_file = sidecar.."/".. - docfile:match(".*%/(.*)")..".lua" - else - new.legacy_sidecar_file = sidecar.."/"..docfile..".lua" - end - end + local sidecar = self:getSidecarDir(docfile) + new.sidecar = sidecar + if lfs.attributes(sidecar, "mode") ~= "directory" then + lfs.mkdir(sidecar) + end + -- If there is a file which has a same name as the sidecar directory, or + -- the file system is read-only, we should not waste time to read it. + if lfs.attributes(sidecar, "mode") == "directory" then + -- New sidecar file name is metadata.{file last suffix}.lua. So we + -- can handle two files with only different suffixes. + new.sidecar_file = sidecar.."/metadata.".. + docfile:match(".*%.(.+)")..".lua" + new.legacy_sidecar_file = sidecar.."/".. + docfile:match("([^%/]+%..+)")..".lua" + end - new.candidates = {} - -- New sidecar file - table.insert(new.candidates, buildCandidate(new.sidecar_file)) - -- Legacy sidecar file - table.insert(new.candidates, buildCandidate(new.legacy_sidecar_file)) - -- Legacy history folder - table.insert(new.candidates, buildCandidate(new.history_file)) - -- Legacy kpdfview setting - table.insert(new.candidates, buildCandidate(docfile..".kpdfview.lua")) - table.sort(new.candidates, function(l, r) - if l == nil then - return false - elseif r == nil then - return true - else - return l[2] > r[2] - end - end) - for _, k in pairs(new.candidates) do - ok, stored = pcall(dofile, k[1]) - if ok then - break - end + local candidates = {} + -- New sidecar file + table.insert(candidates, buildCandidate(new.sidecar_file)) + -- Legacy sidecar file + table.insert(candidates, buildCandidate(new.legacy_sidecar_file)) + -- Legacy history folder + table.insert(candidates, buildCandidate(new.history_file)) + -- Legacy kpdfview setting + table.insert(candidates, buildCandidate(docfile..".kpdfview.lua")) + table.sort(candidates, function(l, r) + if l == nil then + return false + elseif r == nil then + return true + else + return l[2] > r[2] + end + end) + for _, k in pairs(candidates) do + ok, stored = pcall(dofile, k[1]) + if ok then + break end end if ok and stored then new.data = stored + new.candidates = candidates + else + new.data = {} end return setmetatable(new, {__index = DocSettings}) diff --git a/frontend/ui/elements/common_settings_menu_table.lua b/frontend/ui/elements/common_settings_menu_table.lua index ad9ffcc5a..bfadb9f35 100644 --- a/frontend/ui/elements/common_settings_menu_table.lua +++ b/frontend/ui/elements/common_settings_menu_table.lua @@ -1,6 +1,6 @@ local Device = require("device") local Language = require("ui/language") -local NetworkMgr = require("ui/networkmgr") +local NetworkMgr = require("ui/network/manager") local UIManager = require("ui/uimanager") local Screen = require("device").screen local _ = require("gettext") diff --git a/frontend/ui/networkmgr.lua b/frontend/ui/network/manager.lua similarity index 55% rename from frontend/ui/networkmgr.lua rename to frontend/ui/network/manager.lua index 9bbe0883c..2d0a3ce96 100644 --- a/frontend/ui/networkmgr.lua +++ b/frontend/ui/network/manager.lua @@ -1,6 +1,8 @@ local InfoMessage = require("ui/widget/infomessage") local ConfirmBox = require("ui/widget/confirmbox") local UIManager = require("ui/uimanager") +local LuaSettings = require("luasettings") +local DataStorage = require("datastorage") local Device = require("device") local T = require("ffi/util").template local _ = require("gettext") @@ -8,26 +10,37 @@ local _ = require("gettext") local NetworkMgr = {} --- Device specific method, needs to be initialized in Device:initNetworkManager -function NetworkMgr:turnOnWifi() end +function NetworkMgr:init() + self.nw_settings = LuaSettings:open(DataStorage:getSettingsDir().."/network.lua") +end --- Device specific method, needs to be initialized in Device:initNetworkManager +-- Following methods are Device specific which need to be initialized in +-- Device:initNetworkManager. Some of them can be set by calling +-- NetworkMgr:setWirelessBackend +function NetworkMgr:turnOnWifi() end function NetworkMgr:turnOffWifi() end +function NetworkMgr:getNetworkList() end +function NetworkMgr:getCurrentNetwork() end +function NetworkMgr:authenticateNetwork() end +function NetworkMgr:disconnectNetwork() end +function NetworkMgr:obtainIP() end +function NetworkMgr:releaseIP() end +-- End of device specific methods -function NetworkMgr:promptWifiOn() +function NetworkMgr:promptWifiOn(complete_callback) UIManager:show(ConfirmBox:new{ text = _("Do you want to turn on Wi-Fi?"), ok_callback = function() - self:turnOnWifi() + self:turnOnWifi(complete_callback) end, }) end -function NetworkMgr:promptWifiOff() +function NetworkMgr:promptWifiOff(complete_callback) UIManager:show(ConfirmBox:new{ text = _("Do you want to turn off Wi-Fi?"), ok_callback = function() - self:turnOffWifi() + self:turnOffWifi(complete_callback) end, }) end @@ -53,11 +66,15 @@ function NetworkMgr:getWifiMenuTable() text = _("Wi-Fi connection"), enabled_func = function() return Device:isKindle() or Device:isKobo() end, checked_func = function() return NetworkMgr:getWifiStatus() end, - callback = function() + callback = function(menu) + local complete_callback = function() + -- notify touch menu to update item check state + menu:updateItems() + end if NetworkMgr:getWifiStatus() then - NetworkMgr:promptWifiOff() + NetworkMgr:promptWifiOff(complete_callback) else - NetworkMgr:promptWifiOn() + NetworkMgr:promptWifiOn(complete_callback) end end } @@ -100,6 +117,44 @@ function NetworkMgr:getProxyMenuTable() } end +function NetworkMgr:showNetworkMenu(complete_callback) + local info = InfoMessage:new{text = _("Scanning…")} + UIManager:show(info) + UIManager:nextTick(function() + local network_list = self:getNetworkList() + UIManager:close(info) + UIManager:show(require("ui/widget/networksetting"):new{ + network_list = network_list, + connect_callback = complete_callback, + }) + end) +end + +function NetworkMgr:saveNetwork(setting) + if not self.nw_settings then self:init() end + self.nw_settings:saveSetting(setting.ssid, { + ssid = setting.ssid, + password = setting.password, + flags = setting.flags, + }) + self.nw_settings:flush() +end + +function NetworkMgr:deleteNetwork(setting) + if not self.nw_settings then self:init() end + self.nw_settings:delSetting(setting.ssid) + self.nw_settings:flush() +end + +function NetworkMgr:getAllSavedNetworks() + if not self.nw_settings then self:init() end + return self.nw_settings +end + +function NetworkMgr:setWirelessBackend(name, options) + require("ui/network/"..name).init(self, options) +end + -- set network proxy if global variable NETWORK_PROXY is defined if NETWORK_PROXY then NetworkMgr:setHTTPProxy(NETWORK_PROXY) diff --git a/frontend/ui/network/wpa_supplicant.lua b/frontend/ui/network/wpa_supplicant.lua new file mode 100644 index 000000000..e171b8e1d --- /dev/null +++ b/frontend/ui/network/wpa_supplicant.lua @@ -0,0 +1,113 @@ +local UIManager = require("ui/uimanager") +local WpaClient = require('lj-wpaclient/wpaclient') +local InfoMessage = require("ui/widget/infomessage") +local sleep = require("ffi/util").sleep +local _ = require("gettext") + +local WpaSupplicant = {} + +function WpaSupplicant:getNetworkList() + local wcli, _ = WpaClient.new(self.wpa_supplicant.ctrl_interface) + local list = wcli:scanThenGetResults() + wcli:close() + + local saved_networks = self:getAllSavedNetworks() + local curr_network = self:getCurrentNetwork() + + for _,network in ipairs(list) do + network.signal_quality = network:getSignalQuality() + local saved_nw = saved_networks:readSetting(network.ssid) + if saved_nw and saved_nw.flags == network.flags then + network.password = saved_nw.password + end + -- TODO: also verify bssid if it is not set to any + if curr_network and curr_network.ssid == network.ssid then + network.connected = true + network.wpa_supplicant_id = curr_network.id + end + end + return list +end + +function WpaSupplicant:authenticateNetwork(network) + -- TODO: support passwordless network + local err, wcli, nw_id + wcli, err = WpaClient.new(self.wpa_supplicant.ctrl_interface) + + if not wcli then + return false, _("Failed to initialize network control client: ")..err + end + + nw_id, err = wcli:addNetwork() + if err then return false, err end + + wcli:setNetwork(nw_id, "ssid", network.ssid) + wcli:setNetwork(nw_id, "psk", network.password) + wcli:enableNetworkByID(nw_id) + + wcli:attach() + local cnt = 0 + local failure_cnt = 0 + local max_retry = 30 + local info = InfoMessage:new{text = _("Authenticating…")} + local re, msg + UIManager:show(info) + UIManager:forceRePaint() + while cnt < max_retry do + local ev = wcli:readEvent() + if ev ~= nil then + if not ev:isScanEvent() then + UIManager:close(info) + info = InfoMessage:new{text = ev.msg} + UIManager:show(info) + UIManager:forceRePaint() + end + if ev:isAuthSuccessful() then + network.wpa_supplicant_id = nw_id + re = true + break + elseif ev:isAuthFailed() then + failure_cnt = failure_cnt + 1 + if failure_cnt > 3 then + re, msg = false, _('Failed to authenticate') + break + end + end + else + sleep(1) + cnt = cnt + 1 + end + end + if re ~= true then wcli:removeNetwork(nw_id) end + wcli:close() + UIManager:close(info) + UIManager:forceRePaint() + if cnt >= max_retry then + re, msg = false, _('Timed out') + end + return re, msg +end + +function WpaSupplicant:disconnectNetwork(network) + if not network.wpa_supplicant_id then return end + local wcli, _ = WpaClient.new(self.wpa_supplicant.ctrl_interface) + wcli:removeNetwork(network.wpa_supplicant_id) + wcli:close() +end + +function WpaSupplicant:getCurrentNetwork() + local wcli, _ = WpaClient.new(self.wpa_supplicant.ctrl_interface) + local nw = wcli:getCurrentNetwork() + wcli:close() + return nw +end + +function WpaSupplicant.init(network_mgr, options) + network_mgr.wpa_supplicant = {ctrl_interface = options.ctrl_interface} + network_mgr.getNetworkList = WpaSupplicant.getNetworkList + network_mgr.getCurrentNetwork = WpaSupplicant.getCurrentNetwork + network_mgr.authenticateNetwork = WpaSupplicant.authenticateNetwork + network_mgr.disconnectNetwork = WpaSupplicant.disconnectNetwork +end + +return WpaSupplicant diff --git a/frontend/ui/otamanager.lua b/frontend/ui/otamanager.lua index 5231407a4..11e3407a9 100644 --- a/frontend/ui/otamanager.lua +++ b/frontend/ui/otamanager.lua @@ -1,6 +1,6 @@ local InfoMessage = require("ui/widget/infomessage") local ConfirmBox = require("ui/widget/confirmbox") -local NetworkMgr = require("ui/networkmgr") +local NetworkMgr = require("ui/network/manager") local lfs = require("libs/libkoreader-lfs") local DataStorage = require("datastorage") local UIManager = require("ui/uimanager") diff --git a/frontend/ui/widget/confirmbox.lua b/frontend/ui/widget/confirmbox.lua index af075aaa3..e4d1f26af 100644 --- a/frontend/ui/widget/confirmbox.lua +++ b/frontend/ui/widget/confirmbox.lua @@ -1,3 +1,18 @@ +--[[-- +Widget that shows a message and OK/Cancel buttons + +Example: + + UIManager:show(ConfirmBox:new{ + text = _("Save the document?"), + ok_text = _("Save"), -- ok_text defaults to _("OK") + ok_callback = function() + -- save document + end, + }) + +]] + local InputContainer = require("ui/widget/container/inputcontainer") local CenterContainer = require("ui/widget/container/centercontainer") local FrameContainer = require("ui/widget/container/framecontainer") @@ -15,11 +30,7 @@ local DEBUG = require("dbg") local _ = require("gettext") local Blitbuffer = require("ffi/blitbuffer") --- screen ---[[ -Widget that shows a message and OK/Cancel buttons -]] local ConfirmBox = InputContainer:new{ modal = true, text = _("no text"), diff --git a/frontend/ui/widget/imagewidget.lua b/frontend/ui/widget/imagewidget.lua index 9ed5540dd..5e5d3ce0a 100644 --- a/frontend/ui/widget/imagewidget.lua +++ b/frontend/ui/widget/imagewidget.lua @@ -1,3 +1,16 @@ +--[[-- +ImageWidget shows an image from a file + +Example: + + UIManager:show(ImageWidget:new{ + file = "resources/info-i.png", + -- Make sure alpha is set to true if png has transparent background + -- alpha = true, + }) + +]] + local Widget = require("ui/widget/widget") local Screen = require("device").screen local CacheItem = require("cacheitem") @@ -23,9 +36,6 @@ function ImageCacheItem:onFree() end end ---[[ -ImageWidget shows an image from a file ---]] local ImageWidget = Widget:new{ file = nil, image = nil, diff --git a/frontend/ui/widget/inputdialog.lua b/frontend/ui/widget/inputdialog.lua index 014a54133..186097661 100644 --- a/frontend/ui/widget/inputdialog.lua +++ b/frontend/ui/widget/inputdialog.lua @@ -11,6 +11,7 @@ Example: input = "default value", input_hint = "hint text", input_type = "text", + -- text_type = "password", buttons = { { { diff --git a/frontend/ui/widget/inputtext.lua b/frontend/ui/widget/inputtext.lua index 2de3bd35d..7243ea4ac 100644 --- a/frontend/ui/widget/inputtext.lua +++ b/frontend/ui/widget/inputtext.lua @@ -73,23 +73,33 @@ end function InputText:initTextBox(text) self.text = text - self.charlist = util.splitToChars(text) - if self.charpos == nil then - self.charpos = #self.charlist + 1 - end - local fgcolor = Blitbuffer.gray(self.text == "" and 0.5 or 1.0) - - local show_text = self.text - if self.text_type == "password" and show_text ~= "" then - show_text = self.text:gsub("(.-).", function() return "*" end) - show_text = show_text:gsub("(.)$", function() return self.text:sub(-1) end) - elseif show_text == "" then + local fgcolor + local show_charlist + local show_text = text + if show_text == "" or show_text == nil then + -- no preset value, use hint text if set show_text = self.hint + fgcolor = Blitbuffer.COLOR_GREY + self.charlist = {} + self.charpos = 1 + else + fgcolor = Blitbuffer.COLOR_BLACK + if self.text_type == "password" then + show_text = self.text:gsub( + "(.-).", function() return "*" end) + show_text = show_text:gsub( + "(.)$", function() return self.text:sub(-1) end) + end + self.charlist = util.splitToChars(text) + if self.charpos == nil then + self.charpos = #self.charlist + 1 + end end + show_charlist = util.splitToChars(show_text) if self.scroll then self.text_widget = ScrollTextWidget:new{ text = show_text, - charlist = self.charlist, + charlist = show_charlist, charpos = self.charpos, editable = self.focused, face = self.face, @@ -100,7 +110,7 @@ function InputText:initTextBox(text) else self.text_widget = TextBoxWidget:new{ text = show_text, - charlist = self.charlist, + charlist = show_charlist, charpos = self.charpos, editable = self.focused, face = self.face, diff --git a/frontend/ui/widget/keyvaluepage.lua b/frontend/ui/widget/keyvaluepage.lua index 1774133b1..47c0cacdf 100644 --- a/frontend/ui/widget/keyvaluepage.lua +++ b/frontend/ui/widget/keyvaluepage.lua @@ -39,7 +39,7 @@ local Device = require("device") local Screen = Device.screen -local ellipsis, space = "...", " " +local ellipsis, space = "…", " " local ellipsis_width, space_width local function truncateTextByWidth(text, face, max_width, prepend_space) if not ellipsis_width then diff --git a/frontend/ui/widget/listview.lua b/frontend/ui/widget/listview.lua new file mode 100644 index 000000000..760ffc5ab --- /dev/null +++ b/frontend/ui/widget/listview.lua @@ -0,0 +1,123 @@ +--[[-- +Widget compoent that handles pagination for a list of items. + +Example: + + local list_view = ListView:new{ + height = 400, + width = 200, + page_update_cb = function(curr_page_num, total_pages) + -- This callback function will be called whenever there is a + -- page turn event triggered. You can use it to update information + -- on parent widget. + end, + items = { + FrameContainer:new{ + bordersize = 0, + background = Blitbuffer.COLOR_WHITE + TextWidget:new{ + text = "foo", + fact = Font:getFace("cfont"), + } + }, + FrameContainer:new{ + bordersize = 0, + background = Blitbuffer.COLOR_LIGHT_GREY + TextWidget:new{ + text = "bar", + fact = Font:getFace("cfont"), + } + }, + -- You can add as many widgets as you want here... + } + } + +Note that ListView is created mainly to be used as a building block for other +widgets like NetworkSetting so they can share the same pagination code. +]] + +local InputContainer = require("ui/widget/container/inputcontainer") +local FrameContainer = require("ui/widget/container/framecontainer") +local VerticalGroup = require("ui/widget/verticalgroup") +local GestureRange = require("ui/gesturerange") +local Blitbuffer = require("ffi/blitbuffer") +local Device = require("device") +local Screen = Device.screen +local Geom = require("ui/geometry") + +local ListView = InputContainer:new{ + width = nil, + height = nil, + padding = nil, + item_height = nil, + itmes = nil, +} + +function ListView:init() + self.show_page = 1 + self.dimen = Geom:new{w = self.width, h = self.height} + + if Device:isTouchDevice() then + self.ges_events.Swipe = { + GestureRange:new{ + ges = "swipe", + range = self.dimen, + } + } + end + + local padding = self.padding or Screen:scaleBySize(10) + self.item_height = self.item_height or self.items[1]:getSize().h + self.item_width = self.dimen.w - 2 * padding + self.items_per_page = math.floor(self.height / self.item_height) + self.main_content = VerticalGroup:new{} + self:_populateItems() + self[1] = FrameContainer:new{ + height = self.dimen.h, + padding = padding, + bordersize = 0, + background = Blitbuffer.COLOR_WHITE, + self.main_content, + } +end + +-- make sure self.item_height are set before calling this +function ListView:_populateItems() + self.pages = math.ceil(#self.items / self.items_per_page) + self.main_content:clear() + local idx_offset = (self.show_page - 1) * self.items_per_page + for idx = 1, self.items_per_page do + local item = self.items[idx_offset + idx] + if item == nil then break end + table.insert(self.main_content, item) + end + self.page_update_cb(self.show_page, self.pages) +end + +function ListView:nextPage() + local new_page = math.min(self.show_page+1, self.pages) + if new_page > self.show_page then + self.show_page = new_page + self:_populateItems() + end +end + +function ListView:prevPage() + local new_page = math.max(self.show_page-1, 1) + if new_page < self.show_page then + self.show_page = new_page + self:_populateItems() + end +end + +function ListView:onSwipe(arg, ges_ev) + if ges_ev.direction == "west" then + self:nextPage() + return true + elseif ges_ev.direction == "east" then + self:prevPage() + return true + end +end + +return ListView diff --git a/frontend/ui/widget/networksetting.lua b/frontend/ui/widget/networksetting.lua new file mode 100644 index 000000000..bb776483b --- /dev/null +++ b/frontend/ui/widget/networksetting.lua @@ -0,0 +1,466 @@ +--[[-- +Network setting widget. + +Example: + + local network_list = { + { + ssid = "foo", + signal_level = -58, + flags = "[WPA2-PSK-CCMP][ESS]", + signal_quality = 84, + password = "123abc", + connected = true, + }, + { + ssid = "bar", + signal_level = -258, + signal_quality = 44, + flags = "[WEP][ESS]", + }, + } + UIManager:show(require("ui/widget/networksetting"):new{ + network_list = network_list, + connect_callback = function() + -- connect_callback will be called when an connect/disconnect + -- attempt has been made. you can update UI widgets in the + -- callback. + end, + }) + +]] + +local FrameContainer = require("ui/widget/container/framecontainer") +local InputContainer = require("ui/widget/container/inputcontainer") +local LeftContainer = require("ui/widget/container/leftcontainer") +local CenterContainer = require("ui/widget/container/centercontainer") +local RightContainer = require("ui/widget/container/rightcontainer") +local HorizontalGroup = require("ui/widget/horizontalgroup") +local HorizontalSpan = require("ui/widget/horizontalspan") +local VerticalGroup = require("ui/widget/verticalgroup") +local OverlapGroup = require("ui/widget/overlapgroup") +local InfoMessage = require("ui/widget/infomessage") +local InputDialog = require("ui/widget/inputdialog") +local NetworkMgr = require("ui/network/manager") +local ListView = require("ui/widget/listview") +local ImageWidget = require("ui/widget/imagewidget") +local Widget = require("ui/widget/widget") +local TextWidget = require("ui/widget/textwidget") +local GestureRange = require("ui/gesturerange") +local Blitbuffer = require("ffi/blitbuffer") +local UIManager = require("ui/uimanager") +local Geom = require("ui/geometry") +local Device = require("device") +local Screen = Device.screen +local Font = require("ui/font") +local _ = require("gettext") + + +local MinimalPaginator = Widget:new{ + width = nil, + height = nil, + progress = nil, +} + +function MinimalPaginator:getSize() + return Geom:new{w = self.width, h = self.height} +end + +function MinimalPaginator:paintTo(bb, x, y) + self.dimen = self:getSize() + self.dimen.x, self.dimen.y = x, y + -- paint background + bb:paintRoundedRect(x, y, + self.dimen.w, self.dimen.h, + Blitbuffer.COLOR_LIGHT_GREY) + -- paint percentage infill + bb:paintRect(x, y, + math.ceil(self.dimen.w*self.progress), self.dimen.h, + Blitbuffer.COLOR_GREY) +end + +function MinimalPaginator:setProgress(progress) self.progress = progress end + + +local NetworkItem = InputContainer:new{ + dimen = nil, + height = Screen:scaleBySize(44), + width = nil, + info = nil, + background = Blitbuffer.COLOR_WHITE, +} + +function NetworkItem:init() + self.dimen = Geom:new{w = self.width, h = self.height} + if not self.info.ssid then + self.info.ssid = "[hidden]" + end + + local wifi_icon_path + if string.find(self.info.flags, "WPA") then + wifi_icon_path = "resources/icons/koicon.wifi.secure.%d.medium.png" + else + wifi_icon_path = "resources/icons/koicon.wifi.open.%d.medium.png" + end + if self.info.signal_quality == 0 or self.info.signal_quality == 100 then + wifi_icon_path = string.format(wifi_icon_path, self.info.signal_quality) + else + wifi_icon_path = string.format( + wifi_icon_path, + self.info.signal_quality + 25 - self.info.signal_quality % 25) + end + local horizontal_space = HorizontalSpan:new{width = Screen:scaleBySize(8)} + self.content_container = OverlapGroup:new{ + dimen = self.dimen:copy(), + LeftContainer:new{ + dimen = self.dimen:copy(), + HorizontalGroup:new{ + horizontal_space, + ImageWidget:new{ + alpha = true, + file = wifi_icon_path, + }, + horizontal_space, + TextWidget:new{ + text = self.info.ssid, + face = Font:getFace("cfont"), + }, + }, + } + } + self.btn_disconnect = nil + self.btn_edit_nw = nil + if self.info.connected then + self.btn_disconnect = FrameContainer:new{ + bordersize = 0, + padding = 0, + TextWidget:new{ + text = _("disconnect"), + face = Font:getFace("cfont"), + } + } + + table.insert(self.content_container, RightContainer:new{ + dimen = self.dimen:copy(), + HorizontalGroup:new{ + self.btn_disconnect, + horizontal_space, + } + }) + elseif self.info.password then + self.btn_edit_nw = FrameContainer:new{ + bordersize = 0, + padding = 0, + TextWidget:new{ + text = _("edit"), + face = Font:getFace("cfont"), + } + } + + table.insert(self.content_container, RightContainer:new{ + dimen = self.dimen:copy(), + HorizontalGroup:new{ + self.btn_edit_nw, + horizontal_space, + } + }) + end + + self[1] = FrameContainer:new{ + padding = 0, + margin = 0, + background = self.background, + bordersize = 0, + width = self.width, + self.content_container, + } + + if Device:isTouchDevice() then + self.ges_events = { + TapSelect = { + GestureRange:new{ + ges = "tap", + range = self.dimen, + } + } + } + end +end + +function NetworkItem:refresh() + self:init() + UIManager:setDirty(self.setting_ui, function() return "ui", self.dimen end) +end + +function NetworkItem:connect() + local connected_item = self.setting_ui:getConnectedItem(self) + if connected_item then connected_item:disconnect() end + + local success, err_msg = NetworkMgr:authenticateNetwork(self.info) + + local text + if success then + local info = InfoMessage:new{text = _("Obtaining IP address…")} + UIManager:show(info) + UIManager:forceRePaint() + NetworkMgr:obtainIP() + UIManager:close(info) + self.info.connected = true + self.setting_ui:setConnectedItem(self) + text = _("Connected.") + else + text = err_msg + end + + if self.setting_ui.connect_callback then + self.setting_ui.connect_callback() + end + UIManager:show(InfoMessage:new{text = text}) +end + +function NetworkItem:disconnect() + local info = InfoMessage:new{text = _("Disconnecting…")} + UIManager:show(info) + UIManager:forceRePaint() + + NetworkMgr:disconnectNetwork(self.info) + NetworkMgr:releaseIP() + + UIManager:close(info) + self.info.connected = nil + self:refresh() + if self.setting_ui.connect_callback then + self.setting_ui.connect_callback() + end +end + +function NetworkItem:saveAndConnectToNetwork(password_input) + local new_passwd = password_input:getInputText() + if new_passwd == nil or string.len(new_passwd) == 0 then + UIManager:show(InfoMessage:new{ + text = _("Password cannot be empty."), + }) + else + if new_passwd ~= self.info.password then + self.info.password = new_passwd + NetworkMgr:saveNetwork(self.info) + end + self:connect() + self:refresh() + end + + UIManager:close(password_input) +end + +function NetworkItem:onEditNetwork() + local password_input + password_input = InputDialog:new{ + title = self.info.ssid, + input = self.info.password, + input_hint = "password", + input_type = "text", + text_type = "password", + buttons = { + { + { + text = _("Cancel"), + callback = function() + UIManager:close(password_input) + end, + }, + { + text = _("Forget"), + callback = function() + NetworkMgr:deleteNetwork(self.info) + self.info.password = nil + -- remove edit button + table.remove(self.content_container, 2) + UIManager:close(password_input) + self:refresh() + end, + }, + { + text = _("Connect"), + is_enter_default = true, + callback = function() + self:saveAndConnectToNetwork(password_input) + end, + }, + }, + }, + } + password_input:onShowKeyboard() + UIManager:show(password_input) + return true +end + +function NetworkItem:onAddNetwork() + local password_input + password_input = InputDialog:new{ + title = self.info.ssid, + input = "", + input_hint = "password", + input_type = "text", + text_type = "password", + buttons = { + { + { + text = _("Cancel"), + callback = function() + UIManager:close(password_input) + end, + }, + { + text = _("Connect"), + is_enter_default = true, + callback = function() + self:saveAndConnectToNetwork(password_input) + end, + }, + }, + }, + } + password_input:onShowKeyboard() + UIManager:show(password_input) + return true +end + +function NetworkItem:onTapSelect(arg, ges_ev) + if not string.find(self.info.flags, "WPA") then + UIManager:show(InfoMessage:new{ + text = _("Networks without WPA/WPA2 encryption are not supported.") + }) + return + end + if self.btn_disconnect then + -- noop if touch is not on disconnect button + if ges_ev.pos:intersectWith(self.btn_disconnect.dimen) then + self:disconnect() + end + elseif self.info.password then + if self.btn_edit_nw and ges_ev.pos:intersectWith(self.btn_edit_nw.dimen) then + self:onEditNetwork() + else + self:connect() + self:refresh() + end + else + self:onAddNetwork() + end + return true +end + + +local NetworkSetting = InputContainer:new{ + width = nil, + height = nil, + -- sample network_list entry: { + -- bssid = "any", + -- ssid = "foo", + -- signal_level = -58, + -- signal_quality = 84, + -- frequency = 5660, + -- flags = "[WPA2-PSK-CCMP][ESS]", + -- } + network_list = nil, + connect_callback = nil, +} + +function NetworkSetting:init() + self.width = self.width or Screen:getWidth() - Screen:scaleBySize(50) + self.width = math.min(self.width, Screen:scaleBySize(600)) + + local gray_bg = Blitbuffer.gray(0.1) + local items = {} + table.sort(self.network_list, + function(l, r) return l.signal_quality > r.signal_quality end) + for idx,network in ipairs(self.network_list) do + local bg + if idx % 2 == 0 then + bg = gray_bg + else + bg = Blitbuffer.COLOR_WHITE + end + table.insert(items, NetworkItem:new{ + width = self.width, + info = network, + background = bg, + setting_ui = self, + }) + end + + self.status_text = TextWidget:new{ + text = "", + face = Font:getFace("ffont"), + } + self.page_text = TextWidget:new{ + text = "", + face = Font:getFace("ffont"), + } + + self.pagination = MinimalPaginator:new{ + width = self.width, + height = Screen:scaleBySize(8), + percentage = 0, + } + + self.height = self.height or math.min(Screen:getHeight()*3/4, + Screen:scaleBySize(800)) + self.popup = FrameContainer:new{ + background = Blitbuffer.COLOR_WHITE, + padding = 0, + bordersize = 3, + VerticalGroup:new{ + align = "left", + self.pagination, + ListView:new{ + padding = 0, + items = items, + width = self.width, + height = self.height-self.pagination:getSize().h, + page_update_cb = function(curr_page, total_pages) + self.pagination:setProgress(curr_page/total_pages) + -- self.page_text:setText(curr_page .. "/" .. total_pages) + UIManager:setDirty(self, function() + return "ui", self.dimen + end) + end + }, + }, + } + + self[1] = CenterContainer:new{ + dimen = {w = Screen:getWidth(), h = Screen:getHeight()}, + self.popup, + } + + if Device:isTouchDevice() then + self.ges_events.TapClose = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight(), + } + } + } + end +end + +function NetworkSetting:setConnectedItem(item) + self.connected_item = item +end + +function NetworkSetting:getConnectedItem() + return self.connected_item +end + +function NetworkSetting:onTapClose(arg, ges_ev) + if ges_ev.pos:notIntersectWith(self.popup.dimen) then + UIManager:close(self) + return true + end +end + +return NetworkSetting diff --git a/frontend/ui/widget/opdsbrowser.lua b/frontend/ui/widget/opdsbrowser.lua index bac7728be..ed4c15abd 100644 --- a/frontend/ui/widget/opdsbrowser.lua +++ b/frontend/ui/widget/opdsbrowser.lua @@ -3,7 +3,7 @@ local ButtonDialog = require("ui/widget/buttondialog") local InfoMessage = require("ui/widget/infomessage") local LoginDialog = require("ui/widget/logindialog") local OPDSParser = require("ui/opdsparser") -local NetworkMgr = require("ui/networkmgr") +local NetworkMgr = require("ui/network/manager") local UIManager = require("ui/uimanager") local CacheItem = require("cacheitem") local Menu = require("ui/widget/menu") diff --git a/frontend/ui/widget/textboxwidget.lua b/frontend/ui/widget/textboxwidget.lua index 9a8170dd0..43181f407 100644 --- a/frontend/ui/widget/textboxwidget.lua +++ b/frontend/ui/widget/textboxwidget.lua @@ -87,9 +87,7 @@ end -- Split the text into logical lines to fit into the text box. function TextBoxWidget:_splitCharWidthList() - self.vertical_string_list = { - {text = self.text, offset = 1, width = 0} -- hint for empty string - } + self.vertical_string_list = {} local idx = 1 local size = #self.char_width_list @@ -178,10 +176,17 @@ end -- Return the position of the cursor corresponding to `self.charpos`, -- Be aware of virtual line number of the scorllTextWidget. function TextBoxWidget:_findCharPos() + if self.text == nil or string.len(self.text) == 0 then + return 0, 0 + end -- Find the line number. local ln = self.height == nil and 1 or self.virtual_line_num while ln + 1 <= #self.vertical_string_list do - if self.vertical_string_list[ln + 1].offset > self.charpos then break else ln = ln + 1 end + if self.vertical_string_list[ln + 1].offset > self.charpos then + break + else + ln = ln + 1 + end end -- Find the offset at the current line. local x = 0 @@ -193,6 +198,12 @@ function TextBoxWidget:_findCharPos() return x + 1, (ln - 1) * self.line_height_px -- offset `x` by 1 to avoid overlap end +function TextBoxWidget:moveCursorToCharpos(charpos) + self.charpos = charpos + local x, y = self:_findCharPos() + self.cursor_line:paintTo(self._bb, x, y) +end + -- Click event: Move the cursor to a new location with (x, y), in pixels. -- Be aware of virtual line number of the scorllTextWidget. function TextBoxWidget:moveCursor(x, y) diff --git a/frontend/ui/widget/touchmenu.lua b/frontend/ui/widget/touchmenu.lua index a3fd72f19..833e51c9e 100644 --- a/frontend/ui/widget/touchmenu.lua +++ b/frontend/ui/widget/touchmenu.lua @@ -564,7 +564,7 @@ function TouchMenu:onMenuSelect(item) -- put stuff in scheduler so we can see -- the effect of inverted menu item UIManager:scheduleIn(0.1, function() - callback() + callback(self) if refresh then self:updateItems() else diff --git a/plugins/evernote.koplugin/main.lua b/plugins/evernote.koplugin/main.lua index c81d617b9..9b102868a 100644 --- a/plugins/evernote.koplugin/main.lua +++ b/plugins/evernote.koplugin/main.lua @@ -1,7 +1,7 @@ local InputContainer = require("ui/widget/container/inputcontainer") local LoginDialog = require("ui/widget/logindialog") local InfoMessage = require("ui/widget/infomessage") -local NetworkMgr = require("ui/networkmgr") +local NetworkMgr = require("ui/network/manager") local DataStorage = require("datastorage") local DocSettings = require("docsettings") local UIManager = require("ui/uimanager") diff --git a/plugins/kosync.koplugin/main.lua b/plugins/kosync.koplugin/main.lua index adef437c8..c697c8115 100644 --- a/plugins/kosync.koplugin/main.lua +++ b/plugins/kosync.koplugin/main.lua @@ -3,7 +3,7 @@ local LoginDialog = require("ui/widget/logindialog") local InfoMessage = require("ui/widget/infomessage") local ConfirmBox = require("ui/widget/confirmbox") local DocSettings = require("docsettings") -local NetworkMgr = require("ui/networkmgr") +local NetworkMgr = require("ui/network/manager") local UIManager = require("ui/uimanager") local Screen = require("device").screen local Device = require("device") diff --git a/resources/icons/koicon.wifi.open.0.medium.png b/resources/icons/koicon.wifi.open.0.medium.png new file mode 100644 index 000000000..aa72a3a63 Binary files /dev/null and b/resources/icons/koicon.wifi.open.0.medium.png differ diff --git a/resources/icons/koicon.wifi.open.100.medium.png b/resources/icons/koicon.wifi.open.100.medium.png new file mode 100644 index 000000000..e3f76eb69 Binary files /dev/null and b/resources/icons/koicon.wifi.open.100.medium.png differ diff --git a/resources/icons/koicon.wifi.open.25.medium.png b/resources/icons/koicon.wifi.open.25.medium.png new file mode 100644 index 000000000..cbf4866e5 Binary files /dev/null and b/resources/icons/koicon.wifi.open.25.medium.png differ diff --git a/resources/icons/koicon.wifi.open.50.medium.png b/resources/icons/koicon.wifi.open.50.medium.png new file mode 100644 index 000000000..8669026d9 Binary files /dev/null and b/resources/icons/koicon.wifi.open.50.medium.png differ diff --git a/resources/icons/koicon.wifi.open.75.medium.png b/resources/icons/koicon.wifi.open.75.medium.png new file mode 100644 index 000000000..4ae6bdef8 Binary files /dev/null and b/resources/icons/koicon.wifi.open.75.medium.png differ diff --git a/resources/icons/koicon.wifi.secure.0.medium.png b/resources/icons/koicon.wifi.secure.0.medium.png new file mode 100644 index 000000000..cf79ecd91 Binary files /dev/null and b/resources/icons/koicon.wifi.secure.0.medium.png differ diff --git a/resources/icons/koicon.wifi.secure.100.medium.png b/resources/icons/koicon.wifi.secure.100.medium.png new file mode 100644 index 000000000..d3dbe59c1 Binary files /dev/null and b/resources/icons/koicon.wifi.secure.100.medium.png differ diff --git a/resources/icons/koicon.wifi.secure.25.medium.png b/resources/icons/koicon.wifi.secure.25.medium.png new file mode 100644 index 000000000..71531fb8d Binary files /dev/null and b/resources/icons/koicon.wifi.secure.25.medium.png differ diff --git a/resources/icons/koicon.wifi.secure.50.medium.png b/resources/icons/koicon.wifi.secure.50.medium.png new file mode 100644 index 000000000..b0feb26cf Binary files /dev/null and b/resources/icons/koicon.wifi.secure.50.medium.png differ diff --git a/resources/icons/koicon.wifi.secure.75.medium.png b/resources/icons/koicon.wifi.secure.75.medium.png new file mode 100644 index 000000000..bff1e135d Binary files /dev/null and b/resources/icons/koicon.wifi.secure.75.medium.png differ diff --git a/spec/unit/commonrequire.lua b/spec/unit/commonrequire.lua index fea1bb05e..7e70d6991 100644 --- a/spec/unit/commonrequire.lua +++ b/spec/unit/commonrequire.lua @@ -6,7 +6,7 @@ package.cpath = "?.so;common/?.so;/usr/lib/lua/?.so;rocks/lib/lua/5.1/?.so;" .. local DataStorage = require("datastorage") os.remove(DataStorage:getDataDir().."/settings.reader.lua") local DocSettings = require("docsettings") -G_reader_settings = DocSettings:open(".reader") +G_reader_settings = require("luasettings"):open(".reader") -- global einkfb for Screen (do not show SDL window) einkfb = require("ffi/framebuffer") diff --git a/utils/wbuilder.lua b/utils/wbuilder.lua index 8cb69c287..7c77b60c9 100755 --- a/utils/wbuilder.lua +++ b/utils/wbuilder.lua @@ -6,13 +6,14 @@ print(package.path) package.path = "common/?.lua;rocks/share/lua/5.1/?.lua;frontend/?.lua;" .. package.path package.cpath = "common/?.so;common/?.dll;/usr/lib/lua/?.so;rocks/lib/lua/5.1/?.so;" .. package.cpath -local DocSettings = require("docsettings") +local DataStorage = require("datastorage") local _ = require("gettext") -- 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 = DocSettings:open(".reader") +G_reader_settings = require("luasettings"):open( + DataStorage:getDataDir().."/settings.reader.lua") local lang_locale = G_reader_settings:readSetting("language") if lang_locale then _.changeLang(lang_locale) @@ -397,6 +398,49 @@ function testTouchProbe() UIManager:show(TouchProbe:new{}) end +function testNetworkSetting() + local list = { + { + ssid = "CMU-SECURE", + signal_level = -58, + flags = "[WPA2-PSK-CCMP][ESS]", + signal_quality = 84, + }, + { + ssid = "CMU-SECURE 2", + signal_level = -258, + signal_quality = 44, + flags = "[WPA2-PSK-CCMP][ESS]", + password = "okgo", + }, + { + ssid = "218", + signal_level = 58, + signal_quality = 100, + flags = "[WEP][ESS]", + }, + { + ssid = "318", + signal_level = 100, + signal_quality = 100, + flags = "[WPA2-PSK-CCMP][ESS]", + }, + } + + for i=1,10 do + table.insert(list, { + ssid = "918-"..tostring(i), + signal_level = -58-i*2, + signal_quality = 84-i*2, + flags = "[WPA2-PSK-CCMP][ESS]", + }) + end + + local nw = require("ui/widget/networksetting"):new{network_list = list} + UIManager:show(nw) +end + + ----------------------------------------------------------------------- -- you may want to uncomment following show calls to see the changes ----------------------------------------------------------------------- @@ -413,5 +457,6 @@ UIManager:show(Clock:new()) --TestInputText:onShowKeyboard() -- testKeyValuePage() -- testTouchProbe() -testBookStatus() +-- testBookStatus() +testNetworkSetting() UIManager:run()