mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-16 23:48:05 +11:00
Initial commit
Currently eeprog pirate-speak language is unsupported, but... oh well.
This commit is contained in:
commit
2ae3f9a93a
192
API Documentation
Normal file
192
API Documentation
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
KittenOS App Construction
|
||||||
|
|
||||||
|
Firstly, an 'app' is,
|
||||||
|
in essence, a process,
|
||||||
|
with one window.
|
||||||
|
The app's initial Lua
|
||||||
|
script must return 3
|
||||||
|
values: app, w, h.
|
||||||
|
App contains callbacks,
|
||||||
|
w and h are the app's
|
||||||
|
initial window size.
|
||||||
|
In the below section,
|
||||||
|
"dbool" means a boolean
|
||||||
|
which, if true, redraws
|
||||||
|
the whole window.
|
||||||
|
|
||||||
|
Callbacks are:
|
||||||
|
update():dbool
|
||||||
|
key(ascii, scan, down):
|
||||||
|
dbool
|
||||||
|
clipboard(text):dbool
|
||||||
|
get_ch(x, y):character
|
||||||
|
event(t, ...):dbool
|
||||||
|
^ The above is only used
|
||||||
|
under any one of these
|
||||||
|
conditions:
|
||||||
|
1. The event is a
|
||||||
|
modem_message, and
|
||||||
|
the app requested
|
||||||
|
and got one of the
|
||||||
|
following APIs:
|
||||||
|
c.modem, c.tunnel
|
||||||
|
(implemented as a
|
||||||
|
special case in
|
||||||
|
the getAPI for
|
||||||
|
those APIs,
|
||||||
|
giving access to
|
||||||
|
s.modem_message)
|
||||||
|
2. The app managed to
|
||||||
|
get the API named
|
||||||
|
"s.<signal type>".
|
||||||
|
3. The app managed to
|
||||||
|
successfully get
|
||||||
|
the root API
|
||||||
|
rpc(cPkg, cAid, ...):any
|
||||||
|
The application named
|
||||||
|
by cPkg, process cAid,
|
||||||
|
called your app via
|
||||||
|
the "proc" API.
|
||||||
|
The return value is
|
||||||
|
passed on, to the
|
||||||
|
calling application.
|
||||||
|
|
||||||
|
-KittenOS API Reference--
|
||||||
|
|
||||||
|
KittenOS initializes apps
|
||||||
|
with global functions,
|
||||||
|
but only one library
|
||||||
|
table: "A".
|
||||||
|
|
||||||
|
This table contains all
|
||||||
|
basic KittenOS funcs,
|
||||||
|
in the order they are
|
||||||
|
written in the kernel.
|
||||||
|
|
||||||
|
A.listApps()
|
||||||
|
Returns a list of apps
|
||||||
|
that can be launched.
|
||||||
|
(Not running processes,
|
||||||
|
see Request Doc/"proc")
|
||||||
|
Used to allow launcher
|
||||||
|
to avoid requiring a
|
||||||
|
special permission for
|
||||||
|
almost no reason.
|
||||||
|
|
||||||
|
A.launchApp(pkg)
|
||||||
|
Launches an application,
|
||||||
|
from the Lua file:
|
||||||
|
"apps/"..pkg..".lua" on
|
||||||
|
the primary filesystem.
|
||||||
|
The package name must
|
||||||
|
match the Lua pattern:
|
||||||
|
"[a-zA-Z%-_\x80-\xFF]+"
|
||||||
|
The app ID is returned.
|
||||||
|
|
||||||
|
A.opencfg(mode)
|
||||||
|
Opens the file:
|
||||||
|
"cfgs/"..pkg on the
|
||||||
|
primary filesystem.
|
||||||
|
This allows storage of
|
||||||
|
any and all data an app
|
||||||
|
may store without any
|
||||||
|
user involvement.
|
||||||
|
This is not to be used
|
||||||
|
for documents, except
|
||||||
|
as part of a recovery
|
||||||
|
system.
|
||||||
|
|
||||||
|
A.openfile(type, mode)
|
||||||
|
Asks the user to pick a
|
||||||
|
file, and opens it with
|
||||||
|
a specific file mode.
|
||||||
|
This invokes the script
|
||||||
|
"tfilemgr.lua".
|
||||||
|
File modes are 'r and 'w
|
||||||
|
and both are binary.
|
||||||
|
Should the mode be nil,
|
||||||
|
the file manager is
|
||||||
|
opened without any way
|
||||||
|
to open a file.
|
||||||
|
This is useful for
|
||||||
|
file management, and is
|
||||||
|
the implementation of
|
||||||
|
the "filemgr" app.
|
||||||
|
|
||||||
|
A.request(...)
|
||||||
|
Allows you to get APIs.
|
||||||
|
For a list, look in the
|
||||||
|
kernel for "getAPI".
|
||||||
|
Some APIs may require
|
||||||
|
permission, and the
|
||||||
|
request may cause added
|
||||||
|
callbacks to be called,
|
||||||
|
e.g. the "net" API will
|
||||||
|
cause "modem_message"
|
||||||
|
calls on those signals.
|
||||||
|
|
||||||
|
A.timer(t)
|
||||||
|
Sets the "update"
|
||||||
|
callback to be called
|
||||||
|
in a given number of
|
||||||
|
seconds. Note that only
|
||||||
|
one timer can be going
|
||||||
|
at a given time, and
|
||||||
|
passing <= 0 to timer
|
||||||
|
will disable it.
|
||||||
|
|
||||||
|
A.resize(w, h)
|
||||||
|
Resize the running app,
|
||||||
|
among other things,
|
||||||
|
forcing a complete
|
||||||
|
redraw of the app.
|
||||||
|
|
||||||
|
A.die()
|
||||||
|
Kill the running app,
|
||||||
|
immediately.
|
||||||
|
Do not call functions
|
||||||
|
after this is called.
|
||||||
|
|
||||||
|
-KittenOS File API-------
|
||||||
|
This is the interface
|
||||||
|
exposed by filewrap.lua,
|
||||||
|
when loaded.
|
||||||
|
It is not a very good
|
||||||
|
interface, but it works.
|
||||||
|
|
||||||
|
file.read(bytes)
|
||||||
|
Read some amount of
|
||||||
|
bytes from the file.
|
||||||
|
Returns nil on EOF.
|
||||||
|
|
||||||
|
file.write(str)
|
||||||
|
Write some amount of
|
||||||
|
bytes to the file.
|
||||||
|
The bytes are in str,
|
||||||
|
as per the old standard
|
||||||
|
of byte arrays being
|
||||||
|
strings.
|
||||||
|
|
||||||
|
file.close()
|
||||||
|
Closes the file.
|
||||||
|
|
||||||
|
-KittenOS Security Notes-
|
||||||
|
KittenOS has a few parts
|
||||||
|
which add security, but
|
||||||
|
all of them follow one
|
||||||
|
principle: Kernel-local
|
||||||
|
objects are private, and
|
||||||
|
passing an environment
|
||||||
|
to the "load" function
|
||||||
|
will ensure this.
|
||||||
|
Assuming this axiom is
|
||||||
|
true, KittenOS allows
|
||||||
|
access to anything only
|
||||||
|
if the user explicitly
|
||||||
|
allows it.
|
||||||
|
Files are handled on a
|
||||||
|
per-file basis, unless
|
||||||
|
the "fs" permission is
|
||||||
|
granted (which allows
|
||||||
|
complete access to the
|
||||||
|
filesystem).
|
149
Request Documentation
Normal file
149
Request Documentation
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
Read API Documentation,
|
||||||
|
before reading this.
|
||||||
|
|
||||||
|
Note that access to any
|
||||||
|
component can be gotten
|
||||||
|
by prefixing the type
|
||||||
|
of component with "c.",
|
||||||
|
but unless interfacing
|
||||||
|
with an external device,
|
||||||
|
use "stat" and "randr".
|
||||||
|
("randr" covers your
|
||||||
|
multi-screen needs.)
|
||||||
|
In a truly extreme case,
|
||||||
|
like an installer, you
|
||||||
|
can use "c.filesystem".
|
||||||
|
Signals from the devices
|
||||||
|
are not relayed, as I
|
||||||
|
am unsure of a way to
|
||||||
|
verify the source.
|
||||||
|
(It seems to be a default
|
||||||
|
to have the device as
|
||||||
|
the first parameter, but
|
||||||
|
I don't want to rely on
|
||||||
|
this behavior.)
|
||||||
|
|
||||||
|
There are exceptions:
|
||||||
|
A successful "c.modem" or
|
||||||
|
"c.tunnel" request will
|
||||||
|
enable the modem_message
|
||||||
|
event to make it
|
||||||
|
somewhat useful.
|
||||||
|
A similar thing occurs
|
||||||
|
for "c.chat_box",
|
||||||
|
and "root" allows seeing
|
||||||
|
all events.
|
||||||
|
|
||||||
|
Another way to get at
|
||||||
|
signals is the
|
||||||
|
"s.<signal>" permission.
|
||||||
|
Like the "c.<component>"
|
||||||
|
permission, it's a tiny
|
||||||
|
general means of getting
|
||||||
|
events that the kernel
|
||||||
|
has no way (or reason)
|
||||||
|
to access.
|
||||||
|
|
||||||
|
The interface has a
|
||||||
|
function to return an
|
||||||
|
iterator of proxies,
|
||||||
|
called "list".
|
||||||
|
The interface may have a
|
||||||
|
"primary" field if the
|
||||||
|
system has a 'primary'
|
||||||
|
choice for that device.
|
||||||
|
|
||||||
|
-------------------------
|
||||||
|
KittenOS A.request()
|
||||||
|
Acceptable Values:
|
||||||
|
|
||||||
|
"math", "table", "string"
|
||||||
|
, "unicode": Standard,
|
||||||
|
if indirect, access to
|
||||||
|
those Lua APIs.
|
||||||
|
(Done via metatable to
|
||||||
|
preserve memory.)
|
||||||
|
Note that the "unicode"
|
||||||
|
API has an additional
|
||||||
|
function:
|
||||||
|
safeTextFormat(txt,ptr)
|
||||||
|
adds spaces after wide
|
||||||
|
characters, making it
|
||||||
|
easier to display text
|
||||||
|
with wide characters
|
||||||
|
in. "ptr" can be a
|
||||||
|
point in the string,
|
||||||
|
and is also adjusted -
|
||||||
|
this can be used for
|
||||||
|
e.g. console cursors.
|
||||||
|
Returns atxt, aptr.
|
||||||
|
|
||||||
|
"root": The kernel _ENV.
|
||||||
|
Essentially everything.
|
||||||
|
|
||||||
|
"stat": Contains:
|
||||||
|
(names preserved, but
|
||||||
|
namespaces aren't)
|
||||||
|
computer:
|
||||||
|
totalMemory freeMemory
|
||||||
|
energy maxEnergy
|
||||||
|
os:
|
||||||
|
clock date difftime
|
||||||
|
time
|
||||||
|
Also contains
|
||||||
|
"component.list", but
|
||||||
|
named "componentList".
|
||||||
|
|
||||||
|
"proc": Contains:
|
||||||
|
aid: A field containing
|
||||||
|
the running app ID.
|
||||||
|
Usually pkg .. "-" .. n
|
||||||
|
where 'n' is a number.
|
||||||
|
pkg: A field containing
|
||||||
|
the app name (aka. pkg)
|
||||||
|
listApps()
|
||||||
|
Returns a table of
|
||||||
|
{package, aid} entries.
|
||||||
|
|
||||||
|
sendRPC(aid, ...)
|
||||||
|
Causes the "rpc" event
|
||||||
|
to be called, with the
|
||||||
|
given data parameters.
|
||||||
|
|
||||||
|
"lang":
|
||||||
|
getLanguage(): Gets the
|
||||||
|
current system language
|
||||||
|
getTable(): Loads and
|
||||||
|
calls the Lua file at
|
||||||
|
"/lang/<lang>/<pkg>.lua"
|
||||||
|
Failing that, returns
|
||||||
|
nil
|
||||||
|
|
||||||
|
"setlang": Actually one
|
||||||
|
function, setting the
|
||||||
|
system language.
|
||||||
|
|
||||||
|
"kill": Contains:
|
||||||
|
killApp(aid)
|
||||||
|
Kills a process.
|
||||||
|
|
||||||
|
"randr": Contains:
|
||||||
|
getResolution,
|
||||||
|
maxResolution,
|
||||||
|
setResolution,
|
||||||
|
(the above work
|
||||||
|
as on a GPU)
|
||||||
|
iterateScreens,
|
||||||
|
iterateGPUs
|
||||||
|
(the above iterate over
|
||||||
|
*non-primary* proxies,
|
||||||
|
direct control of the
|
||||||
|
primary screen is not
|
||||||
|
allowed)
|
||||||
|
"c.modem": See the notes
|
||||||
|
above on the "c." setup,
|
||||||
|
but note that this adds
|
||||||
|
the "modem_message"
|
||||||
|
callback.
|
||||||
|
"c.<component type>":
|
||||||
|
See the notes above.
|
69
apps/batmon.lua
Normal file
69
apps/batmon.lua
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
local math, stat = A.request("math", "stat")
|
||||||
|
local app = {}
|
||||||
|
-- How much did energy change
|
||||||
|
-- over 1 second?
|
||||||
|
local lastChange = 0
|
||||||
|
local lastValue = nil
|
||||||
|
local lastTimer = nil
|
||||||
|
local usage = {
|
||||||
|
"[####]:",
|
||||||
|
"[###:]:",
|
||||||
|
"[### ]:",
|
||||||
|
"[##: ]:",
|
||||||
|
"[## ]:",
|
||||||
|
"[#: ]:",
|
||||||
|
"[# ]:",
|
||||||
|
"[: ]:",
|
||||||
|
"[ ]:",
|
||||||
|
"WARNING"
|
||||||
|
}
|
||||||
|
local function getText(y)
|
||||||
|
if y == 2 then
|
||||||
|
if not lastChange then
|
||||||
|
return "Wait..."
|
||||||
|
end
|
||||||
|
local ind = "Dc. "
|
||||||
|
local wc = lastChange
|
||||||
|
local wv = stat.energy()
|
||||||
|
if wc > 0 then
|
||||||
|
wc = -wc
|
||||||
|
wv = stat.maxEnergy() - wv
|
||||||
|
ind = "Ch. "
|
||||||
|
end
|
||||||
|
local m = math.floor((wv / -wc) / 60)
|
||||||
|
return ind .. m .. "m"
|
||||||
|
end
|
||||||
|
local dec = stat.energy() / stat.maxEnergy()
|
||||||
|
-- dec is from 0 to 1.
|
||||||
|
local potential = math.floor(dec * #usage)
|
||||||
|
if potential < 0 then potential = 1 end
|
||||||
|
if potential >= #usage then potential = #usage - 1 end
|
||||||
|
return usage[#usage - potential]
|
||||||
|
end
|
||||||
|
function app.key(ka, kc, down)
|
||||||
|
if down then
|
||||||
|
if ka == ("C"):byte() then
|
||||||
|
A.die()
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function app.update()
|
||||||
|
local nv = stat.energy()
|
||||||
|
if lastValue then
|
||||||
|
lastChange = (nv - lastValue) / lastTimer
|
||||||
|
end
|
||||||
|
lastValue = nv
|
||||||
|
lastTimer = 10
|
||||||
|
if lastChange then
|
||||||
|
if lastChange > 10 then
|
||||||
|
lastTimer = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
A.timer(lastTimer)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
function app.get_ch(x, y)
|
||||||
|
return getText(y):sub(x, x)
|
||||||
|
end
|
||||||
|
return app, 7, 2
|
78
apps/eeprog.lua
Normal file
78
apps/eeprog.lua
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
local lang, unicode = A.request("lang", "unicode")
|
||||||
|
local eeprom = A.request("c.eeprom")
|
||||||
|
if eeprom then
|
||||||
|
eeprom = eeprom.list()()
|
||||||
|
end
|
||||||
|
|
||||||
|
local langTable = lang.getTable()
|
||||||
|
local function G(text)
|
||||||
|
if langTable then
|
||||||
|
if langTable[text] then
|
||||||
|
return langTable[text]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
local postFlash = false
|
||||||
|
local label = ""
|
||||||
|
local app = {}
|
||||||
|
function app.key(ka, kc, down)
|
||||||
|
if down then
|
||||||
|
if postFlash then
|
||||||
|
if ka ~= 0 then
|
||||||
|
if ka == 8 then
|
||||||
|
label = unicode.sub(label, 1, unicode.len(label) - 1)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if ka == 13 then
|
||||||
|
eeprom.setLabel(label)
|
||||||
|
postFlash = false
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
label = label .. unicode.char(ka)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if ka == ("r"):byte() then
|
||||||
|
local f = A.openfile(G("EEPROM Dump"), "w")
|
||||||
|
if f then
|
||||||
|
f.write(eeprom.get())
|
||||||
|
f.close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if ka == ("w"):byte() then
|
||||||
|
local f = A.openfile(G("EEPROM to flash"), "r")
|
||||||
|
if f then
|
||||||
|
local txt = f.read(128)
|
||||||
|
local ch = ""
|
||||||
|
while txt do
|
||||||
|
ch = ch .. txt
|
||||||
|
txt = f.read(128)
|
||||||
|
end
|
||||||
|
eeprom.set(ch)
|
||||||
|
postFlash = true
|
||||||
|
label = ""
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if ka == ("C"):byte() then
|
||||||
|
A.die()
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- this string must be the longest, kind of bad but oh well
|
||||||
|
-- at least it's not a forced 29 chars...
|
||||||
|
local baseString = unicode.safeTextFormat(G("EEPROMFlash! (R)ead, (W)rite?"))
|
||||||
|
function app.get_ch(x, y)
|
||||||
|
if postFlash then
|
||||||
|
return unicode.sub(unicode.safeTextFormat(G("Label: ") .. label), x, x)
|
||||||
|
end
|
||||||
|
if not eeprom then
|
||||||
|
return unicode.sub(unicode.safeTextFormat(G("No EEPROM installed?")), x, x)
|
||||||
|
end
|
||||||
|
return unicode.sub(baseString, x, x)
|
||||||
|
end
|
||||||
|
return app, unicode.len(baseString), 1
|
4
apps/filemgr.lua
Normal file
4
apps/filemgr.lua
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
-- Launch the File Manager.
|
||||||
|
-- What more could be said?
|
||||||
|
A.openfile("any file", nil)
|
||||||
|
return {update = A.die}, 0, 0
|
267
apps/installer.lua
Normal file
267
apps/installer.lua
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
local lang, setlang, math, table, unicode, fs = A.request("lang", "setlang", "math", "table", "unicode", "c.filesystem")
|
||||||
|
|
||||||
|
local options = {}
|
||||||
|
local optionCallback = function (index) end
|
||||||
|
local cursor = 1
|
||||||
|
local inited = false
|
||||||
|
|
||||||
|
local languages = {}
|
||||||
|
local languagesMenu = {}
|
||||||
|
local languageNames = {
|
||||||
|
["en"] = "English",
|
||||||
|
["de"] = "German",
|
||||||
|
["ru"] = "Russian",
|
||||||
|
["jbo"] = "Lojban",
|
||||||
|
["ja"] = "Japanese",
|
||||||
|
["kw"] = "Cornish",
|
||||||
|
["nl"] = "Dutch",
|
||||||
|
["pl"] = "Polish",
|
||||||
|
["pt"] = "Portugese",
|
||||||
|
["zh"] = "Chinese",
|
||||||
|
["it"] = "Italian",
|
||||||
|
["ga"] = "Irish",
|
||||||
|
["fr"] = "French",
|
||||||
|
["es"] = "Spanish",
|
||||||
|
["pirate"] = "I be speakin' Pirate!"
|
||||||
|
}
|
||||||
|
for k, v in pairs(languageNames) do
|
||||||
|
if fs.primary.exists("lang/" .. k .. "/installer.lua") or (k == "en") then
|
||||||
|
table.insert(languages, k)
|
||||||
|
table.insert(languagesMenu, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local langTable = nil
|
||||||
|
local function G(text)
|
||||||
|
if langTable then
|
||||||
|
if langTable[text] then
|
||||||
|
return langTable[text]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Config
|
||||||
|
local appDeny = {}
|
||||||
|
local installFS = nil
|
||||||
|
local installLang = nil
|
||||||
|
|
||||||
|
-- Stages
|
||||||
|
local startLanguageSel = nil
|
||||||
|
local startFSSel = nil
|
||||||
|
local startAppSel = nil
|
||||||
|
local startFSConfirm = nil
|
||||||
|
local startInstall = nil
|
||||||
|
|
||||||
|
-- Stuff for actual install
|
||||||
|
local runningInstall = nil
|
||||||
|
local runningInstallPoint = 0
|
||||||
|
|
||||||
|
local function setOptions(ol, callback)
|
||||||
|
options = ol
|
||||||
|
optionCallback = callback
|
||||||
|
cursor = 1
|
||||||
|
local maxlen = 1
|
||||||
|
for k, v in ipairs(options) do
|
||||||
|
options[k] = unicode.safeTextFormat(v)
|
||||||
|
local l = unicode.len(v)
|
||||||
|
if l > maxlen then maxlen = l end
|
||||||
|
end
|
||||||
|
A.resize(maxlen + 1, #options)
|
||||||
|
end
|
||||||
|
|
||||||
|
local app = {}
|
||||||
|
function startLanguageSel()
|
||||||
|
setOptions(languagesMenu, function (i)
|
||||||
|
setlang(languages[i])
|
||||||
|
langTable = lang.getTable()
|
||||||
|
startAppSel()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
function startAppSel()
|
||||||
|
local al = A.listApps()
|
||||||
|
table.sort(al)
|
||||||
|
local tbl = {}
|
||||||
|
table.insert(tbl, G("KittenOS Installer"))
|
||||||
|
table.insert(tbl, G("Applications to install:"))
|
||||||
|
for _, v in ipairs(al) do
|
||||||
|
table.insert(tbl, G("Install Application: ") .. v .. " [" .. G(tostring(not appDeny[v])) .. "]")
|
||||||
|
end
|
||||||
|
table.insert(tbl, G("<Confirm>"))
|
||||||
|
setOptions(tbl, function (i)
|
||||||
|
if i >= 3 and i < #tbl then
|
||||||
|
appDeny[al[i - 2]] = not appDeny[al[i - 2]]
|
||||||
|
startAppSel()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if i == #tbl then
|
||||||
|
startFSSel()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
function startFSSel()
|
||||||
|
local fsl = {}
|
||||||
|
for fsp in fs.list() do
|
||||||
|
if fsp ~= fs.primary then
|
||||||
|
table.insert(fsl, fsp.address)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local tbl = {}
|
||||||
|
table.insert(tbl, G("KittenOS Installer"))
|
||||||
|
table.insert(tbl, G("Filesystem to target:"))
|
||||||
|
for _, v in ipairs(fsl) do
|
||||||
|
table.insert(tbl, "<" .. v .. ">")
|
||||||
|
end
|
||||||
|
setOptions(tbl, function (i)
|
||||||
|
if i > 2 then
|
||||||
|
for fsp in fs.list() do
|
||||||
|
if fsp.address == fsl[i - 2] then
|
||||||
|
installFS = fsp
|
||||||
|
startFSConfirm()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
startFSSel()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
function startFSConfirm()
|
||||||
|
local tbl = {}
|
||||||
|
table.insert(tbl, G("KittenOS Installer"))
|
||||||
|
table.insert(tbl, G("Are you sure you want to install to FS:"))
|
||||||
|
table.insert(tbl, installFS.address)
|
||||||
|
table.insert(tbl, G("These applications will be installed:"))
|
||||||
|
local other = nil
|
||||||
|
for _, v in ipairs(A.listApps()) do
|
||||||
|
if not appDeny[v] then
|
||||||
|
if other then
|
||||||
|
table.insert(tbl, other .. ", " .. v)
|
||||||
|
other = nil
|
||||||
|
else
|
||||||
|
other = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if other then
|
||||||
|
table.insert(tbl, other)
|
||||||
|
end
|
||||||
|
table.insert(tbl, G("<Yes>"))
|
||||||
|
table.insert(tbl, G("<No, change settings>"))
|
||||||
|
setOptions(tbl, function (i)
|
||||||
|
if i == (#tbl - 1) then
|
||||||
|
-- first, create directories.
|
||||||
|
local function forceMakeDirectory(s)
|
||||||
|
if installFS.exists(s) then
|
||||||
|
if not installFS.isDirectory(s) then
|
||||||
|
installFS.remove(s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
installFS.makeDirectory(s)
|
||||||
|
end
|
||||||
|
installLang = lang.getLanguage()
|
||||||
|
forceMakeDirectory("apps")
|
||||||
|
forceMakeDirectory("cfgs")
|
||||||
|
forceMakeDirectory("lang")
|
||||||
|
forceMakeDirectory("lang/" .. installLang)
|
||||||
|
runningInstall = {
|
||||||
|
-- in order of importance
|
||||||
|
"init.lua",
|
||||||
|
"policykit.lua",
|
||||||
|
"tfilemgr.lua",
|
||||||
|
"filewrap.lua",
|
||||||
|
"language"
|
||||||
|
}
|
||||||
|
for _, v in ipairs(A.listApps()) do
|
||||||
|
if not appDeny[v] then
|
||||||
|
table.insert(runningInstall, "apps/" .. v .. ".lua")
|
||||||
|
if fs.primary.exists("lang/" .. installLang .. "/" .. v .. ".lua") then
|
||||||
|
table.insert(runningInstall, "lang/" .. installLang .. "/" .. v .. ".lua")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
runningInstallPoint = 1
|
||||||
|
startInstall()
|
||||||
|
end
|
||||||
|
if i == #tbl then
|
||||||
|
startAppSel()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
function startInstall()
|
||||||
|
local percent = math.floor((runningInstallPoint / #runningInstall) * 100)
|
||||||
|
local tbl = {
|
||||||
|
G("Installing.") .. " " .. percent .. "%"
|
||||||
|
}
|
||||||
|
setOptions(tbl, function (i) end)
|
||||||
|
A.timer(1)
|
||||||
|
end
|
||||||
|
function startComplete()
|
||||||
|
setOptions({G("Installation complete."), G("Press Shift-C to leave.")}, function (i) end)
|
||||||
|
end
|
||||||
|
function app.update()
|
||||||
|
if runningInstall then
|
||||||
|
if runningInstall[runningInstallPoint] then
|
||||||
|
local txt = runningInstall[runningInstallPoint]
|
||||||
|
-- perform copy
|
||||||
|
local h2 = installFS.open(txt, "wb")
|
||||||
|
if txt == "language" then
|
||||||
|
if installLang then
|
||||||
|
installFS.write(h2, installLang)
|
||||||
|
else
|
||||||
|
installFS.write(h2, "en")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local h = fs.primary.open(txt, "rb")
|
||||||
|
local chk = fs.primary.read(h, 1024)
|
||||||
|
while chk do
|
||||||
|
installFS.write(h2, chk)
|
||||||
|
chk = fs.primary.read(h, 1024)
|
||||||
|
end
|
||||||
|
fs.primary.close(h)
|
||||||
|
end
|
||||||
|
installFS.close(h2)
|
||||||
|
startInstall()
|
||||||
|
runningInstallPoint = runningInstallPoint + 1
|
||||||
|
else
|
||||||
|
runningInstall = nil
|
||||||
|
startComplete()
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
-- should only be called once, but just in case
|
||||||
|
if not inited then
|
||||||
|
startLanguageSel()
|
||||||
|
inited = true
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
function app.get_ch(x, y)
|
||||||
|
if x == 1 then
|
||||||
|
if y == cursor then return ">" else return " " end
|
||||||
|
end
|
||||||
|
local s = options[y]
|
||||||
|
if not s then s = "FIXME" end
|
||||||
|
return unicode.sub(s, x - 1, x - 1)
|
||||||
|
end
|
||||||
|
function app.key(ka, kc, down)
|
||||||
|
if down then
|
||||||
|
if kc == 200 then
|
||||||
|
cursor = cursor - 1
|
||||||
|
if cursor < 1 then cursor = 1 end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if kc == 208 then
|
||||||
|
cursor = cursor + 1
|
||||||
|
if cursor > #options then cursor = #options end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if ka == 13 then
|
||||||
|
optionCallback(cursor)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if ka == ("C"):byte() then
|
||||||
|
A.die()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return app, 1, 1
|
30
apps/keycodes.lua
Normal file
30
apps/keycodes.lua
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
local math, table = A.request("math", "table")
|
||||||
|
local app = {}
|
||||||
|
local strs = {"", "Shift-C to quit."}
|
||||||
|
local keys = {}
|
||||||
|
local function rebuildKeys()
|
||||||
|
local keylist = {}
|
||||||
|
for k, v in pairs(keys) do
|
||||||
|
if v then
|
||||||
|
table.insert(keylist, k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(keylist)
|
||||||
|
strs[1] = ""
|
||||||
|
for _, v in ipairs(keylist) do
|
||||||
|
strs[1] = strs[1] .. v .. " "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
app.key = function(ka, kc, down)
|
||||||
|
if ka == ("C"):byte() and down then
|
||||||
|
A.die()
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
keys[kc] = down
|
||||||
|
rebuildKeys()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
app.get_ch = function (x, y)
|
||||||
|
return (strs[y]):sub(x, x)
|
||||||
|
end
|
||||||
|
return app, 20, 2
|
38
apps/launcher.lua
Normal file
38
apps/launcher.lua
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
-- Application launcher
|
||||||
|
local table, unicode = A.request("table", "unicode")
|
||||||
|
local apps = A.listApps()
|
||||||
|
local maxlen = 1
|
||||||
|
for _, v in ipairs(apps) do
|
||||||
|
if unicode.len(v) > maxlen then maxlen = unicode.len(v) end
|
||||||
|
end
|
||||||
|
local app = {}
|
||||||
|
local cursor = 1
|
||||||
|
function app.get_ch(x, y)
|
||||||
|
if x == 1 then
|
||||||
|
if y == cursor then return ">" else return " " end
|
||||||
|
end
|
||||||
|
local s = apps[y]
|
||||||
|
if not s then s = "FIXME" end
|
||||||
|
return unicode.sub(unicode.safeTextFormat(s), x - 1, x - 1)
|
||||||
|
end
|
||||||
|
function app.key(ka, kc, down)
|
||||||
|
if down then
|
||||||
|
if kc == 200 then
|
||||||
|
cursor = cursor - 1
|
||||||
|
if cursor < 1 then cursor = 1 end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if kc == 208 then
|
||||||
|
cursor = cursor + 1
|
||||||
|
if cursor > #apps then cursor = #apps end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if ka == 13 then
|
||||||
|
A.launchApp(apps[cursor])
|
||||||
|
end
|
||||||
|
if ka == ("C"):byte() then
|
||||||
|
A.die()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return app, maxlen + 1, #apps
|
39
apps/lineclip.lua
Normal file
39
apps/lineclip.lua
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
local unicode, proc = A.request("unicode", "proc")
|
||||||
|
for _, v in ipairs(proc.listApps()) do
|
||||||
|
if v[2] == "lineclip" then
|
||||||
|
if v[1] ~= proc.aid then
|
||||||
|
A.die()
|
||||||
|
return {}, 1, 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local app = {}
|
||||||
|
local board = ""
|
||||||
|
function app.rpc(srcP, srcD, cmd, txt)
|
||||||
|
if type(cmd) ~= "string" then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
if cmd == "copy" then
|
||||||
|
if type(txt) ~= "string" then
|
||||||
|
error("RPC->lineclip: bad text")
|
||||||
|
end
|
||||||
|
board = txt
|
||||||
|
A.resize(unicode.len(board), 1)
|
||||||
|
end
|
||||||
|
if cmd == "paste" then
|
||||||
|
return board
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function app.get_ch(x, y)
|
||||||
|
return unicode.sub(unicode.safeTextFormat(board), x, x)
|
||||||
|
end
|
||||||
|
function app.key(ka, kc, down)
|
||||||
|
if down and ka == ("C"):byte() then
|
||||||
|
A.die()
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
function app.update()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return app, 8, 1
|
19
apps/memusage.lua
Normal file
19
apps/memusage.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
local math, stat = A.request("math", "stat")
|
||||||
|
local app = {}
|
||||||
|
app.key = function(ka, kc, down)
|
||||||
|
if ka == ("C"):byte() and down then
|
||||||
|
A.die()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local strs = {"", "Shift-C to quit."}
|
||||||
|
app.update = function ()
|
||||||
|
local tm = stat.totalMemory()
|
||||||
|
local um = math.floor((tm - stat.freeMemory()) / 1024)
|
||||||
|
strs[1] = um .. ":" .. math.floor(tm / 1024)
|
||||||
|
A.timer(1)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
app.get_ch = function (x, y)
|
||||||
|
return (strs[y]):sub(x, x)
|
||||||
|
end
|
||||||
|
return app, 16, 2
|
72
apps/modeset.lua
Normal file
72
apps/modeset.lua
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
-- resset: resolution changer
|
||||||
|
-- Typed from within.
|
||||||
|
local math, randr = A.request("math", "randr")
|
||||||
|
local app = {}
|
||||||
|
local mW, mH = randr.maxResolution()
|
||||||
|
-- important on 5.3, and 5.3 prevents
|
||||||
|
-- the nasty memory self-destructs!
|
||||||
|
mW = math.floor(mW) mH = math.floor(mH)
|
||||||
|
local sW, sH = randr.getResolution()
|
||||||
|
sW = math.floor(sW) sH = math.floor(sH)
|
||||||
|
local function mkstr(title, w, h)
|
||||||
|
return title .. ":" .. math.floor(w) .. "x" .. math.floor(h)
|
||||||
|
end
|
||||||
|
function app.get_ch(x, y)
|
||||||
|
local strs = {
|
||||||
|
mkstr("cur", randr.getResolution()),
|
||||||
|
mkstr("new", sW, sH)
|
||||||
|
}
|
||||||
|
return strs[y]:sub(x, x)
|
||||||
|
end
|
||||||
|
local function modres(w, h)
|
||||||
|
sW = sW + w
|
||||||
|
sH = sH + h
|
||||||
|
if sW > mW then sW = mW end
|
||||||
|
if sH > mH then sH = mH end
|
||||||
|
if sW < 1 then sW = 1 end
|
||||||
|
if sH < 1 then sH = 1 end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
function app.key(ka, kc, down)
|
||||||
|
if down then
|
||||||
|
if kc == 200 then
|
||||||
|
return modres(0, -1)
|
||||||
|
end
|
||||||
|
if kc == 208 then
|
||||||
|
return modres(0, 1)
|
||||||
|
end
|
||||||
|
if kc == 203 then
|
||||||
|
return modres(-1, 0)
|
||||||
|
end
|
||||||
|
if kc == 205 then
|
||||||
|
return modres(1, 0)
|
||||||
|
end
|
||||||
|
if ka == 13 then
|
||||||
|
if randr.setResolution(sW, sH) then
|
||||||
|
pcall(function()
|
||||||
|
local f = A.opencfg("w")
|
||||||
|
f.write(sW .. " " .. sH)
|
||||||
|
f.close()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if ka == ("C"):byte() then
|
||||||
|
A.die()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
-- Config stuff!
|
||||||
|
pcall(function()
|
||||||
|
local f = A.opencfg("r")
|
||||||
|
if f then
|
||||||
|
local txt = f.read(64)
|
||||||
|
local nt = txt:gmatch("[0-9]+")
|
||||||
|
sW = math.floor(tonumber(nt()))
|
||||||
|
sH = math.floor(tonumber(nt()))
|
||||||
|
modres(0, 0)
|
||||||
|
f.close()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
return app, 12, 2
|
352
apps/textedit.lua
Normal file
352
apps/textedit.lua
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
-- 'Femto': Text Editor
|
||||||
|
-- Formatting proc. for
|
||||||
|
-- this file is the def.
|
||||||
|
-- size of a textedit win
|
||||||
|
local lang,
|
||||||
|
table,
|
||||||
|
unicode,
|
||||||
|
math,
|
||||||
|
proc =
|
||||||
|
A.request("lang",
|
||||||
|
"table",
|
||||||
|
"unicode",
|
||||||
|
"math",
|
||||||
|
"proc")
|
||||||
|
local lines = {
|
||||||
|
"Femto: Text Editor",
|
||||||
|
"^W : Close, ^S : Save",
|
||||||
|
"^A : Load , ^Q : New.",
|
||||||
|
"^C : Copy Line,",
|
||||||
|
"^V : Paste Line",
|
||||||
|
"^<arrows>: Resize Win",
|
||||||
|
"'^' is Control.",
|
||||||
|
"Now with wide text!",
|
||||||
|
"Yay!"
|
||||||
|
}
|
||||||
|
local linesTranslated =
|
||||||
|
lang.getTable()
|
||||||
|
if linesTranslated then
|
||||||
|
lines = linesTranslated
|
||||||
|
end
|
||||||
|
|
||||||
|
local cursorX = 1
|
||||||
|
local cursorY = 1
|
||||||
|
local cFlash = true
|
||||||
|
local ctrlFlag = false
|
||||||
|
local sW, sH = 25, 8
|
||||||
|
|
||||||
|
local app = {}
|
||||||
|
|
||||||
|
local function splitCur()
|
||||||
|
local s = lines[cursorY]
|
||||||
|
local st = unicode.sub
|
||||||
|
(s, 1, cursorX - 1)
|
||||||
|
local en = unicode.sub
|
||||||
|
(s, cursorX)
|
||||||
|
return st, en
|
||||||
|
end
|
||||||
|
|
||||||
|
local function
|
||||||
|
clampCursorX()
|
||||||
|
local s = lines[cursorY]
|
||||||
|
if unicode.len(s) <
|
||||||
|
(cursorX - 1) then
|
||||||
|
cursorX =
|
||||||
|
unicode.len(s) + 1
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save/Load
|
||||||
|
local function save()
|
||||||
|
ctrlFlag = false
|
||||||
|
local txt =
|
||||||
|
A.openfile("text", "w")
|
||||||
|
if txt then
|
||||||
|
for k, v in
|
||||||
|
ipairs(lines) do
|
||||||
|
if k ~= 1 then
|
||||||
|
txt.write("\n" .. v)
|
||||||
|
else
|
||||||
|
txt.write(v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
txt.close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function load()
|
||||||
|
ctrlFlag = false
|
||||||
|
local txt =
|
||||||
|
A.openfile("text", "r")
|
||||||
|
if txt then
|
||||||
|
lines = {}
|
||||||
|
local lb = ""
|
||||||
|
while true do
|
||||||
|
local l = txt.read(64)
|
||||||
|
if not l then
|
||||||
|
table.insert
|
||||||
|
(lines, lb)
|
||||||
|
cursorX = 1
|
||||||
|
cursorY = 1
|
||||||
|
txt.close()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local lp =
|
||||||
|
l:find("\n")
|
||||||
|
while lp do
|
||||||
|
lb = lb .. l:sub(1,
|
||||||
|
lp - 1)
|
||||||
|
table.insert
|
||||||
|
(lines, lb)
|
||||||
|
lb = ""
|
||||||
|
l = l:sub(lp + 1)
|
||||||
|
lp = l:find("\n")
|
||||||
|
end
|
||||||
|
lb = lb .. l
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function app.get_ch(x, y)
|
||||||
|
-- do rY first since unw
|
||||||
|
-- only requires that
|
||||||
|
-- horizontal stuff be
|
||||||
|
-- messed with...
|
||||||
|
-- ...thankfully
|
||||||
|
local rY = (y + cursorY)
|
||||||
|
- math.floor(sH / 2)
|
||||||
|
|
||||||
|
-- rX is difficult!
|
||||||
|
local rX = 1
|
||||||
|
local Xthold =
|
||||||
|
math.floor(sW / 2)
|
||||||
|
if cursorX > Xthold then
|
||||||
|
rX = rX + (cursorX -
|
||||||
|
Xthold)
|
||||||
|
end
|
||||||
|
local line = lines[rY]
|
||||||
|
if not line then return
|
||||||
|
"¬" end
|
||||||
|
local _, cursorXP =
|
||||||
|
unicode.safeTextFormat(
|
||||||
|
line, cursorX)
|
||||||
|
line, rX =
|
||||||
|
unicode.safeTextFormat(
|
||||||
|
line, rX)
|
||||||
|
|
||||||
|
-- 1-based cambias stuff
|
||||||
|
rX = rX + (x - 1)
|
||||||
|
if rX == cursorXP then
|
||||||
|
if rY == cursorY then
|
||||||
|
if cFlash then
|
||||||
|
return "_"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return unicode.sub(line,
|
||||||
|
rX, rX)
|
||||||
|
end
|
||||||
|
-- communicate with the
|
||||||
|
-- "lineclip" clipboard,
|
||||||
|
-- for inter-window copy
|
||||||
|
function lineclip(c, m)
|
||||||
|
for _, v in
|
||||||
|
ipairs(proc.listApps())
|
||||||
|
do
|
||||||
|
if v[2] == "lineclip"
|
||||||
|
then
|
||||||
|
return proc.sendRPC(
|
||||||
|
v[1], c, m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local aid = A.launchApp(
|
||||||
|
"lineclip")
|
||||||
|
ctrlFlag = false
|
||||||
|
if aid then return
|
||||||
|
proc.sendRPC(aid, c, m)
|
||||||
|
end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
-- add a single character
|
||||||
|
function putLetter(ch)
|
||||||
|
if ch == "\r" then
|
||||||
|
local a, b = splitCur()
|
||||||
|
lines[cursorY] = a
|
||||||
|
table.insert(lines,
|
||||||
|
cursorY + 1, b)
|
||||||
|
cursorY = cursorY + 1
|
||||||
|
cursorX = 1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local a, b = splitCur()
|
||||||
|
a = a .. ch
|
||||||
|
lines[cursorY] = a .. b
|
||||||
|
cursorX =
|
||||||
|
unicode.len(a) + 1
|
||||||
|
end
|
||||||
|
function app.key(ka, kc,
|
||||||
|
down)
|
||||||
|
if kc == 29 then
|
||||||
|
ctrlFlag = down
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if ctrlFlag then
|
||||||
|
if not down then
|
||||||
|
return false end
|
||||||
|
if kc == 17 -- W
|
||||||
|
then A.die() end
|
||||||
|
if kc == 200 then
|
||||||
|
sH = sH - 1
|
||||||
|
if sH == 0 then
|
||||||
|
sH = 1 end
|
||||||
|
A.resize(sW, sH)
|
||||||
|
end
|
||||||
|
if kc == 208 then
|
||||||
|
sH = sH + 1
|
||||||
|
A.resize(sW, sH)
|
||||||
|
end
|
||||||
|
if kc == 203 then
|
||||||
|
sW = sW - 1
|
||||||
|
if sW == 0 then
|
||||||
|
sW = 1 end
|
||||||
|
A.resize(sW, sH)
|
||||||
|
end
|
||||||
|
if kc == 205 then
|
||||||
|
sW = sW + 1
|
||||||
|
A.resize(sW, sH)
|
||||||
|
end
|
||||||
|
if kc == 31 -- S
|
||||||
|
then return save() end
|
||||||
|
if kc == 30 -- A
|
||||||
|
then return load() end
|
||||||
|
if kc == 16 -- Q
|
||||||
|
then lines = {""}
|
||||||
|
cursorX = 1
|
||||||
|
cursorY = 1
|
||||||
|
return true end
|
||||||
|
if kc == 46 -- C
|
||||||
|
then lineclip("copy",
|
||||||
|
lines[cursorY]) end
|
||||||
|
if kc == 47 then -- V
|
||||||
|
table.insert(lines,
|
||||||
|
cursorY,
|
||||||
|
lineclip("paste"))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
-- action keys
|
||||||
|
if not down then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if kc == 200
|
||||||
|
or kc == 201 then
|
||||||
|
local moveAmount = 1
|
||||||
|
if kc == 201 then
|
||||||
|
moveAmount =
|
||||||
|
math.floor(sH / 2)
|
||||||
|
end
|
||||||
|
cursorY = cursorY -
|
||||||
|
moveAmount
|
||||||
|
if cursorY < 1 then
|
||||||
|
cursorY = 1 end
|
||||||
|
clampCursorX()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if kc == 208
|
||||||
|
or kc == 209 then
|
||||||
|
local moveAmount = 1
|
||||||
|
if kc == 209 then
|
||||||
|
moveAmount =
|
||||||
|
math.floor(sH / 2)
|
||||||
|
end
|
||||||
|
cursorY = cursorY +
|
||||||
|
moveAmount
|
||||||
|
if cursorY> #lines then
|
||||||
|
cursorY = #lines end
|
||||||
|
clampCursorX()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if kc == 203 then
|
||||||
|
if cursorX > 1 then
|
||||||
|
cursorX = cursorX - 1
|
||||||
|
else
|
||||||
|
if cursorY > 1 then
|
||||||
|
cursorY = cursorY - 1
|
||||||
|
cursorX = unicode.len
|
||||||
|
(lines[cursorY]) + 1
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if kc == 205 then
|
||||||
|
cursorX = cursorX + 1
|
||||||
|
if clampCursorX() then
|
||||||
|
if cursorY < #lines
|
||||||
|
then
|
||||||
|
cursorY = cursorY + 1
|
||||||
|
cursorX = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if kc == 199 then
|
||||||
|
cursorX = 1
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if kc == 207 then
|
||||||
|
cursorX = unicode.len(
|
||||||
|
lines[cursorY]) + 1
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if ka == 8 then
|
||||||
|
if cursorX == 1 then
|
||||||
|
if cursorY == 1 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local l = table.remove
|
||||||
|
(lines, cursorY)
|
||||||
|
cursorY = cursorY - 1
|
||||||
|
cursorX = unicode.len(
|
||||||
|
lines[cursorY]) + 1
|
||||||
|
lines[cursorY] =
|
||||||
|
lines[cursorY] .. l
|
||||||
|
else
|
||||||
|
local a, b =splitCur()
|
||||||
|
a = unicode.sub(a, 1,
|
||||||
|
unicode.len(a) - 1)
|
||||||
|
lines[cursorY] = a.. b
|
||||||
|
cursorX = cursorX - 1
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if ka ~= 0 then
|
||||||
|
putLetter
|
||||||
|
(unicode.char(ka))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
function app.clipboard(t)
|
||||||
|
for i = 1,
|
||||||
|
unicode.len(t) do
|
||||||
|
local c =
|
||||||
|
unicode.sub(t, i, i)
|
||||||
|
if c ~= "\r" then
|
||||||
|
if c == "\n" then
|
||||||
|
c = "\r"
|
||||||
|
end
|
||||||
|
putLetter(c)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
function app.update()
|
||||||
|
cFlash = not cFlash
|
||||||
|
A.timer(0.5)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return app, sW, sH
|
70
filewrap.lua
Normal file
70
filewrap.lua
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
-- File Wrapper
|
||||||
|
local fwrap = {}
|
||||||
|
local appTables = {}
|
||||||
|
-- NOTE: May not be error-sandboxed.
|
||||||
|
-- Be careful.
|
||||||
|
function fwrap.appDead(aid)
|
||||||
|
if appTables[aid] then
|
||||||
|
for k, v in ipairs(appTables[aid]) do
|
||||||
|
pcall(function()
|
||||||
|
local prox = component.proxy(v.device)
|
||||||
|
if prox then
|
||||||
|
prox.close(v.handle)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
appTables[aid] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function fwrap.canFree()
|
||||||
|
for _, v in pairs(appTables) do
|
||||||
|
if v then
|
||||||
|
if #v > 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
-- Always error-sandboxed, let errors throw
|
||||||
|
function fwrap.open(aid, path, mode)
|
||||||
|
local finst = {}
|
||||||
|
finst.device = path[1]
|
||||||
|
finst.file = path[2]
|
||||||
|
finst.handle = component.invoke(finst.device, "open", finst.file, mode .. "b")
|
||||||
|
if not appTables[aid] then
|
||||||
|
appTables[aid] = {}
|
||||||
|
end
|
||||||
|
table.insert(appTables, finst)
|
||||||
|
local function closer()
|
||||||
|
pcall(function()
|
||||||
|
component.invoke(finst.device, "close", finst.handle)
|
||||||
|
end)
|
||||||
|
for k, v in ipairs(appTables[aid]) do
|
||||||
|
if v == finst then
|
||||||
|
table.remove(appTables[aid], k)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if mode == "r" then
|
||||||
|
return {
|
||||||
|
close = closer,
|
||||||
|
read = function (len)
|
||||||
|
if type(len) ~= "number" then error("Length of read must be number") end
|
||||||
|
return component.invoke(finst.device, "read", finst.handle, len)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if mode == "w" then
|
||||||
|
return {
|
||||||
|
close = closer,
|
||||||
|
write = function (txt)
|
||||||
|
if type(txt) ~= "string" then error("Write data must be string-bytearray") end
|
||||||
|
return component.invoke(finst.device, "write", finst.handle, txt)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
error("Bad mode")
|
||||||
|
end
|
||||||
|
return fwrap
|
687
init.lua
Normal file
687
init.lua
Normal file
@ -0,0 +1,687 @@
|
|||||||
|
-- KittenOS
|
||||||
|
|
||||||
|
-- ISO 639 language code.
|
||||||
|
local systemLanguage = "en"
|
||||||
|
|
||||||
|
function component.get(tp)
|
||||||
|
local a = component.list(tp, true)()
|
||||||
|
if not a then return nil end
|
||||||
|
return component.proxy(a)
|
||||||
|
end
|
||||||
|
|
||||||
|
local primaryDisk = component.proxy(computer.getBootAddress())
|
||||||
|
local langFile = primaryDisk.open("language", "rb")
|
||||||
|
if langFile then
|
||||||
|
systemLanguage = primaryDisk.read(langFile, 64)
|
||||||
|
primaryDisk.close(langFile)
|
||||||
|
end
|
||||||
|
local function loadfile(s, e)
|
||||||
|
local h = primaryDisk.open(s)
|
||||||
|
if h then
|
||||||
|
local ch = ""
|
||||||
|
local c = primaryDisk.read(h, 256)
|
||||||
|
while c do
|
||||||
|
ch = ch .. c
|
||||||
|
c = primaryDisk.read(h, 256)
|
||||||
|
end
|
||||||
|
primaryDisk.close(h)
|
||||||
|
return load(ch, "=" .. s, "t", e)
|
||||||
|
end
|
||||||
|
return nil, "File Unreadable"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Must be a sane source of time in seconds.
|
||||||
|
local function saneTime()
|
||||||
|
return computer.uptime()
|
||||||
|
end
|
||||||
|
local primaryScreen = component.get("screen")
|
||||||
|
local primaryGPU = component.get("gpu")
|
||||||
|
|
||||||
|
primaryGPU.bind(primaryScreen.address)
|
||||||
|
local scrW, scrH = 50, 16
|
||||||
|
local frameH = 1
|
||||||
|
local redrawWorldSoon = true
|
||||||
|
primaryGPU.setResolution(scrW, scrH)
|
||||||
|
primaryGPU.setBackground(0x000000)
|
||||||
|
primaryGPU.setForeground(0xFFFFFF)
|
||||||
|
primaryGPU.fill(1, 1, scrW, scrH, "#")
|
||||||
|
-- apps maps aid (Running application ID, like Process ID) to app data
|
||||||
|
-- appZ handles Z-order
|
||||||
|
local apps = {}
|
||||||
|
local appZ = {}
|
||||||
|
local function sanech(ch, bch)
|
||||||
|
if not bch then bch = " " end
|
||||||
|
if not ch then return bch end
|
||||||
|
if unicode.len(ch) ~= 1 then return bch end
|
||||||
|
return ch
|
||||||
|
end
|
||||||
|
|
||||||
|
-- aid
|
||||||
|
local launchApp = nil
|
||||||
|
-- text, die
|
||||||
|
local dialogApp = nil
|
||||||
|
-- aid, pkg, txt
|
||||||
|
local openDialog = nil
|
||||||
|
-- Component of the system that handles file access,
|
||||||
|
-- does cleanup after dead apps, etc.
|
||||||
|
local fileWrapper = nil
|
||||||
|
local drawing = false
|
||||||
|
local function handleEv(aid, evt, ...)
|
||||||
|
local f = apps[aid].i[evt]
|
||||||
|
if not f then return end
|
||||||
|
local r2 = {pcall(f, ...)}
|
||||||
|
if r2[1] then
|
||||||
|
return select(2, table.unpack(r2))
|
||||||
|
else
|
||||||
|
-- possible error during death
|
||||||
|
if apps[aid] then
|
||||||
|
-- error, override instance immediately.
|
||||||
|
local i, w, h = dialogApp(r2[2], apps[aid].A.die)
|
||||||
|
-- REALLY BAD STUFF (not much choice)
|
||||||
|
local od = drawing
|
||||||
|
drawing = false
|
||||||
|
apps[aid].i = i
|
||||||
|
--apps[aid].i = {}
|
||||||
|
apps[aid].A.resize(w, h)
|
||||||
|
drawing = od
|
||||||
|
else
|
||||||
|
openDialog("error-" .. aid, "*error-during-death", r2[2])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local needRedraw = {}
|
||||||
|
local function handleEvNRD(aid, ...)
|
||||||
|
local doRedraw = handleEv(aid, ...)
|
||||||
|
if doRedraw then needRedraw[aid] = true end
|
||||||
|
end
|
||||||
|
-- This function is critical to wide text support.
|
||||||
|
-- The measures taken below mean that the *system* can deal with wide text,
|
||||||
|
-- but applications need to have the same spacing rules in place.
|
||||||
|
-- Since these spacing rules,
|
||||||
|
-- and the ability to get a wide-char point from a "normal" point,
|
||||||
|
-- are probably beneficial to everybody anyway,
|
||||||
|
-- they're exposed here as a unicode function.
|
||||||
|
function unicode.safeTextFormat(s, ptr)
|
||||||
|
local res = ""
|
||||||
|
if not ptr then ptr = 1 end
|
||||||
|
local aptr = 1
|
||||||
|
for i = 1, unicode.len(s) do
|
||||||
|
local ch = unicode.sub(s, i, i)
|
||||||
|
local ex = unicode.charWidth(ch)
|
||||||
|
if i < ptr then
|
||||||
|
aptr = aptr + ex
|
||||||
|
end
|
||||||
|
for j = 2, ex do
|
||||||
|
ch = ch .. " "
|
||||||
|
end
|
||||||
|
res = res .. ch
|
||||||
|
end
|
||||||
|
return res, aptr
|
||||||
|
end
|
||||||
|
-- Do not let wide characters cause weirdness outside home window!!!
|
||||||
|
local function wideCharSpillFilter(ch, x, doNotTouch)
|
||||||
|
if (x + 1) == doNotTouch then
|
||||||
|
if unicode.isWide(ch) then
|
||||||
|
return "%"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ch
|
||||||
|
end
|
||||||
|
local function getChar(x, y)
|
||||||
|
-- Note: The colours used here tend to
|
||||||
|
-- game the autoselect so this works
|
||||||
|
-- without any depth nastiness.
|
||||||
|
for i = 1, #appZ do
|
||||||
|
local k = appZ[(#appZ + 1) - i]
|
||||||
|
local title = unicode.safeTextFormat(k)
|
||||||
|
local v = apps[k]
|
||||||
|
if x >= v.x and x < (v.x + v.w) then
|
||||||
|
if y == v.y then
|
||||||
|
local bgc = 0x80FFFF
|
||||||
|
local bch = "-"
|
||||||
|
if i == 1 then bgc = 0x808080 bch = "+" end
|
||||||
|
local ch = sanech(unicode.sub(title, (x - v.x) + 1, (x - v.x) + 1), bch)
|
||||||
|
return 0x000000, bgc, wideCharSpillFilter(ch, x, v.x + v.w)
|
||||||
|
else
|
||||||
|
if y > v.y and y < (v.y + v.h + frameH) then
|
||||||
|
-- get char from app
|
||||||
|
local ch = sanech(handleEv(k, "get_ch", (x - v.x) + 1, (y - (v.y + frameH)) + 1))
|
||||||
|
return 0xFFFFFF, 0x000000, wideCharSpillFilter(ch, x, v.x + v.w)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return 0xFFFFFF, 0x000000, " "
|
||||||
|
end
|
||||||
|
local function failDrawing()
|
||||||
|
if drawing then error("Cannot call when drawing.") end
|
||||||
|
end
|
||||||
|
local function redrawSection(x, y, w, h)
|
||||||
|
drawing = true
|
||||||
|
primaryGPU.setBackground(0x000000)
|
||||||
|
primaryGPU.setForeground(0xFFFFFF)
|
||||||
|
local cfg, cbg = 0xFFFFFF, 0x000000
|
||||||
|
--primaryGPU.fill(x, y, w, h, " ")
|
||||||
|
for ly = 1, h do
|
||||||
|
local buf = ""
|
||||||
|
local bufX = x
|
||||||
|
-- Wide characters are annoying.
|
||||||
|
local wideCharacterAdvance = 0
|
||||||
|
for lx = 1, w do
|
||||||
|
if wideCharacterAdvance == 0 then
|
||||||
|
local fg, bg, tx = getChar(x + (lx - 1), y + (ly - 1))
|
||||||
|
local flush = false
|
||||||
|
if fg ~= cfg then flush = true end
|
||||||
|
if bg ~= cbg then flush = true end
|
||||||
|
if flush then
|
||||||
|
if buf:len() > 0 then
|
||||||
|
primaryGPU.set(bufX, y + (ly - 1), buf)
|
||||||
|
end
|
||||||
|
buf = ""
|
||||||
|
bufX = x + (lx - 1)
|
||||||
|
end
|
||||||
|
if fg ~= cfg then primaryGPU.setForeground(fg) cfg = fg end
|
||||||
|
if bg ~= cbg then primaryGPU.setBackground(bg) cbg = bg end
|
||||||
|
buf = buf .. tx
|
||||||
|
wideCharacterAdvance = unicode.charWidth(tx) - 1
|
||||||
|
else
|
||||||
|
-- nothing to add to buffer, since the extra "letters" don't count
|
||||||
|
wideCharacterAdvance = wideCharacterAdvance - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
primaryGPU.set(bufX, y + (ly - 1), buf)
|
||||||
|
end
|
||||||
|
drawing = false
|
||||||
|
end
|
||||||
|
local function redrawApp(aid, onlyBar)
|
||||||
|
local h = frameH
|
||||||
|
if not onlyBar then h = h + apps[aid].h end
|
||||||
|
redrawSection(apps[aid].x, apps[aid].y, apps[aid].w, h)
|
||||||
|
end
|
||||||
|
local function hideApp(aid, deferRedraw)
|
||||||
|
local function sk()
|
||||||
|
for ri, v in ipairs(appZ) do
|
||||||
|
if v == aid then table.remove(appZ, ri) return end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
sk()
|
||||||
|
if not deferRedraw then
|
||||||
|
redrawApp(aid)
|
||||||
|
local newFocus = appZ[#appZ]
|
||||||
|
if newFocus then redrawApp(newFocus, true) end
|
||||||
|
end
|
||||||
|
return {apps[aid].x, apps[aid].y, apps[aid].w, apps[aid].h + frameH}
|
||||||
|
end
|
||||||
|
local function focusApp(aid, focusUndrawn)
|
||||||
|
hideApp(aid, true) -- just ensure it's not on stack, no need to redraw as it won't move
|
||||||
|
local lastFocus = appZ[#appZ]
|
||||||
|
table.insert(appZ, aid)
|
||||||
|
-- make the focus indicator disappear should one exist.
|
||||||
|
-- focusUndrawn indicates that the focus was transient and never got drawn
|
||||||
|
if lastFocus and (not focusUndrawn) then redrawApp(lastFocus, true) end
|
||||||
|
-- Finally, make absolutely sure the application is shown on the screen
|
||||||
|
redrawApp(aid)
|
||||||
|
end
|
||||||
|
local function moveApp(aid, x, y)
|
||||||
|
local section = hideApp(aid, true) -- remove from stack, do NOT redraw
|
||||||
|
apps[aid].x = x -- (prevents interim focus weirdness)
|
||||||
|
apps[aid].y = y
|
||||||
|
-- put back on stack, redrawing destination, but not the
|
||||||
|
-- interim focus target (since we made sure NOT to redraw that)
|
||||||
|
focusApp(aid, true)
|
||||||
|
redrawSection(table.unpack(section)) -- handle source cleanup
|
||||||
|
end
|
||||||
|
local function ofsApp(aid, x, y)
|
||||||
|
moveApp(aid, apps[aid].x + x, apps[aid].y + y)
|
||||||
|
end
|
||||||
|
local function killApp(aid)
|
||||||
|
hideApp(aid)
|
||||||
|
apps[aid] = nil
|
||||||
|
if fileWrapper then
|
||||||
|
fileWrapper.appDead(aid)
|
||||||
|
if fileWrapper.canFree() then
|
||||||
|
fileWrapper = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local getLCopy = nil
|
||||||
|
function getLCopy(t)
|
||||||
|
if type(t) == "table" then
|
||||||
|
local t2 = {}
|
||||||
|
setmetatable(t2, {__index = function(a, k) return getLCopy(t[k]) end})
|
||||||
|
return t2
|
||||||
|
else
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Used to ensure the "primary" device is safe
|
||||||
|
-- while allowing complete control otherwise.
|
||||||
|
local function omittingComponentL(o, t)
|
||||||
|
local i = component.list(t, true)
|
||||||
|
return function()
|
||||||
|
local ii = i()
|
||||||
|
if ii == o then ii = i() end
|
||||||
|
if not ii then return nil end
|
||||||
|
return component.proxy(ii)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Allows for simple "Control any of these connected to the system" APIs,
|
||||||
|
-- for things the OS shouldn't be poking it's nose in.
|
||||||
|
local function basicComponentSW(t, primary)
|
||||||
|
return {
|
||||||
|
list = function()
|
||||||
|
local i = component.list(t, true)
|
||||||
|
return function ()
|
||||||
|
local ii = i()
|
||||||
|
if not ii then return nil end
|
||||||
|
return component.proxy(ii)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
primary = primary
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local function getAPI(s, cAid, cPkg, access)
|
||||||
|
if s == "math" then return getLCopy(math) end
|
||||||
|
if s == "table" then return getLCopy(table) end
|
||||||
|
if s == "string" then return getLCopy(string) end
|
||||||
|
if s == "unicode" then return getLCopy(unicode) end
|
||||||
|
if s == "root" then return _ENV end
|
||||||
|
if s == "stat" then return {
|
||||||
|
totalMemory = computer.totalMemory,
|
||||||
|
freeMemory = computer.freeMemory,
|
||||||
|
energy = computer.energy,
|
||||||
|
maxEnergy = computer.maxEnergy,
|
||||||
|
clock = os.clock,
|
||||||
|
date = os.date,
|
||||||
|
difftime = os.difftime,
|
||||||
|
time = os.time,
|
||||||
|
componentList = component.list
|
||||||
|
} end
|
||||||
|
if s == "proc" then return {
|
||||||
|
aid = cAid,
|
||||||
|
pkg = cPkg,
|
||||||
|
listApps = function ()
|
||||||
|
local t = {}
|
||||||
|
local t2 = {}
|
||||||
|
for k, v in pairs(apps) do
|
||||||
|
table.insert(t, k)
|
||||||
|
t2[k] = v.pkg
|
||||||
|
end
|
||||||
|
table.sort(t)
|
||||||
|
local t3 = {}
|
||||||
|
for k, v in ipairs(t) do
|
||||||
|
t3[k] = {v, t2[v]}
|
||||||
|
end
|
||||||
|
return t3
|
||||||
|
end,
|
||||||
|
sendRPC = function (aid, ...)
|
||||||
|
if type(aid) ~= "string" then error("aid must be string") end
|
||||||
|
if not apps[aid] then error("RPC target does not exist.") end
|
||||||
|
return handleEv(aid, "rpc", cPkg, cAid, ...)
|
||||||
|
end
|
||||||
|
} end
|
||||||
|
if s == "lang" then return {
|
||||||
|
getLanguage = function ()
|
||||||
|
return systemLanguage
|
||||||
|
end,
|
||||||
|
getTable = function ()
|
||||||
|
local ca, cb = loadfile("lang/" .. systemLanguage .. "/" .. cPkg .. ".lua", {})
|
||||||
|
if not ca then return nil, cb end
|
||||||
|
ca, cb = pcall(ca)
|
||||||
|
if not ca then return nil, cb end
|
||||||
|
return cb
|
||||||
|
end
|
||||||
|
} end
|
||||||
|
if s == "setlang" then return function (lang)
|
||||||
|
if type(lang) ~= "string" then error("Language must be string") end
|
||||||
|
systemLanguage = lang
|
||||||
|
pcall(function ()
|
||||||
|
local langFile = primaryDisk.open("language", "wb")
|
||||||
|
if langFile then
|
||||||
|
primaryDisk.write(langFile, systemLanguage)
|
||||||
|
primaryDisk.close(langFile)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end end
|
||||||
|
if s == "kill" then return {
|
||||||
|
killApp = function (aid)
|
||||||
|
if type(aid) ~= "string" then error("aid must be string") end
|
||||||
|
if apps[aid] then killApp(aid) end
|
||||||
|
end
|
||||||
|
} end
|
||||||
|
if s == "randr" then return {
|
||||||
|
getResolution = primaryGPU.getResolution,
|
||||||
|
maxResolution = primaryGPU.maxResolution,
|
||||||
|
setResolution = function (w, h)
|
||||||
|
failDrawing()
|
||||||
|
if primaryGPU.setResolution(w, h) then
|
||||||
|
scrW = w
|
||||||
|
scrH = h
|
||||||
|
redrawWorldSoon = true
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
iterateScreens = function()
|
||||||
|
-- List all screens, but do NOT give a primary screen.
|
||||||
|
return omittingComponentL(primaryScreen.address, "screen")
|
||||||
|
end,
|
||||||
|
iterateGPUs = function()
|
||||||
|
-- List all GPUs, but do NOT give a primary GPU.
|
||||||
|
return omittingComponentL(primaryGPU.address, "gpu")
|
||||||
|
end
|
||||||
|
} end
|
||||||
|
|
||||||
|
if s == "c.modem" then access["s.modem_message"] = true end
|
||||||
|
if s == "c.tunnel" then access["s.modem_message"] = true end
|
||||||
|
if s == "c.chat_box" then access["s.chat_message"] = true end
|
||||||
|
|
||||||
|
if s == "c.filesystem" then return basicComponentSW("filesystem", primaryDisk) end
|
||||||
|
if s == "c.screen" then return basicComponentSW("screen", primaryScreen) end
|
||||||
|
if s == "c.gpu" then return basicComponentSW("gpu", primaryGPU) end
|
||||||
|
if s:sub(1, 2) == "c." then return basicComponentSW(s:sub(3)) end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local function launchAppCore(aid, pkg, f)
|
||||||
|
if apps[aid] then return end
|
||||||
|
local function fD()
|
||||||
|
-- stops potentially nasty situations
|
||||||
|
if not apps[aid] then error("App already dead") end
|
||||||
|
end
|
||||||
|
local A = {
|
||||||
|
listApps = function (a) fD()
|
||||||
|
local appList = {}
|
||||||
|
for _, v in ipairs(primaryDisk.list("apps")) do
|
||||||
|
if v:sub(v:len() - 3) == ".lua" then
|
||||||
|
table.insert(appList, v:sub(1, v:len() - 4))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return appList
|
||||||
|
end,
|
||||||
|
launchApp = function (a) fD()
|
||||||
|
if type(a) ~= "string" then error("App IDs are strings") end
|
||||||
|
if a:gmatch("[a-zA-Z%-_\x80-\xFF]+")() ~= a then error("App '" .. a .. "' does not seem sane") end
|
||||||
|
failDrawing()
|
||||||
|
return launchApp(a)
|
||||||
|
end,
|
||||||
|
opencfg = function (openmode) fD()
|
||||||
|
if type(openmode) ~= "string" then
|
||||||
|
error("Openmode must be nil or string.")
|
||||||
|
end
|
||||||
|
local ok = false
|
||||||
|
if openmode == "r" then ok = true end
|
||||||
|
if openmode == "w" then ok = true end
|
||||||
|
if not ok then error("Bad openmode.") end
|
||||||
|
if not fileWrapper then
|
||||||
|
fileWrapper = loadfile("filewrap.lua", _ENV)()
|
||||||
|
end
|
||||||
|
return fileWrapper.open(aid, {primaryDisk.address, "cfgs/" .. pkg}, openmode)
|
||||||
|
end,
|
||||||
|
openfile = function (filetype, openmode) fD()
|
||||||
|
if openmode ~= nil then if type(openmode) ~= "string" then
|
||||||
|
error("Openmode must be nil or string.")
|
||||||
|
end end
|
||||||
|
if type(filetype) ~= "string" then error("Filetype must be string.") end
|
||||||
|
filetype = aid .. ": " .. filetype
|
||||||
|
failDrawing()
|
||||||
|
redrawWorldSoon = true
|
||||||
|
local rs, rt = pcall(function()
|
||||||
|
if fileWrapper then
|
||||||
|
if fileWrapper.canFree() then
|
||||||
|
fileWrapper = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local r = loadfile("tfilemgr.lua", _ENV)(filetype, openmode, primaryGPU)
|
||||||
|
if r and openmode then
|
||||||
|
if not fileWrapper then
|
||||||
|
fileWrapper = loadfile("filewrap.lua", _ENV)()
|
||||||
|
end
|
||||||
|
return fileWrapper.open(aid, r, openmode) -- 'r' is table {drive, dir}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if not rs then
|
||||||
|
openDialog("*fmgr", "FMerr/" .. aid, rt)
|
||||||
|
else
|
||||||
|
return rt
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
request = function (...) fD()
|
||||||
|
failDrawing()
|
||||||
|
local requests = {...}
|
||||||
|
-- If the same process requests a permission twice,
|
||||||
|
-- just let it.
|
||||||
|
-- "needed" is true if we still need permission.
|
||||||
|
-- first pass confirms we need permission,
|
||||||
|
-- second pass asks for it
|
||||||
|
local needed = false
|
||||||
|
for _, v in ipairs(requests) do
|
||||||
|
if type(v) == "string" then
|
||||||
|
if not apps[aid].hasAccess[v] then
|
||||||
|
needed = true
|
||||||
|
end
|
||||||
|
if apps[aid].denyAccess[v] then
|
||||||
|
return nil -- Don't even bother.
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if needed then
|
||||||
|
local r, d = loadfile("policykit.lua", _ENV)(primaryGPU, pkg, requests)
|
||||||
|
if r then
|
||||||
|
needed = false
|
||||||
|
end
|
||||||
|
if not d then
|
||||||
|
redrawWorldSoon = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local results = {}
|
||||||
|
for _, v in ipairs(requests) do
|
||||||
|
if type(v) == "string" then
|
||||||
|
if not needed then
|
||||||
|
table.insert(results, getAPI(v, aid, pkg, apps[aid].hasAccess))
|
||||||
|
apps[aid].hasAccess[v] = true
|
||||||
|
else
|
||||||
|
apps[aid].denyAccess[v] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.unpack(results)
|
||||||
|
end,
|
||||||
|
timer = function (ud) fD()
|
||||||
|
if type(ud) ~= "number" then error("Timer must take number.") end
|
||||||
|
failDrawing()
|
||||||
|
if ud > 0 then
|
||||||
|
apps[aid].nextUpdate = saneTime() + ud
|
||||||
|
else
|
||||||
|
apps[aid].nextUpdate = nil
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
resize = function (w, h) fD()
|
||||||
|
if type(w) ~= "number" then error("Width must be number.") end
|
||||||
|
if type(h) ~= "number" then error("Height must be number.") end
|
||||||
|
w = math.floor(w)
|
||||||
|
h = math.floor(h)
|
||||||
|
if w < 1 then w = 1 end
|
||||||
|
if h < 1 then h = 1 end
|
||||||
|
failDrawing()
|
||||||
|
local dW = apps[aid].w
|
||||||
|
local dH = apps[aid].h
|
||||||
|
if dW < w then dW = w end
|
||||||
|
if dH < h then dH = h end
|
||||||
|
apps[aid].w = w
|
||||||
|
apps[aid].h = h
|
||||||
|
redrawSection(apps[aid].x, apps[aid].y, dW, dH + frameH)
|
||||||
|
end,
|
||||||
|
die = function () fD()
|
||||||
|
failDrawing()
|
||||||
|
killApp(aid)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
apps[aid] = {}
|
||||||
|
apps[aid].pkg = pkg
|
||||||
|
local iDummy = {} -- Dummy object to keep app valid during init.
|
||||||
|
apps[aid].i = iDummy
|
||||||
|
apps[aid].A = A
|
||||||
|
apps[aid].nextUpdate = saneTime()
|
||||||
|
apps[aid].hasAccess = {}
|
||||||
|
apps[aid].denyAccess = {}
|
||||||
|
apps[aid].x = 1
|
||||||
|
apps[aid].y = 1
|
||||||
|
apps[aid].w = 1
|
||||||
|
apps[aid].h = 1
|
||||||
|
local i, w, h = f(A)
|
||||||
|
if apps[aid] then
|
||||||
|
-- If the app triggered an error handler,
|
||||||
|
-- then the instance could be replaced, make this act OK
|
||||||
|
if apps[aid].i == iDummy then
|
||||||
|
apps[aid].i = i
|
||||||
|
apps[aid].w = w
|
||||||
|
apps[aid].h = h
|
||||||
|
end
|
||||||
|
focusApp(aid)
|
||||||
|
return aid
|
||||||
|
end
|
||||||
|
-- App self-destructed
|
||||||
|
end
|
||||||
|
function launchApp(pkg)
|
||||||
|
local aid = 0
|
||||||
|
while apps[pkg .. "-" .. aid] do aid = aid + 1 end
|
||||||
|
aid = pkg .. "-" .. aid
|
||||||
|
return launchAppCore(aid, pkg, function (A)
|
||||||
|
local f, fe = loadfile("apps/" .. pkg .. ".lua", {
|
||||||
|
A = A,
|
||||||
|
assert = assert, ipairs = ipairs,
|
||||||
|
load = load, next = next,
|
||||||
|
pairs = pairs, pcall = pcall,
|
||||||
|
xpcall = xpcall, rawequal = rawequal,
|
||||||
|
rawget = rawget, rawlen = rawlen,
|
||||||
|
rawset = rawset, select = select,
|
||||||
|
type = type, error = error,
|
||||||
|
tonumber = tonumber, tostring = tostring
|
||||||
|
})
|
||||||
|
if not f then
|
||||||
|
return dialogApp(fe, A.die)
|
||||||
|
end
|
||||||
|
local ok, app, ww, wh = pcall(f)
|
||||||
|
if ok and ww and wh then
|
||||||
|
ww = math.floor(ww)
|
||||||
|
wh = math.floor(wh)
|
||||||
|
if ww < 1 then ww = 1 end
|
||||||
|
if wh < 1 then wh = 1 end
|
||||||
|
return app, ww, wh
|
||||||
|
end
|
||||||
|
if ok and not ww then app = "No Size" end
|
||||||
|
if ok and not wh then app = "No Size" end
|
||||||
|
return dialogApp(app, A.die)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
-- emergency dialog app
|
||||||
|
function dialogApp(fe, die)
|
||||||
|
fe = tostring(fe)
|
||||||
|
local ww, wh = 32, 1
|
||||||
|
wh = math.floor(unicode.len(fe) / ww) + 1
|
||||||
|
return {
|
||||||
|
key = function (ka, kc, down) if ka == 13 and down then die() end end,
|
||||||
|
update = function() end, get_ch = function (x, y)
|
||||||
|
local p = x + ((y - 1) * ww)
|
||||||
|
return unicode.sub(fe, p, p)
|
||||||
|
end
|
||||||
|
}, ww, wh
|
||||||
|
end
|
||||||
|
function openDialog(pkg, aid, txt)
|
||||||
|
launchAppCore(pkg, aid, function (A) return dialogApp(txt, A.die) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Perhaps outsource this to a file???
|
||||||
|
openDialog("Welcome to KittenOS", "~welcome",
|
||||||
|
--2345678901234567890123456789012
|
||||||
|
"Alt-(arrow key): Move window. " ..
|
||||||
|
"Alt-Enter: Start 'launcher'. " ..
|
||||||
|
"Shift-C will generally stop apps" ..
|
||||||
|
" which don't care about text, or" ..
|
||||||
|
" don't want any text right now. " ..
|
||||||
|
"Tab: Switch window.")
|
||||||
|
|
||||||
|
-- main WM
|
||||||
|
local isAltDown = false
|
||||||
|
local function key(ka, kc, down)
|
||||||
|
local focus = appZ[#appZ]
|
||||||
|
if kc == 56 then isAltDown = down end
|
||||||
|
if isAltDown then
|
||||||
|
if kc == 200 then
|
||||||
|
if focus and down then ofsApp(focus, 0, -1) end return
|
||||||
|
end
|
||||||
|
if kc == 208 then
|
||||||
|
if focus and down then ofsApp(focus, 0, 1) end return
|
||||||
|
end
|
||||||
|
if kc == 203 then
|
||||||
|
if focus and down then ofsApp(focus, -1, 0) end return
|
||||||
|
end
|
||||||
|
if kc == 205 then
|
||||||
|
if focus and down then ofsApp(focus, 1, 0) end return
|
||||||
|
end
|
||||||
|
if kc == 46 then
|
||||||
|
if focus and down then killApp(focus) end return
|
||||||
|
end
|
||||||
|
if ka == 13 then
|
||||||
|
if down then launchApp("launcher") end return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if kc == 15 then
|
||||||
|
if focus and down then focusApp(appZ[1]) end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if focus then
|
||||||
|
handleEvNRD(focus, "key", ka, kc, down)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local maxTime = 480
|
||||||
|
local now = saneTime()
|
||||||
|
for k, v in pairs(apps) do
|
||||||
|
if v.nextUpdate then
|
||||||
|
local timeIn = v.nextUpdate - now
|
||||||
|
if timeIn <= 0 then
|
||||||
|
v.nextUpdate = nil
|
||||||
|
handleEvNRD(k, "update")
|
||||||
|
if v.nextUpdate then
|
||||||
|
timeIn = v.nextUpdate - now
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if timeIn > 0 then
|
||||||
|
maxTime = math.min(maxTime, timeIn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for k, v in pairs(needRedraw) do if v then
|
||||||
|
if apps[k] and not redrawWorldSoon then
|
||||||
|
redrawApp(k)
|
||||||
|
end
|
||||||
|
needRedraw[k] = nil
|
||||||
|
end end
|
||||||
|
if redrawWorldSoon then
|
||||||
|
redrawWorldSoon = false
|
||||||
|
redrawSection(1, 1, scrW, scrH)
|
||||||
|
end
|
||||||
|
local signal = {computer.pullSignal(maxTime)}
|
||||||
|
local t, p1, p2, p3, p4 = table.unpack(signal)
|
||||||
|
if t then
|
||||||
|
for k, v in pairs(apps) do
|
||||||
|
if v.hasAccess["root"] or v.hasAccess["s." .. t] then
|
||||||
|
handleEvNRD(v, "event", table.unpack(signal))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if t == "key_down" then
|
||||||
|
key(p2, p3, true)
|
||||||
|
end
|
||||||
|
if t == "key_up" then
|
||||||
|
key(p2, p3, false)
|
||||||
|
end
|
||||||
|
if t == "clipboard" then
|
||||||
|
local focus = appZ[#appZ]
|
||||||
|
if focus then
|
||||||
|
handleEvNRD(focus, "clipboard", p2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
16
lang/pirate/installer.lua
Normal file
16
lang/pirate/installer.lua
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
return {
|
||||||
|
["KittenOS Installer"] = "Kitt'n Ohess Install'a!",
|
||||||
|
["Applications to install:"] = "Sails to fit:",
|
||||||
|
["Install Application: "] = "Fit Sail: ",
|
||||||
|
["true"] = "Yar",
|
||||||
|
["false"] = "Nay",
|
||||||
|
["<Confirm>"] = "<Continu' with ye fittin'!>",
|
||||||
|
["Filesystem to target:"] = "Island to land cargo:",
|
||||||
|
["Are you sure you want to install to FS:"] = "Are 'ya sure you wanna land on:",
|
||||||
|
["These applications will be installed:"] = "These sails will be fitted:",
|
||||||
|
["<Yes>"] = "<Aye!>",
|
||||||
|
["<No, change settings>"] = "<Nay, I changed me mind>",
|
||||||
|
["Installing."] = "Sailin'!",
|
||||||
|
["Installation complete."] = "Arrived at island!",
|
||||||
|
["Press Shift-C to leave."] = "Push Shift-Cee to disembark!"
|
||||||
|
}
|
14
lang/pirate/textedit.lua
Normal file
14
lang/pirate/textedit.lua
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
return {
|
||||||
|
"Femto, a scribe for ye future!",
|
||||||
|
"The wheels are as follows:",
|
||||||
|
"Control and ye arrow keys",
|
||||||
|
" change the porthole size...",
|
||||||
|
"Control and C copies a line,",
|
||||||
|
"Control and V pastes a line.",
|
||||||
|
"Control and Q wipes the parchment,",
|
||||||
|
"Control and W sinks the ship.",
|
||||||
|
"Control and A takes a parchment from ye logs,",
|
||||||
|
"Control and S puts the parchment back.",
|
||||||
|
"Now supports wide scrawlin's.",
|
||||||
|
"For example, 'Yay!'.",
|
||||||
|
}
|
80
policykit.lua
Normal file
80
policykit.lua
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
local gpu, aid, requests = ...
|
||||||
|
if #requests == 1 then
|
||||||
|
local permits = {
|
||||||
|
-- This is a list of specific permits for specific known apps.
|
||||||
|
-- Do not put an app here lightly - find another way.
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if aid == "launcher" then return true, true end
|
||||||
|
local restrictions = {
|
||||||
|
-- | |
|
||||||
|
["root"] = "Completely, absolutely control the device.",
|
||||||
|
["randr"] = "Control displays and GPUs.", -- not precisely true but close enough
|
||||||
|
["stat"] = "Read energy, sage, memory usage and time.",
|
||||||
|
["setlang"] = "Change the system language.",
|
||||||
|
["kill"] = "Kill other processes.",
|
||||||
|
["c.filesystem"] = "Access filesystems directly (virus risk!).",
|
||||||
|
["c.drive"] = "Access unmanaged drives directly.",
|
||||||
|
["c.modem"] = "Send to and receive from the network.",
|
||||||
|
["c.tunnel"] = "Use Linked Cards, receive from all modems.",
|
||||||
|
["s.modem_message"] = "Listen to all network messages.",
|
||||||
|
["c.internet"] = "Connect to the real-life Internet.",
|
||||||
|
["c.robot"] = "Control the 'robot' abilities.",
|
||||||
|
["c.drone"] = "Control the 'drone' abilities.",
|
||||||
|
["c.redstone"] = "Control Redstone Cards and I/O Blocks.",
|
||||||
|
["c.screen"] = "Screw up screens directly. <USE RANDR!!!>",
|
||||||
|
["c.gpu"] = "Screw up GPUs directly. <USE RANDR!!!>",
|
||||||
|
["c.eeprom"] = "Modify EEPROMs. Extremely dangerous.",
|
||||||
|
["c.debug"] = "Modify the game world. Beyond dangerous.",
|
||||||
|
["c.printer3d"] = "Use connected 3D Printers.",
|
||||||
|
-- disk_drive seems safe enough, same with keyboard
|
||||||
|
["s.key_down"] = "Potentially act as a keylogger. (down)",
|
||||||
|
["s.key_up"] = "Potentially act as a keylogger. (up)",
|
||||||
|
-- COMPUTRONICS
|
||||||
|
["c.chat_box"] = "Listen and talk to players.",
|
||||||
|
["s.chat_message"] = "Listen in on players talking."
|
||||||
|
}
|
||||||
|
local centre = ""
|
||||||
|
local sW, sH = gpu.getResolution()
|
||||||
|
for i = 1, math.floor((sW / 2) - 7) do
|
||||||
|
centre = centre .. " "
|
||||||
|
end
|
||||||
|
local text = {
|
||||||
|
centre .. "Security Alert",
|
||||||
|
"",
|
||||||
|
" '" .. aid .. "' would like to:",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
local automaticOK = true
|
||||||
|
for _, v in ipairs(requests) do
|
||||||
|
if v ~= nil then
|
||||||
|
if type(v) == "string" then
|
||||||
|
if restrictions[v] then
|
||||||
|
automaticOK = false
|
||||||
|
table.insert(text, " + " .. restrictions[v])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Nothing restricted.
|
||||||
|
if automaticOK then return true, true end
|
||||||
|
table.insert(text, "")
|
||||||
|
table.insert(text, " If you agree, press 'y', else 'n'.")
|
||||||
|
gpu.setForeground(0xFFFFFF)
|
||||||
|
gpu.setBackground(0x000000)
|
||||||
|
gpu.fill(1, 1, sW, sH, " ")
|
||||||
|
for k, v in ipairs(text) do
|
||||||
|
gpu.set(1, k, v)
|
||||||
|
end
|
||||||
|
text = nil
|
||||||
|
while true do
|
||||||
|
local t, p1, p2, p3, p4 = computer.pullSignal()
|
||||||
|
if t == "key_down" then
|
||||||
|
if p2 == ("y"):byte() then
|
||||||
|
return true, false
|
||||||
|
end
|
||||||
|
if p2 == ("n"):byte() then
|
||||||
|
return false, false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
127
readme.md
Normal file
127
readme.md
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# KittenOS: A graphical OpenComputers OS that runs light.
|
||||||
|
|
||||||
|
## Why?
|
||||||
|
Because OpenOS needs *two* Tier 1 memory chips to run a text editor,
|
||||||
|
in a basic console environment that reminds me of DOS more than a Unix,
|
||||||
|
despite the inspirations.
|
||||||
|
This OS only needs one Tier 1 memory chip - that's 192KiB...
|
||||||
|
|
||||||
|
## Couldn't save a file from a text editor on a 192KiB system.
|
||||||
|
|
||||||
|
Switch to Lua 5.3 - Lua 5.2 has a nasty habit of leaking a big chunk of memory.
|
||||||
|
|
||||||
|
Given this hasn't happened with Lua 5.3, I can only guess this is an issue with 5.2.
|
||||||
|
|
||||||
|
(Tested on OpenComputers 1.6.0.7-rc.1 on Minecraft 1.7.10.
|
||||||
|
Judging by the "collectgarbage" call in machine.lua,
|
||||||
|
they may have the same problem???)
|
||||||
|
|
||||||
|
## Why the complicated permissions and sandboxing?
|
||||||
|
|
||||||
|
Because I wanted to try out some security ideas, like not letting
|
||||||
|
applications access files unless the user explicitly authorizes access
|
||||||
|
to that file. Aka "ransomware prevention".
|
||||||
|
|
||||||
|
Yes, I know it uses memory, but I actually had *too much* memory.
|
||||||
|
|
||||||
|
## Why is the kernel so big and yet in one file?
|
||||||
|
|
||||||
|
File overhead is one of the things I suspect caused OpenOS's bloat.
|
||||||
|
(It helps that writing the kernel like this reduces boot time -
|
||||||
|
only one file has to be loaded for the system to boot.
|
||||||
|
More files are required to use some functions which are used relatively
|
||||||
|
rarely - like the file manager - as a tradeoff on memory.)
|
||||||
|
|
||||||
|
## Why does get_ch exist? Why not use a window buffer?
|
||||||
|
|
||||||
|
Because memory.
|
||||||
|
This way, 80x25 and 160x50 should use around about the same amount of memory.
|
||||||
|
It also fit nicely with the way only sections of the screen are drawn.
|
||||||
|
|
||||||
|
## Why do the window titlebars look inverted on Tier 3 hardware, but not Tier 2?
|
||||||
|
|
||||||
|
There was a bit of value trickery required to get the same general effect on all 3 tiers.
|
||||||
|
|
||||||
|
## Why do I have to return true in so many functions if I want a redraw?
|
||||||
|
|
||||||
|
Because if you redraw all the time, you use more energy.
|
||||||
|
|
||||||
|
(Though energy's really a secondary priority to memory, costs resources
|
||||||
|
all the same.)
|
||||||
|
|
||||||
|
## Why aren't (partial redraws/GPU copys/GPU copys for moving windows) supported?
|
||||||
|
|
||||||
|
They didn't seem to be a requirement once I optimized the GPU call count.
|
||||||
|
|
||||||
|
If the system had still been running slow after that, I'd have done it.
|
||||||
|
|
||||||
|
## Why does the text editor use up so much (energy/time)?
|
||||||
|
|
||||||
|
The text editor is probably one of the bigger windows in KittenOS.
|
||||||
|
Try making it smaller with the controls it gives.
|
||||||
|
|
||||||
|
## What's "lineclip"?
|
||||||
|
|
||||||
|
A poor excuse for a clipboard.
|
||||||
|
You don't need to interact with it,
|
||||||
|
though *Shift-C* on it kills it (thus clearing the clipboard).
|
||||||
|
|
||||||
|
## Why is *Shift-C* used everywhere to safely close things?
|
||||||
|
|
||||||
|
Because Enter is used as an action button, Escape's right out,
|
||||||
|
Delete's probably missing on keyboards by now,
|
||||||
|
*Ctrl-C* is also copy, and anything with Alt in it
|
||||||
|
is supposed to be a Window Manager trap.
|
||||||
|
|
||||||
|
## Why is there no "kill it" button?
|
||||||
|
|
||||||
|
I'm sticking it here so that people don't make using this a habit -
|
||||||
|
so that application developers can do something before app death.
|
||||||
|
|
||||||
|
If you must ask, *Alt-C* will kill the currently focused app.
|
||||||
|
|
||||||
|
## An app infinite-looped / ate memory and the system died.
|
||||||
|
|
||||||
|
Yep. There isn't much of a way to protect against memory-eaters.
|
||||||
|
On the other hand, you do know not to start that app again, and it
|
||||||
|
didn't get a chance to do harm.
|
||||||
|
|
||||||
|
## Isn't building a window manager into the OS kind of, uh, monolithic?
|
||||||
|
|
||||||
|
Given the inaccuracies relative to real computers anyway,
|
||||||
|
I think of it as that OpenComputers itself is the kernel,
|
||||||
|
but it can only handle a single task, written in Lua,
|
||||||
|
so people build on top of it to make an interface.
|
||||||
|
|
||||||
|
(And given the memory limitations, having a cooperatively multitasking
|
||||||
|
microkernel which then gives all it's capabilities to the window
|
||||||
|
manager would succeed only in complicating things and then using all
|
||||||
|
memory, in that precise order.
|
||||||
|
This way accomplishes the same thing, and it's simpler.)
|
||||||
|
|
||||||
|
## How's multilingual support?
|
||||||
|
|
||||||
|
Multilingual support exists, but the languages are Pirate and English.
|
||||||
|
Most applications are bare-bones enough that they don't have any strings
|
||||||
|
that need to be translated - I consider this a plus.
|
||||||
|
|
||||||
|
The infrastructure for a system language exists, but is not really in use.
|
||||||
|
(This includes the installer copying language files for the selected language,
|
||||||
|
but the only thing with a language selector at the moment is the installer.
|
||||||
|
Language selection is performed by editing the "language" file
|
||||||
|
at the drive root and rebooting, or switching language during install.)
|
||||||
|
|
||||||
|
The infrastructure is quite minimal so as not to bloat the system too badly -
|
||||||
|
it's up to the applications to decide how to implement multi-language support,
|
||||||
|
but the system can load files from lang/<language>/<pkg>.lua to aid in this.
|
||||||
|
(Why loading files? To avoid having every single language loaded at once.
|
||||||
|
Why loading Lua files? To avoid making this feature bloat the system.)
|
||||||
|
|
||||||
|
As for the issue of wide characters (Chinese/Japanese/Korean support):
|
||||||
|
|
||||||
|
Wide characters are supported in all supplied apps that handle text,
|
||||||
|
including the text editor -
|
||||||
|
the helper API function unicode.safeTextFormat should make these things easier.
|
||||||
|
|
||||||
|
(safeTextFormat is allowed to rearrange the text however it needs to for
|
||||||
|
display - this leaves the possibility of RTL layout open.)
|
234
tfilemgr.lua
Normal file
234
tfilemgr.lua
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
-- The File Manager (manager of files).
|
||||||
|
-- Args:
|
||||||
|
local filetype, openmode, gpu = ...
|
||||||
|
local fileManager = nil
|
||||||
|
function fileManager(filetype, openmode)
|
||||||
|
-- Like policykit, this is a trusted gateway.
|
||||||
|
-- Note that The File Manager just returns a path {fs, path}.
|
||||||
|
-- The File Wrapper is given that path, and the open mode.
|
||||||
|
local title = nil
|
||||||
|
-- Valid open modes are:
|
||||||
|
-- nil: The File Wrapper should not be invoked -
|
||||||
|
-- likely a "file manager launcher" application.
|
||||||
|
if openmode == nil then title = "File Manager" end
|
||||||
|
-- "r": Open the file for reading. Binary mode is assumed.
|
||||||
|
if openmode == "r" then
|
||||||
|
title = "Read " .. filetype
|
||||||
|
end
|
||||||
|
if openmode == "w" then
|
||||||
|
title = "Write " .. filetype
|
||||||
|
end
|
||||||
|
-- "w": Open the file for truncate-writing, again binary assumed.
|
||||||
|
if not title then error("Bad openmode") end
|
||||||
|
|
||||||
|
local scrW, scrH = gpu.getResolution()
|
||||||
|
gpu.setBackground(0)
|
||||||
|
gpu.setForeground(0xFFFFFF)
|
||||||
|
local function cls()
|
||||||
|
gpu.fill(1, 1, scrW, scrH, " ")
|
||||||
|
end
|
||||||
|
local function menuKey(cursor, el, text, ka, kc, allowEntry)
|
||||||
|
if ka == 13 then
|
||||||
|
-- entry denied, so we hit here.
|
||||||
|
return cursor, text
|
||||||
|
end
|
||||||
|
if kc == 200 then
|
||||||
|
cursor = cursor - 1
|
||||||
|
if cursor < 1 then cursor = el end
|
||||||
|
return cursor, text, false, true
|
||||||
|
end
|
||||||
|
if kc == 208 then
|
||||||
|
cursor = cursor + 1
|
||||||
|
if cursor > el then cursor = 1 end
|
||||||
|
return cursor, text, false, true
|
||||||
|
end
|
||||||
|
if allowEntry then
|
||||||
|
if ka == 8 then
|
||||||
|
return cursor, unicode.sub(text, 1, unicode.len(text) - 1), true
|
||||||
|
end
|
||||||
|
if (ka ~= 0) and (ka ~= ("/"):byte()) and (ka ~= ("\\"):byte()) then
|
||||||
|
text = text .. unicode.char(ka)
|
||||||
|
return cursor, text, true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return cursor, text
|
||||||
|
end
|
||||||
|
local function menu(title, entries, allowEntry)
|
||||||
|
cls()
|
||||||
|
gpu.fill(1, 1, scrW, 1, "-")
|
||||||
|
gpu.set(1, 1, title)
|
||||||
|
local cursor = 1
|
||||||
|
local escrH = scrH
|
||||||
|
local entryText = ""
|
||||||
|
local cursorBlinky = false
|
||||||
|
if allowEntry then escrH = scrH - 1 end
|
||||||
|
while true do
|
||||||
|
for y = 2, escrH do
|
||||||
|
local o = cursor + (y - 8)
|
||||||
|
local s = tostring(entries[o])
|
||||||
|
if not entries[o] then s = "" end
|
||||||
|
if o == cursor then s = ">" .. s else s = " " .. s end
|
||||||
|
gpu.fill(1, y, scrW, 1, " ")
|
||||||
|
gpu.set(1, y, s)
|
||||||
|
end
|
||||||
|
cursorBlinky = not cursorBlinky
|
||||||
|
if allowEntry then
|
||||||
|
gpu.fill(1, scrH, scrW, 1, " ")
|
||||||
|
if cursorBlinky then
|
||||||
|
gpu.set(1, scrH, ":" .. entryText)
|
||||||
|
else
|
||||||
|
gpu.set(1, scrH, ":" .. entryText .. "_")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local t, p1, p2, p3, p4 = computer.pullSignal(1)
|
||||||
|
if t == "key_down" then
|
||||||
|
if p2 == 13 then
|
||||||
|
if allowEntry then
|
||||||
|
if entryText ~= "" then
|
||||||
|
return entryText
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return entries[cursor]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cursor, entryText, search, lookup = menuKey(cursor, #entries, entryText, p2, p3, allowEntry)
|
||||||
|
if search then
|
||||||
|
for k, v in ipairs(entries) do
|
||||||
|
if v:sub(1, v:len()) == entryText then cursor = k end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if lookup then
|
||||||
|
entryText = entries[cursor]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local currentDir = nil
|
||||||
|
local currentDrive = nil
|
||||||
|
local function listDir(dv, dr)
|
||||||
|
if dv == nil then
|
||||||
|
local l = {}
|
||||||
|
local t = {}
|
||||||
|
for c in component.list("filesystem") do
|
||||||
|
l[c] = {c, "/"}
|
||||||
|
table.insert(t, c)
|
||||||
|
end
|
||||||
|
return l, t, "Filesystems"
|
||||||
|
end
|
||||||
|
local names = component.invoke(dv, "list", dr)
|
||||||
|
local l = {}
|
||||||
|
for k, v in ipairs(names) do
|
||||||
|
if component.invoke(dv, "isDirectory", dr .. v) then
|
||||||
|
l[v] = {dv, dr .. v}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return l, names, dv .. ":" .. dr
|
||||||
|
end
|
||||||
|
local function isDir(dv, dr)
|
||||||
|
if dv == nil then return true end
|
||||||
|
return component.invoke(dv, "isDirectory", dr)
|
||||||
|
end
|
||||||
|
|
||||||
|
local tagMkdir = "// Create Directory //"
|
||||||
|
local tagCancel = "// Cancel //"
|
||||||
|
local tagOpen = "// Open //"
|
||||||
|
local tagDelete = "// Delete //"
|
||||||
|
local tagRename = "// Rename //"
|
||||||
|
local tagCopy = "// Copy //"
|
||||||
|
local tagBack = ".."
|
||||||
|
|
||||||
|
local function textEntry(title)
|
||||||
|
local txt = menu(title, {tagCancel}, true)
|
||||||
|
if txt ~= tagCancel then return txt end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local function report(title)
|
||||||
|
menu(title, {"OK"}, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
local history = {}
|
||||||
|
local function navigate(ndr, ndd)
|
||||||
|
table.insert(history, {currentDrive, currentDir})
|
||||||
|
currentDrive, currentDir = ndr, ndd
|
||||||
|
end
|
||||||
|
while true do
|
||||||
|
local map, sl, name = listDir(currentDrive, currentDir)
|
||||||
|
if #history ~= 0 then
|
||||||
|
table.insert(sl, tagBack)
|
||||||
|
end
|
||||||
|
table.insert(sl, tagCancel)
|
||||||
|
if currentDrive then
|
||||||
|
table.insert(sl, tagMkdir)
|
||||||
|
end
|
||||||
|
local str = menu(title .. " " .. name, sl, (openmode == "w") and currentDrive)
|
||||||
|
if str == tagBack then
|
||||||
|
local r = table.remove(history, #history)
|
||||||
|
currentDrive, currentDir = table.unpack(r)
|
||||||
|
else
|
||||||
|
if str == tagCancel then return nil end
|
||||||
|
if str == tagMkdir then
|
||||||
|
local nam = textEntry("Create Directory...")
|
||||||
|
if nam then
|
||||||
|
component.invoke(currentDrive, "makeDirectory", currentDir .. nam)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if map[str] then
|
||||||
|
if map[str][1] and currentDrive then
|
||||||
|
local act = menu(name .. ":" .. str, {tagOpen, tagRename, tagDelete, tagCancel})
|
||||||
|
if act == tagOpen then
|
||||||
|
navigate(table.unpack(map[str]))
|
||||||
|
end
|
||||||
|
if act == tagRename then
|
||||||
|
local s = textEntry("Rename " .. str)
|
||||||
|
if s then
|
||||||
|
component.invoke(map[str][1], "rename", map[str][2], currentDir .. s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if act == tagDelete then
|
||||||
|
component.invoke(map[str][1], "remove", map[str][2])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
navigate(table.unpack(map[str]))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if openmode == "w" then return {currentDrive, currentDir .. str} end
|
||||||
|
local r = currentDir .. str
|
||||||
|
local subTag = "Size: " .. math.ceil(component.invoke(currentDrive, "size", r) / 1024) .. "KiB"
|
||||||
|
if openmode == "r" then subTag = tagOpen end
|
||||||
|
local act = menu(name .. ":" .. str, {subTag, tagRename, tagCopy, tagDelete, tagCancel})
|
||||||
|
if act == tagOpen then return {currentDrive, currentDir .. str} end
|
||||||
|
if act == tagRename then
|
||||||
|
local s = textEntry("Rename " .. str)
|
||||||
|
component.invoke(currentDrive, "rename", currentDir .. str, currentDir .. s)
|
||||||
|
end
|
||||||
|
if act == tagCopy then
|
||||||
|
local f2 = fileManager("Copy " .. str, "w")
|
||||||
|
if f2 then
|
||||||
|
local h = component.invoke(currentDrive, "open", currentDir .. str, "rb")
|
||||||
|
if not h then
|
||||||
|
report("Couldn't open file!")
|
||||||
|
else
|
||||||
|
local h2 = component.invoke(f2[1], "open", f2[2], "wb")
|
||||||
|
if not h2 then
|
||||||
|
report("Couldn't open dest. file!")
|
||||||
|
else
|
||||||
|
local chk = component.invoke(currentDrive, "read", h, 128)
|
||||||
|
while chk do
|
||||||
|
component.invoke(f2[1], "write", h2, chk)
|
||||||
|
chk = component.invoke(currentDrive, "read", h, 128)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
component.invoke(currentDrive, "close", h)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if act == tagDelete then
|
||||||
|
component.invoke(currentDrive, "remove", currentDir .. str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return fileManager(filetype, openmode)
|
Loading…
Reference in New Issue
Block a user