Initial commit

Currently eeprog pirate-speak language is unsupported, but... oh well.
This commit is contained in:
gamemanj 2016-10-23 13:22:12 +01:00
commit 2ae3f9a93a
20 changed files with 2538 additions and 0 deletions

192
API Documentation Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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!",
""
}
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
View 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
View 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
View 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
View 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, ''.",
}

1
license Normal file
View File

@ -0,0 +1 @@
I, 20kdc, release this work into the public domain.

80
policykit.lua Normal file
View 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
View 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
View 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)