From a36c6528b10cb5b5d48fbf30a941443f738c9dd1 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Sun, 1 Apr 2018 23:23:34 +0200 Subject: Terminal inside Lua window manager --- src/kernel/user/syscall.c | 1 + src/lib/include/kogata/mainloop.h | 2 + src/lib/libkogata/mainloop.c | 20 ++++- src/sysapp/login/main.lua | 25 ++++++ src/sysbin/terminal/main.c | 44 +++++++++- src/syslua/lx/gip.lua | 177 ++++++++++++++++++++++++++++++++++++++ src/syslua/lx/gui.lua | 19 ++-- src/syslua/lx/protodef.lua | 29 +++++++ src/syslua/lx/sysdef.lua | 2 + src/syslua/lx/tk.lua | 56 ++++++++++++ 10 files changed, 361 insertions(+), 14 deletions(-) create mode 100644 src/syslua/lx/gip.lua create mode 100644 src/syslua/lx/protodef.lua diff --git a/src/kernel/user/syscall.c b/src/kernel/user/syscall.c index db32fa9..34b8802 100644 --- a/src/kernel/user/syscall.c +++ b/src/kernel/user/syscall.c @@ -724,6 +724,7 @@ void setup_syscall_table() { sc_handlers[SC_SELECT] = select_sc; sc_handlers[SC_MK_CHANNEL] = make_channel_sc; + sc_handlers[SC_MK_SHM] = make_shm_sc; sc_handlers[SC_GEN_TOKEN] = gen_token_sc; sc_handlers[SC_USE_TOKEN] = use_token_sc; diff --git a/src/lib/include/kogata/mainloop.h b/src/lib/include/kogata/mainloop.h index 2b447ef..6352916 100644 --- a/src/lib/include/kogata/mainloop.h +++ b/src/lib/include/kogata/mainloop.h @@ -12,6 +12,7 @@ typedef struct mainloop_fd mainloop_fd_t; typedef void (*buf_full_callback_t)(mainloop_fd_t *fd); typedef void (*fd_error_callback_t)(mainloop_fd_t *fd); +typedef void (*idle_callback_t)(void* data); typedef struct { size_t size, written; @@ -41,6 +42,7 @@ void mainloop_rm_fd(mainloop_fd_t* fd); void mainloop_expect(mainloop_fd_t *fd, void* buf, size_t size, buf_full_callback_t cb); bool mainloop_nonblocking_write(mainloop_fd_t *fd, void* buf, size_t size, bool must_free_buf); +void mainloop_when_idle(idle_callback_t cb, void* data); void mainloop_run(); void mainloop_exit(); diff --git a/src/lib/libkogata/mainloop.c b/src/lib/libkogata/mainloop.c index c1758a4..75fb252 100644 --- a/src/lib/libkogata/mainloop.c +++ b/src/lib/libkogata/mainloop.c @@ -7,6 +7,9 @@ mainloop_fd_t *mainloop_fds = 0; bool mainloop_fds_change = false; bool mainloop_must_exit = false; +idle_callback_t mainloop_idle_cb = 0; +void* mainloop_idle_cb_data = 0; + void mainloop_add_fd(mainloop_fd_t* fd) { mainloop_fds_change = true; @@ -53,12 +56,19 @@ bool mainloop_nonblocking_write(mainloop_fd_t *fd, void* buf, size_t size, bool return false; } +void mainloop_when_idle(idle_callback_t cb, void* data) { + mainloop_idle_cb = cb; + mainloop_idle_cb_data = data; +} + void mainloop_run() { sel_fd_t *sel_arg = 0; int nfds = 0; mainloop_fds_change = true; mainloop_must_exit = false; + + bool after_idle_step = false; while(!mainloop_must_exit) { if (mainloop_fds_change) { nfds = 0; @@ -91,11 +101,12 @@ void mainloop_run() { // ---- Do the select /*dbg_printf("(mainloop) begin select\n");*/ - bool ok = sc_select(sel_arg, nfds, -1); + bool ok = sc_select(sel_arg, nfds, (after_idle_step || mainloop_idle_cb == 0 ? -1 : 0)); if (!ok) { - dbg_printf("(mainloop) Failed to select.\n"); - free(sel_arg); - return; + // nothing happenned + if (mainloop_idle_cb != 0) mainloop_idle_cb(mainloop_idle_cb_data); + after_idle_step = true; + continue; } /*dbg_printf("(mainloop) end select\n");*/ @@ -131,6 +142,7 @@ void mainloop_run() { } i++; } + after_idle_step = false; } } } diff --git a/src/sysapp/login/main.lua b/src/sysapp/login/main.lua index 67ce3fe..3aa0695 100644 --- a/src/sysapp/login/main.lua +++ b/src/sysapp/login/main.lua @@ -145,6 +145,31 @@ local grid = tk.grid({border_size = 1}, { wm:add({title = "Filesystems", x = 300, y = 20}, tk.box({vresize = true, hresize = true, vscroll = true, hscroll = true}, grid)) +function open_terminal(title, binfile) + local term_widget = tk.gipwidget({width = 480, height = 360}) + wm:add({title = title, x = 300, y = 200}, term_widget) + local tty_a, tty_b = sys.make_channel(false) + + local term_bin_pid = sys.new_proc() + sys.bind_fs(term_bin_pid, "sys", "sys") + sys.bind_fs(term_bin_pid, "config", "config") + sys.bind_fd(term_bin_pid, sysdef.STD_FD_GIP, term_widget.ch_cli) + sys.bind_fd(term_bin_pid, sysdef.STD_FD_TTYSRV, tty_a) + sys.dbg_print("Start terminal.bin, pid: " .. tonumber(term_bin_pid)) + sys.proc_exec(term_bin_pid, "sys:/bin/terminal.bin") + + local shell_bin_pid = sys.new_proc() + sys.bind_fs(shell_bin_pid, "root", "root") + sys.bind_fs(shell_bin_pid, "sys", "sys") + sys.bind_fs(shell_bin_pid, "config", "config") + sys.bind_fd(shell_bin_pid, sysdef.STD_FD_TTY_STDIO, tty_b) + sys.dbg_print("Start " .. binfile .. ", pid: " .. tonumber(shell_bin_pid)) + sys.proc_exec(shell_bin_pid, binfile) +end + +open_terminal("Shell", "sys:/bin/shell.bin") +open_terminal("Lua prompt", "sys:/bin/lx.bin") + mainloop.run() os.exit() diff --git a/src/sysbin/terminal/main.c b/src/sysbin/terminal/main.c index 77bf141..604228a 100644 --- a/src/sysbin/terminal/main.c +++ b/src/sysbin/terminal/main.c @@ -39,6 +39,7 @@ typedef struct { fb_t *fb; uint32_t sv_features, cl_features; + fb_region_t damage; font_t *font; int cw, ch; // size of a character @@ -56,6 +57,7 @@ typedef struct { int csrl, csrc; bool csr_visible; + gip_handler_t *handler; mainloop_fd_t app; char rd_c_buf; char wr_c_buf; @@ -72,6 +74,8 @@ void gip_async_initiate(gip_handler_t *s, gip_msg_header *p, void* msgdata, void void term_on_rd_c(mainloop_fd_t *fd); void term_app_on_error(mainloop_fd_t *fd); +void idle_cb(void* data); + gip_handler_callbacks_t term_gip_cb = { .reset = 0, @@ -106,8 +110,11 @@ int main(int argc, char **argv) { term.cw = g_text_width(term.font, "#", 8); term.ch = g_text_height(term.font, "#", 8); + term.damage.x = term.damage.y = term.damage.h = term.damage.w = -1; + gip_handler_t *h = new_gip_handler(&term_gip_cb, &term); ASSERT(h != 0); + term.handler = h; h->mainloop_item.fd = STD_FD_GIP; mainloop_add_fd(&h->mainloop_item); @@ -121,6 +128,8 @@ int main(int argc, char **argv) { gip_msg_header reset_msg = { .code = GIPC_RESET, .arg = 0 }; gip_cmd(h, &reset_msg, 0, gip_async_initiate, 0); + mainloop_when_idle(idle_cb, &term); + mainloop_run(); return 0; @@ -128,6 +137,24 @@ int main(int argc, char **argv) { // Terminal features +void term_damage_at(term_t *t, int x, int y, int w, int h) { + if (t->damage.x == -1) { + t->damage.x = x; + t->damage.y = y; + t->damage.w = w; + t->damage.h = h; + } else { + int px1 = t->damage.x + t->damage.w; + int py1 = t->damage.y + t->damage.h; + int nx1 = (px1 > x+w ? px1 : x+w); + int ny1 = (py1 > y+h ? py1 : y+h); + t->damage.x = (t->damage.x < x ? t->damage.x : x); + t->damage.y = (t->damage.y < y ? t->damage.y : y); + t->damage.w = nx1 - t->damage.x; + t->damage.h = ny1 - t->damage.y; + } +} + void term_draw_c(term_t *t, int l, int c) { color_t bg = t->bg; if (l == t->csrl && c == t->csrc) bg = t->csr_bg; @@ -139,6 +166,7 @@ void term_draw_c(term_t *t, int l, int c) { if (t->fb) { g_fillrect(t->fb, c * t->cw, l * t->ch, t->cw, t->ch, bg); g_write(t->fb, c * t->cw, l * t->ch, ss, t->font, 16, t->fg); + term_damage_at(t, c * t->cw, l * t->ch, t->cw, t->ch); } } @@ -162,8 +190,10 @@ void term_clear_screen(term_t *t) { for (int i = 0; i < t->w * t->h; i++) t->scr_chars[i] = ' '; - if (t->fb) + if (t->fb) { g_fillrect(t->fb, 0, 0, t->mode.width, t->mode.height, t->bg); + term_damage_at(t, 0, 0, t->mode.width, t->mode.height); + } term_move_cursor(t, 0, 0); } @@ -179,6 +209,7 @@ void term_scroll1(term_t *t) { if (t->fb) { g_scroll_up(t->fb, t->ch); g_fillrect(t->fb, 0, t->ch * (t->h - 1), t->cw * t->w, t->ch, t->bg); + term_damage_at(t, 0, 0, t->mode.width, t->mode.height); } } @@ -386,6 +417,17 @@ void term_on_rd_c(mainloop_fd_t *fd) { term_putc(t, t->rd_c_buf); } +void idle_cb(void* data) { + term_t *t = (term_t*)data; + + // we are done processing events ; flush damaged region + if (t->damage.x != -1) { + gip_msg_header damage_msg = { .code = GIPN_BUFFER_DAMAGE, .arg = 0 }; + gip_cmd(t->handler, &damage_msg, &t->damage, 0, 0); + t->damage.x = t->damage.y = t->damage.w = t->damage.h = -1; + } +} + void term_app_on_error(mainloop_fd_t *fd) { // TODO } diff --git a/src/syslua/lx/gip.lua b/src/syslua/lx/gip.lua new file mode 100644 index 0000000..8ae06bf --- /dev/null +++ b/src/syslua/lx/gip.lua @@ -0,0 +1,177 @@ +local sys = require 'lx.sys' +local protodef = require 'lx.protodef' +local mainloop = require 'lx.mainloop' + +local gip = {} + +gip.proto = { + -- Definitions from proto/gip.h + GIPF_DOUBLE_BUFFER = 0x1, + GIPF_DAMAGE_NOTIF = 0x2, + GIPF_MODESET = 0x4, + GIPF_MOUSE_XY = 0x10, + GIPF_MOUSE_CURSOR = 0x20, + + GIPC_RESET = 0, + GIPR_INITIATE = 1, + GIPR_OK = 2, + GIPR_FAILURE = 3, + GIPC_ENABLE_FEATURES = 4, + GIPC_DISABLE_FEATURES = 5, + + GIPN_BUFFER_INFO = 10, + GIPC_QUERY_MODE = 11, + GIPR_MODE_INFO = 12, + GIPC_SET_MODE = 13, + + GIPN_BUFFER_DAMAGE = 14, + GIPC_SWITCH_BUFFER = 15, + + GIPN_KEY_DOWN = 20, + GIPN_KEY_UP = 21, + + GIPN_MOUSE_DATA = 30, + GIPN_MOUSE_XY = 31, + GIPN_MOUSE_PRESSED = 32, + GIPN_MOUSE_RELEASED = 33, + + gip_msg_header = { + fmt = 'LLL', -- code, req_id, arg + len = 12 + }, + -- buffer_info_msg = protodef.token .. protodef.fb_info + buffer_info_msg = { + len = protodef.token.len + protodef.fb_info.len + }, + -- mode_info_msg = protodef.fb_info + mode_info_msg = { + len = protodef.fb_info.len + }, + -- buffer_damage_msg = protodef.fb_region + buffer_damage_msg = { + len = protodef.fb_region.len + }, +} + +gip.new_handler = function(fd) + local h = {} + h.fd = fd + + -- Replacable callbacks + function h:cb_reset(req_id, arg) end + function h:cb_initiate(req_id, arg) end + function h:cb_ok(req_id, arg) end + function h:cb_failure(req_id, arg) end + function h:cb_enable_features(req_id, arg) end + function h:cb_disable_features(req_id, arg) end + function h:cb_query_mode(req_id, arg) end + function h:cb_set_mode(req_id, arg) end + function h:cb_switch_buffer(req_id, arg) end + function h:cb_key_down(req_id, arg) end + function h:cb_key_up(req_id, arg) end + function h:cb_buffer_info(req_id, arg, token, fb_info) end + function h:cb_mode_info(req_id, arg, fb_info) end + function h:cb_buffer_damage(req_id, arg, region) end + function h:cb_unknown_message(code, req_id, arg) error("Unknown GIP message") end + function h:fd_error() error("GIP FD error") end + + h.requests_in_progress = {} + h.msg_id = 0 + + h.mainloop_fd = mainloop.add_fd(h.fd, function() h:fd_error() end) + + function h:send_msg(code, req_id, arg, data) + if req_id == nil then + req_id = h.msg_id + h.msg_id = h.msg_id + 1 + end + + local msgdata = string.pack(gip.proto.gip_msg_header.fmt, code, req_id, arg) + if code == gip.proto.GIPN_BUFFER_INFO then + msgdata = msgdata .. string.pack(protodef.token.fmt, data.tok) + .. string.pack(protodef.fb_info.fmt, data.geom.width, data.geom.height, + data.geom.pitch, data.geom.bpp, data.geom.memory_model) + elseif code == gip.proto.GIPN_MODE_INFO then + error("Not implemented") --TODO + elseif code == gip.proto.GIPN_BUFFER_DAMAGE then + error("Not implemented") --TODO + end + + h.mainloop_fd:write(msgdata) + return req_id + end + + function h:cmd(code, arg, data, cb) + local req_id = h:send_msg(code, nil, arg, data) + h.requests_in_progress[req_id] = cb + end + + function h:got_reply(code, req_id, arg, data) + if h.requests_in_progress[req_id] ~= nil then + h.requests_in_progress[req_id](code, arg, data) + h.requests_in_progress[req_id] = nil + end + end + + local function gip_handler(ev) + local code, req_id, arg = string.unpack(gip.proto.gip_msg_header.fmt, ev) + + if code == gip.proto.GIPC_RESET then + h:cb_reset(req_id, arg) + elseif code == gip.proto.GIPR_INITIATE then + h:cb_initiate(req_id, arg) + h:got_reply(code, req_id, arg) + elseif code == gip.proto.GIPR_OK then + h:cb_ok(req_id, arg) + h:got_reply(code, req_id, arg) + elseif code == gip.proto.GIPR_FAILURE then + h:cb_failure(req_id, arg) + h:got_reply(code, req_id, arg) + elseif code == gip.proto.GIPC_ENABLE_FEATURE then + h:cb_enable_feature(req_id, arg) + elseif code == gip.proto.GIPC_DISABLE_FEATURE then + h:cb_disable_feature(req_id, arg) + elseif code == gip.proto.GIPC_QUERY_MODE then + h:cb_query_mode(req_id, arg) + elseif code == gip.proto.GIPC_SET_MODE then + h:cb_set_mode(req_id, arg) + elseif code == gip.proto.GIPC_SWITCH_BUFFER then + h:cb_switch_buffer(req_id, arg) + elseif code == gip.proto.GIPN_KEY_DOWN then + h:cb_key_down(req_id, arg) + elseif code == gip.proto.GIPN_KEY_UP then + h:cb_key_up(req_id, arg) + elseif code == gip.proto.GIPN_BUFFER_INFO then + h.mainloop_fd:expect(gip.proto.buffer_info_msg.len, function(msg) + local tok, p1 = string.unpack(protodef.token.fmt, msg) + local w, h, pitch, bpp, mm = string.unpack(protodef.fb_info.fmt, msg, p1) + h:cb_buffer_info(req_id, arg, token, { + width = w, height = h, + pitch = pitch, bpp = bpp, memory_model = mm }) + end) + elseif code == gip.proto.GIPR_MODE_INFO then + h.mainloop_fd:expect(gip.proto.mode_info_msg.len, function(msg) + local w, h, pitch, bpp, mm = string.unpack(protodef.fb_info.fmt, msg) + local info = { + width = w, height = h, + pitch = pitch, bpp = bpp, memory_model = mm } + h:cb_mode_info(req_id, arg, info) + h:got_reply(code, req_id, arg, info) + end) + elseif code == gip.proto.GIPN_BUFFER_DAMAGE then + h.mainloop_fd:expect(gip.proto.buffer_damage_msg.len, function(msg) + local rx, ry, rw, rh = string.unpack(protodef.fb_region.fmt, msg) + h:cb_buffer_damage(req_id, arg, { x = rx, y = ry, w = rw, h = rh }) + end) + else + h:cb_unknown_message(code, req_id, arg) + end + + h.mainloop_fd:expect(gip.proto.gip_msg_header.len, gip_handler) + end + h.mainloop_fd:expect(gip.proto.gip_msg_header.len, gip_handler) + + return h +end + +return gip diff --git a/src/syslua/lx/gui.lua b/src/syslua/lx/gui.lua index 6cfb545..9342025 100644 --- a/src/syslua/lx/gui.lua +++ b/src/syslua/lx/gui.lua @@ -1,5 +1,6 @@ local sys = require 'lx.sys' -local sysdef= require 'lx.sysdef' +local sysdef = require 'lx.sysdef' +local protodef = require 'lx.protodef' local ioctl = require 'lx.ioctl' local draw = require 'lx.draw' @@ -44,12 +45,12 @@ function gui.open_io() gui.pckbd_mainloop_fd = mainloop.add_fd(gui.pckbd_fd, function() error('pckbd fd error') end) local function pckbd_handler(ev) - local scancode, ty = string.unpack("HH", ev) + local scancode, ty = string.unpack(protodef.kbd_event.fmt, ev) gui.on_keyboard(scancode, ty) - gui.pckbd_mainloop_fd:expect(4, pckbd_handler) + gui.pckbd_mainloop_fd:expect(protodef.kbd_event.len, pckbd_handler) end - gui.pckbd_mainloop_fd:expect(4, pckbd_handler) + gui.pckbd_mainloop_fd:expect(protodef.kbd_event.len, pckbd_handler) gui.kbdlib = kbd.init() @@ -59,13 +60,13 @@ function gui.open_io() gui.pcmouse_mainloop_fd = mainloop.add_fd(gui.pcmouse_fd, function() error("pcmouse fd error") end) local function pcmouse_handler(ev) - local dx, dy, dw, lb, rb, mb = string.unpack("hhbBBB", ev) + local dx, dy, dw, lb, rb, mb = string.unpack(protodef.mouse_event.fmt, ev) local change_but = false while not change_but do - local bytes, n = sys.read(gui.pcmouse_fd, 1, 8) + local bytes, n = sys.read(gui.pcmouse_fd, 1, protodef.mouse_event.len) if n == 8 then - local dx2, dy2, dw2, lb2, rb2, mb2 = string.unpack("hhbBBB", ev) + local dx2, dy2, dw2, lb2, rb2, mb2 = string.unpack(protodef.mouse_event.fmt, ev) change_but = change_but or (lb2 ~= lb) or (rb2 ~= rb) or (mb2 ~= mb) dx = dx + dx2 dy = dy + dy2 @@ -78,9 +79,9 @@ function gui.open_io() end gui.on_mouse(dx, dy, dw, lb, rb, mb) - gui.pcmouse_mainloop_fd:expect(8, pcmouse_handler) + gui.pcmouse_mainloop_fd:expect(protodef.mouse_event.len, pcmouse_handler) end - gui.pcmouse_mainloop_fd:expect(8, pcmouse_handler) + gui.pcmouse_mainloop_fd:expect(protodef.mouse_event.len, pcmouse_handler) end function gui.open_gip() diff --git a/src/syslua/lx/protodef.lua b/src/syslua/lx/protodef.lua new file mode 100644 index 0000000..f7f3fc0 --- /dev/null +++ b/src/syslua/lx/protodef.lua @@ -0,0 +1,29 @@ +local protodef = { + mouse_event = { + -- common/include/proto/mouse.h + fmt = 'hhbBBB', + len = 8 + }, + kbd_event = { + -- common/include/proto/keyboard.h + fmt = 'HH', + len = 4 + }, + token = { + -- common/include/proto/token.h + fmt = 'c16', + len = 16 + } +} + +-- common/include/proto/fb.h +protodef.fb_info = { + fmt = 'lllll', + len = 20 +} +protodef.fb_region = { + fmt = 'llll', + len = 16 +} + +return protodef diff --git a/src/syslua/lx/sysdef.lua b/src/syslua/lx/sysdef.lua index 43015e2..e2144bc 100644 --- a/src/syslua/lx/sysdef.lua +++ b/src/syslua/lx/sysdef.lua @@ -62,4 +62,6 @@ return { PS_FINISHED = 3, PS_FAILURE = 4, PS_KILLED = 5, + } + diff --git a/src/syslua/lx/tk.lua b/src/syslua/lx/tk.lua index e8686ba..f38efe2 100644 --- a/src/syslua/lx/tk.lua +++ b/src/syslua/lx/tk.lua @@ -1,5 +1,8 @@ local draw = require 'lx.draw' local sys = require 'lx.sys' +local gui = require 'lx.gui' +local gip = require 'lx.gip' +local kbdcode = require 'lx.kbdcode' local tk = {} @@ -225,6 +228,10 @@ function tk.widget(width, height, opts) self.left_click_valid = false end + function w:on_keyboard(scancode, ty) + -- Handler for raw keyboard event + end + function w:on_text_input(char) -- Handler for text input end @@ -266,6 +273,12 @@ function tk.init(gui, root_widget) root_widget:do_resize(gui.surface:width(), gui.surface:height()) + local prev_gui_on_keyboard = gui.on_keyboard + gui.on_keyboard = function(scancode, ty) + root_widget:on_keyboard(scancode, ty) + prev_gui_on_keyboard(scancode, ty) + end + gui.on_key_down = function(key) root_widget:on_key_down(key) end gui.on_key_up = function(key) root_widget:on_key_up(key) end gui.on_text_input = function(key) root_widget:on_text_input(char) end @@ -919,8 +932,51 @@ function tk.window_manager() end end + function wm:on_keyboard(scancode, ty) + wm.windows[#wm.windows].content:on_keyboard(scancode, ty) + end + return wm end +function tk.gipwidget(opts) + local w = tk.widget(nil, nil, opts) + + w.geom = { + bpp = gui.surface_geom.bpp, + memory_model = gui.surface_geom.memory_model, + width = opts.width, + height = opts.height, + pitch = opts.width * gui.surface_geom.bpp // 8 + } + w.shm_fd = sys.make_shm(w.geom.height * w.geom.pitch) + w.framebuffer = draw.surface_from_fd(w.shm_fd, w.geom) + + w.ch_srv, w.ch_cli = sys.make_channel(false) + w.gip_handler = gip.new_handler(w.ch_srv) + + function w.gip_handler:cb_reset(req_id, arg) + w.gip_handler:send_msg(gip.proto.GIPR_INITIATE, req_id, gip.proto.GIPF_DAMAGE_NOTIF) + w.gip_handler:send_msg(gip.proto.GIPN_BUFFER_INFO, 0, 0, { tok = sys.gen_token(w.shm_fd), geom = w.geom }) + end + + function w.gip_handler:cb_buffer_damage(req_id, arg, region) + w:redraw(region.x, region.y, region.w, region.h) + end + + function w:draw(x0, y0, buf) + if x0 < self.framebuffer:width() and y0 < self.framebuffer:height() then + buf:blit(0, 0, self.framebuffer:sub(x0, y0, self.framebuffer:width(), self.framebuffer:height())) + end + end + + function w:on_keyboard(scancode, ty) + sys.dbg_print("gipwidget:on_keyboard " .. tostring(scancode) .. "\n") + w.gip_handler:send_msg(ty == kbdcode.event.KEYPRESS and gip.proto.GIPN_KEY_DOWN or gip.proto.GIPN_KEY_UP, nil, scancode) + end + + return w +end + return tk -- cgit v1.2.3