include("arch")
include("hal")
-@Term("Version")
+@Term("Kernel Version")
@ReadOnly
def lunaix_ver():
"""
seq_num = int(time.time() / 3600)
default("%s dev-2024_%d"%(v(arch), seq_num))
-@Collection
+@Collection("Kernel Debug and Testing")
def debug_and_testing():
"""
General settings for kernel debugging feature
include("x86/LConfig")
-@Collection
+@Collection("Platform")
def architecture_support():
"""
Config ISA related features
"""
- @Term
+ @Term("Architecture")
def arch():
- """ Config ISA support """
- type(["i386", "x86_64", "aarch64", "rv64"])
- default("i386")
+ """
+ Config ISA support
+ """
+ # type(["i386", "x86_64", "aarch64", "rv64"])
+ type(["i386", "x86_64"])
+ default("x86_64")
env_val = env("ARCH")
if env_val:
set_value(env_val)
- @Term
+ @Term("Base operand size")
@ReadOnly
def arch_bits():
+ """
+ Defines the base size of a general register of the
+ current selected ISA.
+
+ This the 'bits' part when we are talking about a CPU
+ """
+
type(["64", "32"])
match v(arch):
case "i386":
add_to_collection(architecture_support)
- @Term
+ @Term("Use SSE2/3/4 extension")
def x86_enable_sse_feature():
"""
Config whether to allow using SSE feature for certain
default(False)
- @Term
+ @Term("Bootloader Model")
def x86_bl():
"""
Select the bootloader interface
Supported interface
- mb: multiboot compliant
- mb2: multiboot2 compliant
+ mb: multiboot compliance
+ mb2: multiboot2 compliance
none: do not use any interface
"""
include("bus")
include("ahci")
-@Collection
+@Collection("Devices & Peripherials")
def hal():
""" Lunaix hardware asbtraction layer """
-@Collection
+@Collection("AHCI")
def sata_ahci():
add_to_collection(hal)
- @Term
+ @Term("Enable AHCI support")
def ahci_enable():
""" Enable the support of SATA AHCI.
Must require PCI at current stage """
-@Collection
+@Collection("Buses & Interconnects")
def bus_if():
""" System/platform bus interface """
add_to_collection(hal)
- @Term
+ @Term("PCI")
def pci_enable():
""" Peripheral Component Interconnect (PCI) Bus """
type(bool)
default(True)
- @Term
+ @Term("PCI Express")
def pcie_ext():
""" Enable support of PCI-Express extension """
type(bool)
return v(pci_enable)
- @Term
+ @Term("Use PMIO for PCI")
def pci_pmio():
""" Use port-mapped I/O interface for controlling PCI """
type(bool)
include("uart")
-@Collection
+@Collection("Character Devices")
def char_device():
""" Controlling support of character devices """
add_to_collection(hal)
- @Term
+ @Term("VGA 80x25 text-mode console")
def vga_console():
""" Enable VGA console device (text mode only) """
type(bool)
default(True)
- @Term
+ @Term("VGA character game device")
def chargame_console():
- """ Enable VGA Charactor Game console device (text mode only) """
+ """
+ Enable VGA Charactor Game console device (text mode only)
+
+ You normally don't need to include this, unless you want some user space fun ;)
+ """
type(bool)
default(False)
\ No newline at end of file
#include <lunaix/mm/valloc.h>
#include <lunaix/owloysius.h>
#include <lunaix/syslog.h>
+#include <sys/muldiv64.h>
LOG_MODULE("blkbuf")
static inline u64_t
__tolba(struct blkbuf_cache* cache, unsigned int blk_id)
{
- return ((u64_t)cache->blksize * (u64_t)blk_id) / cache->blkdev->blk_size;
+ return udiv64(((u64_t)cache->blksize * (u64_t)blk_id),
+ cache->blkdev->blk_size);
}
static void
-@Collection
+@Collection("File Systems")
def file_system():
""" Config feature related to file system supports """
add_to_collection(kernel_feature)
- @Term
+ @Term("ext2 support")
def fs_ext2():
""" Enable ext2 file system support """
type(bool)
default(True)
- @Term
+ @Term("iso9660 support")
def fs_iso9660():
""" Enable iso9660 file system support """
-@Collection
+@Collection("Memory Management")
def memory_subsystem():
""" Config the memory subsystem """
- @Collection
+ @Collection("Physical Memory")
def physical_mm():
""" Physical memory manager """
- @Term
+ @Term("Allocation policy")
def pmalloc_method():
""" Allocation policy for phiscal memory """
type(["simple", "buddy", "ncontig"])
default("simple")
- @Group
+ @Group("Simple")
def pmalloc_simple_po_thresholds():
- @Term
+ @Term("Maximum cached order-0 free pages")
def pmalloc_simple_max_po0():
""" free list capacity for order-0 pages """
type(int)
default(4096)
- @Term
+ @Term("Maximum cached order-1 free pages")
def pmalloc_simple_max_po1():
""" free list capacity for order-1 pages """
type(int)
default(2048)
- @Term
+ @Term("Maximum cached order-2 free pages")
def pmalloc_simple_max_po2():
""" free list capacity for order-2 pages """
type(int)
default(2048)
- @Term
+ @Term("Maximum cached order-3 free pages")
def pmalloc_simple_max_po3():
""" free list capacity for order-3 pages """
type(int)
default(2048)
- @Term
+ @Term("Maximum cached order-4 free pages")
def pmalloc_simple_max_po4():
""" free list capacity for order-4 pages """
type(int)
default(512)
- @Term
+ @Term("Maximum cached order-5 free pages")
def pmalloc_simple_max_po5():
""" free list capacity for order-5 pages """
type(int)
default(512)
- @Term
+ @Term("Maximum cached order-6 free pages")
def pmalloc_simple_max_po6():
""" free list capacity for order-6 pages """
type(int)
default(128)
- @Term
+ @Term("Maximum cached order-7 free pages")
def pmalloc_simple_max_po7():
""" free list capacity for order-7 pages """
type(int)
default(128)
- @Term
+ @Term("Maximum cached order-8 free pages")
def pmalloc_simple_max_po8():
""" free list capacity for order-8 pages """
type(int)
default(64)
- @Term
+ @Term("Maximum cached order-9 free pages")
def pmalloc_simple_max_po9():
""" free list capacity for order-9 pages """
all_lconfigs = $(shell find $(CURDIR) -name "LConfig")
+LCONFIG_FLAGS += --config $(lbuild_opts) --config-save $(lconfig_save)
+
export
$(lconfig_save): $(all_lconfigs)
@echo restarting configuration...
- @$(LBUILD) --config $(lbuild_opts) --config-save $(lconfig_save) --force\
- -o $(lbuild_dir)/
+ @$(LBUILD) $(LCONFIG_FLAGS) --force -o $(lbuild_dir)/
export
$(lbuild_config_h): $(lconfig_save)
- @$(LBUILD) --config $(lbuild_opts) --config-save $(lconfig_save) -o $(@D)
+ @$(LBUILD) $(LCONFIG_FLAGS) -o $(@D)
export
$(lbuild_mkinc): $(lbuild_config_h)
.PHONY: config
export
config: $(all_lconfigs)
- @$(LBUILD) --config $(lbuild_opts) --config-save $(lconfig_save) --force\
+ @$(LBUILD) $(LCONFIG_FLAGS) --force\
-o $(lbuild_dir)/
--- /dev/null
+import curses
+import integration.libtui as tui
+from integration.libtui import ColorScope, TuiColor, Alignment, EventType
+
+def create_buttons(main_ctx, btn_defs, sizes = "*,*"):
+ size_defs = ",".join(['*'] * len(btn_defs))
+
+ layout = tui.FlexLinearLayout(main_ctx, "buttons", size_defs)
+ layout.orientation(tui.FlexLinearLayout.LANDSCAPE)
+ layout.set_size(*(sizes.split(',')[:2]))
+ layout.set_padding(1, 1, 1, 1)
+ layout.set_alignment(Alignment.CENTER | Alignment.BOT)
+
+ for i, btn_def in enumerate(btn_defs):
+ but1 = tui.TuiButton(main_ctx, "b1")
+ but1.set_text(btn_def["text"])
+ but1.set_click_callback(btn_def["onclick"])
+ but1.set_alignment(Alignment.CENTER)
+
+ layout.set_cell(i, but1)
+
+ return layout
+
+def create_title(ctx, title):
+ _t = tui.TuiLabel(ctx, "label")
+ _t.set_text(title)
+ _t.set_local_pos(1, 0)
+ _t.set_alignment(Alignment.TOP | Alignment.CENTER)
+ _t.hightlight(True)
+ _t.pad_around(True)
+ return _t
+
+class ListView(tui.TuiObject):
+ def __init__(self, context, id):
+ super().__init__(context, id)
+
+ self.__create_layout()
+
+ self.__sel_changed = None
+ self.__sel = None
+
+ def __create_layout(self):
+ hint_moveup = tui.TuiLabel(self._context, "movup")
+ hint_moveup.override_color(ColorScope.HINT)
+ hint_moveup.set_text("^^^ - MORE")
+ hint_moveup.set_visbility(False)
+ hint_moveup.set_alignment(Alignment.TOP)
+
+ hint_movedown = tui.TuiLabel(self._context, "movdown")
+ hint_movedown.override_color(ColorScope.HINT)
+ hint_movedown.set_text("vvv - MORE")
+ hint_movedown.set_visbility(False)
+ hint_movedown.set_alignment(Alignment.BOT)
+
+ list_ = tui.SimpleList(self._context, "list")
+ list_.set_size("*", "*")
+ list_.set_alignment(Alignment.CENTER | Alignment.TOP)
+
+ list_.set_onselected_cb(self._on_selected)
+ list_.set_onselection_change_cb(self._on_sel_changed)
+
+ scroll = tui.TuiScrollable(self._context, "scroll")
+ scroll.set_size("*", "*")
+ scroll.set_alignment(Alignment.CENTER)
+ scroll.set_content(list_)
+
+ layout = tui.FlexLinearLayout(
+ self._context, f"main_layout", "2,*,2")
+ layout.set_size("*", "*")
+ layout.set_alignment(Alignment.CENTER)
+ layout.orientation(tui.FlexLinearLayout.PORTRAIT)
+ layout.set_parent(self)
+
+ layout.set_cell(0, hint_moveup)
+ layout.set_cell(1, scroll)
+ layout.set_cell(2, hint_movedown)
+
+ self.__hint_up = hint_moveup
+ self.__hint_down = hint_movedown
+ self.__list = list_
+ self.__scroll = scroll
+ self.__layout = layout
+
+ def add_item(self, item):
+ self.__list.add_item(item)
+
+ def clear(self):
+ self.__list.clear()
+
+ def on_draw(self):
+ super().on_draw()
+
+ more_above = not self.__scroll.reached_top()
+ more_below = not self.__scroll.reached_last()
+ self.__hint_up.set_visbility(more_above)
+ self.__hint_down.set_visbility(more_below)
+
+ self.__layout.on_draw()
+
+ def on_layout(self):
+ super().on_layout()
+ self.__layout.on_layout()
+
+ def _on_sel_changed(self, listv, prev, new):
+ h = self.__scroll._size.y()
+ self.__scroll.set_scrollY((new + 1) // h * h)
+
+ if self.__sel_changed:
+ self.__sel_changed(listv, prev, new)
+
+ def _on_selected(self, listv, index, item):
+ if self.__sel:
+ self.__sel(listv, index, item)
+
+ def set_onselected_cb(self, cb):
+ self.__sel = cb
+
+ def set_onselect_changed_cb(self, cb):
+ self.__sel_changed = cb
+
+class Dialogue(tui.TuiContext):
+ Pending = 0
+ Yes = 1
+ No = 2
+ Abort = 3
+ def __init__(self, session, title = "", content = "", input=False,
+ ok_btn = "OK", no_btn = "No", abort_btn = None):
+ super().__init__(session)
+
+ self.__btns = [
+ { "text": ok_btn, "onclick": lambda x: self._ok_onclick() }
+ ]
+
+ if no_btn:
+ self.__btns.append({
+ "text": no_btn,
+ "onclick": lambda x: self._no_onclick()
+ })
+ if abort_btn:
+ self.__btns.append({
+ "text": abort_btn,
+ "onclick": lambda x: self._abort_onclick()
+ })
+
+ self.__title_txt = title
+ self.__status = Dialogue.Pending
+ self.__content = content
+ self.__input_dialog = input
+ self._textbox = None
+
+ self.set_size("70", "0.5*")
+ self.set_alignment(Alignment.CENTER)
+
+ def set_content(self, content):
+ self.__content = content
+
+ def set_input_dialogue(self, yes):
+ self.__input_dialog = yes
+
+ def prepare(self):
+ self.__create_layout(self.__title_txt)
+
+ def _handle_key_event(self, key):
+ if key == 27:
+ self.__close()
+ return
+ super()._handle_key_event(key)
+
+
+ def _ok_onclick(self):
+ self.__status = Dialogue.Yes
+ self.__close()
+
+ def _no_onclick(self):
+ self.__status = Dialogue.No
+ self.__close()
+
+ def _abort_onclick(self):
+ self.__status = Dialogue.Abort
+ self.__close()
+
+ def __create_layout(self, title):
+ panel = tui.TuiPanel(self, "panel")
+ layout = tui.FlexLinearLayout(self, "layout", "*,3")
+ btn_grp = create_buttons(self, self.__btns)
+ t = create_title(self, title)
+ content = self.__create_content()
+
+ self.__title = t
+ self.__layout = layout
+ self.__panel = panel
+
+ panel._dyn_size.set(self._dyn_size)
+ panel._local_pos.set(self._local_pos)
+ panel.set_alignment(self._align)
+ panel.drop_shadow(1, 2)
+ panel.border(True)
+
+ layout.orientation(tui.FlexLinearLayout.PORTRAIT)
+ layout.set_size("*", "*")
+ layout.set_padding(4, 1, 1, 1)
+
+ t.set_alignment(Alignment.CENTER | Alignment.TOP)
+
+ layout.set_cell(0, content)
+ layout.set_cell(1, btn_grp)
+
+ panel.add(t)
+ panel.add(layout)
+
+ self.set_root(panel)
+
+ def __create_content(self):
+ text = None
+ if isinstance(self.__content, str):
+ text = tui.TuiTextBlock(self, "tb")
+ text.set_size("0.6*", "0.5*")
+ text.set_alignment(Alignment.CENTER)
+ text.set_text(self.__content)
+ elif self.__content is not None:
+ return self.__content
+
+ if not self.__input_dialog:
+ self.set_size(h = "20")
+ return text
+
+ tb = tui.TuiTextBox(self, "input")
+ tb.set_size("0.5*", "3")
+ tb.set_alignment(Alignment.CENTER)
+
+ if text:
+ layout = tui.FlexLinearLayout(self, "layout", "*,5")
+ layout.orientation(tui.FlexLinearLayout.PORTRAIT)
+ layout.set_size("*", "*")
+ layout.set_cell(0, text)
+ layout.set_cell(1, tb)
+ else:
+ layout = tb
+ self.set_size(h = "10")
+
+ self.set_curser_mode(1)
+
+ self._textbox = tb
+
+ return layout
+
+ def __close(self):
+ self.session().pop_context()
+
+ def status(self):
+ return self.__status
+
+ def show(self, title=None):
+ if title:
+ self.__title.set_text(title)
+ self.session().push_context(self)
+
+
+def show_dialog(session, title, text):
+ dia = Dialogue(session, title=title, content=text, no_btn=None)
+ dia.show()
\ No newline at end of file
--- /dev/null
+#
+# libtui - TUI framework using ncurses
+# (c) 2024 Lunaixsky
+#
+# I sware, this is the last time I ever touch
+# any sort of the GUI messes.
+#
+
+import curses
+import re
+import curses.panel as cpanel
+import curses.textpad as textpad
+import textwrap
+
+def __invoke_fn(obj, fn, *args):
+ try:
+ fn(*args)
+ except:
+ _id = obj._id if obj else "<root>"
+ raise Exception(_id, str(fn), args)
+
+def resize_safe(obj, co, y, x):
+ __invoke_fn(obj, co.resize, y, x)
+
+def move_safe(obj, co, y, x):
+ __invoke_fn(obj, co.move, y, x)
+
+def addstr_safe(obj, co, y, x, str, *args):
+ __invoke_fn(obj, co.addstr, y, x, str, *args)
+
+class _TuiColor:
+ def __init__(self, v) -> None:
+ self.__v = v
+ def __int__(self):
+ return self.__v
+ def bright(self):
+ return self.__v + 8
+
+class TuiColor:
+ black = _TuiColor(curses.COLOR_BLACK)
+ red = _TuiColor(curses.COLOR_RED)
+ green = _TuiColor(curses.COLOR_GREEN)
+ yellow = _TuiColor(curses.COLOR_YELLOW)
+ blue = _TuiColor(curses.COLOR_BLUE)
+ magenta = _TuiColor(curses.COLOR_MAGENTA)
+ cyan = _TuiColor(curses.COLOR_CYAN)
+ white = _TuiColor(curses.COLOR_WHITE)
+
+class Alignment:
+ LEFT = 0b000001
+ RIGHT = 0b000010
+ CENTER = 0b000100
+ TOP = 0b001000
+ BOT = 0b010000
+ ABS = 0b000000
+ REL = 0b100000
+
+class ColorScope:
+ WIN = 1
+ PANEL = 2
+ TEXT = 3
+ TEXT_HI = 4
+ SHADOW = 5
+ SELECT = 6
+ HINT = 7
+ BOX = 8
+
+class EventType:
+ E_KEY = 0
+ E_REDRAW = 1
+ E_QUIT = 2
+ E_TASK = 3
+ E_CHFOCUS = 4
+ E_M_FOCUS = 0b10000000
+
+ def focused_only(t):
+ return (t & EventType.E_M_FOCUS)
+
+ def value(t):
+ return t & ~EventType.E_M_FOCUS
+
+ def key_press(t):
+ return (t & ~EventType.E_M_FOCUS) == EventType.E_KEY
+
+class Matchers:
+ RelSize = re.compile(r"(?P<mult>[0-9]+(?:\.[0-9]+)?)?\*(?P<add>[+-][0-9]+)?")
+
+class BoundExpression:
+ def __init__(self, expr = None):
+ self._mult = 0
+ self._add = 0
+
+ if expr:
+ self.update(expr)
+
+ def set_pair(self, mult, add):
+ self._mult = mult
+ self._add = add
+
+ def set(self, expr):
+ self._mult = expr._mult
+ self._add = expr._add
+
+ def update(self, expr):
+ if isinstance(expr, int):
+ m = None
+ else:
+ m = Matchers.RelSize.match(expr)
+
+ if m:
+ g = m.groupdict()
+ mult = 1 if not g["mult"] else float(g["mult"])
+ add = 0 if not g["add"] else int(g["add"])
+ self._mult = mult
+ self._add = add
+ else:
+ self.set_pair(0, int(expr))
+
+ def calc(self, ref_val):
+ return int(self._mult * ref_val + self._add)
+
+ def absolute(self):
+ return self._mult == 0
+
+ def nullity(self):
+ return self._mult == 0 and self._add == 0
+
+ def scale_mult(self, scalar):
+ self._mult *= scalar
+ return self
+
+ @staticmethod
+ def normalise(*exprs):
+ v = BoundExpression()
+ for e in exprs:
+ v += e
+ return [e.scale_mult(1 / v._mult) for e in exprs]
+
+ def __add__(self, b):
+ v = BoundExpression()
+ v.set(self)
+ v._mult += b._mult
+ v._add += b._add
+ return v
+
+ def __sub__(self, b):
+ v = BoundExpression()
+ v.set(self)
+ v._mult -= b._mult
+ v._add -= b._add
+ return v
+
+ def __iadd__(self, b):
+ self._mult += b._mult
+ self._add += b._add
+ return self
+
+ def __isub__(self, b):
+ self._mult -= b._mult
+ self._add -= b._add
+ return self
+
+ def __rmul__(self, scalar):
+ v = BoundExpression()
+ v.set(self)
+ v._mult *= scalar
+ v._add *= scalar
+ return v
+
+ def __truediv__(self, scalar):
+ v = BoundExpression()
+ v.set(self)
+ v._mult /= float(scalar)
+ v._add /= scalar
+ return v
+
+class DynamicBound:
+ def __init__(self):
+ self.__x = BoundExpression()
+ self.__y = BoundExpression()
+
+ def dyn_x(self):
+ return self.__x
+
+ def dyn_y(self):
+ return self.__y
+
+ def resolve(self, ref_w, ref_h):
+ return (self.__y.calc(ref_h), self.__x.calc(ref_w))
+
+ def set(self, dyn_bound):
+ self.__x.set(dyn_bound.dyn_x())
+ self.__y.set(dyn_bound.dyn_y())
+
+class Bound:
+ def __init__(self) -> None:
+ self.__x = 0
+ self.__y = 0
+
+ def shrinkX(self, dx):
+ self.__x -= dx
+ def shrinkY(self, dy):
+ self.__y -= dy
+
+ def growX(self, dx):
+ self.__x += dx
+ def growY(self, dy):
+ self.__y += dy
+
+ def resetX(self, x):
+ self.__x = x
+ def resetY(self, y):
+ self.__y = y
+
+ def update(self, dynsz, ref_bound):
+ y, x = dynsz.resolve(ref_bound.x(), ref_bound.y())
+ self.__x = x
+ self.__y = y
+
+ def reset(self, x, y):
+ self.__x, self.__y = x, y
+
+ def x(self):
+ return self.__x
+
+ def y(self):
+ return self.__y
+
+ def yx(self, scale = 1):
+ return int(self.__y * scale), int(self.__x * scale)
+
+class TuiStackWindow:
+ def __init__(self, obj) -> None:
+ self.__obj = obj
+ self.__win = curses.newwin(0, 0)
+ self.__pan = cpanel.new_panel(self.__win)
+ self.__pan.hide()
+
+ def resize(self, h, w):
+ resize_safe(self.__obj, self.__win, h, w)
+
+ def relocate(self, y, x):
+ move_safe(self.__obj, self.__pan, y, x)
+
+ def set_geometric(self, h, w, y, x):
+ resize_safe(self.__obj, self.__win, h, w)
+ move_safe(self.__obj, self.__pan, y, x)
+
+ def set_background(self, color_scope):
+ self.__win.bkgd(' ', curses.color_pair(color_scope))
+
+ def show(self):
+ self.__pan.show()
+
+ def hide(self):
+ self.__pan.hide()
+
+ def send_back(self):
+ self.__pan.bottom()
+
+ def send_front(self):
+ self.__pan.top()
+
+ def window(self):
+ return self.__win
+
+class SpatialObject:
+ def __init__(self) -> None:
+ self._local_pos = DynamicBound()
+ self._pos = Bound()
+ self._dyn_size = DynamicBound()
+ self._size = Bound()
+ self._margin = (0, 0, 0, 0)
+ self._padding = (0, 0, 0, 0)
+ self._align = Alignment.TOP | Alignment.LEFT
+
+ def set_local_pos(self, x, y):
+ self._local_pos.dyn_x().update(x)
+ self._local_pos.dyn_y().update(y)
+
+ def set_alignment(self, align):
+ self._align = align
+
+ def set_size(self, w = None, h = None):
+ if w:
+ self._dyn_size.dyn_x().update(w)
+ if h:
+ self._dyn_size.dyn_y().update(h)
+
+ def set_margin(self, top, right, bottom, left):
+ self._margin = (top, right, bottom, left)
+
+ def set_padding(self, top, right, bottom, left):
+ self._padding = (top, right, bottom, left)
+
+ def reset(self):
+ self._pos.reset(0, 0)
+ self._size.reset(0, 0)
+
+ def deduce_spatial(self, constrain):
+ self.reset()
+ self.__satisfy_bound(constrain)
+ self.__satisfy_alignment(constrain)
+ self.__satisfy_margin(constrain)
+ self.__satisfy_padding(constrain)
+
+ self.__to_corner_pos(constrain)
+
+ def __satisfy_alignment(self, constrain):
+ local_pos = self._local_pos
+ cbound = constrain._size
+ size = self._size
+
+ cy, cx = cbound.yx()
+ ry, rx = local_pos.resolve(cx, cy)
+ ay, ax = size.yx(0.5)
+
+ if self._align & Alignment.CENTER:
+ ax = cx // 2
+ ay = cy // 2
+
+ if self._align & Alignment.BOT:
+ ay = min(cy - ay, cy - 1)
+ ry = -ry
+ elif self._align & Alignment.TOP:
+ ay = size.y() // 2
+
+ if self._align & Alignment.RIGHT:
+ ax = cx - ax
+ rx = -rx
+ elif self._align & Alignment.LEFT:
+ ax = size.x() // 2
+
+ self._pos.reset(ax + rx, ay + ry)
+
+ def __satisfy_margin(self, constrain):
+ tm, lm, bm, rm = self._margin
+
+ self._pos.growX(rm - lm)
+ self._pos.growY(bm - tm)
+
+ def __satisfy_padding(self, constrain):
+ csize = constrain._size
+ ch, cw = csize.yx()
+ h, w = self._size.yx(0.5)
+ y, x = self._pos.yx()
+
+ tp, lp, bp, rp = self._padding
+
+ if not (tp or lp or bp or rp):
+ return
+
+ dtp = min(y - h, tp) - tp
+ dbp = min(ch - (y + h), bp) - bp
+
+ dlp = min(x - w, lp) - lp
+ drp = min(cw - (x + w), rp) - rp
+
+ self._size.growX(drp + dlp)
+ self._size.growY(dtp + dbp)
+
+ def __satisfy_bound(self, constrain):
+ self._size.update(self._dyn_size, constrain._size)
+
+ def __to_corner_pos(self, constrain):
+ h, w = self._size.yx(0.5)
+ g_pos = constrain._pos
+
+ self._pos.shrinkX(w)
+ self._pos.shrinkY(h)
+
+ self._pos.growX(g_pos.x())
+ self._pos.growY(g_pos.y())
+
+
+class TuiObject(SpatialObject):
+ def __init__(self, context, id):
+ super().__init__()
+ self._id = id
+ self._context = context
+ self._parent = None
+ self._visible = True
+ self._focused = False
+
+ def set_parent(self, parent):
+ self._parent = parent
+
+ def canvas(self):
+ if self._parent:
+ return self._parent.canvas()
+ return (self, self._context.window())
+
+ def context(self):
+ return self._context
+
+ def session(self):
+ return self._context.session()
+
+ def on_create(self):
+ pass
+
+ def on_destory(self):
+ pass
+
+ def on_quit(self):
+ pass
+
+ def on_layout(self):
+ if self._parent:
+ self.deduce_spatial(self._parent)
+
+ def on_draw(self):
+ pass
+
+ def on_event(self, ev_type, ev_arg):
+ pass
+
+ def on_focused(self):
+ self._focused = True
+
+ def on_focus_lost(self):
+ self._focused = False
+
+ def set_visbility(self, visible):
+ self._visible = visible
+
+ def do_draw(self):
+ if self._visible:
+ self.on_draw()
+
+ def do_layout(self):
+ self.on_layout()
+
+class TuiWidget(TuiObject):
+ def __init__(self, context, id):
+ super().__init__(context, id)
+
+ def on_layout(self):
+ super().on_layout()
+
+ co, _ = self.canvas()
+
+ y, x = co._pos.yx()
+ self._pos.shrinkX(x)
+ self._pos.shrinkY(y)
+
+class TuiContainerObject(TuiObject):
+ def __init__(self, context, id):
+ super().__init__(context, id)
+ self._children = []
+
+ def add(self, child):
+ child.set_parent(self)
+ self._children.append(child)
+
+ def children(self):
+ return self._children
+
+ def on_create(self):
+ super().on_create()
+ for child in self._children:
+ child.on_create()
+
+ def on_destory(self):
+ super().on_destory()
+ for child in self._children:
+ child.on_destory()
+
+ def on_quit(self):
+ super().on_quit()
+ for child in self._children:
+ child.on_quit()
+
+ def on_layout(self):
+ super().on_layout()
+ for child in self._children:
+ child.do_layout()
+
+ def on_draw(self):
+ super().on_draw()
+ for child in self._children:
+ child.do_draw()
+
+ def on_event(self, ev_type, ev_arg):
+ super().on_event(ev_type, ev_arg)
+ for child in self._children:
+ child.on_event(ev_type, ev_arg)
+
+
+class TuiScrollable(TuiObject):
+ def __init__(self, context, id):
+ super().__init__(context, id)
+ self.__spos = Bound()
+
+ self.__pad = curses.newpad(1, 1)
+ self.__pad.bkgd(' ', curses.color_pair(ColorScope.PANEL))
+ self.__pad_panel = cpanel.new_panel(self.__pad)
+ self.__content = None
+
+ def canvas(self):
+ return (self, self.__pad)
+
+ def set_content(self, content):
+ self.__content = content
+ self.__content.set_parent(self)
+
+ def set_scrollY(self, y):
+ self.__spos.resetY(y)
+
+ def set_scrollX(self, x):
+ self.__spos.resetX(x)
+
+ def reached_last(self):
+ off = self.__spos.y() + self._size.y()
+ return off >= self.__content._size.y()
+
+ def reached_top(self):
+ return self.__spos.y() < self._size.y()
+
+ def on_layout(self):
+ super().on_layout()
+
+ if not self.__content:
+ return
+
+ self.__content.on_layout()
+
+ h, w = self._size.yx()
+ ch, cw = self.__content._size.yx()
+ sh, sw = max(ch, h), max(cw, w)
+
+ self.__spos.resetX(min(self.__spos.x(), max(cw, w) - w))
+ self.__spos.resetY(min(self.__spos.y(), max(ch, h) - h))
+
+ resize_safe(self, self.__pad, sh, sw)
+
+ def on_draw(self):
+ if not self.__content:
+ return
+
+ self.__pad.erase()
+
+ self.__pad_panel.top()
+ self.__content.on_draw()
+
+ wminy, wminx = self._pos.yx()
+ wmaxy, wmaxx = self._size.yx()
+ wmaxy, wmaxx = wmaxy + wminy, wmaxx + wminx
+ self.__pad.touchwin()
+ self.__pad.refresh(*self.__spos.yx(),
+ wminy, wminx, wmaxy - 1, wmaxx - 1)
+
+
+class Layout(TuiContainerObject):
+ class Cell(TuiObject):
+ def __init__(self, context):
+ super().__init__(context, "cell")
+ self.__obj = None
+
+ def set_obj(self, obj):
+ self.__obj = obj
+ self.__obj.set_parent(self)
+
+ def on_create(self):
+ if self.__obj:
+ self.__obj.on_create()
+
+ def on_destory(self):
+ if self.__obj:
+ self.__obj.on_destory()
+
+ def on_quit(self):
+ if self.__obj:
+ self.__obj.on_quit()
+
+ def on_layout(self):
+ super().on_layout()
+ if self.__obj:
+ self.__obj.do_layout()
+
+ def on_draw(self):
+ if self.__obj:
+ self.__obj.do_draw()
+
+ def on_event(self, ev_type, ev_arg):
+ if self.__obj:
+ self.__obj.on_event(ev_type, ev_arg)
+
+ def __init__(self, context, id, ratios):
+ super().__init__(context, id)
+
+ rs = [BoundExpression(r) for r in ratios.split(',')]
+ self._rs = BoundExpression.normalise(*rs)
+
+ for _ in range(len(self._rs)):
+ cell = Layout.Cell(self._context)
+ super().add(cell)
+
+ self._adjust_to_fit()
+
+ def _adjust_to_fit(self):
+ pass
+
+ def add(self, child):
+ raise RuntimeError("invalid operation")
+
+ def set_cell(self, i, obj):
+ if i > len(self._children):
+ raise ValueError(f"cell #{i} out of bound")
+
+ self._children[i].set_obj(obj)
+
+
+class FlexLinearLayout(Layout):
+ LANDSCAPE = 0
+ PORTRAIT = 1
+ def __init__(self, context, id, ratios):
+ self.__horizontal = False
+
+ super().__init__(context, id, ratios)
+
+ def orientation(self, orient):
+ self.__horizontal = orient == FlexLinearLayout.LANDSCAPE
+
+ def on_layout(self):
+ self.__apply_ratio()
+ super().on_layout()
+
+ def _adjust_to_fit(self):
+ sum_abs = BoundExpression()
+ i = 0
+ for r in self._rs:
+ if r.absolute():
+ sum_abs += r
+ else:
+ i += 1
+
+ sum_abs /= i
+ for i, r in enumerate(self._rs):
+ if not r.absolute():
+ self._rs[i] -= sum_abs
+
+ def __apply_ratio(self):
+ if self.__horizontal:
+ self.__adjust_horizontal()
+ else:
+ self.__adjust_vertical()
+
+ def __adjust_horizontal(self):
+ acc = BoundExpression()
+ for r, cell in zip(self._rs, self.children()):
+ cell._dyn_size.dyn_y().set_pair(1, 0)
+ cell._dyn_size.dyn_x().set(r)
+
+ cell.set_alignment(Alignment.LEFT)
+ cell._local_pos.dyn_y().set_pair(0, 0)
+ cell._local_pos.dyn_x().set(acc)
+
+ acc += r
+
+ def __adjust_vertical(self):
+ acc = BoundExpression()
+ for r, cell in zip(self._rs, self.children()):
+ cell._dyn_size.dyn_x().set_pair(1, 0)
+ cell._dyn_size.dyn_y().set(r)
+
+ cell.set_alignment(Alignment.TOP | Alignment.CENTER)
+ cell._local_pos.dyn_x().set_pair(0, 0)
+ cell._local_pos.dyn_y().set(acc)
+
+ acc += r
+
+
+class TuiPanel(TuiContainerObject):
+ def __init__(self, context, id):
+ super().__init__(context, id)
+
+ self.__use_border = False
+ self.__use_shadow = False
+ self.__shadow_param = (0, 0)
+
+ self.__swin = TuiStackWindow(self)
+ self.__shad = TuiStackWindow(self)
+
+ self.__swin.set_background(ColorScope.PANEL)
+ self.__shad.set_background(ColorScope.SHADOW)
+
+ def canvas(self):
+ return (self, self.__swin.window())
+
+ def drop_shadow(self, off_y, off_x):
+ self.__shadow_param = (off_y, off_x)
+ self.__use_shadow = not (off_y == off_x and off_y == 0)
+
+ def border(self, _b):
+ self.__use_border = _b
+
+ def bkgd_override(self, scope):
+ self.__swin.set_background(scope)
+
+ def on_layout(self):
+ super().on_layout()
+
+ self.__swin.hide()
+
+ h, w = self._size.y(), self._size.x()
+ y, x = self._pos.y(), self._pos.x()
+ self.__swin.set_geometric(h, w, y, x)
+
+ if self.__use_shadow:
+ sy, sx = self.__shadow_param
+ self.__shad.set_geometric(h, w, y + sy, x + sx)
+
+ def on_destory(self):
+ super().on_destory()
+ self.__swin.hide()
+ self.__shad.hide()
+
+ def on_draw(self):
+ win = self.__swin.window()
+ win.erase()
+
+ if self.__use_border:
+ win.border()
+
+ if self.__use_shadow:
+ self.__shad.show()
+ else:
+ self.__shad.hide()
+
+ self.__swin.show()
+ self.__swin.send_front()
+
+ super().on_draw()
+
+ win.touchwin()
+
+class TuiLabel(TuiWidget):
+ def __init__(self, context, id):
+ super().__init__(context, id)
+ self._text = "TuiLabel"
+ self._wrapped = []
+
+ self.__auto_fit = True
+ self.__trunc = False
+ self.__dopad = False
+ self.__highlight = False
+ self.__color_scope = -1
+
+ def __try_fit_text(self, txt):
+ if self.__auto_fit:
+ self._dyn_size.dyn_x().set_pair(0, len(txt))
+ self._dyn_size.dyn_y().set_pair(0, 1)
+
+ def __pad_text(self):
+ for i, t in enumerate(self._wrapped):
+ self._wrapped[i] = str.rjust(t, self._size.x())
+
+ def set_text(self, text):
+ self._text = text
+ self.__try_fit_text(text)
+
+ def override_color(self, color = -1):
+ self.__color_scope = color
+
+ def auto_fit(self, _b):
+ self.__auto_fit = _b
+
+ def truncate(self, _b):
+ self.__trunc = _b
+
+ def hightlight(self, _b):
+ self.__highlight = _b
+
+ def pad_around(self, _b):
+ self.__dopad = _b
+
+ def on_layout(self):
+ txt = self._text
+ if self.__dopad:
+ txt = f" {txt} "
+ self.__try_fit_text(txt)
+
+ super().on_layout()
+
+ if len(txt) <= self._size.x():
+ self._wrapped = [txt]
+ self.__pad_text()
+ return
+
+ if not self.__trunc:
+ txt = txt[:self._size.x() - 1]
+ self._wrapped = [txt]
+ self.__pad_text()
+ return
+
+ self._wrapped = textwrap.wrap(txt, self._size.x())
+ self.__pad_text()
+
+ def on_draw(self):
+ _, win = self.canvas()
+ y, x = self._pos.yx()
+
+ if self.__color_scope != -1:
+ color = curses.color_pair(self.__color_scope)
+ elif self.__highlight:
+ color = curses.color_pair(ColorScope.TEXT_HI)
+ else:
+ color = curses.color_pair(ColorScope.TEXT)
+
+ for i, t in enumerate(self._wrapped):
+ addstr_safe(self, win, y + i, x, t, color)
+
+
+class TuiTextBlock(TuiWidget):
+ def __init__(self, context, id):
+ super().__init__(context, id)
+ self.__lines = []
+ self.__wrapped = []
+ self.__fit_to_height = False
+
+ def set_text(self, text):
+ text = textwrap.dedent(text)
+ self.__lines = text.split('\n')
+ if self.__fit_to_height:
+ self._dyn_size.dyn_y().set_pair(0, 0)
+
+ def height_auto_fit(self, yes):
+ self.__fit_to_height = yes
+
+ def on_layout(self):
+ super().on_layout()
+
+ self.__wrapped.clear()
+ for t in self.__lines:
+ if not t:
+ self.__wrapped.append(t)
+ continue
+ wrap = textwrap.wrap(t, self._size.x())
+ self.__wrapped += wrap
+
+ if self._dyn_size.dyn_y().nullity():
+ h = len(self.__wrapped)
+ self._dyn_size.dyn_y().set_pair(0, h)
+
+ # redo layouting
+ super().on_layout()
+
+ def on_draw(self):
+ _, win = self.canvas()
+ y, x = self._pos.yx()
+
+ color = curses.color_pair(ColorScope.TEXT)
+ for i, t in enumerate(self.__wrapped):
+ addstr_safe(self, win, y + i, x, t, color)
+
+
+class TuiTextBox(TuiWidget):
+ def __init__(self, context, id):
+ super().__init__(context, id)
+ self.__box = TuiStackWindow(self)
+ self.__box.set_background(ColorScope.PANEL)
+ self.__textb = textpad.Textbox(self.__box.window(), True)
+ self.__textb.stripspaces = True
+ self.__str = ""
+ self.__scheduled_edit = False
+
+ self._context.focus_group().register(self, 0)
+
+ def __validate(self, x):
+ if x == 10 or x == 9:
+ return 7
+ return x
+
+ def on_layout(self):
+ super().on_layout()
+
+ co, _ = self.canvas()
+ h, w = self._size.yx()
+ y, x = self._pos.yx()
+ cy, cx = co._pos.yx()
+ y, x = y + cy, x + cx
+
+ self.__box.hide()
+ self.__box.set_geometric(1, w - 1, y + h // 2, x + 1)
+
+ def on_draw(self):
+ self.__box.show()
+ self.__box.send_front()
+
+ _, cwin = self.canvas()
+
+ h, w = self._size.yx()
+ y, x = self._pos.yx()
+ textpad.rectangle(cwin, y, x, y + h - 1, x+w)
+
+ win = self.__box.window()
+ win.touchwin()
+
+ def __edit(self):
+ self.__str = self.__textb.edit(lambda x: self.__validate(x))
+ self.session().schedule(EventType.E_CHFOCUS)
+ self.__scheduled_edit = False
+
+ def get_text(self):
+ return self.__str
+
+ def on_focused(self):
+ self.__box.set_background(ColorScope.BOX)
+ if not self.__scheduled_edit:
+ # edit will block, defer to next update cycle
+ self.session().schedule_task(self.__edit)
+ self.__scheduled_edit = True
+
+ def on_focus_lost(self):
+ self.__box.set_background(ColorScope.PANEL)
+
+
+class SimpleList(TuiWidget):
+ class Item:
+ def __init__(self) -> None:
+ pass
+ def get_text(self):
+ return "list_item"
+ def on_selected(self):
+ pass
+ def on_key_pressed(self, key):
+ pass
+
+ def __init__(self, context, id):
+ super().__init__(context, id)
+ self.__items = []
+ self.__selected = 0
+ self.__on_sel_confirm_cb = None
+ self.__on_sel_change_cb = None
+
+ self._context.focus_group().register(self)
+
+ def set_onselected_cb(self, cb):
+ self.__on_sel_confirm_cb = cb
+
+ def set_onselection_change_cb(self, cb):
+ self.__on_sel_change_cb = cb
+
+ def count(self):
+ return len(self.__items)
+
+ def index(self):
+ return self.__selected
+
+ def add_item(self, item):
+ self.__items.append(item)
+
+ def clear(self):
+ self.__items.clear()
+
+ def on_layout(self):
+ super().on_layout()
+ self.__selected = min(self.__selected, len(self.__items))
+ self._size.resetY(len(self.__items) + 1)
+
+ def on_draw(self):
+ _, win = self.canvas()
+ w = self._size.x()
+
+ for i, item in enumerate(self.__items):
+ color = curses.color_pair(ColorScope.TEXT)
+ if i == self.__selected:
+ if self._focused:
+ color = curses.color_pair(ColorScope.SELECT)
+ else:
+ color = curses.color_pair(ColorScope.BOX)
+
+ txt = str.ljust(item.get_text(), w)
+ txt = txt[:w]
+ addstr_safe(self, win, i, 0, txt, color)
+
+ def on_event(self, ev_type, ev_arg):
+ if not EventType.key_press(ev_type):
+ return
+
+ if len(self.__items) == 0:
+ return
+
+ sel = self.__items[self.__selected]
+
+ if ev_arg == 10:
+ sel.on_selected()
+
+ if self.__on_sel_confirm_cb:
+ self.__on_sel_confirm_cb(self, self.__selected, sel)
+ return
+
+ sel.on_key_pressed(ev_arg)
+
+ if (ev_arg != curses.KEY_DOWN and
+ ev_arg != curses.KEY_UP):
+ return
+
+ prev = self.__selected
+ if ev_arg == curses.KEY_DOWN:
+ self.__selected += 1
+ else:
+ self.__selected -= 1
+
+ self.__selected = max(self.__selected, 0)
+ self.__selected = self.__selected % len(self.__items)
+
+ if self.__on_sel_change_cb:
+ self.__on_sel_change_cb(self, prev, self.__selected)
+
+
+class TuiButton(TuiLabel):
+ def __init__(self, context, id):
+ super().__init__(context, id)
+ self.__onclick = None
+
+ context.focus_group().register(self)
+
+ def set_text(self, text):
+ return super().set_text(f"<{text}>")
+
+ def set_click_callback(self, cb):
+ self.__onclick = cb
+
+ def hightlight(self, _b):
+ raise NotImplemented()
+
+ def on_draw(self):
+ _, win = self.canvas()
+ y, x = self._pos.yx()
+
+ if self._focused:
+ color = curses.color_pair(ColorScope.SELECT)
+ else:
+ color = curses.color_pair(ColorScope.TEXT)
+
+ addstr_safe(self, win, y, x, self._wrapped[0], color)
+
+ def on_event(self, ev_type, ev_arg):
+ if not EventType.focused_only(ev_type):
+ return
+ if not EventType.key_press(ev_type):
+ return
+
+ if ev_arg == ord('\n') and self.__onclick:
+ self.__onclick(self)
+
+
+class TuiSession:
+ def __init__(self) -> None:
+ self.stdsc = curses.initscr()
+ curses.start_color()
+
+ curses.noecho()
+ curses.cbreak()
+
+ self.__context_stack = []
+ self.__sched_events = []
+
+ ws = self.window_size()
+ self.__win = curses.newwin(*ws)
+ self.__winbg = curses.newwin(*ws)
+ self.__panbg = cpanel.new_panel(self.__winbg)
+
+ self.__winbg.bkgd(' ', curses.color_pair(ColorScope.WIN))
+
+ self.__win.timeout(50)
+ self.__win.keypad(True)
+
+ def window_size(self):
+ return self.stdsc.getmaxyx()
+
+ def set_color(self, scope, fg, bg):
+ curses.init_pair(scope, int(fg), int(bg))
+
+ def schedule_redraw(self):
+ self.schedule(EventType.E_REDRAW)
+
+ def schedule_task(self, task):
+ self.schedule(EventType.E_REDRAW)
+ self.schedule(EventType.E_TASK, task)
+
+ def schedule(self, event, arg = None):
+ if len(self.__sched_events) > 0:
+ if self.__sched_events[-1] == event:
+ return
+
+ self.__sched_events.append((event, arg))
+
+ def push_context(self, tuictx):
+ tuictx.prepare()
+ self.__context_stack.append(tuictx)
+ self.schedule(EventType.E_REDRAW)
+
+ curses.curs_set(self.active().curser_mode())
+
+ def pop_context(self):
+ if len(self.__context_stack) == 1:
+ return
+
+ ctx = self.__context_stack.pop()
+ ctx.on_destory()
+ self.schedule(EventType.E_REDRAW)
+
+ curses.curs_set(self.active().curser_mode())
+
+ def active(self):
+ return self.__context_stack[-1]
+
+ def event_loop(self):
+ if len(self.__context_stack) == 0:
+ raise RuntimeError("no tui context to display")
+
+ while True:
+ key = self.__win.getch()
+ if key != -1:
+ self.schedule(EventType.E_KEY, key)
+
+ if len(self.__sched_events) == 0:
+ continue
+
+ evt, arg = self.__sched_events.pop(0)
+ if evt == EventType.E_REDRAW:
+ self.__redraw()
+ elif evt == EventType.E_QUIT:
+ self.__notify_quit()
+ break
+
+ self.active().dispatch_event(evt, arg)
+
+ def __notify_quit(self):
+ while len(self.__context_stack) == 0:
+ ctx = self.__context_stack.pop()
+ ctx.dispatch_event(EventType.E_QUIT, None)
+
+ def __redraw(self):
+ self.stdsc.erase()
+ self.__win.erase()
+
+ self.active().redraw(self.__win)
+
+ self.__panbg.bottom()
+ self.__win.touchwin()
+ self.__winbg.touchwin()
+
+ self.__win.refresh()
+ self.__winbg.refresh()
+
+ cpanel.update_panels()
+ curses.doupdate()
+
+class TuiFocusGroup:
+ def __init__(self) -> None:
+ self.__grp = []
+ self.__id = 0
+ self.__sel = 0
+ self.__focused = None
+
+ def register(self, tui_obj, pos=-1):
+ if pos == -1:
+ self.__grp.append((self.__id, tui_obj))
+ else:
+ self.__grp.insert(pos, (self.__id, tui_obj))
+ self.__id += 1
+ return self.__id - 1
+
+ def navigate_focus(self, dir = 1):
+ self.__sel = (self.__sel + dir) % len(self.__grp)
+ f = None if not len(self.__grp) else self.__grp[self.__sel][1]
+ if f and f != self.__focused:
+ if self.__focused:
+ self.__focused.on_focus_lost()
+ f.on_focused()
+ self.__focused = f
+
+ def focused(self):
+ return self.__focused
+
+class TuiContext(TuiObject):
+ def __init__(self, session: TuiSession):
+ super().__init__(self, "context")
+ self.__root = None
+ self.__sobj = None
+ self.__session = session
+
+ self.__win = None
+
+ y, x = self.__session.window_size()
+ self._size.reset(x, y)
+ self.set_parent(None)
+
+ self.__focus_group = TuiFocusGroup()
+ self.__curser_mode = 0
+
+ def set_curser_mode(self, mode):
+ self.__curser_mode = mode
+
+ def curser_mode(self):
+ return self.__curser_mode
+
+ def set_root(self, root):
+ self.__root = root
+ self.__root.set_parent(self)
+
+ def set_state(self, obj):
+ self.__sobj = obj
+
+ def state(self):
+ return self.__sobj
+
+ def prepare(self):
+ self.__root.on_create()
+
+ def on_destory(self):
+ self.__root.on_destory()
+
+ def canvas(self):
+ return (self, self.__win)
+
+ def session(self):
+ return self.__session
+
+ def dispatch_event(self, evt, arg):
+ if evt == EventType.E_REDRAW:
+ self.__focus_group.navigate_focus(0)
+ elif evt == EventType.E_CHFOCUS:
+ self.__focus_group.navigate_focus(1)
+ self.__session.schedule(EventType.E_REDRAW)
+ return
+ elif evt == EventType.E_TASK:
+ arg()
+ elif evt == EventType.E_QUIT:
+ self.__root.on_quit()
+ elif evt == EventType.E_KEY:
+ self._handle_key_event(arg)
+ else:
+ self.__root.on_event(evt, arg)
+
+ focused = self.__focus_group.focused()
+ if focused:
+ focused.on_event(evt | EventType.E_M_FOCUS, arg)
+
+ def redraw(self, win):
+ self.__win = win
+ self.on_layout()
+ self.on_draw()
+
+ def on_layout(self):
+ self.__root.on_layout()
+
+ def on_draw(self):
+ self.__root.on_draw()
+
+ def focus_group(self):
+ return self.__focus_group
+
+ def _handle_key_event(self, key):
+ if key == ord('\t') or key == curses.KEY_RIGHT:
+ self.__focus_group.navigate_focus()
+ elif key == curses.KEY_LEFT:
+ self.__focus_group.navigate_focus(-1)
+ else:
+ self.__root.on_event(EventType.E_KEY, key)
+
+ if self.__focus_group.focused():
+ self.__session.schedule(EventType.E_REDRAW)
\ No newline at end of file
--- /dev/null
+from lcfg.api import RenderContext
+from lcfg.types import (
+ PrimitiveType,
+ MultipleChoiceType
+)
+
+import subprocess
+import curses
+import textwrap
+import integration.libtui as tui
+import integration.libmenu as menu
+
+from integration.libtui import ColorScope, TuiColor, Alignment, EventType
+from integration.libmenu import Dialogue, ListView, show_dialog
+
+__git_repo_info = None
+__tainted = False
+
+def mark_tainted():
+ global __tainted
+ __tainted = True
+
+def unmark_tainted():
+ global __tainted
+ __tainted = False
+
+def get_git_hash():
+ try:
+ hsh = subprocess.check_output([
+ 'git', 'rev-parse', '--short', 'HEAD'
+ ]).decode('ascii').strip()
+ branch = subprocess.check_output([
+ 'git', 'branch', '--show-current'
+ ]).decode('ascii').strip()
+ return f"{branch}@{hsh}"
+ except:
+ return None
+
+def get_git_info():
+ global __git_repo_info
+ return __git_repo_info
+
+def do_save(session):
+ show_dialog(session, "Notice", "Configuration saved")
+ unmark_tainted()
+
+def do_exit(session):
+ global __tainted
+ if not __tainted:
+ session.schedule(EventType.E_QUIT)
+ return
+
+ quit = QuitDialogue(session)
+ quit.show()
+
+class MainMenuContext(tui.TuiContext):
+ def __init__(self, session, view_title):
+ super().__init__(session)
+
+ self.__title = view_title
+
+ self.__prepare_layout()
+
+ def __prepare_layout(self):
+
+ root = tui.TuiPanel(self, "main_panel")
+ root.set_size("*-10", "*-5")
+ root.set_alignment(Alignment.CENTER)
+ root.drop_shadow(1, 2)
+ root.border(True)
+
+ layout = tui.FlexLinearLayout(self, "layout", "6,*,5")
+ layout.orientation(tui.FlexLinearLayout.PORTRAIT)
+ layout.set_size("*", "*")
+ layout.set_padding(1, 1, 1, 1)
+
+ listv = ListView(self, "list_view")
+ listv.set_size("70", "*")
+ listv.set_alignment(Alignment.CENTER)
+
+ hint = tui.TuiTextBlock(self, "hint")
+ hint.set_size(w="*")
+ hint.set_local_pos("0.1*", 0)
+ hint.height_auto_fit(True)
+ hint.set_text(
+ "Use <UP>/<DOWN>/<ENTER> to select from list\n"
+ "Use <TAB>/<RIGHT>/<LEFT> to change focus\n"
+ "<H>: show help (if applicable), <BACKSPACE>: back previous level"
+ )
+ hint.set_alignment(Alignment.CENTER | Alignment.LEFT)
+
+ suffix = ""
+ btns_defs = [
+ {
+ "text": "Save",
+ "onclick": lambda x: do_save(self.session())
+ },
+ {
+ "text": "Exit",
+ "onclick": lambda x: do_exit(self.session())
+ }
+ ]
+
+ repo_info = get_git_info()
+
+ if self.__title:
+ suffix += f" - {self.__title}"
+ btns_defs.insert(1, {
+ "text": "Back",
+ "onclick": lambda x: self.session().pop_context()
+ })
+
+ btns = menu.create_buttons(self, btns_defs, sizes="50,*")
+
+ layout.set_cell(0, hint)
+ layout.set_cell(1, listv)
+ layout.set_cell(2, btns)
+
+ t = menu.create_title(self, "Lunaix Kernel Configuration" + suffix)
+ t2 = menu.create_title(self, repo_info)
+ t2.set_alignment(Alignment.BOT | Alignment.RIGHT)
+
+ root.add(t)
+ root.add(t2)
+ root.add(layout)
+
+ self.set_root(root)
+ self.__menu_list = listv
+
+ def menu(self):
+ return self.__menu_list
+
+ def _handle_key_event(self, key):
+ if key == curses.KEY_BACKSPACE or key == 8:
+ self.session().pop_context()
+ elif key == 27:
+ do_exit(self.session())
+ return
+
+ super()._handle_key_event(key)
+
+class ItemType:
+ Expandable = 0
+ Switch = 1
+ Choice = 2
+ Other = 3
+ def __init__(self, node, expandable) -> None:
+ self.__node = node
+
+ if expandable:
+ self.__type = ItemType.Expandable
+ return
+
+ self.__type = ItemType.Other
+ self.__primitive = False
+ type_provider = node.get_type()
+
+ if isinstance(type_provider, PrimitiveType):
+ self.__primitive = True
+
+ if isinstance(type_provider, MultipleChoiceType):
+ self.__type = ItemType.Choice
+ elif type_provider._type == bool:
+ self.__type = ItemType.Switch
+
+ self.__provider = type_provider
+
+ def get_formatter(self):
+ if self.__type == ItemType.Expandable:
+ return "%s ---->"
+
+ v = self.__node.get_value()
+
+ if self.is_switch():
+ mark = "*" if v else " "
+ return f"[{mark}] %s"
+
+ if self.is_choice() or isinstance(v, int):
+ return f"({v}) %s"
+
+ return "%s"
+
+ def expandable(self):
+ return self.__type == ItemType.Expandable
+
+ def is_switch(self):
+ return self.__type == ItemType.Switch
+
+ def is_choice(self):
+ return self.__type == ItemType.Choice
+
+ def read_only(self):
+ return not self.expandable() and self.__node.read_only()
+
+ def provider(self):
+ return self.__provider
+
+class MultiChoiceItem(tui.SimpleList.Item):
+ def __init__(self, value, get_val) -> None:
+ super().__init__()
+ self.__val = value
+ self.__getval = get_val
+
+ def get_text(self):
+ marker = "*" if self.__getval() == self.__val else " "
+ return f" ({marker}) {self.__val}"
+
+ def value(self):
+ return self.__val
+
+class LunaConfigItem(tui.SimpleList.Item):
+ def __init__(self, session, node, name, expand_cb = None):
+ super().__init__()
+ self.__node = node
+ self.__type = ItemType(node, expand_cb is not None)
+ self.__name = name
+ self.__expand_cb = expand_cb
+ self.__session = session
+
+ def get_text(self):
+ fmt = self.__type.get_formatter()
+ if self.__type.read_only():
+ fmt += "*"
+ return f" {fmt%(self.__name)}"
+
+ def on_selected(self):
+ if self.__type.read_only():
+ show_dialog(
+ self.__session,
+ f"Read-only: \"{self.__name}\"",
+ f"Value defined in this field:\n\n'{self.__node.get_value()}'")
+ return
+
+ if self.__type.expandable():
+ view = CollectionView(self.__session, self.__node, self.__name)
+ view.set_reloader(self.__expand_cb)
+ view.show()
+ return
+
+ if self.__type.is_switch():
+ v = self.__node.get_value()
+ self.change_value(not v)
+ else:
+ dia = ValueEditDialogue(self.__session, self)
+ dia.show()
+
+ self.__session.schedule(EventType.E_REDRAW)
+
+ def name(self):
+ return self.__name
+
+ def node(self):
+ return self.__node
+
+ def type(self):
+ return self.__type
+
+ def change_value(self, val):
+ try:
+ self.__node.set_value(val)
+ except:
+ show_dialog(
+ self.__session, "Invalid value",
+ f"Value: '{val}' does not match the type")
+ return False
+
+ mark_tainted()
+ CollectionView.reload_active(self.__session)
+ return True
+
+ def on_key_pressed(self, key):
+ if (key & ~0b100000) != ord('H'):
+ return
+
+ h = self.__node.help_prompt()
+ if not self.__type.expandable():
+ h = "\n".join([
+ h, "", "--------",
+ "Supported Values:",
+ textwrap.indent(str(self.__type.provider()), " ")
+ ])
+
+ dia = HelpDialogue(self.__session, f"Help: '{self.__name}'", h)
+ dia.show()
+
+class CollectionView(RenderContext):
+ def __init__(self, session, node, label = None) -> None:
+ super().__init__()
+
+ ctx = MainMenuContext(session, label)
+ self.__node = node
+ self.__tui_ctx = ctx
+ self.__listv = ctx.menu()
+ self.__session = session
+ self.__reloader = lambda x: node.render(x)
+
+ ctx.set_state(self)
+
+ def set_reloader(self, cb):
+ self.__reloader = cb
+
+ def add_expandable(self, label, node, on_expand_cb):
+ item = LunaConfigItem(self.__session, node, label, on_expand_cb)
+ self.__listv.add_item(item)
+
+ def add_field(self, label, node):
+ item = LunaConfigItem(self.__session, node, label)
+ self.__listv.add_item(item)
+
+ def show(self):
+ self.reload()
+ self.__session.push_context(self.__tui_ctx)
+
+ def reload(self):
+ self.__listv.clear()
+ self.__reloader(self)
+ self.__session.schedule(EventType.E_REDRAW)
+
+ @staticmethod
+ def reload_active(session):
+ state = session.active().state()
+ if isinstance(state, CollectionView):
+ state.reload()
+
+class ValueEditDialogue(menu.Dialogue):
+ def __init__(self, session, item: LunaConfigItem):
+ name = item.name()
+ title = f"Edit \"{name}\""
+ super().__init__(session, title, None, False,
+ "Confirm", "Cancle")
+
+ self.__item = item
+ self.__value = item.node().get_value()
+
+ self.decide_content()
+
+ def __get_val(self):
+ return self.__value
+
+ def decide_content(self):
+ if not self.__item.type().is_choice():
+ self.set_input_dialogue(True)
+ return
+
+ listv = ListView(self.context(), "choices")
+ listv.set_size("0.8*", "*")
+ listv.set_alignment(Alignment.CENTER)
+ listv.set_onselected_cb(self.__on_selected)
+
+ for t in self.__item.type().provider()._type:
+ listv.add_item(MultiChoiceItem(t, self.__get_val))
+
+ self.set_content(listv)
+ self.set_size()
+
+ def __on_selected(self, listv, index, item):
+ self.__value = item.value()
+
+ def _ok_onclick(self):
+ if self._textbox:
+ self.__value = self._textbox.get_text()
+
+ if self.__item.change_value(self.__value):
+ super()._ok_onclick()
+
+class QuitDialogue(menu.Dialogue):
+ def __init__(self, session):
+ super().__init__(session,
+ "Quit ?", "Unsaved changes, sure to quit?", False,
+ "Quit Anyway", "No", "Save and Quit")
+
+ def _ok_onclick(self):
+ self.session().schedule(EventType.E_QUIT)
+
+ def _abort_onclick(self):
+ unmark_tainted()
+ self._ok_onclick()
+
+
+class HelpDialogue(menu.Dialogue):
+ def __init__(self, session, title="", content=""):
+ super().__init__(session, title, None, no_btn=None)
+
+ self.__content = content
+ self.__scroll_y = 0
+ self.set_local_pos(0, -2)
+
+ def prepare(self):
+ tb = tui.TuiTextBlock(self._context, "content")
+ tb.set_size(w="70")
+ tb.set_text(self.__content)
+ tb.height_auto_fit(True)
+ self.__tb = tb
+
+ self.__scroll = tui.TuiScrollable(self._context, "scroll")
+ self.__scroll.set_size("65", "*")
+ self.__scroll.set_alignment(Alignment.CENTER)
+ self.__scroll.set_content(tb)
+
+ self.set_size(w="75")
+ self.set_content(self.__scroll)
+
+ super().prepare()
+
+ def _handle_key_event(self, key):
+ if key == curses.KEY_UP:
+ self.__scroll_y = max(self.__scroll_y - 1, 0)
+ self.__scroll.set_scrollY(self.__scroll_y)
+ elif key == curses.KEY_DOWN:
+ y = self.__tb._size.y()
+ self.__scroll_y = min(self.__scroll_y + 1, y)
+ self.__scroll.set_scrollY(self.__scroll_y)
+ super()._handle_key_event(key)
+
+class TerminalSizeCheckFailed(Exception):
+ def __init__(self, *args: object) -> None:
+ super().__init__(*args)
+
+def main(_, root_node):
+ global __git_repo_info
+
+ __git_repo_info = get_git_hash()
+
+ session = tui.TuiSession()
+
+ h, w = session.window_size()
+ if h < 30 or w < 85:
+ raise TerminalSizeCheckFailed((90, 40), (w, h))
+
+ base_background = TuiColor.white.bright()
+ session.set_color(ColorScope.WIN,
+ TuiColor.black, TuiColor.blue)
+ session.set_color(ColorScope.PANEL,
+ TuiColor.black, base_background)
+ session.set_color(ColorScope.TEXT,
+ TuiColor.black, base_background)
+ session.set_color(ColorScope.TEXT_HI,
+ TuiColor.magenta, base_background)
+ session.set_color(ColorScope.SHADOW,
+ TuiColor.black, TuiColor.black)
+ session.set_color(ColorScope.SELECT,
+ TuiColor.white, TuiColor.black.bright())
+ session.set_color(ColorScope.HINT,
+ TuiColor.cyan, base_background)
+ session.set_color(ColorScope.BOX,
+ TuiColor.black, TuiColor.white)
+
+ main_view = CollectionView(session, root_node)
+ main_view.show()
+
+ session.event_loop()
+
+def menuconfig(root_node):
+ global __tainted
+ curses.wrapper(main, root_node)
+
+ return not __tainted
\ No newline at end of file
return None in self._type
def __str__(self) -> str:
- accepted = [f" {t}" for t in self._type]
+ accepted = [f" * {t}" for t in self._type]
return "\n".join([
- "choose one: \n",
+ "choose one:",
*accepted
])
from integration.lbuild_bridge import LConfigProvider
from integration.render_ishell import InteractiveShell
from integration.build_gen import MakefileBuildGen, install_lbuild_functions
+from integration.lunamenu import menuconfig, TerminalSizeCheckFailed
import lcfg.types as lcfg_type
import lcfg.builtins as builtin
def do_config(opt, lcfg_env):
redo_config = not exists(opt.config_save) or opt.force
- if not redo_config:
+ if not redo_config or opt.quiet:
return
+
+ try:
+ clean_quit = menuconfig(lcfg_env)
+ except TerminalSizeCheckFailed as e:
+ least = e.args[0]
+ current = e.args[1]
+ print(
+ f"Your terminal size: {current} is less than minimum requirement of {least}.\n"
+ "menuconfig will not function properly, switch to prompt based.\n")
+
+ shell = InteractiveShell(lcfg_env)
+ clean_quit = shell.render_loop()
- shell = InteractiveShell(lcfg_env)
- if not shell.render_loop():
- print("Configuration aborted.")
+ if not clean_quit:
+ print("Configuration aborted. Nothing has been saved.")
exit(-1)
def do_buildfile_gen(opts, lcfg_env):
def main():
parser = ArgumentParser()
parser.add_argument("--config", action="store_true", default=False)
+ parser.add_argument("--quiet", action="store_true", default=False)
parser.add_argument("--lconfig-file", default="LConfig")
parser.add_argument("--config-save", default=".config.json")
parser.add_argument("--force", action="store_true", default=False)
import subprocess, time, os, re, argparse, json
from pathlib import PurePosixPath
import logging
+import uuid
logger = logging.getLogger("auto_qemu")
+logging.basicConfig(level=logging.INFO)
g_lookup = {}
def join_attrs(attrs):
return ",".join(attrs)
-def parse_protocol(opt):
- protocol = get_config(opt, "protocol", "telnet")
- addr = get_config(opt, "addr", ":12345")
- logfile = get_config(opt, "logfile")
+def get_uniq():
+ return uuid.uuid4().hex[:8]
- return (f"{protocol}:{addr}", logfile)
+def map_bool(b):
+ return "on" if b else "off"
+
+
+
+#################################
+# IO Backend Definitions
+#
+
+class IOBackend:
+ def __init__(self, opt, id_prefix="io") -> None:
+ self._type = get_config(opt, "type", required=True)
+ self._logfile = get_config(opt, "logfile")
+ self._id = f"{id_prefix}.{get_uniq()}"
+
+ def get_options(self):
+ opts = []
+ if self._logfile:
+ opts.append(f"logfile={self._logfile}")
+ return opts
+
+ def to_cmdline(self):
+ return join_attrs([
+ self._type, f"id={self._id}", *self.get_options()
+ ])
+
+ def name(self):
+ return self._id
+
+class FileIOBackend(IOBackend):
+ def __init__(self, opt) -> None:
+ super().__init__(opt)
+ self.__path = get_config(opt, "path", required=True)
+
+ def get_options(self):
+ opts = [
+ f"path={self.__path}"
+ ]
+ return opts + super().get_options()
+
+class SocketIOBackend(IOBackend):
+ def __init__(self, opt) -> None:
+ super().__init__(opt)
+ self.__protocol = self._type
+ self._type = "socket"
+ self.__host = get_config(opt, "host", default="localhost")
+ self.__port = get_config(opt, "port", required=True)
+ self.__server = bool(get_config(opt, "server", True))
+ self.__wait = bool(get_config(opt, "wait", True))
+
+ def get_options(self):
+ opts = [
+ f"host={self.__host}",
+ f"port={self.__port}",
+ f"server={map_bool(self.__server)}",
+ f"wait={map_bool(self.__wait)}",
+ ]
+ if self.__protocol == "telnet":
+ opts.append("telnet=on")
+ if self.__protocol == "ws":
+ opts.append("websocket=on")
+ return opts + super().get_options()
+
+def select_backend(opt):
+ bopt = get_config(opt, "io", required=True)
+ backend_type = get_config(bopt, "type", required=True)
+
+ if backend_type in ["telnet", "ws", "tcp"]:
+ return SocketIOBackend(bopt)
+
+ if backend_type in ["file", "pipe", "serial", "parallel"]:
+ return FileIOBackend(bopt)
+
+ return IOBackend(bopt)
+
+
+
+#################################
+# QEMU Emulated Device Definitions
+#
class QEMUPeripherals:
def __init__(self, name, opt) -> None:
def get_qemu_opts(self) -> list:
pass
-class BasicSerialDevice(QEMUPeripherals):
+class ISASerialDevice(QEMUPeripherals):
def __init__(self, opt) -> None:
- super().__init__("serial", opt)
+ super().__init__("isa-serial", opt)
def get_qemu_opts(self):
- link, logfile = parse_protocol(self._opt)
+ chardev = select_backend(self._opt)
+
+ cmds = [
+ "isa-serial",
+ f"id=com.{get_uniq()}",
+ f"chardev={chardev.name()}"
+ ]
- cmds = [ link, "server", "nowait" ]
- if logfile:
- cmds.append(f"logfile={logfile}")
- return [ "-serial", join_attrs(cmds) ]
+ return [
+ "-chardev", chardev.to_cmdline(),
+ "-device", join_attrs(cmds)
+ ]
class PCISerialDevice(QEMUPeripherals):
def __init__(self, opt) -> None:
super().__init__("pci-serial", opt)
def get_qemu_opts(self):
- uniq = hex(self.__hash__())[2:]
- name = f"chrdev.{uniq}"
- cmds = [ "pci-serial", f"id=uart.{uniq}", f"chardev={name}" ]
- chrdev = [ "file", f"id={name}" ]
-
- logfile = get_config(self._opt, "logfile", required=True)
- chrdev.append(f"path={logfile}")
+ chardev = select_backend(self._opt)
+
+ cmds = [
+ "pci-serial",
+ f"id=uart.{get_uniq()}",
+ f"chardev={chardev.name()}"
+ ]
return [
- "-chardev", join_attrs(chrdev),
+ "-chardev", chardev.to_cmdline(),
"-device", join_attrs(cmds)
]
class AHCIBus(QEMUPeripherals):
def __init__(self, opt) -> None:
super().__init__("ahci", opt)
+
+ def __create_disklet(self, index, bus, opt):
+ d_type = get_config(opt, "type", default="ide-hd")
+ d_img = get_config(opt, "img", required=True)
+ d_ro = get_config(opt, "ro", default=False)
+ d_fmt = get_config(opt, "format", default="raw")
+ d_id = f"disk_{index}"
+
+ if not os.path.exists(d_img):
+ logger.warning(f"AHCI bus: {d_img} not exists, skipped")
+ return []
+
+ return [
+ "-drive", join_attrs([
+ f"id={d_id}",
+ f"file={d_img}",
+ f"readonly={'on' if d_ro else 'off'}",
+ f"if=none",
+ f"format={d_fmt}"
+ ]),
+ "-device", join_attrs([
+ d_type,
+ f"drive={d_id}",
+ f"bus={bus}.{index}"
+ ])
+ ]
def get_qemu_opts(self):
opt = self._opt
name = name.strip().replace(" ", "_")
cmds = [ "-device", f"ahci,id={name}" ]
- for i, disk in enumerate(get_config(opt, "disks", default=[])):
- d_type = get_config(disk, "type", default="ide-hd")
- d_img = get_config(disk, "img", required=True)
- d_ro = get_config(disk, "ro", default=False)
- d_fmt = get_config(disk, "format", default="raw")
- d_id = f"disk_{i}"
-
- if not os.path.exists(d_img):
- logger.warning(f"AHCI bus: {d_img} not exists, disk skipped")
- continue
-
- cmds += [
- "-drive", join_attrs([
- f"id={d_id},"
- f"file={d_img}",
- f"readonly={'on' if d_ro else 'off'}",
- f"if=none",
- f"format={d_fmt}"
- ]),
- "-device", join_attrs([
- d_type,
- f"drive={d_id}",
- f"bus={name}.{i}"
- ])
- ]
+ disklets = get_config(opt, "disks", default=[])
+ for i, disk in enumerate(disklets):
+ cmds += self.__create_disklet(i, name, disk)
return cmds
super().__init__("monitor", opt)
def get_qemu_opts(self):
- link, logfile = parse_protocol(self._opt)
+
+ chardev = select_backend(self._opt)
return [
- "-monitor", join_attrs([
- link,
- "server",
- "nowait",
- f"logfile={logfile}"
+ "-chardev", chardev.to_cmdline(),
+ "-mon", join_attrs([
+ chardev.name(),
+ "mode=readline",
])
]
-class QEMUExec:
- devices = {
- "basic_serial": BasicSerialDevice,
+class QEMUDevices:
+ __devs = {
+ "isa-serial": ISASerialDevice,
"ahci": AHCIBus,
"rtc": RTCDevice,
"hmp": QEMUMonitor,
"pci-serial": PCISerialDevice
}
+ @staticmethod
+ def get(name):
+ if name not in QEMUDevices.__devs:
+ raise Exception(f"device class: {name} is not defined")
+ return QEMUDevices.__devs[name]
+
+
+
+#################################
+# QEMU Machine Definitions
+#
+
+class QEMUExec:
+
+
def __init__(self, options) -> None:
self._opt = options
self._devices = []
for dev in get_config(options, "devices", default=[]):
dev_class = get_config(dev, "class")
- if dev_class not in QEMUExec.devices:
- raise Exception(f"device class: {dev_class} is not defined")
-
- self._devices.append(QEMUExec.devices[dev_class](dev))
+ device = QEMUDevices.get(dev_class)
+ self._devices.append(device(dev))
def get_qemu_exec_name(self):
pass
def add_peripheral(self, peripheral):
self._devices.append(peripheral)
- def start(self, qemu_dir_override=""):
+ def start(self, qemu_dir_override="", dryrun=False, extras=[]):
qemu_path = self.get_qemu_exec_name()
qemu_path = os.path.join(qemu_dir_override, qemu_path)
cmds = [
for dev in self._devices:
cmds += dev.get_qemu_opts()
+ cmds += extras
print(" ".join(cmds), "\n")
+
+ if dryrun:
+ logger.info("[DRY RUN] QEMU not invoked")
+ return
handle = subprocess.Popen(cmds)
arg.add_argument("config_file")
arg.add_argument("--qemu-dir", default="")
+ arg.add_argument("--dry", action='store_true')
arg.add_argument("-v", "--values", action='append', default=[])
- arg_opt = arg.parse_args()
+ arg_opt, extras = arg.parse_known_args()
opts = {}
with open(arg_opt.config_file, 'r') as f:
else:
raise Exception(f"undefined arch: {arch}")
- q.start(arg_opt.qemu_dir)
+ extras = [ x for x in extras if x != '--']
+ q.start(arg_opt.qemu_dir, arg_opt.dry, extras)
if __name__ == "__main__":
main()
\ No newline at end of file
},
"devices": [
{
- "class": "basic_serial",
- "protocol": "telnet",
- "addr": ":12345",
- "logfile": "lunaix_ttyS0.log"
+ "class": "isa-serial",
+ "io": {
+ "type": "telnet",
+ "port": "12345",
+ "logfile": "lunaix_ttyS0.log"
+ }
},
{
"class": "pci-serial",
- "logfile": "ttyPCI0.log"
+ "io": {
+ "type": "null",
+ "logfile": "ttypci1.log"
+ }
},
{
"class": "pci-serial",
- "logfile": "ttyPCI1.log"
+ "io": {
+ "type": "null",
+ "logfile": "ttypci2.log"
+ }
},
{
"class": "rtc",
},
{
"class": "hmp",
- "protocol": "telnet",
- "addr": ":$QMPORT",
- "logfile": "qm.log"
+ "io": {
+ "type": "telnet",
+ "port": "$QMPORT",
+ "logfile": "qm.log"
+ }
}
]
}
\ No newline at end of file
-@Term
+@Term("Architecture")
def arch():
"""
set the ISA target
include utils.mkinc
include toolchain.mkinc
+
+LCONFIG_FLAGS := --quiet
+
include lunabuild.mkinc
include $(lbuild_mkinc)