├── .vscode ├── .gitignore ├── tasks.json ├── launch.json └── settings.json ├── .gitignore ├── review-profile.sh ├── util ├── md5.h ├── crc32.h ├── crc32.cpp └── md5.cpp ├── apis ├── os.cpp ├── os.h ├── native-lua.cpp ├── native-lua.h ├── system.h ├── userdata.h ├── system.cpp ├── unicode.h └── userdata.cpp ├── run-profiler.sh ├── drivers ├── basic_term.h ├── basic_term.cpp ├── mouse_drv.h ├── term_buffer.h ├── factory_shell.cpp ├── kb_drv.h ├── ansi_escape.h ├── term_buffer.cpp ├── worker.h ├── raw_tty.h ├── server_pool.h ├── modem_drv.h ├── internet_http.h ├── fs_utils.h ├── ansi.h ├── connection.h ├── worker.cpp ├── mouse_drv.cpp ├── kb_data.h ├── internet_drv.h ├── ansi.cpp ├── modem_drv.cpp ├── ansi_escape.cpp ├── server_pool.cpp ├── raw_tty.cpp ├── internet_drv.cpp ├── connection.cpp ├── internet_http.cpp ├── kb_drv.cpp └── fs_utils.cpp ├── haiku ├── wordexp.h ├── wordexp.cpp ├── filesystem.h └── filesystem.cpp ├── color ├── color_map.h ├── color_types.h └── color_map.cpp ├── components ├── sandbox.h ├── data_card.h ├── keyboard.h ├── internet.h ├── eeprom.h ├── data_card.cpp ├── drive.h ├── component.h ├── modem.h ├── screen.h ├── filesystem.h ├── sandbox.cpp ├── keyboard.cpp ├── computer.h ├── component.cpp ├── gpu.h ├── internet.cpp ├── eeprom.cpp ├── screen.cpp └── drive.cpp ├── model ├── prof_log.h ├── config.h ├── log.cpp ├── host.h ├── luaproxy.h ├── log.h ├── prof_log.cpp ├── client.h ├── host.cpp ├── luaproxy.cpp ├── config.cpp ├── value.h └── client.cpp ├── io ├── event.h ├── frame.cpp └── frame.h ├── client.cfg ├── README.md ├── Makefile ├── .clang-format └── main.cpp /.vscode/.gitignore: -------------------------------------------------------------------------------- 1 | *.vc.db 2 | *.vc.db* 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/** 2 | ocvm** 3 | system/** 4 | /log 5 | stack.log 6 | tmp/** 7 | -------------------------------------------------------------------------------- /review-profile.sh: -------------------------------------------------------------------------------- 1 | ../gperftools-2.5/src/pprof -gv ocvm-profiled ocvm.prof 2 | 3 | -------------------------------------------------------------------------------- /util/md5.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace util 5 | { 6 | std::vector md5(std::vector input); 7 | }; 8 | -------------------------------------------------------------------------------- /util/crc32.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | using std::vector; 5 | 6 | namespace util 7 | { 8 | uint32_t crc32(vector data); 9 | } -------------------------------------------------------------------------------- /apis/os.cpp: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | OSApi::OSApi() 4 | : LuaProxy("os") 5 | { 6 | } 7 | 8 | OSApi* OSApi::get() 9 | { 10 | static OSApi it; 11 | return ⁢ 12 | } 13 | -------------------------------------------------------------------------------- /apis/os.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "model/luaproxy.h" 3 | 4 | class OSApi : public LuaProxy 5 | { 6 | public: 7 | static OSApi* get(); 8 | 9 | private: 10 | OSApi(); 11 | }; 12 | -------------------------------------------------------------------------------- /run-profiler.sh: -------------------------------------------------------------------------------- 1 | make prof=1 && env LD_PRELOAD=../gperftools-2.5/.libs/libprofiler.so LD_LIBRARY_PATH=~/code/gperftools-2.5/.libs CPUPROFILE_FREQUENCY=10000 CPUPROFILE=ocvm.prof ./ocvm-profiled tmp --frame=basic 2 | 3 | -------------------------------------------------------------------------------- /drivers/basic_term.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "io/frame.h" 4 | 5 | class BasicTerm : public Frame 6 | { 7 | protected: 8 | void onWrite(int x, int y, const Cell& cell, ColorState& cst) override; 9 | tuple onOpen() override; 10 | }; 11 | -------------------------------------------------------------------------------- /haiku/wordexp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct wordexp_t 7 | { 8 | int we_wordc; 9 | std::vector we_wordv; 10 | }; 11 | 12 | bool wordexp(const char* cpath, wordexp_t* pWord, int flags); 13 | void wordfree(wordexp_t* pWord); 14 | -------------------------------------------------------------------------------- /drivers/basic_term.cpp: -------------------------------------------------------------------------------- 1 | #include "basic_term.h" 2 | #include 3 | 4 | void BasicTerm::onWrite(int x, int y, const Cell& cell, ColorState& cst) 5 | { 6 | std::cout << cell.value; 7 | } 8 | 9 | tuple BasicTerm::onOpen() 10 | { 11 | return std::make_tuple(40, 10); 12 | } 13 | -------------------------------------------------------------------------------- /drivers/mouse_drv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "io/event.h" 4 | #include 5 | using std::vector; 6 | 7 | class TermBuffer; 8 | class MouseTerminalDriver 9 | { 10 | public: 11 | vector parse(TermBuffer* buffer); 12 | 13 | private: 14 | int _pressed = 0x3; 15 | bool _dragging = false; 16 | }; 17 | -------------------------------------------------------------------------------- /drivers/term_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | using std::deque; 5 | using std::size_t; 6 | 7 | class TermBuffer 8 | { 9 | public: 10 | size_t size() const; 11 | void push(char ch); 12 | char get(); 13 | char peek(size_t offset = 0) const; 14 | bool hasMouseCode() const; 15 | 16 | private: 17 | deque _data; 18 | }; 19 | -------------------------------------------------------------------------------- /apis/native-lua.cpp: -------------------------------------------------------------------------------- 1 | #include "native-lua.h" 2 | 3 | int ocvm_resume(lua_State *L, lua_State *from, int narg, int *nres) 4 | { 5 | assert(nres != nullptr); 6 | #if LUA_VERSION_NUM == 504 7 | return lua_resume(L, from, narg, nres); 8 | #else 9 | int status = lua_resume(L, from, narg); 10 | *nres = lua_gettop(L); 11 | return status; 12 | #endif 13 | } 14 | -------------------------------------------------------------------------------- /drivers/factory_shell.cpp: -------------------------------------------------------------------------------- 1 | #include "ansi_escape.h" 2 | #include "basic_term.h" 3 | 4 | Frame* Factory::create_frame(const string& frameTypeName) 5 | { 6 | if (frameTypeName == "basic") 7 | { 8 | return new BasicTerm; 9 | } 10 | else if (frameTypeName == "ansi") 11 | { 12 | return new AnsiEscapeTerm; 13 | } 14 | 15 | return nullptr; 16 | } 17 | -------------------------------------------------------------------------------- /color/color_map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "color_types.h" 4 | 5 | class ColorMap 6 | { 7 | public: 8 | static int inflate(const ColorState& state, int value); 9 | static int deflate(const ColorState& state, const Color& color); 10 | 11 | static int deflate(int rgb); 12 | 13 | static void initialize_color_state(ColorState& state, EDepthType depth); 14 | static void set_monochrome(int rgb); 15 | }; 16 | -------------------------------------------------------------------------------- /components/sandbox.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "component.h" 3 | 4 | class Client; 5 | 6 | class Sandbox : public Component 7 | { 8 | public: 9 | Sandbox(); 10 | 11 | int add_component(lua_State* lua); 12 | int remove_component(lua_State* lua); 13 | int log(lua_State* lua); 14 | int state_name(lua_State* lua); 15 | 16 | protected: 17 | bool onInitialize() override; 18 | 19 | private: 20 | static bool s_registered; 21 | }; 22 | -------------------------------------------------------------------------------- /drivers/kb_drv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "io/event.h" 4 | 5 | #include 6 | using std::vector; 7 | 8 | class TermBuffer; 9 | 10 | class KeyboardTerminalDriver 11 | { 12 | public: 13 | virtual vector parse(TermBuffer* buffer) = 0; 14 | virtual vector idle() = 0; 15 | virtual ~KeyboardTerminalDriver() = default; 16 | 17 | static std::unique_ptr create(bool bMaster); 18 | }; 19 | -------------------------------------------------------------------------------- /model/prof_log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | using std::string; 6 | using std::vector; 7 | 8 | class ProfLog 9 | { 10 | public: 11 | ~ProfLog(); 12 | void release(void* ptr); 13 | void trace(const string& stacktrace, void* ptr, size_t size); 14 | void flush(); 15 | bool open(const string& filename); 16 | bool is_open(); 17 | 18 | private: 19 | void push(const string& text); 20 | vector _buffer; 21 | string _filename; 22 | }; 23 | -------------------------------------------------------------------------------- /color/color_types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Color 6 | { 7 | int rgb; 8 | bool paletted; 9 | unsigned code; 10 | }; 11 | 12 | namespace Colors 13 | { 14 | const Color Black{ 0x000000 }; 15 | const Color White{ 0xffffff }; 16 | }; 17 | 18 | enum class EDepthType 19 | { 20 | _1 = 1, 21 | _4 = 4, 22 | _8 = 8 23 | }; 24 | 25 | struct ColorState 26 | { 27 | enum 28 | { 29 | PALETTE_SIZE = 16 30 | }; 31 | int palette[PALETTE_SIZE]; 32 | EDepthType depth; 33 | }; 34 | -------------------------------------------------------------------------------- /apis/native-lua.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // lua.hpp 4 | // Lua header files for C++ 5 | // <> not supplied automatically because Lua also compiles as C++ 6 | 7 | #if __has_include("lua.hpp") 8 | #include 9 | #else 10 | extern "C" { 11 | #include "lua.h" 12 | #include "lualib.h" 13 | #include "lauxlib.h" 14 | } 15 | #endif 16 | 17 | #undef NEDBUG 18 | #include 19 | 20 | // lua 5.3 -> 5.4 changes lua_resume 21 | 22 | struct lua_State; 23 | int ocvm_resume(lua_State *L, lua_State *from, int narg, int *nres); 24 | 25 | -------------------------------------------------------------------------------- /apis/system.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "model/luaproxy.h" 3 | 4 | class SystemApi : public LuaProxy 5 | { 6 | public: 7 | static SystemApi* get(); 8 | 9 | static int timeout(lua_State* lua); 10 | static int allowGC(lua_State* lua); 11 | static int allowBytecode(lua_State* lua); 12 | 13 | static int max_connections(); 14 | 15 | static void configure(const Value& settings); 16 | 17 | private: 18 | SystemApi(); 19 | 20 | static double _timeout; 21 | static bool _gc; 22 | static bool _bytecode; 23 | static int _max_connections; 24 | }; 25 | -------------------------------------------------------------------------------- /components/data_card.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "component.h" 3 | #include "model/value.h" 4 | #include 5 | #include 6 | 7 | class DataCard : public Component 8 | { 9 | public: 10 | DataCard(); 11 | ~DataCard() override; 12 | 13 | enum ConfigIndex 14 | { 15 | Tier = Component::ConfigIndex::Next, 16 | }; 17 | 18 | // Tier 1 19 | int crc32(lua_State* lua); 20 | int md5(lua_State* lua); 21 | 22 | protected: 23 | bool onInitialize() override; 24 | 25 | int _tier = 1; 26 | 27 | private: 28 | static bool s_registered; 29 | }; 30 | -------------------------------------------------------------------------------- /haiku/wordexp.cpp: -------------------------------------------------------------------------------- 1 | #include "wordexp.h" 2 | 3 | bool wordexp(const char* cpath, wordexp_t* pWord, int flags) 4 | { 5 | pWord->we_wordc = 0; 6 | pWord->we_wordv.clear(); 7 | 8 | if (cpath == nullptr) 9 | return false; 10 | 11 | if (cpath[0] == '~') 12 | { 13 | static std::string home = "/boot/home"; 14 | std::string right(static_cast(cpath + 1)); 15 | pWord->we_wordv.push_back(home + right); 16 | pWord->we_wordc++; 17 | } 18 | 19 | return true; 20 | } 21 | 22 | void wordfree(wordexp_t* pWord) 23 | { 24 | // no op 25 | } 26 | -------------------------------------------------------------------------------- /model/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "value.h" 4 | 5 | class Logger; 6 | 7 | class Config 8 | { 9 | public: 10 | Config(); 11 | 12 | const Value& get(const string& key) const; 13 | Value& get(const string& key); 14 | bool set(const string& key, const Value& value, bool bCreateOnly = false); 15 | 16 | bool load(const string& path, const string& name); 17 | bool save() const; 18 | string name() const; 19 | vector keys() const; 20 | 21 | private: 22 | string savePath() const; 23 | void clear_n(Value& t); 24 | Value _data; 25 | string _path; 26 | string _name; 27 | string _cache; 28 | }; 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "presentation": { 4 | "echo": true, 5 | "reveal": "always", 6 | "focus": false, 7 | "panel": "shared", 8 | "showReuseMessage": false 9 | }, 10 | "tasks": [ 11 | { 12 | "label": "build", 13 | "type": "shell", 14 | "command": "make", 15 | "args": ["-j"], 16 | "group": {"kind": "build", "isDefault": true}, 17 | "problemMatcher": { 18 | "base": "$gcc", 19 | "fileLocation": "relative" 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /components/keyboard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "component.h" 3 | #include "io/event.h" 4 | #include "model/value.h" 5 | 6 | class Screen; 7 | 8 | class Keyboard : public Component, public EventSource 9 | { 10 | public: 11 | enum ConfigIndex 12 | { 13 | ScreenAddress = Component::ConfigIndex::Next 14 | }; 15 | 16 | Keyboard(); 17 | ~Keyboard(); 18 | bool postInit() override; 19 | RunState update() override; 20 | void detach(); 21 | 22 | protected: 23 | bool onInitialize() override; 24 | Screen* screen() const; 25 | 26 | private: 27 | string _preferredScreen; 28 | 29 | static bool s_registered; 30 | }; 31 | -------------------------------------------------------------------------------- /drivers/ansi_escape.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "io/frame.h" 4 | #include "raw_tty.h" 5 | 6 | #include 7 | 8 | class AnsiEscapeTerm : public Frame 9 | { 10 | public: 11 | AnsiEscapeTerm(); 12 | virtual ~AnsiEscapeTerm(); 13 | 14 | protected: 15 | void onWrite(int x, int y, const Cell& cell, ColorState& cst) override; 16 | virtual tuple onOpen() override; 17 | void onUpdate() override; 18 | void onClose() override; 19 | void onClear() override; 20 | 21 | private: 22 | string scrub(const string& value) const; 23 | 24 | int _x = 1; 25 | int _y = 1; 26 | int _fg_rgb = 0; 27 | int _bg_rgb = 0; 28 | }; 29 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "gdb" 6 | ,"type": "cppdbg" 7 | ,"request": "launch" 8 | ,"MIMode": "gdb" 9 | ,"program": "${workspaceRoot}/ocvm" 10 | ,"args": [ ] 11 | ,"cwd": "${workspaceRoot}" 12 | ,"externalConsole": true 13 | ,"setupCommands": [ 14 | {"text": "set print array on"}, 15 | {"text": "set print pretty on"}, 16 | {"text": "set print elements 0"}, 17 | {"text": "-enable-pretty-printing"} 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /drivers/term_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "term_buffer.h" 2 | #include "ansi.h" 3 | 4 | size_t TermBuffer::size() const 5 | { 6 | return _data.size(); 7 | } 8 | 9 | void TermBuffer::push(char ch) 10 | { 11 | _data.push_back(ch); 12 | } 13 | 14 | char TermBuffer::get() 15 | { 16 | if (size() == 0) 17 | return 0; 18 | char ch = _data.at(0); 19 | _data.pop_front(); 20 | return ch; 21 | } 22 | 23 | char TermBuffer::peek(size_t offset) const 24 | { 25 | if (offset >= size()) 26 | return 0; 27 | return _data.at(offset); 28 | } 29 | 30 | bool TermBuffer::hasMouseCode() const 31 | { 32 | if (size() > 3 && peek(0) == Ansi::ESC && peek(1) == '[' && peek(2) == 'M') 33 | { 34 | return true; 35 | } 36 | return false; 37 | } 38 | -------------------------------------------------------------------------------- /drivers/worker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | using std::mutex; 7 | using std::thread; 8 | using std::unique_lock; 9 | 10 | class NiceWork 11 | { 12 | public: 13 | void set(); 14 | ~NiceWork(); 15 | 16 | private: 17 | std::atomic_bool _work_done { false }; 18 | }; 19 | 20 | class Worker 21 | { 22 | public: 23 | virtual ~Worker() = default; 24 | bool isRunning(); 25 | 26 | bool start(); 27 | void stop(); 28 | 29 | protected: 30 | virtual bool onStart() = 0; 31 | virtual bool runOnce() = 0; 32 | virtual void onStop() = 0; 33 | 34 | unique_lock make_lock(); 35 | 36 | private: 37 | void proc(); 38 | volatile bool _continue = false; 39 | thread* _pthread = nullptr; 40 | bool _running = false; 41 | mutex _m; 42 | }; 43 | -------------------------------------------------------------------------------- /drivers/raw_tty.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kb_drv.h" 4 | #include "mouse_drv.h" 5 | #include "term_buffer.h" 6 | #include "worker.h" 7 | 8 | #include 9 | using std::unique_ptr; 10 | 11 | struct termios; 12 | class AnsiEscapeTerm; 13 | 14 | class TtyReader : public Worker 15 | { 16 | public: 17 | TtyReader(TtyReader&) = delete; 18 | void operator=(TtyReader&) = delete; 19 | 20 | static TtyReader* engine(); 21 | void start(AnsiEscapeTerm* pTerm); 22 | 23 | private: 24 | bool hasMasterTty() const; 25 | bool hasTerminalOut() const; 26 | TtyReader(); 27 | bool onStart() override; 28 | bool runOnce() override; 29 | void onStop() override; 30 | 31 | bool _master_tty; 32 | bool _terminal_out; 33 | TermBuffer _buffer; 34 | 35 | unique_ptr _mouse_drv; 36 | unique_ptr _kb_drv; 37 | 38 | AnsiEscapeTerm* _pTerm = nullptr; 39 | }; 40 | -------------------------------------------------------------------------------- /drivers/server_pool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "connection.h" 9 | #include "worker.h" 10 | 11 | class FileLock 12 | { 13 | public: 14 | static std::unique_ptr create(const std::string& path); 15 | ~FileLock(); 16 | 17 | private: 18 | FileLock(const std::string& path, int fd); 19 | std::string _path; 20 | int _fd; 21 | }; 22 | 23 | class ServerPool : public Worker 24 | { 25 | public: 26 | static std::unique_ptr create(int system_port); 27 | virtual ~ServerPool(); 28 | 29 | protected: 30 | ServerPool(int id, std::unique_ptr lock); 31 | bool onStart() override; 32 | bool runOnce() override; 33 | void onStop() override; 34 | bool remove(int id); 35 | 36 | private: 37 | std::map _connections; 38 | int _id; 39 | std::unique_ptr _lock; 40 | }; 41 | -------------------------------------------------------------------------------- /drivers/modem_drv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | using std::string; 7 | using std::unique_ptr; 8 | using std::vector; 9 | 10 | #include "io/event.h" 11 | #include "worker.h" 12 | 13 | #include "connection.h" 14 | 15 | class ServerPool; 16 | class Connection; 17 | 18 | class ModemDriver : public Worker 19 | { 20 | public: 21 | ModemDriver(EventSource* source, int system_port, const std::string& system_address); 22 | ~ModemDriver(); 23 | bool send(const vector& payload); 24 | 25 | protected: 26 | bool onStart() override; 27 | bool runOnce() override; 28 | void onStop() override; 29 | bool readNextModemMessage(ModemEvent& mev); 30 | 31 | private: 32 | // ctor assigned 33 | EventSource* _source; 34 | int _system_port; 35 | std::string _system_address; 36 | 37 | // default values 38 | unique_ptr _local_server; 39 | unique_ptr _connection; 40 | }; 41 | -------------------------------------------------------------------------------- /components/internet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "component.h" 3 | #include "model/value.h" 4 | 5 | #include "drivers/connection.h" 6 | 7 | #include 8 | using std::set; 9 | 10 | class InternetConnection; 11 | class Internet : public Component 12 | { 13 | public: 14 | enum ConfigIndex 15 | { 16 | TcpEnabled = Component::ConfigIndex::Next, 17 | HttpEnabled, 18 | }; 19 | 20 | int isTcpEnabled(lua_State*); 21 | int isHttpEnabled(lua_State*); 22 | int connect(lua_State*); 23 | int request(lua_State*); 24 | 25 | bool release(InternetConnection* pConn); 26 | void monitor_connection(InternetConnection* inet); 27 | void monitor_data(InternetConnection* inet); 28 | 29 | Internet(); 30 | ~Internet(); 31 | 32 | protected: 33 | bool onInitialize() override; 34 | RunState update() override; 35 | 36 | bool parsePort(string* pAddr, int* pPort) const; 37 | 38 | private: 39 | bool _tcp; 40 | bool _http; 41 | 42 | set _connections; 43 | 44 | static bool s_registered; 45 | }; 46 | -------------------------------------------------------------------------------- /drivers/internet_http.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "internet_drv.h" 4 | 5 | class PipedCommand 6 | { 7 | public: 8 | PipedCommand(); 9 | virtual ~PipedCommand(); 10 | bool open(const string& command, const vector& args); 11 | Connection* stdin() const; 12 | Connection* stdout() const; 13 | Connection* stderr() const; 14 | int id() const; 15 | void close(); 16 | 17 | private: 18 | unique_ptr _stdin; 19 | unique_ptr _stdout; 20 | unique_ptr _stderr; 21 | pid_t _child_id; 22 | }; 23 | 24 | class HttpObject : public InternetConnection 25 | { 26 | public: 27 | HttpObject(const HttpAddress& addr, const string& post, const map& header); 28 | int read(lua_State* lua); 29 | int response(lua_State* lua); 30 | 31 | protected: 32 | bool update() override; 33 | Connection* connection() const override; 34 | 35 | private: 36 | bool _response_ready; 37 | ValuePack _response; 38 | PipedCommand _cmd; 39 | 40 | static bool s_registered; 41 | }; 42 | -------------------------------------------------------------------------------- /apis/userdata.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "model/luaproxy.h" 4 | #include 5 | 6 | class UserData : public LuaProxy 7 | { 8 | public: 9 | UserData(); 10 | virtual ~UserData() = default; 11 | virtual void dispose() = 0; 12 | private: 13 | }; 14 | 15 | class UserDataApi : public LuaProxy 16 | { 17 | public: 18 | static UserDataApi* get(); 19 | static int methods(lua_State* lua); 20 | static int invoke(lua_State* lua); 21 | static int dispose(lua_State* lua); 22 | static int apply(lua_State* lua); 23 | static int unapply(lua_State* lua); 24 | static int call(lua_State* lua); 25 | static int save(lua_State* lua); 26 | static int load(lua_State* lua); 27 | static int doc(lua_State* lua); 28 | 29 | private: 30 | UserDataApi(); 31 | }; 32 | 33 | class UserDataAllocator 34 | { 35 | public: 36 | UserDataAllocator(lua_State* lua) 37 | : _lua(lua) 38 | { 39 | } 40 | 41 | UserData* operator()(size_t n) const 42 | { 43 | return reinterpret_cast(lua_newuserdata(_lua, n)); 44 | } 45 | 46 | private: 47 | lua_State* _lua; 48 | }; 49 | -------------------------------------------------------------------------------- /components/eeprom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "component.h" 3 | #include "model/value.h" 4 | 5 | class Eeprom : public Component 6 | { 7 | public: 8 | Eeprom(); 9 | 10 | enum ConfigIndex 11 | { 12 | BiosSize = Component::ConfigIndex::Next, 13 | DataSize, 14 | Label 15 | }; 16 | 17 | int get(lua_State* lua); 18 | int set(lua_State* lua); 19 | int getData(lua_State* lua); 20 | int setData(lua_State* lua); 21 | int getSize(lua_State* lua); 22 | int getDataSize(lua_State* lua); 23 | int getLabel(lua_State* lua); 24 | int setLabel(lua_State* lua); 25 | int getChecksum(lua_State* lua); 26 | 27 | protected: 28 | bool onInitialize() override; 29 | string biosPath() const; 30 | string dataPath() const; 31 | vector load(const string& path) const; 32 | bool postInit() override; 33 | 34 | Value getDeviceInfo() const override; 35 | 36 | private: 37 | string _dir; // real on disk storage location 38 | 39 | int _bios_size_limit = 1024 * 4; // also read from configuration 40 | int _data_size_limit = 256; 41 | 42 | static bool s_registered; 43 | }; 44 | -------------------------------------------------------------------------------- /components/data_card.cpp: -------------------------------------------------------------------------------- 1 | #include "data_card.h" 2 | 3 | #include "model/host.h" 4 | #include "model/log.h" 5 | 6 | #include "util/crc32.h" 7 | #include "util/md5.h" 8 | 9 | bool DataCard::s_registered = Host::registerComponentType("data"); 10 | 11 | DataCard::DataCard() 12 | { 13 | add("crc32", &DataCard::crc32); 14 | add("md5", &DataCard::md5); 15 | } 16 | 17 | DataCard::~DataCard() 18 | { 19 | } 20 | 21 | bool DataCard::onInitialize() 22 | { 23 | int config_tier = config().get(ConfigIndex::Tier).toNumber(); 24 | 25 | _tier = config_tier == 0 ? _tier : config_tier; 26 | 27 | return true; 28 | } 29 | 30 | int DataCard::crc32(lua_State* lua) 31 | { 32 | vector value = Value::checkArg>(lua, 1); 33 | uint32_t crc = util::crc32(value); 34 | 35 | vector ret{ 36 | (char)((crc >> 24) & 0xFF), 37 | (char)((crc >> 16) & 0xFF), 38 | (char)((crc >> 8) & 0xFF), 39 | (char)(crc & 0xFF), 40 | }; 41 | 42 | return ValuePack::ret(lua, ret); 43 | } 44 | 45 | int DataCard::md5(lua_State* lua) 46 | { 47 | vector value = Value::checkArg>(lua, 1); 48 | 49 | return ValuePack::ret(lua, util::md5(value)); 50 | } 51 | -------------------------------------------------------------------------------- /components/drive.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "component.h" 3 | 4 | #include 5 | #include 6 | 7 | class Drive : public Component 8 | { 9 | public: 10 | Drive(); 11 | virtual ~Drive(); 12 | 13 | enum ConfigIndex 14 | { 15 | Tier = Component::ConfigIndex::Next, 16 | Label 17 | }; 18 | 19 | int writeByte(lua_State* lua); 20 | int readByte(lua_State* lua); 21 | int setLabel(lua_State* lua); 22 | int getSectorSize(lua_State* lua); 23 | int getPlatterCount(lua_State* lua); 24 | int getLabel(lua_State* lua); 25 | int writeSector(lua_State* lua); 26 | int getCapacity(lua_State* lua); 27 | int readSector(lua_State* lua); 28 | 29 | protected: 30 | bool onInitialize() override; 31 | 32 | int getSectorSize(); 33 | int getCapacity(); 34 | 35 | int offsetToSector(int offset); 36 | int sectorToOffset(int sector); 37 | int validateSector(lua_State* lua, int sector); 38 | 39 | std::vector read(int offset, int size); 40 | void write(int offset, std::vector data); 41 | 42 | private: 43 | std::string _hostPath; // empty when invalid 44 | int _tier; 45 | std::vector _buffer; 46 | 47 | static bool s_registered; 48 | }; 49 | -------------------------------------------------------------------------------- /drivers/fs_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | using std::function; 8 | using std::string; 9 | using std::vector; 10 | 11 | namespace fs_utils 12 | { 13 | bool read(const string& path, vector& outData); 14 | bool read(const string& path, string* pOutData = nullptr); 15 | bool copy(const string& src, const string& dst); 16 | bool write(const string& data, const string& dst); 17 | bool write(const vector& data, const string& dst); 18 | 19 | bool mkdir(const string& path); 20 | bool exists(const string& path); 21 | 22 | vector list(const string& path); 23 | bool isDirectory(const string& path); 24 | 25 | size_t size(const string& path, bool recursive = false); 26 | uint64_t lastModified(const string& path); 27 | 28 | bool remove(const string& path); 29 | bool rename(const string& from, const string& to); 30 | bool resize(const string& path, int size); 31 | 32 | bool run_safely(function func, function onError = nullptr); 33 | 34 | string make_proc_path(const string& given_path); 35 | string make_pwd_path(const string& given_path); 36 | void set_prog_name(const string& prog_name); 37 | 38 | string filename(const string& path); 39 | }; 40 | -------------------------------------------------------------------------------- /components/component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "model/log.h" 3 | #include "model/luaproxy.h" 4 | #include "model/value.h" 5 | #include 6 | #include 7 | #include 8 | 9 | class Component; 10 | typedef ValuePack (Component::*ComponentMethod)(const ValuePack& args); 11 | 12 | class Client; 13 | 14 | enum class RunState 15 | { 16 | Continue, 17 | Reboot, 18 | Halt 19 | }; 20 | 21 | class Component : public LuaProxy 22 | { 23 | public: 24 | enum ConfigIndex 25 | { 26 | Type = 1, 27 | Address, 28 | Next 29 | }; 30 | 31 | Component(); 32 | bool initialize(Client* client, Value& config); 33 | virtual bool postInit() 34 | { 35 | return true; 36 | } 37 | virtual ~Component() = default; 38 | string type() const; 39 | string address() const; 40 | int slot() const; 41 | 42 | virtual RunState update() 43 | { 44 | return RunState::Continue; 45 | } 46 | 47 | static string make_address(); 48 | virtual Value getDeviceInfo() const; 49 | 50 | protected: 51 | virtual bool onInitialize() = 0; 52 | Client* client() const; 53 | const Value& config() const; 54 | void update(int key, const Value& value); 55 | 56 | private: 57 | string _address; 58 | int _slot; 59 | Client* _client; 60 | Value* _config; 61 | }; 62 | -------------------------------------------------------------------------------- /components/modem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "component.h" 4 | #include "io/event.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class ModemDriver; 11 | using std::set; 12 | using std::unique_ptr; 13 | using std::vector; 14 | 15 | class Modem : public Component, public EventSource 16 | { 17 | public: 18 | Modem(); 19 | virtual ~Modem(); 20 | 21 | enum ConfigIndex 22 | { 23 | SystemPort = Component::ConfigIndex::Next, 24 | MaxPacketSize, 25 | MaxArguments, 26 | HostAddress // defaults to 127.0.0.1 27 | }; 28 | 29 | int setWakeMessage(lua_State*); 30 | int isWireless(lua_State*); 31 | int close(lua_State*); 32 | int getWakeMessage(lua_State*); 33 | int isOpen(lua_State*); 34 | int broadcast(lua_State*); 35 | int send(lua_State*); 36 | int open(lua_State*); 37 | int setStrength(lua_State*); 38 | 39 | protected: 40 | bool onInitialize() override; 41 | RunState update() override; 42 | int tryPack(lua_State* lua, const vector* pAddr, int port, vector* pOut) const; 43 | bool isApplicable(int port, vector* target); 44 | 45 | unique_ptr _modem; 46 | size_t _maxPacketSize; 47 | int _maxArguments; 48 | 49 | set _ports; 50 | 51 | static bool s_registered; 52 | }; 53 | -------------------------------------------------------------------------------- /drivers/ansi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "color/color_types.h" 4 | #include 5 | #include 6 | using std::string; 7 | 8 | struct Color; 9 | 10 | namespace Ansi 11 | { 12 | static const char ESC = 0x1B; 13 | static const string esc = string{ ESC } + "["; 14 | static const string cursor_on = esc + "?25h"; 15 | static const string cursor_off = esc + "?25l"; 16 | static const string track_on = esc + "?1002h"; 17 | static const string track_off = esc + "?1002l"; 18 | static const string mouse_p_on = esc + "?9h"; 19 | static const string mouse_p_off = esc + "?9l"; // press events only (X10) 20 | static const string mouse_pr_on = esc + "?1000h"; 21 | static const string mouse_pr_off = esc + "?1000l"; // press and release events (X11) 22 | static const string mouse_prd_on = esc + "?1003h"; 23 | static const string mouse_prd_off = esc + "?1003l"; // press, release, and drag events (X11) 24 | static const string clear_scroll = esc + "3J"; 25 | static const string clear_term = esc + "2J" + clear_scroll; 26 | static const string clean_line = esc + "K"; 27 | static const string save_pos = esc + "s"; 28 | static const string restore_pos = esc + "u"; 29 | static const string color_reset = esc + "0m"; 30 | 31 | string set_color(const Color& fg, const Color& bg, ColorState& cst); 32 | string set_pos(int x, int y); 33 | }; 34 | -------------------------------------------------------------------------------- /io/event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | using std::mutex; 8 | using std::queue; 9 | using std::string; 10 | using std::unique_lock; 11 | using std::vector; 12 | 13 | #include "model/value.h" 14 | 15 | enum class EPressType 16 | { 17 | Press, 18 | Release, 19 | Drag, 20 | Drop 21 | }; 22 | 23 | struct MouseEvent 24 | { 25 | EPressType press; 26 | int x; 27 | int y; 28 | int btn; 29 | }; 30 | 31 | struct KeyEvent 32 | { 33 | unsigned int keysym; 34 | unsigned int keycode; 35 | bool bPressed; // false: released 36 | 37 | bool bShift; //0x01 38 | bool bCaps; //0x02 39 | bool bControl; //0x04 40 | bool bAlt; //0x08 41 | bool bNumLock; //0x10 42 | 43 | std::vector insert; 44 | }; 45 | 46 | struct ModemEvent 47 | { 48 | vector payload; 49 | }; 50 | 51 | template 52 | class EventSource 53 | { 54 | public: 55 | bool pop(TEvent& te) 56 | { 57 | unique_lock lk(_m); 58 | if (_events.size() == 0) 59 | return false; // empty 60 | te = _events.front(); 61 | _events.pop(); 62 | return true; 63 | } 64 | 65 | void push(const TEvent& te) 66 | { 67 | unique_lock lk(_m); 68 | _events.push(te); 69 | } 70 | 71 | private: 72 | mutex _m; 73 | queue _events; 74 | }; 75 | -------------------------------------------------------------------------------- /apis/system.cpp: -------------------------------------------------------------------------------- 1 | #include "system.h" 2 | #include "drivers/fs_utils.h" 3 | #include "model/log.h" 4 | 5 | // statics 6 | double SystemApi::_timeout = 5; 7 | bool SystemApi::_gc = false; 8 | bool SystemApi::_bytecode = false; 9 | int SystemApi::_max_connections = 4; 10 | 11 | SystemApi::SystemApi() 12 | : LuaProxy("system") 13 | { 14 | cadd("allowGC", &SystemApi::allowGC); 15 | cadd("timeout", &SystemApi::timeout); 16 | cadd("allowBytecode", &SystemApi::allowBytecode); 17 | } 18 | 19 | SystemApi* SystemApi::get() 20 | { 21 | static SystemApi it; 22 | return ⁢ 23 | } 24 | 25 | int SystemApi::allowGC(lua_State* lua) 26 | { 27 | return ValuePack::ret(lua, get()->_gc); 28 | } 29 | 30 | int SystemApi::timeout(lua_State* lua) 31 | { 32 | return ValuePack::ret(lua, get()->_timeout); 33 | } 34 | 35 | int SystemApi::allowBytecode(lua_State* lua) 36 | { 37 | return ValuePack::ret(lua, get()->_bytecode); 38 | } 39 | 40 | //////// 41 | void SystemApi::configure(const Value& settings) 42 | { 43 | _timeout = settings.get("timeout").Or(_timeout).toNumber(); 44 | _bytecode = settings.get("allowBytecode").Or(_bytecode).toBool(); 45 | _gc = settings.get("allowGC").Or(_gc).toBool(); 46 | _max_connections = settings.get("maxTcpConnections").Or(_max_connections).toNumber(); 47 | } 48 | 49 | int SystemApi::max_connections() 50 | { 51 | return _max_connections; 52 | } 53 | -------------------------------------------------------------------------------- /components/screen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "component.h" 3 | #include "io/event.h" 4 | #include "io/frame.h" 5 | #include "model/value.h" 6 | 7 | class Keyboard; 8 | class Gpu; 9 | 10 | class Screen : public Component, public IScreen, private EventSource 11 | { 12 | public: 13 | Screen(); 14 | ~Screen(); 15 | RunState update() override; 16 | 17 | // Component API 18 | int isOn(lua_State*); 19 | int getAspectRatio(lua_State*); 20 | int getKeyboards(lua_State*); 21 | int setTouchModeInverted(lua_State*); 22 | int turnOn(lua_State*); 23 | int turnOff(lua_State*); 24 | int isPrecise(lua_State*); 25 | int isTouchInverted(lua_State*); 26 | int setPrecise(lua_State*); 27 | 28 | bool connectKeyboard(Keyboard* kb); 29 | bool disconnectKeyboard(Keyboard* kb); 30 | vector keyboards() const; 31 | 32 | void push(const KeyEvent& ke) override; 33 | void push(const MouseEvent& ke) override; 34 | bool setResolution(int width, int height) override; 35 | void invalidate() override; 36 | 37 | void gpu(Gpu* gpu); 38 | Gpu* gpu() const; 39 | Frame* frame() const; 40 | 41 | protected: 42 | bool onInitialize() override; 43 | 44 | Value getDeviceInfo() const override; 45 | 46 | private: 47 | vector _keyboards; 48 | unique_ptr _frame; 49 | Gpu* _gpu = nullptr; 50 | 51 | static bool s_registered; 52 | }; 53 | -------------------------------------------------------------------------------- /model/log.cpp: -------------------------------------------------------------------------------- 1 | #include "model/log.h" 2 | #include "io/frame.h" 3 | 4 | #include 5 | #include 6 | #include 7 | using std::cout; 8 | using std::fstream; 9 | using std::function; 10 | using std::ofstream; 11 | 12 | LoggerContext Logger::s_context{ "" }; 13 | Logger& Logging::lout = Logger::getSingleLogger(); 14 | 15 | const std::string log_file_name = "log"; 16 | 17 | void Logger::context(LoggerContext ctx) 18 | { 19 | s_context = ctx; 20 | } 21 | 22 | LoggerContext Logger::context() 23 | { 24 | return s_context; 25 | } 26 | 27 | Logger& Logger::getSingleLogger() 28 | { 29 | static Logger the_one; 30 | return the_one; 31 | } 32 | 33 | Logger& Logger::operator<<(const string& text) 34 | { 35 | ofstream flog; 36 | if (s_context.path.empty()) 37 | { 38 | std::cerr << "[no log ctx]" << text; 39 | } 40 | else 41 | { 42 | flog.open(s_context.path + "/" + log_file_name, fstream::app); 43 | if (flog) 44 | { 45 | flog << text; 46 | flog.close(); 47 | } 48 | else 49 | { 50 | std::cerr << "[log write failure]" << text; 51 | } 52 | } 53 | return *this; 54 | } 55 | 56 | Logger& Logger::operator<<(std::ostream& (*)(std::ostream&)) 57 | { 58 | return *this << string("\n"); 59 | } 60 | 61 | Logger& Logger::operator<<(const char* cstr) 62 | { 63 | return *this << string(cstr); 64 | } 65 | -------------------------------------------------------------------------------- /drivers/connection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | enum class ConnectionState 8 | { 9 | Starting, 10 | Failed, 11 | Ready, 12 | Finished, 13 | Closed 14 | }; 15 | 16 | class Connection 17 | { 18 | public: 19 | Connection(int id); 20 | Connection(const std::string& host, int system_port); 21 | virtual ~Connection(); 22 | 23 | bool readyNextPacket(std::vector* buffer, bool keepPacketSize); 24 | bool write(const std::vector& vec); 25 | 26 | std::string label() const; 27 | ConnectionState state() const; 28 | ssize_t bytes_available() const; 29 | bool preload(ssize_t bytes); 30 | bool back_insert(std::vector* pOut, ssize_t offset, ssize_t bytes); 31 | bool move(ssize_t bytes); 32 | bool can_read() const; 33 | bool can_write() const; 34 | void close(); 35 | 36 | const static ssize_t max_buffer_size = 1024 * 16; // 16K, 8K is the max OC packet, double that for fun 37 | 38 | protected: 39 | bool read(ssize_t bytes); 40 | 41 | private: 42 | int _id = -1; 43 | std::string _host; 44 | int _port; 45 | 46 | ConnectionState _state; 47 | std::thread _connection_thread; 48 | 49 | bool _client_side = false; 50 | char _internal_buffer[max_buffer_size]; 51 | ssize_t _buffer_size = 0; 52 | 53 | static void async_open(Connection* pc); 54 | }; 55 | 56 | bool set_nonblocking(int id); 57 | -------------------------------------------------------------------------------- /model/host.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | class Component; 8 | class Frame; 9 | 10 | class Host 11 | { 12 | public: 13 | Host(std::string frameType); 14 | ~Host(); 15 | 16 | Frame* createFrame() const; 17 | std::unique_ptr create(const std::string& type) const; 18 | void close(); 19 | 20 | std::string stackLog() const; 21 | void stackLog(const std::string& stack_log); 22 | 23 | std::string biosPath() const; 24 | void biosPath(const std::string& bios_path); 25 | 26 | std::string fontsPath() const; 27 | void fontsPath(const std::string& fonts_path); 28 | 29 | std::string machinePath() const; 30 | void machinePath(const std::string& machine_path); 31 | 32 | typedef std::function()> GeneratorCallback; 33 | 34 | static bool registerComponentType(const std::string& type, GeneratorCallback generator); 35 | 36 | template 37 | static bool registerComponentType(const std::string& type) 38 | { 39 | return registerComponentType(type, [] { return std::unique_ptr(new T); }); 40 | } 41 | 42 | private: 43 | std::string _frameType; 44 | std::string _stack_log; 45 | std::string _bios_path; 46 | std::string _fonts_path; 47 | std::string _machine_path; 48 | 49 | static std::map& generators(); 50 | }; 51 | -------------------------------------------------------------------------------- /drivers/worker.cpp: -------------------------------------------------------------------------------- 1 | #include "worker.h" 2 | 3 | #include 4 | 5 | void NiceWork::set() 6 | { 7 | _work_done = true; 8 | } 9 | 10 | NiceWork::~NiceWork() 11 | { 12 | if (!_work_done) 13 | std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 20)); 14 | } 15 | 16 | bool Worker::isRunning() 17 | { 18 | return _pthread && _running; 19 | } 20 | 21 | bool Worker::start() 22 | { 23 | auto lock = make_lock(); 24 | if (isRunning()) 25 | return false; 26 | 27 | _running = true; 28 | _continue = true; 29 | _pthread = new thread(&Worker::proc, this); 30 | 31 | return true; 32 | } 33 | 34 | void Worker::stop() 35 | { 36 | { 37 | auto lock = make_lock(); 38 | _continue = false; 39 | } 40 | 41 | if (_pthread) 42 | _pthread->join(); 43 | 44 | auto lock = make_lock(); 45 | _running = false; 46 | delete _pthread; 47 | _pthread = nullptr; 48 | } 49 | 50 | void Worker::proc() 51 | { 52 | { 53 | auto lock = make_lock(); 54 | _continue = _continue && onStart(); 55 | } 56 | while (_continue) 57 | { 58 | { 59 | auto lock = make_lock(); 60 | if (!runOnce()) 61 | break; 62 | } 63 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 64 | } 65 | auto lock = make_lock(); 66 | onStop(); 67 | _running = false; 68 | } 69 | 70 | unique_lock Worker::make_lock() 71 | { 72 | return unique_lock(_m); 73 | } 74 | -------------------------------------------------------------------------------- /model/luaproxy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "value.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using std::tuple; 9 | using std::unordered_map; 10 | 11 | class LuaProxy; 12 | typedef int (LuaProxy::*ProxyMethod)(lua_State* lua); 13 | typedef tuple LuaMethod; 14 | 15 | class LuaProxy 16 | { 17 | public: 18 | LuaProxy(const string& name); 19 | virtual ~LuaProxy(); 20 | 21 | void operator=(LuaProxy) = delete; 22 | LuaProxy(const LuaProxy&) = delete; 23 | LuaProxy(LuaProxy&&) = delete; 24 | 25 | const string& name() const; 26 | vector methods() const; 27 | const string& doc(const string& methodName) const; 28 | int invoke(const string& methodName, lua_State* lua); 29 | 30 | protected: 31 | void name(const string& v); 32 | 33 | void add(const string& methodName, ProxyMethod method, const string& doc = ""); 34 | template 35 | void add(const string& methodName, int (Derived::*derivedMethod)(lua_State* lua), const string& doc = "") 36 | { 37 | add(methodName, static_cast(derivedMethod), doc); 38 | } 39 | void cadd(const string& methodName, lua_CFunction cfunction); 40 | 41 | private: 42 | unordered_map _methods; 43 | unordered_map _docs; 44 | unordered_map _cmethods; // for statics - faster dispatch 45 | string _name; 46 | }; 47 | -------------------------------------------------------------------------------- /model/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | using std::endl; 6 | using std::string; 7 | using std::stringstream; 8 | 9 | struct LoggerContext 10 | { 11 | std::string path; 12 | }; 13 | 14 | class Logger 15 | { 16 | public: 17 | static void context(LoggerContext ctx); 18 | static LoggerContext context(); 19 | static Logger& getSingleLogger(); 20 | 21 | Logger& operator<<(const string& text); 22 | Logger& operator<<(std::ostream& (*)(std::ostream&)); 23 | Logger& operator<<(const char* cstr); 24 | template 25 | Logger& operator<<(const T& t) 26 | { 27 | stringstream ss; 28 | ss << t; 29 | return *this << ss.str(); 30 | } 31 | 32 | string serialize(stringstream& ss) 33 | { 34 | return ss.str() + "\n"; 35 | } 36 | 37 | template 38 | string serialize(stringstream& ss, const T& arg, const Ts&... args) 39 | { 40 | ss << arg; 41 | return serialize(ss, args...); 42 | } 43 | 44 | template 45 | Logger& write(const Ts&... args) 46 | { 47 | stringstream ss; 48 | return *this << serialize(ss, args...); 49 | } 50 | static Logger& lout; 51 | 52 | private: 53 | Logger() 54 | { 55 | } // cannot create mulitple loggers 56 | Logger(Logger&) = delete; // no copies 57 | 58 | static LoggerContext s_context; 59 | }; 60 | 61 | namespace Logging 62 | { 63 | extern Logger& lout; 64 | }; 65 | -------------------------------------------------------------------------------- /model/prof_log.cpp: -------------------------------------------------------------------------------- 1 | #include "prof_log.h" 2 | 3 | #include 4 | #include 5 | using std::endl; 6 | using std::fstream; 7 | using std::ofstream; 8 | using std::stringstream; 9 | 10 | ofstream open_file(const string& filename, bool append) 11 | { 12 | return ofstream(filename, append ? fstream::app : fstream::out); 13 | } 14 | 15 | bool ProfLog::open(const string& dump_file) 16 | { 17 | _filename = ""; 18 | if (dump_file.empty()) 19 | return false; 20 | 21 | ofstream ofs = open_file(dump_file, false); 22 | bool ok = ofs.is_open(); 23 | if (ok) 24 | _filename = dump_file; 25 | ofs.close(); 26 | return ok; 27 | } 28 | 29 | bool ProfLog::is_open() 30 | { 31 | return !_filename.empty(); 32 | } 33 | 34 | ProfLog::~ProfLog() 35 | { 36 | flush(); 37 | } 38 | 39 | void ProfLog::release(void* ptr) 40 | { 41 | stringstream ss; 42 | ss << ptr << " " << 0 << endl; 43 | push(ss.str()); 44 | } 45 | 46 | void ProfLog::trace(const string& stacktrace, void* ptr, size_t size) 47 | { 48 | stringstream ss; 49 | ss << ptr << " " << size << " " << stacktrace << endl; 50 | push(ss.str()); 51 | } 52 | 53 | void ProfLog::flush() 54 | { 55 | ofstream ofs = open_file(_filename, true); 56 | for (const auto& text : _buffer) 57 | ofs << text; 58 | ofs.close(); 59 | _buffer.clear(); 60 | } 61 | 62 | void ProfLog::push(const string& text) 63 | { 64 | _buffer.push_back(text); 65 | 66 | if (_buffer.size() > 10000) 67 | { 68 | flush(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /model/client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "luaproxy.h" 3 | #include "model/log.h" 4 | #include "value.h" 5 | 6 | #include 7 | #include 8 | 9 | class Host; 10 | class Config; 11 | class Component; 12 | class Computer; 13 | class SandboxMethods; 14 | enum class RunState; 15 | 16 | class Client : public LuaProxy 17 | { 18 | public: 19 | Client(Host*, const string& env_path); 20 | ~Client(); 21 | bool load(); 22 | void close(); 23 | vector components(string filter = "", bool exact = false) const; 24 | Component* component(const string& address) const; 25 | const string& envPath() const; 26 | Host* host() const; 27 | void computer(Computer*); 28 | Computer* computer() const; 29 | void pushSignal(const ValuePack& pack); 30 | RunState run(); 31 | bool add_component(Value& component_config); 32 | bool remove_component(const string& address); 33 | 34 | void appendCrashText(const string& report); 35 | string getAllCrashText() const; 36 | 37 | // global api that is actually computer specific 38 | // invoke by address 39 | int component_invoke(lua_State* lua); 40 | int component_list(lua_State* lua); 41 | int component_methods(lua_State* lua); 42 | int component_type(lua_State* lua); 43 | int component_slot(lua_State* lua); 44 | int component_doc(lua_State* lua); 45 | 46 | protected: 47 | bool createComponents(); 48 | bool postInit(); 49 | bool loadLuaComponentApi(); 50 | 51 | private: 52 | vector> _components; 53 | Computer* _computer; 54 | std::unique_ptr _config; 55 | string _env_path; 56 | Host* _host; 57 | string _crash; 58 | }; 59 | -------------------------------------------------------------------------------- /io/frame.cpp: -------------------------------------------------------------------------------- 1 | #include "frame.h" 2 | #include "color/color_types.h" 3 | 4 | Frame::~Frame() 5 | { 6 | _screen = nullptr; 7 | } 8 | 9 | void Frame::open(IScreen* screen) 10 | { 11 | _screen = screen; 12 | _isOn = true; 13 | auto rez = onOpen(); 14 | _width = std::get<0>(rez); 15 | _height = std::get<1>(rez); 16 | } 17 | 18 | bool Frame::update() 19 | { 20 | if (!_screen) 21 | return false; 22 | 23 | onUpdate(); 24 | return true; 25 | } 26 | 27 | void Frame::close() 28 | { 29 | onClose(); 30 | _screen = nullptr; 31 | } 32 | 33 | void Frame::write(int x, int y, const Cell& cell, ColorState& cst) 34 | { 35 | if (cell.locked || !on()) 36 | return; 37 | onWrite(x, y, cell, cst); 38 | } 39 | 40 | tuple Frame::size() const 41 | { 42 | return std::make_tuple(_width, _height); 43 | } 44 | 45 | void Frame::clear() 46 | { 47 | onClear(); 48 | } 49 | 50 | void Frame::winched(int width, int height) 51 | { 52 | _width = width; 53 | _height = height; 54 | if (_screen) 55 | _screen->setResolution(width, height); 56 | } 57 | 58 | void Frame::mouseEvent(const MouseEvent& me) 59 | { 60 | if (_screen) 61 | _screen->push(me); 62 | } 63 | 64 | void Frame::keyEvent(const KeyEvent& ke) 65 | { 66 | if (_screen) 67 | _screen->push(ke); 68 | } 69 | 70 | bool Frame::on() const 71 | { 72 | return _isOn; 73 | } 74 | 75 | bool Frame::on(bool bOn) 76 | { 77 | bool was_changed = _isOn != bOn; 78 | _isOn = bOn; 79 | if (was_changed) 80 | { 81 | if (bOn) 82 | { 83 | _screen->invalidate(); 84 | } 85 | else 86 | { 87 | clear(); 88 | } 89 | } 90 | return was_changed; 91 | } 92 | -------------------------------------------------------------------------------- /components/filesystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "component.h" 3 | 4 | #include 5 | #include 6 | using std::fstream; 7 | using std::set; 8 | 9 | class FileHandle; 10 | 11 | class Filesystem : public Component 12 | { 13 | public: 14 | Filesystem(); 15 | 16 | enum ConfigIndex 17 | { 18 | SourceUri = Component::ConfigIndex::Next, 19 | Label 20 | }; 21 | 22 | string path() const; 23 | string src() const; 24 | bool isReadOnly() const; 25 | bool isTmpfs() const; 26 | 27 | void release(FileHandle*); 28 | 29 | int open(lua_State* lua); 30 | int read(lua_State* lua); 31 | int write(lua_State* lua); 32 | int close(lua_State* lua); 33 | int getLabel(lua_State* lua); 34 | int setLabel(lua_State* lua); 35 | int list(lua_State* lua); 36 | int isDirectory(lua_State* lua); 37 | int exists(lua_State* lua); 38 | int isReadOnly(lua_State* lua); 39 | int seek(lua_State* lua); 40 | int size(lua_State* lua); 41 | int lastModified(lua_State* lua); 42 | int spaceUsed(lua_State* lua); 43 | int spaceTotal(lua_State* lua); 44 | int remove(lua_State* lua); 45 | int makeDirectory(lua_State* lua); 46 | int rename(lua_State* lua); 47 | 48 | protected: 49 | bool onInitialize() override; 50 | static string clean(string arg, bool bAbs, bool removeEnd); 51 | static string relative(const string& requested, const string& full); 52 | 53 | FileHandle* create(lua_State* lua, const string& uri, fstream::openmode mode); 54 | FileHandle* getFileHandle(lua_State* lua) const; 55 | 56 | private: 57 | bool _isReadOnly; 58 | set _handles; 59 | string _src; 60 | bool _tmpfs; 61 | 62 | static bool s_registered; 63 | }; 64 | -------------------------------------------------------------------------------- /components/sandbox.cpp: -------------------------------------------------------------------------------- 1 | #include "sandbox.h" 2 | #include "computer.h" 3 | #include "drivers/fs_utils.h" 4 | #include "model/client.h" 5 | #include "model/host.h" 6 | #include "model/log.h" 7 | 8 | bool Sandbox::s_registered = Host::registerComponentType("sandbox"); 9 | 10 | Sandbox::Sandbox() 11 | { 12 | add("add_component", &Sandbox::add_component); 13 | add("remove_component", &Sandbox::remove_component); 14 | add("log", &Sandbox::log); 15 | add("state_name", &Sandbox::state_name); 16 | } 17 | 18 | int Sandbox::log(lua_State* lua) 19 | { 20 | return client()->computer()->print(lua); 21 | } 22 | 23 | int Sandbox::state_name(lua_State* lua) 24 | { 25 | string stateName = fs_utils::filename(client()->envPath()); 26 | return ValuePack::ret(lua, stateName); 27 | } 28 | 29 | int Sandbox::add_component(lua_State* lua) 30 | { 31 | Value component_config(lua, 1); 32 | if (component_config.len() == 0 || component_config.get(1).type() != "string") 33 | { 34 | return luaL_error(lua, "missing component type name"); 35 | } 36 | 37 | if (!client()->add_component(component_config)) 38 | { 39 | string err = "failed to add component: " + component_config.serialize(); 40 | return luaL_error(lua, err.c_str()); 41 | } 42 | 43 | return ValuePack::ret(lua, component_config.get(2).toString()); 44 | } 45 | 46 | int Sandbox::remove_component(lua_State* lua) 47 | { 48 | string address = Value::checkArg(lua, 1); 49 | if (!client()->remove_component(address)) 50 | { 51 | string err = "Failed to remove component: " + address; 52 | return luaL_error(lua, err.c_str()); 53 | } 54 | 55 | return ValuePack::ret(lua, true); 56 | } 57 | 58 | bool Sandbox::onInitialize() 59 | { 60 | return true; 61 | } 62 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "cstdarg": "cpp", 4 | "cwchar": "cpp", 5 | "cstdint": "cpp", 6 | "type_traits": "cpp", 7 | "cctype": "cpp", 8 | "initializer_list": "cpp", 9 | "clocale": "cpp", 10 | "cstdlib": "cpp", 11 | "cstdio": "cpp", 12 | "new": "cpp", 13 | "exception": "cpp", 14 | "typeinfo": "cpp", 15 | "cwctype": "cpp", 16 | "tuple": "cpp", 17 | "utility": "cpp", 18 | "array": "cpp", 19 | "fstream": "cpp", 20 | "ostream": "cpp", 21 | "ctime": "cpp", 22 | "system_error": "cpp", 23 | "functional": "cpp", 24 | "thread": "cpp", 25 | "string": "cpp", 26 | "chrono": "cpp", 27 | "iostream": "cpp", 28 | "mutex": "cpp", 29 | "unordered_map": "cpp", 30 | "filesystem": "cpp", 31 | "bitset": "cpp", 32 | "*.tcc": "cpp", 33 | "istream": "cpp", 34 | "cerrno": "cpp", 35 | "cmath": "cpp", 36 | "codecvt": "cpp", 37 | "cstring": "cpp", 38 | "deque": "cpp", 39 | "vector": "cpp", 40 | "string_view": "cpp", 41 | "iosfwd": "cpp", 42 | "limits": "cpp", 43 | "memory": "cpp", 44 | "numeric": "cpp", 45 | "ratio": "cpp", 46 | "sstream": "cpp", 47 | "stdexcept": "cpp", 48 | "streambuf": "cpp" 49 | }, 50 | "files.exclude": { 51 | "**/.git": true, 52 | "**/.svn": true, 53 | "**/.hg": true, 54 | "**/.DS_Store": true, 55 | "ocpc": true, 56 | "bin": true, 57 | "tmp": true, 58 | ".gitignore": true, 59 | ".vscode": true, 60 | "system/loot": true 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /drivers/mouse_drv.cpp: -------------------------------------------------------------------------------- 1 | #include "mouse_drv.h" 2 | #include "term_buffer.h" 3 | 4 | #include 5 | using std::cout; 6 | 7 | vector MouseTerminalDriver::parse(TermBuffer* buffer) 8 | { 9 | if (!buffer->hasMouseCode()) 10 | { 11 | return {}; // ignore 12 | } 13 | 14 | // eat the mouse code header 15 | buffer->get(); 16 | buffer->get(); 17 | buffer->get(); 18 | 19 | if (buffer->size() < 3) 20 | return {}; // ignore 21 | 22 | char b0 = buffer->get(); 23 | char b1 = buffer->get(); 24 | char b2 = buffer->get(); 25 | 26 | EPressType press; 27 | int btn = b0 - 0x20; 28 | if (btn == 0x3) 29 | { 30 | if (_pressed == 0x3) // odd 31 | { 32 | return {}; 33 | } 34 | 35 | if (_dragging) 36 | { 37 | press = EPressType::Drop; 38 | } 39 | else 40 | { 41 | press = EPressType::Release; 42 | } 43 | _dragging = false; 44 | } 45 | else if (btn >= 0x20) 46 | { 47 | if (_pressed == 0x3) 48 | return {}; // ignore drags if button state is released 49 | 50 | press = EPressType::Drag; 51 | _dragging = true; 52 | btn -= 0x20; 53 | } 54 | else // Press 55 | { 56 | if (_dragging || _pressed != 0x3) // ignore press if dragging or pressed 57 | { 58 | _pressed = btn; // but still update 59 | return {}; 60 | } 61 | 62 | press = EPressType::Press; 63 | } 64 | 65 | if (_pressed == 0x3) 66 | _pressed = btn; 67 | 68 | MouseEvent me; 69 | me.press = press; 70 | me.btn = _pressed; 71 | 72 | if (press != EPressType::Drag) 73 | _pressed = btn; 74 | 75 | me.x = (unsigned char)b1 - 32; 76 | me.y = (unsigned char)b2 - 32; 77 | 78 | if (me.x < 0) 79 | me.x += 256; 80 | if (me.y < 0) 81 | me.y += 256; 82 | 83 | return { me }; 84 | } 85 | -------------------------------------------------------------------------------- /drivers/kb_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | using std::shared_ptr; 8 | using std::tuple; 9 | using std::unordered_map; 10 | using std::vector; 11 | 12 | typedef unsigned int _Mod; 13 | typedef unsigned char _Code; 14 | typedef unsigned char _Sym; 15 | 16 | class TermBuffer; 17 | 18 | struct KeyCodeData 19 | { 20 | _Code code = 0; 21 | _Sym syms[8]{}; 22 | }; 23 | 24 | enum class ModBit 25 | { 26 | None = 0, 27 | Shift = 1, 28 | Caps = 2, 29 | Control = 4, 30 | Control_Shift = ModBit::Control | ModBit::Shift, 31 | Alt = 8, 32 | Shift_Alt = ModBit::Shift | ModBit::Alt, 33 | Control_Alt = ModBit::Control | ModBit::Alt, 34 | Shift_Control_Alt = ModBit::Shift | ModBit::Control | ModBit::Alt, 35 | NumLock = 0x10, 36 | }; 37 | 38 | struct KeySymData; 39 | typedef unordered_map<_Sym, shared_ptr> KeySymDataLinks; 40 | struct KeySymData 41 | { 42 | _Code code; 43 | ModBit mod; 44 | KeySymDataLinks links; 45 | }; 46 | 47 | class KBData 48 | { 49 | public: 50 | KBData(); 51 | vector<_Code> getModCodes(_Mod mod); 52 | _Sym lookup(_Code code, ModBit mod); 53 | bool lookup(TermBuffer* buffer, _Code* pCode, _Mod* pMod); 54 | void add_code_sym(_Code code, _Sym s0, _Sym s1, _Sym s2, _Sym s3, _Sym s4, _Sym s5, _Sym s6, _Sym s7); 55 | void add_alt_sequence(_Sym ch); 56 | void add_sequence(_Code code, ModBit mod, KeySymDataLinks& links, const vector<_Sym> seq, size_t seq_index); 57 | void add_sequence(_Code code, vector<_Sym> seq0, vector<_Sym> seq1, vector<_Sym> seq2, vector<_Sym> seq3, vector<_Sym> seq4, vector<_Sym> seq5, vector<_Sym> seq6, vector<_Sym> seq7); 58 | unordered_map<_Code, tuple> modifiers; 59 | 60 | private: 61 | unordered_map<_Code, KeyCodeData> _db; 62 | KeySymDataLinks _root; 63 | }; 64 | -------------------------------------------------------------------------------- /drivers/internet_drv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "apis/userdata.h" 4 | #include "connection.h" 5 | #include "io/event.h" 6 | #include 7 | #include 8 | using std::unique_ptr; 9 | 10 | struct HttpAddress 11 | { 12 | public: 13 | explicit HttpAddress(string url); 14 | 15 | string raw; 16 | bool valid; 17 | bool https; 18 | string hostname; 19 | string path; 20 | string params; 21 | int port; 22 | }; 23 | 24 | class InternetConnection; 25 | struct InternetConnectionEventSet 26 | { 27 | using OnClosedCallback = std::function; 28 | OnClosedCallback onClosed = nullptr; 29 | }; 30 | 31 | struct TcpConstructionParameters 32 | { 33 | string addr; 34 | int port; 35 | }; 36 | 37 | struct HttpConstructionParameters 38 | { 39 | HttpAddress addr; 40 | string post; 41 | map header; 42 | }; 43 | 44 | class InternetConnection : public UserData 45 | { 46 | public: 47 | InternetConnection(); 48 | 49 | void dispose() override; 50 | 51 | int finishConnect(lua_State* lua); 52 | int read(lua_State* lua); 53 | int close(lua_State* lua); 54 | 55 | virtual bool update(); 56 | 57 | void setOnClose(InternetConnectionEventSet::OnClosedCallback cb); 58 | 59 | using HttpGenRegistry = std::function; 60 | 61 | static HttpGenRegistry& http_gen(); 62 | 63 | static InternetConnection* openTcp(UserDataAllocator allocator, const InternetConnectionEventSet& eventSet, const TcpConstructionParameters& args); 64 | static InternetConnection* openHttp(UserDataAllocator allocator, const InternetConnectionEventSet& eventSet, const HttpConstructionParameters& args); 65 | 66 | protected: 67 | void open(const string& addr, int port); 68 | virtual Connection* connection() const = 0; 69 | 70 | InternetConnectionEventSet handler; 71 | bool _needs_connection; 72 | bool _needs_data; 73 | 74 | virtual void _close(); 75 | }; 76 | -------------------------------------------------------------------------------- /components/keyboard.cpp: -------------------------------------------------------------------------------- 1 | #include "keyboard.h" 2 | #include "model/client.h" 3 | #include "model/host.h" 4 | #include "model/log.h" 5 | #include "screen.h" 6 | using Logging::lout; 7 | 8 | bool Keyboard::s_registered = Host::registerComponentType("keyboard"); 9 | 10 | Keyboard::Keyboard() 11 | { 12 | } 13 | 14 | Keyboard::~Keyboard() 15 | { 16 | detach(); 17 | } 18 | 19 | void Keyboard::detach() 20 | { 21 | Screen* pScreen = screen(); 22 | if (pScreen) 23 | { 24 | pScreen->disconnectKeyboard(this); 25 | } 26 | } 27 | 28 | bool Keyboard::onInitialize() 29 | { 30 | _preferredScreen = config().get(ConfigIndex::ScreenAddress).Or("").toString(); 31 | return true; 32 | } 33 | 34 | bool Keyboard::postInit() 35 | { 36 | for (auto* pc : client()->components("screen", true)) 37 | { 38 | Screen* screen = dynamic_cast(pc); 39 | if (screen && (_preferredScreen.empty() || _preferredScreen == screen->address())) 40 | { 41 | return screen->connectKeyboard(this); 42 | } 43 | } 44 | 45 | lout << "warning: kb had no screen to join\n"; 46 | return true; 47 | } 48 | 49 | RunState Keyboard::update() 50 | { 51 | KeyEvent ke; 52 | while (EventSource::pop(ke)) 53 | { 54 | if (ke.insert.size()) 55 | { 56 | client()->pushSignal({ "clipboard", address(), ke.insert }); 57 | } 58 | else if (ke.keycode == 1) 59 | { 60 | lout << "shell abort"; 61 | return RunState::Halt; 62 | } 63 | else 64 | { 65 | client()->pushSignal({ ke.bPressed ? "key_down" : "key_up", address(), ke.keysym, ke.keycode }); 66 | } 67 | } 68 | 69 | return RunState::Continue; 70 | } 71 | 72 | Screen* Keyboard::screen() const 73 | { 74 | for (auto* pc : client()->components("screen", true)) 75 | { 76 | Screen* screen = dynamic_cast(pc); 77 | for (const auto& kb_addr : screen->keyboards()) 78 | { 79 | if (kb_addr == address()) 80 | { 81 | return screen; 82 | } 83 | } 84 | } 85 | return nullptr; 86 | } 87 | -------------------------------------------------------------------------------- /client.cfg: -------------------------------------------------------------------------------- 1 | -- This lua table configuration generally should not be modified 2 | -- This configuration is a template, copied to each new instance you create with ocvm 3 | { 4 | components = 5 | { 6 | -- all componets first have 7 | -- "type": component type name 8 | -- "address": optional guid address. If nil a random address is generated 9 | {"screen", "67c66973-51ff-4aec-29cd-baabf2fbe346"}, 10 | -- palette, monochrome color 11 | {"gpu", nil, 0x00af00}, 12 | -- bios size, data size, label 13 | {"eeprom", nil, 4096, 256, "EEPROM"}, 14 | -- total memory (in bytes) 15 | -- T1 : 196608 16 | -- T1.5: 262144 17 | -- T2 : 393216 18 | -- T2.5: 524288 19 | -- T3 : 786432 20 | -- T3.5: 1048576 21 | {"computer", nil, 1048576}, 22 | -- filesystem 23 | -- 1. source: uri for readonly loot, nil/false for hdd, and true for tmpfs 24 | -- 2. label 25 | {"filesystem", nil, "system/loot/openos", "OpenOS"}, 26 | {"filesystem", nil, true, "tmpfs"}, 27 | {"filesystem"}, 28 | -- attached screen address 29 | {"keyboard", nil, "67c66973-51ff-4aec-29cd-baabf2fbe346"}, 30 | -- system port: all modems on the same system port will act as if on the same network (port 56k in honor of 56k modems) 31 | -- max packet size, if nil defaults to 8192 32 | -- max arguments, if nil defaults to 8 33 | {"modem", nil, 56000, 8192, 8}, 34 | -- tcp enabled: default true 35 | -- http enabled: default true 36 | {"internet",nil,true,true}, 37 | -- tier 1, 2, or 3 for more advanced data card features 38 | {"data", nil, tier=1}, 39 | {"drive", 3}, 40 | {"sandbox"}, 41 | }, 42 | system = 43 | { 44 | timeout = 5, -- defaults to 5 45 | allowGC = false, -- defaults to false 46 | allowBytecode = false, -- defaults to false 47 | maxTcpConnections = 4, --defaults to 4 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ocvm 2 | OpenComputer Emulator 3 | 4 | This emulator is inspired by the outstanding work done by gamax with OCEmu ( https://github.com/gamax92/OCEmu ). 5 | 6 | This project will not be nearly as impressive as OCEmu, nonetheless I embark. 7 | 8 | **Project Goals** 9 | 10 | 1. Learn lua binding and environment emulation from a C++ application. 11 | 2. Provide memory and cpu profiling for debugging operating systems for OpenComputers 12 | 3. A purely command line emulator (e.g. over-ssh support) 13 | 14 | **Expected Disadvantages** 15 | 16 | 1. The terminal graphics layer depends on ansi escape codes, not quite cross platform 17 | 2. I am building the C++ binding and (de)serialization for all lua callback and arguments by hand (error-prone) 18 | 3. Displayed font will be host machine fonts using utf8 encoding, I will not be rendering OC fonts 19 | 4. pty applications do not get all key events, such as no key releases. I can emulate key releases some of the time, but not all of the time. 20 | 21 | But this should be a lot of fun! 22 | 23 | **How to Build** 24 | 25 | Run `make deps` at least once to download OpenComputers machine system files. These files are cached and not deleted by `make clean`. Delete the temporary `system` directory manually if you want `make deps` to redownload them 26 | 27 | Run `make` to build 28 | 29 | Run `./ocvm` 30 | 31 | 1. tools required: c++ compiler and lua sdk files 32 | 2. c++17 support 33 | 3. pkg-config defaults to `lua`. If your system has a different lua pkg name, you can override the default with `make lua=lua5.3`. If you are unsure, see your systems lua packages with `pkg-config --list-all`. 34 | 4. A vt100 compatible terminal 35 | 36 | **Custom Build Overrides** 37 | 38 | * lua=lua 39 | - You can override the lua pkg config with 'lua' environment variable: e.g. `make lua=lua5.2` 40 | 41 | * CXX=g++ 42 | - You can override the lua pkg config with 'lua' environment variable: e.g. `make CXX=g++-12` 43 | 44 | **Future Scope** 45 | 46 | I plan to add support for building ocvm on Mac using boost filesystem 47 | I do not plan to add support for non-ansi terminals nor windows. If this works in cygwin it wasn't on purpose 48 | 49 | -------------------------------------------------------------------------------- /model/host.cpp: -------------------------------------------------------------------------------- 1 | #include "host.h" 2 | 3 | #include "apis/unicode.h" 4 | #include "drivers/fs_utils.h" 5 | 6 | #include "components/component.h" 7 | #include "io/frame.h" 8 | 9 | Host::Host(string frameType) 10 | : _frameType(frameType) 11 | { 12 | } 13 | 14 | Host::~Host() 15 | { 16 | close(); 17 | } 18 | 19 | /*static*/ 20 | bool Host::registerComponentType(const std::string& type, Host::GeneratorCallback generator) 21 | { 22 | auto& gens = Host::generators(); 23 | if (gens.find(type) != gens.end()) 24 | return false; 25 | 26 | gens[type] = generator; 27 | return true; 28 | } 29 | 30 | /*static*/ 31 | std::map& Host::generators() 32 | { 33 | static std::map _generators; 34 | return _generators; 35 | } 36 | 37 | std::unique_ptr Host::create(const string& type) const 38 | { 39 | const auto& gens = Host::generators(); 40 | const auto& genit = gens.find(type); 41 | if (genit == gens.end()) 42 | { 43 | return nullptr; 44 | } 45 | return genit->second(); 46 | } 47 | 48 | Frame* Host::createFrame() const 49 | { 50 | return Factory::create_frame(_frameType); 51 | } 52 | 53 | void Host::close() 54 | { 55 | } 56 | 57 | string Host::stackLog() const 58 | { 59 | return _stack_log; 60 | } 61 | 62 | void Host::stackLog(const string& stack_log) 63 | { 64 | _stack_log = stack_log; 65 | } 66 | 67 | string Host::biosPath() const 68 | { 69 | return _bios_path; 70 | } 71 | 72 | void Host::biosPath(const string& bios_path) 73 | { 74 | _bios_path = bios_path; 75 | } 76 | 77 | string Host::fontsPath() const 78 | { 79 | return _fonts_path; 80 | } 81 | 82 | void Host::fontsPath(const string& fonts_path) 83 | { 84 | _fonts_path = fonts_path; 85 | if (!UnicodeApi::configure(_fonts_path)) 86 | { 87 | std::cerr << "Failed to load fonts: " << fonts_path << std::endl; 88 | ::exit(1); 89 | } 90 | } 91 | 92 | string Host::machinePath() const 93 | { 94 | return _machine_path; 95 | } 96 | 97 | void Host::machinePath(const string& machine_path) 98 | { 99 | _machine_path = machine_path; 100 | } 101 | -------------------------------------------------------------------------------- /apis/unicode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "model/luaproxy.h" 3 | #include 4 | #include 5 | using std::unordered_map; 6 | using std::vector; 7 | 8 | class UnicodeIterator 9 | { 10 | public: 11 | struct UnicodeIt 12 | { 13 | bool operator!=(const UnicodeIt& other) const; 14 | void operator++(); 15 | vector operator*() const; 16 | size_t next() const; 17 | 18 | UnicodeIterator& parent; 19 | size_t start; 20 | }; 21 | 22 | UnicodeIt begin(); 23 | UnicodeIt end(); 24 | const char* source; 25 | const size_t size; 26 | }; 27 | 28 | class UnicodeApi : public LuaProxy 29 | { 30 | public: 31 | static UnicodeApi* get(); 32 | static UnicodeIterator subs(const vector& src); 33 | 34 | static vector wtrunc(const vector& text, const size_t width); 35 | static bool isWide(const vector& text); 36 | static vector upper(const vector& text); 37 | static vector tochar(const uint32_t n); 38 | static uint32_t tocodepoint(const vector& text, const size_t index = 0); 39 | static size_t wlen(const vector& text); 40 | static size_t len(const vector& text); 41 | static vector sub(const vector& text, int from, int to); 42 | static int charWidth(const vector& text, bool bAlreadySingle = false); 43 | static vector reverse(const vector& text); 44 | static vector lower(const vector& text); 45 | 46 | int wtrunc(lua_State* lua); 47 | int isWide(lua_State* lua); 48 | int upper(lua_State* lua); 49 | int tochar(lua_State* lua); 50 | int wlen(lua_State* lua); 51 | int len(lua_State* lua); 52 | int sub(lua_State* lua); 53 | int charWidth(lua_State* lua); 54 | int reverse(lua_State* lua); 55 | int lower(lua_State* lua); 56 | 57 | static inline vector toRawString(const string& text) 58 | { 59 | return vector(text.begin(), text.end()); 60 | } 61 | 62 | static inline string toString(const vector& buffer) 63 | { 64 | return string(buffer.begin(), buffer.end()); 65 | } 66 | 67 | static bool configure(const string& fonts_path); 68 | 69 | private: 70 | UnicodeApi(); 71 | static std::unordered_map font_width; 72 | }; 73 | -------------------------------------------------------------------------------- /components/computer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "component.h" 4 | #include "model/prof_log.h" 5 | #include 6 | using std::queue; 7 | 8 | class Computer : public Component 9 | { 10 | public: 11 | Computer(); 12 | ~Computer(); 13 | 14 | enum ConfigIndex 15 | { 16 | TotalMemory = Component::ConfigIndex::Next 17 | }; 18 | 19 | RunState update() override; 20 | bool newlib(LuaProxy* proxy); 21 | void close(); 22 | void setTmpAddress(const string& addr); 23 | void pushSignal(const ValuePack& pack); 24 | bool postInit() override; 25 | 26 | void* alloc(void* ptr, size_t osize, size_t nsize); 27 | void stackLog(const string& stack_log); 28 | 29 | int isRunning(lua_State* lua); 30 | int setArchitecture(lua_State* lua); 31 | int getArchitecture(lua_State* lua); 32 | int getArchitectures(lua_State* lua); 33 | int beep(lua_State* lua); 34 | int getDeviceInfo(lua_State* lua); 35 | int getProgramLocations(lua_State* lua); 36 | int pushSignal(lua_State* lua); 37 | int removeUser(lua_State* lua); 38 | int addUser(lua_State* lua); 39 | int isRobot(lua_State* lua); 40 | int tmpAddress(lua_State* lua); 41 | int freeMemory(lua_State* lua); 42 | int totalMemory(lua_State* lua); 43 | int energy(lua_State* lua); 44 | int maxEnergy(lua_State* lua); 45 | int realTime(lua_State* lua); 46 | int uptime(lua_State* lua); 47 | 48 | // non-spec methods (for vm debugging) 49 | int crash(lua_State* lua); 50 | int print(lua_State* lua); 51 | 52 | protected: 53 | bool onInitialize() override; 54 | RunState resume(int nargs); 55 | 56 | int get_address(lua_State* lua); 57 | void mark_gc(); 58 | size_t memoryUsedRaw(); 59 | size_t memoryUsedVM(); 60 | size_t freeMemory(); 61 | Value getDeviceInfo() const override; 62 | 63 | private: 64 | void injectCustomLua(); 65 | 66 | double _start_time; 67 | string _tmp_address; 68 | lua_State* _state = nullptr; 69 | lua_State* _machine = nullptr; 70 | double _standby = 0; 71 | 72 | size_t _peek_memory = 0; // for debugging purposes 73 | size_t _total_memory = 0; 74 | size_t _baseline = 0; 75 | bool _baseline_initialized = false; 76 | 77 | queue _signals; 78 | 79 | size_t _gc_ticks = 0; 80 | ProfLog _prof; 81 | 82 | static bool s_registered; 83 | }; 84 | -------------------------------------------------------------------------------- /components/component.cpp: -------------------------------------------------------------------------------- 1 | #include "component.h" 2 | #include "model/client.h" 3 | #include "model/log.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | Component::Component() 13 | : LuaProxy("unnamed") 14 | , _address(make_address()) 15 | , _slot(-1) 16 | , _client(nullptr) 17 | { 18 | } 19 | 20 | bool Component::initialize(Client* client, Value& config) 21 | { 22 | _config = &config; 23 | this->name(config.get(ConfigIndex::Type).toString()); 24 | _client = client; 25 | 26 | Value& vaddr = config.get(ConfigIndex::Address); 27 | if (vaddr.type() != "string" || vaddr.toString().empty()) 28 | { 29 | vaddr = _address; 30 | } 31 | else 32 | { 33 | _address = vaddr.toString(); 34 | } 35 | 36 | return onInitialize(); 37 | } 38 | 39 | string Component::type() const 40 | { 41 | return LuaProxy::name(); 42 | } 43 | 44 | string Component::address() const 45 | { 46 | return _address; 47 | } 48 | 49 | int Component::slot() const 50 | { 51 | return _slot; 52 | } 53 | 54 | string Component::make_address() 55 | { 56 | // -- e.g. 3c44c8a9-0613-46a2-ad33-97b6ba2e9d9a 57 | // -- 8-4-4-4-12 (halved sizes because bytes make hex pairs) 58 | vector sets{ 4, 2, 2, 2, 6 }; 59 | string result = ""; 60 | 61 | static bool rand_initialized = false; 62 | if (!rand_initialized) 63 | { 64 | srand(time(nullptr)); 65 | rand_initialized = true; 66 | } 67 | 68 | for (auto len : sets) 69 | { 70 | if (!result.empty()) 71 | result += "-"; 72 | for (int index = 0; index < len; index++) 73 | { 74 | stringstream ss; 75 | ss << std::hex; 76 | ss << int(rand() % 256); 77 | string pair = ss.str(); 78 | if (pair.size() == 1) 79 | pair = "0" + pair; 80 | result += pair; 81 | } 82 | } 83 | 84 | return result; 85 | } 86 | 87 | Client* Component::client() const 88 | { 89 | return _client; 90 | } 91 | 92 | const Value& Component::config() const 93 | { 94 | return *static_cast(_config); 95 | } 96 | 97 | void Component::update(int key, const Value& value) 98 | { 99 | _config->set(key, value); 100 | } 101 | 102 | Value Component::getDeviceInfo() const 103 | { 104 | return Value::table(); 105 | } 106 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | lua ?= lua 2 | 3 | TARGET_EXEC ?= ocvm 4 | OPENCOMPUTERS=https://github.com/MightyPirates/OpenComputers/ 5 | 6 | LUA_CFLAGS := $(shell pkg-config --cflags $(lua) --silence-errors) 7 | LUA_LDFLAGS := $(shell pkg-config --libs $(lua) --silence-errors) 8 | 9 | ifeq ($(strip $(LUA_CFLAGS) $(LUA_LDFLAGS)),) 10 | $(error "ERROR: pkg-config failed to find lua package: '$(lua)'") 11 | endif 12 | 13 | HOST=$(shell uname -s) 14 | 15 | ldflags.Haiku = -lnetwork 16 | dirs.Haiku = haiku 17 | 18 | LDFLAGS+=-lstdc++ 19 | ifeq ($(shell uname -s 2>/dev/null),Haiku) 20 | LDFLAGS+=-lnetwork 21 | else 22 | LDFLAGS+=-lstdc++fs -pthread -ldl 23 | endif 24 | 25 | SRC_DIRS = . apis color components drivers io model util $(dirs.$(HOST)) 26 | SRCS = $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.cpp)) 27 | 28 | CXXFLAGS += $(LUA_CFLAGS) 29 | CXXFLAGS += -MMD -MP -Wall -g --std=c++17 -O0 -Wl,--no-as-needed 30 | CXXFLAGS += $(addprefix -I,$(SRC_DIRS)) 31 | LDFLAGS += $(LUA_LDFLAGS) $(ldflags.$(HOST)) 32 | 33 | SYSTEM_DIR := system 34 | SYSTEM_FILES := $(SYSTEM_DIR)/ 35 | 36 | BUILD_DIR ?= ./bin 37 | OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) 38 | DEPS := $(OBJS:.o=.d) 39 | 40 | $(TARGET_EXEC): $(OBJS) 41 | $(CXX) $(OBJS) -o $@ $(LDFLAGS) 42 | 43 | -include $(DEPS) 44 | $(BUILD_DIR)/%.cpp.o: %.cpp 45 | @mkdir -p $(dir $@) 46 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ 47 | 48 | deps: 49 | @if ! command -v git >/dev/null; then \ 50 | echo deps cancelled: 'make deps' requires 'git' command. Aborting \ 51 | false; \ 52 | fi 53 | @if [ -d $(SYSTEM_DIR) ]; then \ 54 | echo deps skipped: ./$(SYSTEM_DIR) already exists. To redownload system files, remove ./$(SYSTEM_DIR) and run 'make deps' again; \ 55 | else \ 56 | set -e; \ 57 | mkdir -p $(SYSTEM_DIR); \ 58 | cd $(SYSTEM_DIR); \ 59 | git clone -n --depth=1 --filter=tree:0 $(OPENCOMPUTERS); \ 60 | cd OpenComputers; \ 61 | git sparse-checkout set --no-cone /src/main/resources/assets/opencomputers/ && git checkout; \ 62 | set -x; \ 63 | mv src/main/resources/assets/opencomputers/loot ../; \ 64 | mv src/main/resources/assets/opencomputers/lua/machine.lua ../; \ 65 | mv src/main/resources/assets/opencomputers/lua/bios.lua ../; \ 66 | mv src/main/resources/assets/opencomputers/font.hex ../; \ 67 | cd ../..; \ 68 | rm -rf $(SYSTEM_DIR)/OpenComputers; \ 69 | fi 70 | 71 | help: 72 | @echo See README.md for build instructions 73 | @echo Likely all you need is: make deps \$$\$$ make 74 | 75 | clean: 76 | $(RM) -r $(BUILD_DIR) $(TARGET_EXEC) 77 | -------------------------------------------------------------------------------- /components/gpu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "color/color_types.h" 3 | #include "component.h" 4 | #include "io/frame.h" 5 | #include "model/value.h" 6 | #include 7 | #include 8 | using std::tuple; 9 | using std::vector; 10 | 11 | enum class EDepthType; 12 | class Screen; 13 | 14 | class Gpu : public Component 15 | { 16 | public: 17 | Gpu(); 18 | ~Gpu(); 19 | 20 | enum ConfigIndex 21 | { 22 | MonochromeColor 23 | }; 24 | 25 | int getResolution(lua_State* lua); 26 | int setResolution(lua_State* lua); 27 | int bind(lua_State* lua); 28 | int set(lua_State* lua); 29 | int get(lua_State* lua); 30 | int maxResolution(lua_State* lua); 31 | int setBackground(lua_State* lua); 32 | int getBackground(lua_State* lua); 33 | int setForeground(lua_State* lua); 34 | int getForeground(lua_State* lua); 35 | int fill(lua_State* lua); 36 | int copy(lua_State* lua); 37 | int getDepth(lua_State* lua); 38 | int setDepth(lua_State* lua); 39 | int getViewport(lua_State* lua); 40 | int setViewport(lua_State* lua); 41 | int getScreen(lua_State* lua); 42 | int maxDepth(lua_State* lua); 43 | int getPaletteColor(lua_State* lua); 44 | int setPaletteColor(lua_State* lua); 45 | 46 | // Screen callbacks 47 | bool setResolution(int width, int height); 48 | void unbind(); 49 | void invalidate(); 50 | 51 | protected: 52 | vector scan(int x, int y, int width) const; 53 | const Cell* get(int x, int y) const; 54 | int set(int x, int y, const Cell& cell, bool bForce); 55 | void set(int x, int y, const vector& text, bool bVertical); 56 | 57 | Cell* at(int x, int y) const; 58 | 59 | bool onInitialize() override; 60 | void check(lua_State* lua) const; // throws if no screen 61 | 62 | int setColorContext(lua_State* lua, bool bBack); // returns color, index (or nil) 63 | int getColorAssignment(lua_State* lua, bool bBack); // returns color, boolean 64 | tuple makeColorContext(const Color& color); 65 | 66 | void resizeBuffer(int width, int height); 67 | 68 | Value getDeviceInfo() const override; 69 | 70 | // color mapping to oc 256 codes 71 | Color deflate(const Color& color); 72 | void inflate_all(); 73 | void deflate_all(); 74 | unsigned char encode(int rgb); 75 | 76 | private: 77 | Screen* _screen = nullptr; 78 | 79 | int _width = 0; 80 | int _height = 0; 81 | 82 | Cell* _cells = nullptr; 83 | Color _bg = Colors::Black; 84 | Color _fg = Colors::White; 85 | ColorState _color_state; 86 | 87 | static bool s_registered; 88 | }; 89 | -------------------------------------------------------------------------------- /apis/userdata.cpp: -------------------------------------------------------------------------------- 1 | #include "userdata.h" 2 | 3 | UserData::UserData() 4 | : LuaProxy("unnamed") 5 | { 6 | } 7 | 8 | UserDataApi::UserDataApi() 9 | : LuaProxy("userdata") 10 | { 11 | cadd("methods", &UserDataApi::methods); 12 | cadd("invoke", &UserDataApi::invoke); 13 | cadd("dispose", &UserDataApi::dispose); 14 | cadd("apply", &UserDataApi::apply); 15 | cadd("unapply", &UserDataApi::unapply); 16 | cadd("call", &UserDataApi::call); 17 | cadd("save", &UserDataApi::save); 18 | cadd("load", &UserDataApi::load); 19 | cadd("doc", &UserDataApi::doc); 20 | } 21 | 22 | static UserData* check(lua_State* lua) 23 | { 24 | UserData* pData = reinterpret_cast(Value::checkArg(lua, 1)); 25 | 26 | if (!pData) 27 | { 28 | luaL_error(lua, "bad pointer to user data"); 29 | } 30 | 31 | return pData; 32 | } 33 | 34 | int UserDataApi::methods(lua_State* lua) 35 | { 36 | UserData* data = check(lua); 37 | Value methods_table = Value::table(); 38 | for (const LuaMethod& info : data->methods()) 39 | { 40 | methods_table.set(std::get<0>(info), true); 41 | } 42 | 43 | return ValuePack::ret(lua, methods_table); 44 | } 45 | 46 | int UserDataApi::invoke(lua_State* lua) 47 | { 48 | UserData* data = check(lua); 49 | // the pointer is probably safe from gc..probably 50 | lua_remove(lua, 1); 51 | string methodName = Value::checkArg(lua, 1); 52 | lua_remove(lua, 1); 53 | 54 | int stacked = data->invoke(methodName, lua); 55 | lua_pushboolean(lua, true); 56 | lua_insert(lua, 1); 57 | return stacked + 1; 58 | } 59 | 60 | int UserDataApi::dispose(lua_State* lua) 61 | { 62 | UserData* data = check(lua); 63 | data->dispose(); 64 | data->~UserData(); 65 | return 0; 66 | } 67 | 68 | int UserDataApi::apply(lua_State* lua) 69 | { 70 | return ValuePack::ret(lua, true); 71 | } 72 | 73 | int UserDataApi::unapply(lua_State* lua) 74 | { 75 | return ValuePack::ret(lua, true); 76 | } 77 | 78 | int UserDataApi::call(lua_State* lua) 79 | { 80 | return UserDataApi::invoke(lua); 81 | } 82 | 83 | int UserDataApi::save(lua_State* lua) 84 | { 85 | return luaL_error(lua, "userdata.save is not implemented"); 86 | } 87 | 88 | int UserDataApi::load(lua_State* lua) 89 | { 90 | return luaL_error(lua, "userdata.load is not implemented"); 91 | } 92 | 93 | int UserDataApi::doc(lua_State* lua) 94 | { 95 | return ValuePack::ret(lua, Value::nil); 96 | } 97 | 98 | UserDataApi* UserDataApi::get() 99 | { 100 | static UserDataApi api; 101 | return &api; 102 | } 103 | -------------------------------------------------------------------------------- /drivers/ansi.cpp: -------------------------------------------------------------------------------- 1 | #include "ansi.h" 2 | #include "color/color_types.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | using std::stringstream; 8 | 9 | #include 10 | 11 | // clang-format off 12 | static const unsigned char oc_to_ansi[256] = 13 | { 14 | 234,235,236,238,239,240,241,242,244,245,246,247,249,250,251,253, // grays 15 | 16, 17, 18, 19, 21, 16, 17, 18, 19, 21, 22, 23, 24, 25, 27, 22, 23, 24, 25, 27, 16 | 28, 29, 30, 31, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 45, 46, 47, 48, 49, 51, 17 | 18 | 52, 53, 54, 55, 57, 52, 53, 54, 55, 57, 58, 59, 60, 61, 63, 58, 59, 60, 61, 63, 19 | 64, 65, 66, 67, 69, 70, 71, 72, 73, 75, 76, 77, 78, 79, 81, 82, 83, 84, 85, 87, 20 | 21 | 52, 53, 54, 55, 57, 52, 53, 54, 55, 57, 58, 59, 60, 61, 63, 58, 59, 60, 61, 63, 22 | 64, 65, 66, 67, 69, 70, 71, 72, 73, 75, 76, 77, 78, 79, 81, 82, 83, 84, 85, 87, 23 | 24 | 88, 89, 90, 91, 93, 88, 89, 90, 91, 93, 94, 95, 96, 97, 99, 94, 95, 96, 97, 99, 25 | 100,101,102,103,105, 106,107,108,109,111, 112,113,114,115,117, 118,119,120,121,123, 26 | 27 | 160,161,162,163,165, 160,161,162,163,165, 166,167,168,169,171, 166,167,168,169,171, 28 | 172,173,174,175,177, 178,179,180,181,183, 184,185,186,187,189, 190,191,192,193,195, 29 | 30 | 1,197,198,199,201, 196,197,198,199,201, 202,203,204,205,207, 202,203,204,205,207, 31 | 208,209,210,211,213, 214,215,216,217,219, 220,221,222,223,225, 226,227,228,229,255, 32 | }; 33 | // clang-format on 34 | 35 | string to_ansi_deflated(unsigned char deflated_rgb, bool foreground) 36 | { 37 | stringstream ss; 38 | ss << (foreground ? "3" : "4") << "8;5;" << (int)oc_to_ansi[deflated_rgb]; 39 | return ss.str(); 40 | } 41 | 42 | string to_ansi_rgb(int rgb, bool foreground) 43 | { 44 | stringstream ss; 45 | ss << (foreground ? "3" : "4") << "8;2;" << (int)((rgb >> 16) & 0xFF) << ";" << (int)((rgb >> 8) & 0xFF) << ";" << (int)(rgb & 0xFF); 46 | return ss.str(); 47 | } 48 | 49 | string to_ansi(const Color& col, bool foreground, ColorState& cst) 50 | { 51 | if (col.paletted) 52 | { 53 | return to_ansi_rgb(cst.palette[col.rgb], foreground); 54 | } 55 | else 56 | { 57 | return to_ansi_deflated(col.code, foreground); 58 | } 59 | } 60 | 61 | string Ansi::set_color(const Color& fg, const Color& bg, ColorState& cst) 62 | { 63 | string fg_txt = to_ansi(fg, true, cst); 64 | string bg_txt = to_ansi(bg, false, cst); 65 | 66 | return esc + fg_txt + ";" + bg_txt + "m"; 67 | } 68 | 69 | string Ansi::set_pos(int x, int y) 70 | { 71 | stringstream ss; 72 | ss << esc << y << ";" << x << "f"; 73 | return ss.str(); 74 | } 75 | -------------------------------------------------------------------------------- /model/luaproxy.cpp: -------------------------------------------------------------------------------- 1 | #include "luaproxy.h" 2 | #include "drivers/fs_utils.h" 3 | #include 4 | #include 5 | 6 | LuaProxy::LuaProxy(const string& name) 7 | : _name(name) 8 | { 9 | } 10 | 11 | LuaProxy::~LuaProxy() 12 | { 13 | } 14 | 15 | const string& LuaProxy::name() const 16 | { 17 | return _name; 18 | } 19 | 20 | void LuaProxy::name(const string& v) 21 | { 22 | _name = v; 23 | } 24 | 25 | int lua_proxy_static_caller(lua_State* lua) 26 | { 27 | lua_pushstring(lua, "instance"); //+1 28 | lua_gettable(lua, lua_upvalueindex(1)); //-1,+1 29 | auto _this = const_cast(lua_topointer(lua, -1)); 30 | lua_pop(lua, 1); //-1 31 | 32 | if (!_this) 33 | { 34 | luaL_error(lua, "invalid callback, no instance pointer in closure"); 35 | return 0; 36 | } 37 | 38 | lua_pushstring(lua, "name"); //+1 39 | lua_gettable(lua, lua_upvalueindex(1)); //-1,+1 40 | string methodName = lua_tostring(lua, -1); 41 | lua_pop(lua, 1); //-1 42 | 43 | LuaProxy* p = reinterpret_cast(_this); 44 | 45 | int stacked = 0; 46 | if (!fs_utils::run_safely( 47 | [&stacked, &p, &methodName, &lua]() { 48 | stacked = p->invoke(methodName, lua); 49 | }, 50 | [&lua](const string& exception_message) { 51 | luaL_error(lua, exception_message.c_str()); 52 | })) 53 | { 54 | stacked = 0; 55 | } 56 | 57 | return stacked; 58 | } 59 | 60 | vector LuaProxy::methods() const 61 | { 62 | vector result; 63 | for (const auto& pair : _methods) 64 | { 65 | result.push_back(std::make_tuple(pair.first, false, &lua_proxy_static_caller)); 66 | } 67 | for (const auto& pair : _cmethods) 68 | { 69 | result.push_back(std::make_tuple(pair.first, true, pair.second)); 70 | } 71 | return result; 72 | } 73 | 74 | const string& LuaProxy::doc(const string& methodName) const 75 | { 76 | static const string no_docs = ""; 77 | const auto& it = _docs.find(methodName); 78 | if (it == _docs.end()) 79 | return no_docs; 80 | return it->second; 81 | } 82 | 83 | void LuaProxy::add(const string& methodName, ProxyMethod method, const string& doc) 84 | { 85 | _methods[methodName] = method; 86 | _docs[methodName] = doc; 87 | } 88 | 89 | void LuaProxy::cadd(const string& methodName, lua_CFunction cfunction) 90 | { 91 | _cmethods[methodName] = cfunction; 92 | } 93 | 94 | int LuaProxy::invoke(const string& methodName, lua_State* lua) 95 | { 96 | const auto& mit = _methods.find(methodName); 97 | if (mit == _methods.end()) 98 | { 99 | std::stringstream ss; 100 | ss << "no such method: " << methodName; 101 | luaL_error(lua, ss.str().c_str()); 102 | } 103 | 104 | ProxyMethod pmethod = mit->second; 105 | return ((*this).*pmethod)(lua); 106 | } 107 | -------------------------------------------------------------------------------- /drivers/modem_drv.cpp: -------------------------------------------------------------------------------- 1 | #include "modem_drv.h" 2 | #include "model/log.h" 3 | 4 | // c includes for sockets 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // explicitly needed for include on Haiku OS 11 | #include 12 | #include 13 | 14 | #include "server_pool.h" 15 | 16 | bool ModemDriver::readNextModemMessage(ModemEvent& mev) 17 | { 18 | return _connection->readyNextPacket(&mev.payload, false); 19 | } 20 | 21 | ModemDriver::ModemDriver(EventSource* source, int system_port, const std::string& system_address) 22 | : _source(source) 23 | , _system_port(system_port) 24 | , _system_address(system_address) 25 | { 26 | } 27 | 28 | ModemDriver::~ModemDriver() 29 | { 30 | } 31 | 32 | bool ModemDriver::send(const vector& payload) 33 | { 34 | // runOnce can reset the connection, lock it 35 | auto lock = make_lock(); 36 | if (!_connection || !_connection->can_write()) 37 | { 38 | // modem::send failed: not connected 39 | return false; 40 | } 41 | 42 | int32_t data_size = static_cast(payload.size()); 43 | char* p = reinterpret_cast(&data_size); 44 | vector header{ p[0], p[1], p[2], p[3] }; 45 | if (!_connection->write(header)) 46 | return false; 47 | return _connection->write(payload); 48 | } 49 | 50 | bool ModemDriver::onStart() 51 | { 52 | return true; 53 | } 54 | 55 | bool ModemDriver::runOnce() 56 | { 57 | NiceWork work; 58 | if (!_connection) 59 | { 60 | _connection.reset(new Connection(_system_address, _system_port)); 61 | } 62 | switch (_connection->state()) 63 | { 64 | case ConnectionState::Starting: 65 | // still waiting for connection to complete 66 | return true; 67 | case ConnectionState::Failed: 68 | // no server 69 | _connection.reset(nullptr); 70 | if (!_local_server) 71 | { 72 | _local_server = ServerPool::create(_system_port); 73 | } 74 | return true; 75 | case ConnectionState::Ready: 76 | break; 77 | case ConnectionState::Finished: 78 | case ConnectionState::Closed: 79 | // the server may have closed this connection 80 | _connection.reset(nullptr); 81 | return true; 82 | } 83 | 84 | // current read next message is clearing the payload, so we're safe to reuse the ModemEvent here 85 | // but, I would like the code to be more obviously correct 86 | while (true) 87 | { 88 | ModemEvent me; 89 | if (!readNextModemMessage(me)) 90 | break; 91 | work.set(); 92 | _source->push(me); 93 | } 94 | 95 | return true; 96 | } 97 | 98 | void ModemDriver::onStop() 99 | { 100 | _connection.reset(nullptr); 101 | if (_local_server) 102 | { 103 | _local_server->stop(); 104 | } 105 | _local_server.reset(nullptr); 106 | // modem shutdown 107 | } 108 | -------------------------------------------------------------------------------- /haiku/filesystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace haiku 9 | { 10 | namespace filesystem 11 | { 12 | enum class copy_options : unsigned short 13 | { 14 | none = 0, 15 | skip_existing = 1, 16 | overwrite_existing = 2, 17 | update_existing = 4, 18 | recursive = 8, 19 | copy_symlinks = 16, 20 | skip_symlinks = 32, 21 | directories_only = 64, 22 | create_symlinks = 128, 23 | create_hard_links = 256 24 | }; 25 | 26 | enum class file_type : signed char 27 | { 28 | none = 0, 29 | not_found = -1, 30 | regular = 1, 31 | directory = 2, 32 | symlink = 3, 33 | block = 4, 34 | character = 5, 35 | fifo = 6, 36 | socket = 7, 37 | unknown = 8 38 | }; 39 | 40 | class file_status 41 | { 42 | public: 43 | file_status(file_type ftype) 44 | : _type(ftype) 45 | { 46 | } 47 | file_type type() const; 48 | 49 | private: 50 | file_type _type; 51 | }; 52 | 53 | using file_time_type = std::chrono::system_clock::time_point; 54 | 55 | void copy(const std::string& from, const std::string& to, copy_options options, std::error_code&) noexcept; 56 | void rename(const std::string& from, const std::string& to, std::error_code&) noexcept; 57 | void create_directories(const std::string& path, std::error_code& ec) noexcept; 58 | bool exists(const std::string& path, std::error_code& ec) noexcept; 59 | bool is_directory(const std::string& path, std::error_code& ec) noexcept; 60 | size_t file_size(const std::string& path, std::error_code& ec) noexcept; 61 | file_time_type last_write_time(const std::string& path, std::error_code& ec) noexcept; 62 | file_status symlink_status(const std::string&, std::error_code&) noexcept; 63 | bool remove(const std::string&, std::error_code&) noexcept; 64 | std::string current_path(std::error_code& ec) noexcept; 65 | 66 | class directory_iterator 67 | { 68 | public: 69 | class directory_entry 70 | { 71 | public: 72 | directory_entry(const std::string& path) 73 | : _path(path) 74 | { 75 | } 76 | std::string path() const 77 | { 78 | return _path; 79 | } 80 | 81 | private: 82 | std::string _path; 83 | }; 84 | 85 | directory_iterator() = default; 86 | explicit directory_iterator(const std::string& path, std::error_code& ec) noexcept; 87 | const directory_entry& operator*() const; 88 | const directory_entry* operator->() const 89 | { 90 | return &**this; 91 | } 92 | bool operator!=(const directory_iterator&) const; 93 | directory_iterator& operator++(); 94 | 95 | directory_iterator begin() noexcept; 96 | directory_iterator end() noexcept; 97 | 98 | private: 99 | std::queue _entries; 100 | std::string _root; 101 | }; 102 | }; 103 | }; 104 | -------------------------------------------------------------------------------- /io/frame.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using std::string; 10 | using std::tuple; 11 | using std::unique_ptr; 12 | using std::vector; 13 | 14 | #include "color/color_types.h" 15 | #include "io/event.h" 16 | 17 | struct Cell 18 | { 19 | string value; // must be a string for multibyte chars 20 | Color fg; 21 | Color bg; 22 | bool locked; // locked when a double wide is placed before it 23 | int width; 24 | }; 25 | 26 | class IScreen 27 | { 28 | public: 29 | virtual bool setResolution(int width, int height) = 0; 30 | virtual void push(const MouseEvent& me) = 0; 31 | virtual void push(const KeyEvent& me) = 0; 32 | virtual void invalidate() = 0; 33 | }; 34 | 35 | class Frame 36 | { 37 | protected: 38 | // REQUIRED PURE VIRTUALS 39 | // this is called when the emulator wants you to render text in your window 40 | virtual void onWrite(int x, int y, const Cell& cell, ColorState& cst) = 0; 41 | 42 | // It is required to return the initial size of the window from onOpen 43 | // If the user later changes the size of the window ... 44 | // (e.g. dragging the border with a mouse) 45 | // then you need to call winched with the new resolution 46 | virtual tuple onOpen() = 0; 47 | 48 | // The following methods are likely helpful to derived types 49 | public: 50 | // call close when this window would like to abort the vm 51 | // e.g. the user tries to close the window frame 52 | void close(); 53 | 54 | // Call mouseEvent when you want to push mouse events to the vm 55 | // see io/event.h for MouseEvent details 56 | void mouseEvent(const MouseEvent& me); 57 | 58 | // Call keyEvent when you want to push key events to the vm 59 | // see io/event.h for KeyEvent details 60 | void keyEvent(const KeyEvent& ke); 61 | 62 | // it is optional to override depth 63 | virtual EDepthType depth() 64 | { 65 | return EDepthType::_8; 66 | } 67 | 68 | protected: 69 | // You need to call winched if the user is resizing the Frame window manually 70 | // The VM does not call winched, you do (e.g. from onUpdate()) 71 | // this is reported to component.gpu.maxResolution() 72 | void winched(int width, int height); 73 | 74 | // it is optional to override these methods as you need them 75 | virtual void onUpdate() 76 | { 77 | } 78 | virtual void onClose() 79 | { 80 | } 81 | virtual void onClear() 82 | { 83 | } 84 | 85 | /////////////////////////////////////////////////////////////////////////////////////////// 86 | // The remaining methods should only be called by the emulator 87 | public: 88 | virtual ~Frame(); 89 | 90 | // called by Screen when initializing 91 | void open(IScreen* screen); 92 | 93 | // called by Screen during vm loop 94 | bool update(); 95 | 96 | // called by the gpu 97 | void write(int x, int y, const Cell& cell, ColorState& _cst); 98 | void clear(); 99 | 100 | // size is updated by calling winched 101 | tuple size() const; 102 | 103 | // the screen component uses the frame for turning on and off 104 | bool on() const; 105 | bool on(bool bOn); 106 | 107 | private: 108 | IScreen* _screen = nullptr; 109 | 110 | int _width; 111 | int _height; 112 | bool _isOn; 113 | }; 114 | 115 | namespace Factory 116 | { 117 | // Add your frame type to create_frame 118 | // see drivers/factory_shell.cpp 119 | Frame* create_frame(const string& frameTypeName); 120 | }; 121 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | # BasedOnStyle: WebKit 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: DontAlign 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlines: false 8 | AlignOperands: false 9 | AlignTrailingComments: true 10 | AllowAllParametersOfDeclarationOnNextLine: false 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: false 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterDefinitionReturnType: None 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: false 19 | AlwaysBreakTemplateDeclarations: false 20 | BinPackArguments: true 21 | BinPackParameters: true 22 | BraceWrapping: 23 | AfterClass: true 24 | AfterControlStatement: true 25 | AfterEnum: true 26 | AfterFunction: true 27 | AfterNamespace: true 28 | AfterObjCDeclaration: true 29 | AfterStruct: true 30 | AfterUnion: true 31 | AfterExternBlock: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | SplitEmptyFunction: true 36 | SplitEmptyRecord: true 37 | SplitEmptyNamespace: true 38 | BreakBeforeBinaryOperators: None 39 | BreakBeforeBraces: Allman 40 | BreakBeforeInheritanceComma: false 41 | BreakBeforeTernaryOperators: false 42 | BreakConstructorInitializersBeforeComma: false 43 | BreakConstructorInitializers: BeforeComma 44 | BreakAfterJavaFieldAnnotations: false 45 | BreakStringLiterals: false 46 | ColumnLimit: 0 47 | CommentPragmas: '^ IWYU pragma:' 48 | CompactNamespaces: false 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 50 | ConstructorInitializerIndentWidth: 4 51 | ContinuationIndentWidth: 0 52 | Cpp11BracedListStyle: false 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | ExperimentalAutoDetectBinPacking: false 56 | FixNamespaceComments: false 57 | ForEachMacros: 58 | - foreach 59 | IncludeBlocks: Preserve 60 | IncludeCategories: 61 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 62 | Priority: 2 63 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 64 | Priority: 3 65 | - Regex: '.*' 66 | Priority: 1 67 | IncludeIsMainRegex: '(Test)?$' 68 | IndentCaseLabels: false 69 | IndentPPDirectives: None 70 | IndentWidth: 2 71 | IndentWrappedFunctionNames: false 72 | JavaScriptQuotes: Leave 73 | JavaScriptWrapImports: true 74 | KeepEmptyLinesAtTheStartOfBlocks: true 75 | MacroBlockBegin: '' 76 | MacroBlockEnd: '' 77 | MaxEmptyLinesToKeep: 1 78 | NamespaceIndentation: Inner 79 | ObjCBlockIndentWidth: 2 80 | ObjCSpaceAfterProperty: true 81 | ObjCSpaceBeforeProtocolList: true 82 | PenaltyBreakAssignment: 2 83 | PenaltyBreakBeforeFirstCallParameter: 19 84 | PenaltyBreakComment: 300 85 | PenaltyBreakFirstLessLess: 120 86 | PenaltyBreakString: 1000 87 | PenaltyExcessCharacter: 1000000 88 | PenaltyReturnTypeOnItsOwnLine: 60 89 | PointerAlignment: Left 90 | RawStringFormats: 91 | - Delimiters: 92 | - pb 93 | Language: TextProto 94 | BasedOnStyle: google 95 | ReflowComments: true 96 | SortIncludes: true 97 | SortUsingDeclarations: true 98 | SpaceAfterCStyleCast: false 99 | SpaceAfterTemplateKeyword: true 100 | SpaceBeforeAssignmentOperators: true 101 | SpaceBeforeParens: ControlStatements 102 | SpaceInEmptyParentheses: false 103 | SpacesBeforeTrailingComments: 1 104 | SpacesInAngles: false 105 | SpacesInContainerLiterals: true 106 | SpacesInCStyleCastParentheses: false 107 | SpacesInParentheses: false 108 | SpacesInSquareBrackets: false 109 | Standard: Cpp11 110 | TabWidth: 2 111 | UseTab: Never 112 | 113 | -------------------------------------------------------------------------------- /util/crc32.cpp: -------------------------------------------------------------------------------- 1 | #include "crc32.h" 2 | 3 | static uint32_t crcLookupTable[] = { 4 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 5 | 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 6 | 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 7 | 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 8 | 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 9 | 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 10 | 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 11 | 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 12 | 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 13 | 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 14 | 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 15 | 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 16 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 17 | 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 18 | 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 19 | 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 20 | 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 21 | 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 22 | 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 23 | 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 24 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 25 | 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 26 | 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 27 | 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 28 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 29 | 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 30 | 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 31 | 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 32 | 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 33 | 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 34 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 35 | 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 36 | 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 37 | 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 38 | 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 39 | 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 40 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 41 | 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 42 | 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 43 | 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 44 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 45 | 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 46 | 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 47 | 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 48 | 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 49 | 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 50 | 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 51 | 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 52 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 53 | 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 54 | 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 55 | 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 56 | 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 57 | 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 58 | 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 59 | 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 60 | 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 61 | 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 62 | 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 63 | 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 64 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 65 | 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 66 | 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 67 | 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D 68 | }; 69 | 70 | uint32_t util::crc32(vector data) 71 | { 72 | uint32_t crc = 0xFFFFFFFF; 73 | for (vector::iterator i = data.begin(); i != data.end(); i++) 74 | { 75 | uint8_t lookupIndex = (crc ^ *i) & 0xFF; 76 | crc = (crc >> 8) ^ crcLookupTable[lookupIndex]; 77 | } 78 | 79 | crc ^= 0xFFFFFFFF; 80 | return crc; 81 | } 82 | -------------------------------------------------------------------------------- /model/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "model/log.h" 3 | 4 | #include "drivers/fs_utils.h" 5 | #include 6 | #include 7 | 8 | using Logging::lout; 9 | 10 | extern "C" 11 | { 12 | static int l_cpp_store(lua_State* lua) 13 | { 14 | const void* raw = lua_topointer(lua, 1); 15 | Value* pdata = const_cast(static_cast(raw)); 16 | string key = Value::checkArg(lua, 2); 17 | Value value(lua, 3); 18 | pdata->set(key, value); 19 | return 0; 20 | } 21 | } 22 | 23 | Config::Config() 24 | : _data(Value::table()) 25 | { 26 | } 27 | 28 | bool Config::load(const string& path, const string& name) 29 | { 30 | // clear out previous values (if any) 31 | _data = Value::nil; 32 | _path = path; 33 | _name = name; 34 | 35 | string table; 36 | 37 | // if save path has no client.cfg, copy it from proc_root 38 | if (!fs_utils::exists(savePath())) 39 | { 40 | if (!fs_utils::read(fs_utils::make_proc_path("client.cfg"), &table)) 41 | { 42 | lout << "failed to read default client.cfg" << endl; 43 | return false; 44 | } 45 | } 46 | else if (!fs_utils::read(savePath(), &table)) 47 | { 48 | lout << "config could not load: " << name << endl; 49 | return false; 50 | } 51 | 52 | lout << "config [" << _name << "]: table: " << table; 53 | lout << endl; 54 | 55 | if (table.empty()) 56 | { 57 | return true; // ok, just nothing to load 58 | } 59 | 60 | string loader = 61 | "for k,v in pairs(" + table + ") do\n" 62 | " cpp_store(_data, k, v)\n" 63 | "end"; 64 | 65 | lua_State* lua = luaL_newstate(); 66 | if (luaL_loadstring(lua, loader.c_str()) == 0) 67 | { 68 | Value tmpData = Value::table(); 69 | luaL_openlibs(lua); 70 | lua_pushcfunction(lua, l_cpp_store); 71 | lua_setglobal(lua, "cpp_store"); 72 | lua_pushlightuserdata(lua, &tmpData); 73 | lua_setglobal(lua, "_data"); 74 | int result_status = lua_pcall(lua, 0, LUA_MULTRET, 0); 75 | if (result_status != LUA_OK) 76 | { 77 | lout << "Failed to digest the configuration\n"; 78 | lout << lua_tostring(lua, -1) << endl; 79 | return false; 80 | } 81 | _data = tmpData; 82 | clear_n(_data); 83 | _cache = _data.serialize(true); 84 | } 85 | else 86 | { 87 | lout << "\nConfiguration could not load\n"; 88 | lout << lua_tostring(lua, -1) << endl; 89 | return false; 90 | } 91 | lua_close(lua); 92 | return true; 93 | } 94 | 95 | string Config::name() const 96 | { 97 | return _name; 98 | } 99 | 100 | string Config::savePath() const 101 | { 102 | return _path + "/" + _name + ".cfg"; 103 | } 104 | 105 | const Value& Config::get(const string& key) const 106 | { 107 | return _data.get(key); 108 | } 109 | Value& Config::get(const string& key) 110 | { 111 | return _data.get(key); 112 | } 113 | 114 | bool Config::set(const string& key, const Value& value, bool bCreateOnly) 115 | { 116 | if (!bCreateOnly || !_data.contains(key)) 117 | { 118 | _data.set(key, value); 119 | return true; 120 | } 121 | return false; 122 | } 123 | 124 | bool Config::save() const 125 | { 126 | if (_data) 127 | { 128 | string updated_config = _data.serialize(true); 129 | if (updated_config != _cache) 130 | { 131 | lout << "saving " << _name << ": config\n"; 132 | return fs_utils::write(updated_config, savePath()); 133 | } 134 | } 135 | return true; 136 | } 137 | 138 | vector Config::keys() const 139 | { 140 | return _data.keys(); 141 | } 142 | 143 | void Config::clear_n(Value& t) 144 | { 145 | if (t.type() == "table") 146 | { 147 | t.set("n", Value::nil); 148 | auto pairs = t.pairs(); 149 | for (const auto& pair : pairs) 150 | { 151 | clear_n(*std::get<1>(pair)); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /util/md5.cpp: -------------------------------------------------------------------------------- 1 | #include "md5.h" 2 | 3 | #include 4 | 5 | using std::vector; 6 | 7 | // clang-format off 8 | static uint32_t s[64] = 9 | { 10 | 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 11 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 12 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 13 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 14 | }; 15 | 16 | static uint32_t K[64] = 17 | { 18 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 19 | 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 20 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 21 | 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 22 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 23 | 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 24 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 25 | 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 26 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 27 | 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 28 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, 29 | }; 30 | // clang-format on 31 | 32 | vector util::md5(vector message) 33 | { 34 | auto originalLength = (message.size() * 8) % 0x8000000000000000; 35 | 36 | message.push_back(0x80); 37 | while (message.size() % 64 != 56) 38 | { 39 | message.push_back(0x00); 40 | } 41 | 42 | message.push_back(originalLength & 0xFF); 43 | message.push_back((originalLength >> 8) & 0xFF); 44 | message.push_back((originalLength >> 16) & 0xFF); 45 | message.push_back((originalLength >> 24) & 0xFF); 46 | message.push_back((originalLength >> 32) & 0xFF); 47 | message.push_back((originalLength >> 40) & 0xFF); 48 | message.push_back((originalLength >> 48) & 0xFF); 49 | message.push_back((originalLength >> 56) & 0x8F); 50 | 51 | uint32_t a0 = 0x67452301; 52 | uint32_t b0 = 0xefcdab89; 53 | uint32_t c0 = 0x98badcfe; 54 | uint32_t d0 = 0x10325476; 55 | 56 | for (uint32_t i = 0; i < message.size(); i += 64) 57 | { 58 | vector words{}; 59 | for (uint32_t j = i; j < i + 64; j += 4) 60 | { 61 | words.push_back(((message[j] << 24) & (message[j + 1] << 16) & (message[j + 2] << 8) & message[j + 3]) % 0x80000000); 62 | } 63 | 64 | uint32_t A = a0; 65 | uint32_t B = b0; 66 | uint32_t C = c0; 67 | uint32_t D = d0; 68 | 69 | for (uint32_t j = 0; j < 64; j++) 70 | { 71 | uint32_t F, g; 72 | 73 | if (j < 15) 74 | { 75 | F = (B & C) | ((~B) & D); 76 | g = j; 77 | } 78 | else if (j < 32) 79 | { 80 | F = (D & B) | ((~D) & C); 81 | g = (5 * j + 1) % 16; 82 | } 83 | else if (j < 48) 84 | { 85 | F = B ^ C ^ D; 86 | g = (3 * j + 5) % 16; 87 | } 88 | else 89 | { 90 | F = C ^ (B | (~D)); 91 | g = (7 * j) % 16; 92 | } 93 | 94 | F += A + K[j] + words[g]; 95 | A = D; 96 | D = C; 97 | C = B; 98 | B += ((F << s[j]) | (F >> s[j])); 99 | } 100 | 101 | a0 = (a0 + A) % 0x80000000; 102 | b0 = (b0 + B) % 0x80000000; 103 | c0 = (c0 + C) % 0x80000000; 104 | d0 = (d0 + D) % 0x80000000; 105 | } 106 | 107 | return vector{ 108 | (char)(a0), 109 | (char)((a0 >> 8) & 0xFF), 110 | (char)((a0 >> 16) & 0xFF), 111 | (char)((a0 >> 24) & 0xFF), 112 | 113 | (char)(b0), 114 | (char)((b0 >> 8) & 0xFF), 115 | (char)((b0 >> 16) & 0xFF), 116 | (char)((b0 >> 24) & 0xFF), 117 | 118 | (char)(c0), 119 | (char)((c0 >> 8) & 0xFF), 120 | (char)((c0 >> 16) & 0xFF), 121 | (char)((c0 >> 24) & 0xFF), 122 | 123 | (char)(d0), 124 | (char)((d0 >> 8) & 0xFF), 125 | (char)((d0 >> 16) & 0xFF), 126 | (char)((d0 >> 24) & 0xFF), 127 | }; 128 | } 129 | -------------------------------------------------------------------------------- /drivers/ansi_escape.cpp: -------------------------------------------------------------------------------- 1 | #include "ansi_escape.h" 2 | #include "model/log.h" 3 | 4 | #include 5 | using std::cout; 6 | using std::flush; 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "ansi.h" 13 | #include "apis/unicode.h" 14 | #include "raw_tty.h" 15 | 16 | tuple current_resolution() 17 | { 18 | int cols = 80; 19 | int lines = 24; 20 | 21 | #ifdef TIOCGSIZE 22 | struct ttysize ts; 23 | ioctl(STDOUT_FILENO, TIOCGSIZE, &ts); 24 | cols = ts.ts_cols; 25 | lines = ts.ts_lines; 26 | #elif defined(TIOCGWINSZ) 27 | struct winsize ts; 28 | ioctl(STDOUT_FILENO, TIOCGWINSZ, &ts); 29 | cols = ts.ws_col; 30 | lines = ts.ws_row; 31 | #endif /* TIOCGSIZE */ 32 | 33 | return std::make_tuple(cols, lines); 34 | } 35 | 36 | static sigset_t g_sigset; 37 | 38 | AnsiEscapeTerm::AnsiEscapeTerm() 39 | { 40 | sigemptyset(&g_sigset); 41 | sigaddset(&g_sigset, SIGWINCH); 42 | 43 | if (pthread_sigmask(SIG_BLOCK, &g_sigset, nullptr)) 44 | { 45 | return; 46 | } 47 | } 48 | 49 | AnsiEscapeTerm::~AnsiEscapeTerm() 50 | { 51 | pthread_sigmask(SIG_UNBLOCK, &g_sigset, nullptr); 52 | cout << Ansi::color_reset << Ansi::clear_term << Ansi::set_pos(1, 1) << flush; 53 | } 54 | 55 | void AnsiEscapeTerm::onUpdate() 56 | { 57 | #ifndef __APPLE__ 58 | timespec timeout{ 0, 0 }; // poll, do not block 59 | if (sigtimedwait(&g_sigset, nullptr, &timeout) == SIGWINCH) // winched 60 | { 61 | cout << Ansi::clear_scroll << flush; 62 | auto rez = current_resolution(); 63 | int width = std::get<0>(rez); 64 | int height = std::get<1>(rez); 65 | winched(width, height); 66 | } 67 | cout << flush; 68 | #endif 69 | } 70 | 71 | tuple AnsiEscapeTerm::onOpen() 72 | { 73 | //switch to alternative buffer screen 74 | // cout << esc << "47h"; 75 | 76 | cout << Ansi::cursor_off; 77 | clear(); 78 | 79 | TtyReader::engine()->start(this); 80 | 81 | return current_resolution(); 82 | } 83 | 84 | void AnsiEscapeTerm::onClose() 85 | { 86 | TtyReader::engine()->stop(); 87 | cout << Ansi::set_pos(1, 1); 88 | cout << flush; 89 | } 90 | 91 | static string replace_all(const string& src, const string& match, const string& replacement) 92 | { 93 | size_t index = 0; 94 | string result = ""; 95 | while (index < src.size()) 96 | { 97 | size_t next = src.find(match, index); 98 | result += src.substr(index, next - index); 99 | index = next; 100 | if (next != string::npos) 101 | { 102 | result += replacement; 103 | index += match.size() + 1; 104 | } 105 | else 106 | { 107 | // in case of signed size_t index is not >= src.size() here 108 | break; 109 | } 110 | } 111 | return result; 112 | } 113 | 114 | string AnsiEscapeTerm::scrub(const string& value) const 115 | { 116 | // replace tabs with (U+2409 for HT symbol) 117 | // I could use the ht unicode symbol in the source file 118 | // but i prefer to keep the source files in ascii 119 | return replace_all(value, "\t", string{ (char)226, (char)144, (char)137, ' ' }); 120 | } 121 | 122 | void AnsiEscapeTerm::onWrite(int x, int y, const Cell& cell, ColorState& cst) 123 | { 124 | string cmd = ""; 125 | if (x != _x || y != _y) 126 | { 127 | if (x == 1 && y == _y + 1) // new line 128 | { 129 | cmd += "\r\n"; 130 | } 131 | else 132 | { 133 | cmd += Ansi::set_pos(x, y); 134 | } 135 | } 136 | if (cell.fg.rgb != _fg_rgb || cell.bg.rgb != _bg_rgb) 137 | cmd += Ansi::set_color(cell.fg, cell.bg, cst); 138 | 139 | string text = scrub(cell.value); 140 | cout << cmd << text; 141 | _x = x + cell.width; 142 | _y = y; 143 | _fg_rgb = cell.fg.rgb; 144 | _bg_rgb = cell.bg.rgb; 145 | 146 | // if _x or _y are outside the window, flush 147 | auto rez = size(); 148 | if (_x > std::get<0>(rez) || _y > std::get<1>(rez)) 149 | cout << flush; 150 | } 151 | 152 | void AnsiEscapeTerm::onClear() 153 | { 154 | cout << Ansi::save_pos << Ansi::color_reset << Ansi::clear_term << Ansi::restore_pos << Ansi::set_pos(1, 1) << flush; 155 | } 156 | -------------------------------------------------------------------------------- /components/internet.cpp: -------------------------------------------------------------------------------- 1 | #include "internet.h" 2 | #include "apis/system.h" 3 | #include "model/client.h" 4 | #include "model/host.h" 5 | 6 | #include "drivers/internet_drv.h" 7 | 8 | #include 9 | using std::stringstream; 10 | 11 | bool Internet::s_registered = Host::registerComponentType("internet"); 12 | 13 | Internet::Internet() 14 | { 15 | add("isTcpEnabled", &Internet::isTcpEnabled); 16 | add("isHttpEnabled", &Internet::isHttpEnabled); 17 | add("connect", &Internet::connect); 18 | add("request", &Internet::request); 19 | } 20 | 21 | Internet::~Internet() 22 | { 23 | while (!_connections.empty()) 24 | release(*_connections.begin()); 25 | } 26 | 27 | bool Internet::onInitialize() 28 | { 29 | _tcp = config().get(ConfigIndex::TcpEnabled).Or(true).toBool(); 30 | _http = config().get(ConfigIndex::HttpEnabled).Or(true).toBool(); 31 | return true; 32 | } 33 | 34 | int Internet::isTcpEnabled(lua_State* lua) 35 | { 36 | return ValuePack::ret(lua, _tcp); 37 | } 38 | 39 | int Internet::isHttpEnabled(lua_State* lua) 40 | { 41 | return ValuePack::ret(lua, _http); 42 | } 43 | 44 | int Internet::connect(lua_State* lua) 45 | { 46 | string addr = Value::checkArg(lua, 1); 47 | int default_port = -1; 48 | int port = Value::checkArg(lua, 2, &default_port); 49 | if (!_tcp) 50 | { 51 | return ValuePack::ret(lua, Value::nil, "tcp connections are unavailable"); 52 | } 53 | if (static_cast(_connections.size()) >= SystemApi::max_connections()) 54 | { 55 | return luaL_error(lua, "too many open connections"); 56 | } 57 | 58 | int parsed_port; 59 | if (parsePort(&addr, &parsed_port)) 60 | { 61 | port = parsed_port; 62 | } 63 | 64 | if (port < 0 || port >= 0xffff) 65 | { 66 | return luaL_error(lua, "address could not be parsed or no valid port given"); 67 | } 68 | 69 | lua_settop(lua, 0); 70 | auto pConn = InternetConnection::openTcp(UserDataAllocator(lua), { { [this](InternetConnection* pc) { this->release(pc); } } }, { addr, port }); 71 | _connections.insert(pConn); 72 | 73 | return 1; 74 | } 75 | 76 | int Internet::request(lua_State* lua) 77 | { 78 | string addr = Value::checkArg(lua, 1); 79 | static const string default_post = ""; 80 | string post = Value::checkArg(lua, 2, &default_post); 81 | map header; 82 | header = Value::checkArg(lua, 3, &header); 83 | 84 | if (!_http) 85 | { 86 | return ValuePack::ret(lua, Value::nil, "http connections are unavailable"); 87 | } 88 | HttpAddress httpAddr(addr); 89 | if (!httpAddr.valid) 90 | { 91 | return ValuePack::ret(lua, Value::nil, "invalid address"); 92 | } 93 | if (static_cast(_connections.size()) >= SystemApi::max_connections()) 94 | { 95 | return luaL_error(lua, "too many open connections"); 96 | } 97 | 98 | lua_settop(lua, 0); 99 | auto pConn = InternetConnection::openHttp(UserDataAllocator(lua), { { [this](InternetConnection* pc) { this->release(pc); } } }, { httpAddr, post, header }); 100 | if (!pConn) 101 | { 102 | return ValuePack::ret(lua, Value::nil, "http requests require libcurl, consider apt-get install libcurl4-openssl-dev"); 103 | } 104 | _connections.insert(pConn); 105 | 106 | return 1; 107 | } 108 | 109 | bool Internet::parsePort(string* pAddr, int* pPort) const 110 | { 111 | size_t c_index = pAddr->find_last_of(":"); 112 | if (c_index == string::npos) 113 | return false; 114 | 115 | stringstream ss; 116 | ss << pAddr->substr(c_index + 1); 117 | int port; 118 | ss >> port; 119 | 120 | if (!ss || !ss.eof() || port < 0 || port >= 0xffff) 121 | return false; 122 | 123 | *pAddr = pAddr->substr(0, c_index); 124 | *pPort = port; 125 | return true; 126 | } 127 | 128 | bool Internet::release(InternetConnection* pConn) 129 | { 130 | return _connections.erase(pConn) > 0; 131 | } 132 | 133 | RunState Internet::update() 134 | { 135 | for (InternetConnection* inc : _connections) 136 | { 137 | if (inc->update()) 138 | { 139 | ValuePack pack{ "internet_ready", address() }; 140 | client()->pushSignal(pack); 141 | } 142 | } 143 | 144 | return RunState::Continue; 145 | } 146 | -------------------------------------------------------------------------------- /model/value.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "apis/native-lua.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using std::map; 11 | using std::ostream; 12 | using std::shared_ptr; 13 | using std::size_t; 14 | using std::string; 15 | using std::vector; 16 | 17 | class Value 18 | { 19 | public: 20 | Value(const vector& v); 21 | Value(const string& v) : Value(vector(v.begin(), v.end())) {} 22 | Value(bool b); 23 | Value(double d); 24 | Value(const void* p, bool bLight); 25 | Value(const char* cstr) : Value(string(cstr)) {} 26 | 27 | Value(uint64_t n); 28 | Value(int64_t n) : Value(uint64_t(n)) {} 29 | Value(uint32_t n) : Value(uint64_t(n)) {} 30 | Value(int32_t n) : Value(uint64_t(n)) {} 31 | 32 | Value(lua_State*); 33 | Value(lua_State*, int); 34 | 35 | Value(); 36 | 37 | static string stack(lua_State* state); 38 | static Value table(); 39 | static const Value nil; 40 | 41 | const Value& Or(const Value& def) const; 42 | 43 | void push(lua_State* lua) const; 44 | 45 | Value& insert(const Value& value); 46 | int len() const; 47 | string type() const; 48 | int type_id() const; 49 | 50 | string toString() const; 51 | vector toRawString() const; 52 | double toNumber() const; 53 | bool toBool() const; 54 | void* toPointer() const; 55 | lua_State* toThread() const; 56 | int status() const; 57 | 58 | template 59 | static T checkArg(lua_State* lua, int index, const T* pDefault = nullptr); 60 | 61 | // table functions 62 | vector keys() const; 63 | vector> pairs(); 64 | const Value& get(const string& key) const; 65 | Value& get(const string& key); 66 | const Value& get(int index) const; 67 | Value& get(int index); 68 | Value& set(const string& key, const Value& value); 69 | Value& set(int key, const Value& value); 70 | bool contains(int key) const; 71 | bool contains(const string& key) const; 72 | 73 | string serialize(bool pretty = false, int depth = 0) const; 74 | explicit operator bool() const; 75 | bool operator<(const Value& rhs) const; 76 | 77 | private: 78 | string _type; 79 | int _id; 80 | vector _string; 81 | bool _bool = false; 82 | double _number = 0; 83 | bool _isInteger = false; 84 | void* _pointer = nullptr; 85 | lua_State* _thread = nullptr; 86 | int _thread_status = 0; 87 | map _stable; 88 | map _ntable; 89 | }; 90 | 91 | struct ValuePack : public vector 92 | { 93 | ValuePack(std::initializer_list); 94 | ValuePack() = default; 95 | 96 | static ValuePack pack(lua_State* lua); 97 | 98 | int push(lua_State* lua) const; 99 | 100 | template 101 | inline static int ret(lua_State* lua, const Ts&... args) 102 | { 103 | ::lua_settop(lua, 0); 104 | return ValuePack::push_ret(lua, args...); 105 | } 106 | 107 | private: 108 | inline static int push_ret(lua_State* lua, bool arg) 109 | { 110 | lua_pushboolean(lua, arg); 111 | return 1; 112 | } 113 | 114 | inline static int push_ret(lua_State* lua, int32_t arg) 115 | { 116 | lua_pushinteger(lua, arg); 117 | return 1; 118 | } 119 | 120 | inline static int push_ret(lua_State* lua, uint32_t arg) 121 | { 122 | lua_pushinteger(lua, arg); 123 | return 1; 124 | } 125 | 126 | inline static int push_ret(lua_State* lua, int64_t arg) 127 | { 128 | lua_pushinteger(lua, arg); 129 | return 1; 130 | } 131 | 132 | inline static int push_ret(lua_State* lua, uint64_t arg) 133 | { 134 | lua_pushinteger(lua, arg); 135 | return 1; 136 | } 137 | 138 | inline static int push_ret(lua_State* lua, double arg) 139 | { 140 | lua_pushnumber(lua, arg); 141 | return 1; 142 | } 143 | 144 | inline static int push_ret(lua_State* lua, float arg) 145 | { 146 | lua_pushnumber(lua, arg); 147 | return 1; 148 | } 149 | 150 | inline static int push_ret(lua_State* lua, const string& arg) 151 | { 152 | lua_pushlstring(lua, arg.c_str(), arg.length()); 153 | return 1; 154 | } 155 | 156 | inline static int push_ret(lua_State* lua, const char* arg) 157 | { 158 | lua_pushstring(lua, arg); 159 | return 1; 160 | } 161 | 162 | inline static int push_ret(lua_State* lua, const void* arg) 163 | { 164 | lua_pushlightuserdata(lua, const_cast(arg)); 165 | return 1; 166 | } 167 | 168 | inline static int push_ret(lua_State* lua, const vector& arg) 169 | { 170 | lua_pushlstring(lua, arg.data(), arg.size()); 171 | return 1; 172 | } 173 | 174 | inline static int push_ret(lua_State* lua, const Value& v) 175 | { 176 | v.push(lua); 177 | return 1; 178 | } 179 | 180 | inline static int push_ret(lua_State* lua) 181 | { 182 | return 0; 183 | } 184 | 185 | template 186 | inline static int push_ret(lua_State* lua, const T& arg, const Ts&... args) 187 | { 188 | return ValuePack::push_ret(lua, arg) + ValuePack::push_ret(lua, args...); 189 | } 190 | }; 191 | ostream& operator<<(ostream& os, const ValuePack& pack); 192 | -------------------------------------------------------------------------------- /components/eeprom.cpp: -------------------------------------------------------------------------------- 1 | #include "eeprom.h" 2 | #include "apis/system.h" 3 | #include "drivers/fs_utils.h" 4 | #include "model/client.h" 5 | #include "model/host.h" 6 | #include "model/log.h" 7 | #include "util/crc32.h" 8 | #include 9 | #include 10 | #include 11 | 12 | using Logging::lout; 13 | 14 | bool Eeprom::s_registered = Host::registerComponentType("eeprom"); 15 | 16 | Eeprom::Eeprom() 17 | { 18 | add("set", &Eeprom::set, 19 | "function(data:string) -- Overwrite the currently stored byte array."); 20 | add("get", &Eeprom::get, 21 | "function():string -- Get the currently stored byte array."); 22 | add("getData", &Eeprom::getData, 23 | "function():string -- Get the currently stored byte array."); 24 | add("setData", &Eeprom::setData, 25 | "function(data:string) -- Overwrite the currently stored byte array."); 26 | add("getSize", &Eeprom::getSize, 27 | "function():number -- Get the storage capacity of this EEPROM."); 28 | add("getDataSize", &Eeprom::getDataSize, 29 | "function():number -- Get the storage capacity of this EEPROM."); 30 | add("getLabel", &Eeprom::getLabel, 31 | "function():string -- Get the label of the EEPROM."); 32 | add("setLabel", &Eeprom::setLabel, 33 | "function(data:string):string -- Set the label of the EEPROM."); 34 | add("getChecksum", &Eeprom::getChecksum, 35 | "function():string -- Get the checksum of the data on this EEPROM."); 36 | } 37 | 38 | bool Eeprom::onInitialize() 39 | { 40 | int config_bios_size = config().get(ConfigIndex::BiosSize).toNumber(); 41 | int config_data_size = config().get(ConfigIndex::DataSize).toNumber(); 42 | 43 | _bios_size_limit = config_bios_size == 0 ? _bios_size_limit : config_bios_size; 44 | _data_size_limit = config_data_size == 0 ? _data_size_limit : config_data_size; 45 | 46 | if (client()->envPath().empty()) 47 | { 48 | lout << "bug, eeprom env dir path empty\n"; 49 | return false; 50 | } 51 | 52 | return true; 53 | } 54 | 55 | int Eeprom::get(lua_State* lua) 56 | { 57 | return ValuePack::ret(lua, this->load(biosPath())); 58 | } 59 | 60 | int Eeprom::getChecksum(lua_State* lua) 61 | { 62 | vector bios = this->load(biosPath()); 63 | uint32_t crc = util::crc32(bios); 64 | std::stringstream ret; 65 | ret << std::hex << crc; 66 | 67 | return ValuePack::ret(lua, ret.str()); 68 | } 69 | 70 | int Eeprom::set(lua_State* lua) 71 | { 72 | static const vector default_value{}; 73 | vector value = Value::checkArg>(lua, 1, &default_value); 74 | size_t len = value.size(); 75 | if (len > static_cast(_bios_size_limit)) 76 | return ValuePack::ret(lua, Value::nil, "bios size exceeded"); 77 | 78 | return ValuePack::ret(lua, fs_utils::write(value, biosPath())); 79 | } 80 | 81 | bool Eeprom::postInit() 82 | { 83 | if (!fs_utils::read(biosPath())) 84 | { 85 | string originalBiosPath = client()->host()->biosPath(); 86 | lout << "no computer eeprom found, copying from system\n"; 87 | if (!fs_utils::copy(originalBiosPath, biosPath())) 88 | { 89 | lout << "Could not create an initial bios from: " << originalBiosPath << endl; 90 | return false; 91 | } 92 | } 93 | 94 | return true; 95 | } 96 | 97 | int Eeprom::getData(lua_State* lua) 98 | { 99 | return ValuePack::ret(lua, this->load(dataPath())); 100 | } 101 | 102 | int Eeprom::getSize(lua_State* lua) 103 | { 104 | return ValuePack::ret(lua, _bios_size_limit); 105 | } 106 | 107 | int Eeprom::getDataSize(lua_State* lua) 108 | { 109 | return ValuePack::ret(lua, _data_size_limit); 110 | } 111 | 112 | int Eeprom::setData(lua_State* lua) 113 | { 114 | static const vector default_value{}; 115 | vector value = Value::checkArg>(lua, 1, &default_value); 116 | size_t len = value.size(); 117 | if (_data_size_limit < 0 || len > static_cast(_data_size_limit)) 118 | return ValuePack::ret(lua, Value::nil, "data size exceeded"); 119 | 120 | return ValuePack::ret(lua, fs_utils::write(value, dataPath())); 121 | } 122 | 123 | string Eeprom::biosPath() const 124 | { 125 | return client()->envPath() + "/bios.lua"; 126 | } 127 | 128 | string Eeprom::dataPath() const 129 | { 130 | return client()->envPath() + "/data"; 131 | } 132 | 133 | vector Eeprom::load(const string& path) const 134 | { 135 | vector buffer; 136 | fs_utils::read(path, buffer); 137 | return buffer; 138 | } 139 | 140 | int Eeprom::getLabel(lua_State* lua) 141 | { 142 | return ValuePack::ret(lua, config().get(ConfigIndex::Label)); 143 | } 144 | 145 | int Eeprom::setLabel(lua_State* lua) 146 | { 147 | update(ConfigIndex::Label, Value::checkArg(lua, 1)); 148 | return 0; 149 | } 150 | 151 | Value Eeprom::getDeviceInfo() const 152 | { 153 | auto deviceInfoMap = Value::table(); 154 | deviceInfoMap.set("class", "memory"); 155 | deviceInfoMap.set("capacity", _bios_size_limit); 156 | deviceInfoMap.set("description", "EEPROM"); 157 | deviceInfoMap.set("product", "FlashStick2k"); 158 | deviceInfoMap.set("size", _bios_size_limit); 159 | deviceInfoMap.set("vendor", "MightyPirates GmbH & Co. KG"); 160 | 161 | return deviceInfoMap; 162 | } 163 | -------------------------------------------------------------------------------- /components/screen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "gpu.h" 6 | #include "keyboard.h" 7 | #include "screen.h" 8 | 9 | #include "model/client.h" 10 | #include "model/host.h" 11 | #include "model/log.h" 12 | 13 | #include "apis/unicode.h" 14 | 15 | bool Screen::s_registered = Host::registerComponentType("screen"); 16 | 17 | Screen::Screen() 18 | { 19 | add("isOn", &Screen::isOn); 20 | add("getAspectRatio", &Screen::getAspectRatio); 21 | add("getKeyboards", &Screen::getKeyboards); 22 | add("setTouchModeInverted", &Screen::setTouchModeInverted); 23 | add("turnOn", &Screen::turnOn); 24 | add("turnOff", &Screen::turnOff); 25 | add("isPrecise", &Screen::isPrecise); 26 | add("isTouchInverted", &Screen::isTouchInverted); 27 | add("setPrecise", &Screen::setPrecise); 28 | } 29 | 30 | ///////////////////////////// COMPONENT API ///////////////////////////// 31 | 32 | int Screen::isOn(lua_State* lua) 33 | { 34 | return ValuePack::ret(lua, _frame->on()); 35 | } 36 | 37 | int Screen::getAspectRatio(lua_State* lua) 38 | { 39 | return ValuePack::ret(lua, 1, 1); 40 | } 41 | 42 | int Screen::getKeyboards(lua_State* lua) 43 | { 44 | Value list = Value::table(); 45 | for (const auto& kb : _keyboards) 46 | { 47 | list.insert(kb->address()); 48 | } 49 | return ValuePack::ret(lua, list); 50 | } 51 | 52 | int Screen::setTouchModeInverted(lua_State* lua) 53 | { 54 | return ValuePack::ret(lua, Value::nil, "not supported"); 55 | } 56 | 57 | int Screen::turnOn(lua_State* lua) 58 | { 59 | return ValuePack::ret(lua, _frame->on(true)); 60 | } 61 | 62 | int Screen::turnOff(lua_State* lua) 63 | { 64 | return ValuePack::ret(lua, _frame->on(false)); 65 | } 66 | 67 | int Screen::isPrecise(lua_State* lua) 68 | { 69 | return ValuePack::ret(lua, false); 70 | } 71 | 72 | int Screen::isTouchInverted(lua_State* lua) 73 | { 74 | return ValuePack::ret(lua, false); 75 | } 76 | 77 | int Screen::setPrecise(lua_State* lua) 78 | { 79 | return ValuePack::ret(lua, Value::nil, "not supported"); 80 | } 81 | 82 | ///////////////////////////////////////////////////////////////////////// 83 | 84 | Screen::~Screen() 85 | { 86 | // make a copy of the vector because detach modifies the vector 87 | vector kb_copy = _keyboards; 88 | for (auto& kb : kb_copy) 89 | kb->detach(); 90 | 91 | if (_frame) 92 | { 93 | _frame->close(); 94 | } 95 | if (_gpu) 96 | { 97 | _gpu->unbind(); 98 | } 99 | 100 | _gpu = nullptr; 101 | } 102 | 103 | bool Screen::onInitialize() 104 | { 105 | // we now have a client and we are ready to create the frame for this screen 106 | _frame.reset(client()->host()->createFrame()); 107 | if (!_frame) 108 | { 109 | return false; 110 | } 111 | _frame->open(this); 112 | 113 | return true; 114 | } 115 | 116 | void Screen::gpu(Gpu* gpu) 117 | { 118 | _gpu = gpu; 119 | } 120 | 121 | Gpu* Screen::gpu() const 122 | { 123 | return _gpu; 124 | } 125 | 126 | Value Screen::getDeviceInfo() const 127 | { 128 | auto deviceInfoMap = Value::table(); 129 | deviceInfoMap.set("class", "display"); 130 | deviceInfoMap.set("capacity", "8000"); 131 | deviceInfoMap.set("description", "Text buffer"); 132 | deviceInfoMap.set("product", "Text Screen V0"); 133 | deviceInfoMap.set("vendor", "MightyPirates GmbH & Co. KG"); 134 | deviceInfoMap.set("width", "8"); 135 | 136 | return deviceInfoMap; 137 | }; 138 | 139 | RunState Screen::update() 140 | { 141 | MouseEvent me; 142 | while (EventSource::pop(me)) 143 | { 144 | string msg; 145 | switch (me.press) 146 | { 147 | case EPressType::Press: 148 | msg = "touch"; 149 | break; 150 | case EPressType::Drag: 151 | msg = "drag"; 152 | break; 153 | case EPressType::Release: 154 | break; // release could always be drop 155 | case EPressType::Drop: 156 | msg = "drop"; 157 | break; 158 | } 159 | if (!msg.empty()) 160 | client()->pushSignal({ msg, address(), me.x, me.y, me.btn / 2 }); 161 | } 162 | 163 | if (!_frame->update()) 164 | { 165 | return RunState::Halt; 166 | } 167 | 168 | return RunState::Continue; 169 | } 170 | 171 | vector Screen::keyboards() const 172 | { 173 | vector addrs; 174 | for (const Keyboard* kb : _keyboards) 175 | { 176 | addrs.push_back(kb->address()); 177 | } 178 | return addrs; 179 | } 180 | 181 | bool Screen::connectKeyboard(Keyboard* kb) 182 | { 183 | _keyboards.push_back(kb); 184 | return true; 185 | } 186 | 187 | bool Screen::disconnectKeyboard(Keyboard* kb) 188 | { 189 | return _keyboards.erase(std::remove(_keyboards.begin(), _keyboards.end(), kb), _keyboards.end()) == _keyboards.end(); 190 | } 191 | 192 | void Screen::push(const KeyEvent& ke) 193 | { 194 | // kb events are duplicated to all kbs 195 | for (auto* kb : _keyboards) 196 | { 197 | kb->push(ke); 198 | } 199 | } 200 | 201 | void Screen::push(const MouseEvent& me) 202 | { 203 | EventSource::push(me); 204 | } 205 | 206 | Frame* Screen::frame() const 207 | { 208 | return _frame.get(); 209 | } 210 | 211 | // IScreen passthroughs to GPU 212 | bool Screen::setResolution(int width, int height) 213 | { 214 | return _gpu && _gpu->setResolution(width, height); 215 | } 216 | 217 | void Screen::invalidate() 218 | { 219 | if (_gpu) 220 | _gpu->invalidate(); 221 | } 222 | -------------------------------------------------------------------------------- /drivers/server_pool.cpp: -------------------------------------------------------------------------------- 1 | #include "server_pool.h" 2 | 3 | // c includes for sockets 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // explicitly needed for include on Haiku OS 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | std::unique_ptr FileLock::create(const std::string& path) 16 | { 17 | // we can only attempt to create the server if we only the file lock 18 | int server_pool_file = ::open(path.c_str(), O_CREAT | O_TRUNC, 0600); 19 | if (::flock(server_pool_file, LOCK_EX | LOCK_NB) == -1) 20 | { 21 | // flock denied 22 | ::close(server_pool_file); 23 | return nullptr; //denied 24 | } 25 | 26 | // flock acquired 27 | return std::unique_ptr(new FileLock(path, server_pool_file)); 28 | } 29 | 30 | FileLock::FileLock(const std::string& path, int fd) 31 | : _path(path) 32 | , _fd(fd) 33 | { 34 | } 35 | 36 | FileLock::~FileLock() 37 | { 38 | ::flock(_fd, LOCK_UN); 39 | ::close(_fd); 40 | 41 | ::unlink(_path.c_str()); 42 | } 43 | 44 | ServerPool::ServerPool(int id, std::unique_ptr lock) 45 | : _id(id) 46 | , _lock(std::move(lock)) 47 | { 48 | } 49 | 50 | ServerPool::~ServerPool() 51 | { 52 | ::close(_id); 53 | } 54 | 55 | bool ServerPool::onStart() 56 | { 57 | return true; 58 | } 59 | 60 | bool ServerPool::remove(int id) 61 | { 62 | const auto& conn_it = _connections.find(id); 63 | if (conn_it == _connections.end()) 64 | return false; 65 | 66 | delete conn_it->second; 67 | _connections.erase(conn_it); 68 | return true; 69 | } 70 | 71 | bool ServerPool::runOnce() 72 | { 73 | // accept new connections 74 | NiceWork work; 75 | while (true) 76 | { 77 | sockaddr_storage client_addr{}; 78 | socklen_t addr_size{}; 79 | int client_socket = ::accept(_id, reinterpret_cast(&client_addr), &addr_size); 80 | 81 | if (client_socket <= 0) 82 | { 83 | if (errno != EAGAIN && errno != EWOULDBLOCK) 84 | { 85 | return false; 86 | } 87 | break; 88 | } 89 | 90 | work.set(); 91 | 92 | if (!set_nonblocking(client_socket)) 93 | { 94 | // modem ServerPool accepted client socket but failed to set non blocking 95 | ::close(client_socket); 96 | } 97 | else 98 | { 99 | auto client = new Connection(client_socket); 100 | _connections[client_socket] = client; 101 | } 102 | } 103 | 104 | // now rebroadcast all the messages 105 | std::vector ids; 106 | std::vector removals; 107 | for (auto pair : _connections) 108 | { 109 | if (pair.second->state() == ConnectionState::Ready) 110 | ids.push_back(pair.first); 111 | else 112 | removals.push_back(pair.first); 113 | } 114 | 115 | for (auto id : removals) 116 | { 117 | remove(id); 118 | work.set(); 119 | } 120 | 121 | for (auto id : ids) 122 | { 123 | Connection* conn = _connections.at(id); 124 | 125 | std::vector buffer; 126 | if (!conn->readyNextPacket(&buffer, true)) 127 | continue; 128 | 129 | work.set(); 130 | // rebroadcast 131 | for (auto other_id : ids) 132 | { 133 | Connection* sib = _connections.at(other_id); 134 | sib->write(buffer); 135 | } 136 | } 137 | 138 | return true; 139 | } 140 | 141 | void ServerPool::onStop() 142 | { 143 | std::vector copy; 144 | for (auto pair : _connections) 145 | { 146 | copy.push_back(pair.first); 147 | } 148 | 149 | for (auto id : copy) 150 | { 151 | remove(id); 152 | } 153 | } 154 | 155 | std::unique_ptr ServerPool::create(int system_port) 156 | { 157 | std::unique_ptr lock = FileLock::create("/tmp/ocvm.modem.lock"); 158 | if (!lock) 159 | return nullptr; 160 | 161 | // we assume there is a super modem in this network 162 | int status; 163 | addrinfo hints{}; 164 | hints.ai_family = AF_UNSPEC; 165 | hints.ai_socktype = SOCK_STREAM; 166 | hints.ai_flags = AI_PASSIVE; 167 | addrinfo* server_info = nullptr; 168 | 169 | std::string port_text; 170 | { 171 | std::stringstream ss; 172 | ss << system_port; 173 | port_text = ss.str(); 174 | } 175 | 176 | if ((status = ::getaddrinfo(nullptr, port_text.c_str(), &hints, &server_info)) != 0) 177 | { 178 | // modem failed: getaddrinfo error 179 | return nullptr; 180 | } 181 | 182 | int id = -1; 183 | 184 | for (auto pServer = server_info; pServer; pServer = pServer->ai_next) 185 | { 186 | int yes = 1; 187 | if ((id = ::socket(pServer->ai_family, pServer->ai_socktype, pServer->ai_protocol)) == -1) 188 | { 189 | // modem failed: bad socket 190 | } 191 | else if (setsockopt(id, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)) == -1) 192 | { 193 | // modem ServerPool socket created but failed to switch to reuse 194 | } 195 | else if (::bind(id, pServer->ai_addr, pServer->ai_addrlen) == -1) 196 | { 197 | // if (errno != 98) 198 | // { 199 | // modem ServerPool failed to bind 200 | // } 201 | } 202 | // bind succeeded, so listen better! 203 | else if (::listen(id, 20) == -1) 204 | { 205 | // modem was able to bind, but then failed to listen 206 | } 207 | else if (!set_nonblocking(id)) 208 | { 209 | // modem was able to listen, but then failed to switch to nonblocking 210 | } 211 | else 212 | { 213 | break; 214 | } 215 | 216 | ::close(id); 217 | id = -1; 218 | } 219 | 220 | freeaddrinfo(server_info); 221 | 222 | if (id == -1) 223 | { 224 | return nullptr; 225 | } 226 | 227 | std::unique_ptr server(new ServerPool(id, std::move(lock))); 228 | if (!server->start()) 229 | { 230 | // server failed to start 231 | return nullptr; 232 | } 233 | 234 | return server; 235 | } 236 | -------------------------------------------------------------------------------- /drivers/raw_tty.cpp: -------------------------------------------------------------------------------- 1 | #include "raw_tty.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifndef __APPLE__ 13 | #ifdef __linux__ 14 | #include 15 | #endif 16 | #include 17 | 18 | // explicitly needed for include on Haiku OS 19 | #include 20 | #include 21 | #endif 22 | 23 | #ifdef __HAIKU__ 24 | #define KDGKBMODE 0x4B44 /* gets current keyboard mode */ 25 | #define KDSKBMODE 0x4B45 /* sets current keyboard mode */ 26 | #endif 27 | 28 | #include 29 | #include // memset 30 | #include 31 | 32 | #include "ansi.h" 33 | #include "drivers/ansi_escape.h" 34 | 35 | using std::cerr; 36 | using std::cout; 37 | using std::flush; 38 | using std::set; 39 | 40 | #ifndef __APPLE__ 41 | static unsigned long _original_kb_mode = 0; 42 | #endif 43 | 44 | static struct sigaction sig_action_data; 45 | static termios* original_termios = nullptr; 46 | 47 | static void exit_function() 48 | { 49 | #ifdef __linux__ 50 | // leave raw mode 51 | ioctl(0, KDSKBMODE, _original_kb_mode); 52 | #endif 53 | if (original_termios) 54 | { 55 | ::tcsetattr(STDIN_FILENO, TCSANOW, original_termios); 56 | } 57 | cout << Ansi::mouse_prd_off; 58 | cout << Ansi::cursor_on; 59 | cout << Ansi::color_reset << flush; 60 | delete original_termios; 61 | original_termios = nullptr; 62 | } 63 | 64 | static void segfault_sigaction(int signal, siginfo_t* pSigInfo, void* arg) 65 | { 66 | exit_function(); 67 | cout << "\nocvm caught a SIGSEGV signal. " << flush; 68 | std::abort(); 69 | } 70 | 71 | void TtyReader::start(AnsiEscapeTerm* pTerm) 72 | { 73 | _pTerm = pTerm; 74 | _kb_drv = KeyboardTerminalDriver::create(hasMasterTty()); 75 | 76 | if (hasTerminalOut()) 77 | { 78 | cout << Ansi::mouse_prd_on << flush; 79 | _mouse_drv.reset(new MouseTerminalDriver); 80 | } 81 | 82 | Worker::start(); 83 | } 84 | 85 | // static 86 | TtyReader* TtyReader::engine() 87 | { 88 | static TtyReader one; 89 | static bool init = false; 90 | if (!init) 91 | { 92 | init = true; 93 | memset(&sig_action_data, 0, sizeof(sig_action_data)); 94 | sig_action_data.sa_sigaction = segfault_sigaction; 95 | sig_action_data.sa_flags = SA_SIGINFO; 96 | sigaction(SIGSEGV, &sig_action_data, nullptr); 97 | } 98 | return &one; 99 | } 100 | 101 | bool TtyReader::hasMasterTty() const 102 | { 103 | return _master_tty; 104 | } 105 | 106 | bool TtyReader::hasTerminalOut() const 107 | { 108 | return _terminal_out; 109 | } 110 | 111 | TtyReader::TtyReader() 112 | { 113 | _master_tty = false; 114 | _terminal_out = false; 115 | 116 | #ifndef __APPLE__ 117 | int ec = 0; 118 | ec = ioctl(0, KDGKBMODE, &_original_kb_mode); 119 | if (ec == 0) // success 120 | { 121 | ec = ioctl(0, KDSKBMODE, _original_kb_mode); 122 | } 123 | _master_tty = ec == 0 && errno == 0; 124 | #endif 125 | 126 | _terminal_out = (isatty(fileno(stdout))); 127 | } 128 | 129 | bool TtyReader::onStart() 130 | { 131 | //save current settings 132 | original_termios = new termios; 133 | ::tcgetattr(STDIN_FILENO, original_termios); 134 | 135 | //put in raw mod 136 | termios raw; 137 | memset(&raw, 0, sizeof(termios)); 138 | ::cfmakeraw(&raw); 139 | ::tcsetattr(STDIN_FILENO, TCSANOW, &raw); 140 | 141 | #ifdef __linux__ 142 | if (_master_tty) 143 | { 144 | int ec = ioctl(0, KDGKBMODE, &_original_kb_mode); 145 | ec = ioctl(0, KDSKBMODE, K_RAW); 146 | if (ec != 0 || errno != 0) 147 | { 148 | // try to reset kb JUST IN CASE 149 | exit_function(); 150 | cerr << "\ncritical failure: could not set raw mode\n"; 151 | std::abort(); 152 | } 153 | 154 | atexit(&exit_function); 155 | } 156 | #endif 157 | 158 | cout << flush; 159 | return true; 160 | } 161 | 162 | // void log_codes(TermBuffer* buffer) 163 | // { 164 | // auto size = buffer->size(); 165 | // std::cerr << "{"; 166 | // for (decltype(buffer->size()) i = 0; i < size; i++) 167 | // { 168 | // std::cerr << static_cast(buffer->peek(i)); 169 | // if (i < size - 1) 170 | // std::cerr << ","; 171 | // } 172 | // std::cerr << "}\n"; 173 | // } 174 | 175 | bool TtyReader::runOnce() 176 | { 177 | while (true) 178 | { 179 | char byte = 0; 180 | struct timeval tv 181 | { 182 | 0L, 0L 183 | }; 184 | fd_set fds; 185 | FD_ZERO(&fds); 186 | FD_SET(0, &fds); 187 | if (select(1, &fds, nullptr, nullptr, &tv)) 188 | { 189 | if (read(0, &byte, sizeof(char)) > 0) 190 | { 191 | _buffer.push(byte); 192 | continue; 193 | } 194 | } 195 | 196 | break; 197 | } 198 | 199 | auto old_size = _buffer.size(); 200 | if (old_size > 0) 201 | { 202 | // log_codes(&_buffer); 203 | if (_mouse_drv && _pTerm) 204 | { 205 | auto vme = _mouse_drv->parse(&_buffer); 206 | for (const auto& me : vme) 207 | _pTerm->mouseEvent(me); 208 | } 209 | if (_kb_drv && _pTerm) 210 | { 211 | auto vke = _kb_drv->parse(&_buffer); 212 | for (const auto& ke : vke) 213 | _pTerm->keyEvent(ke); 214 | } 215 | 216 | if (old_size == _buffer.size()) // nothing could read the buffer 217 | { 218 | if (_buffer.hasMouseCode()) 219 | { 220 | _buffer.get(); 221 | _buffer.get(); 222 | _buffer.get(); 223 | _buffer.get(); 224 | _buffer.get(); 225 | } 226 | _buffer.get(); // pop one off 227 | } 228 | } 229 | else 230 | { 231 | if (_kb_drv && _pTerm) 232 | { 233 | auto vke = _kb_drv->idle(); 234 | for (const auto& ke : vke) 235 | _pTerm->keyEvent(ke); 236 | } 237 | } 238 | 239 | return true; 240 | } 241 | 242 | void TtyReader::onStop() 243 | { 244 | _pTerm = nullptr; 245 | exit_function(); 246 | } 247 | -------------------------------------------------------------------------------- /drivers/internet_drv.cpp: -------------------------------------------------------------------------------- 1 | #include "internet_drv.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "model/log.h" 7 | 8 | class TcpObject : public InternetConnection 9 | { 10 | public: 11 | TcpObject(const string& addr, int port); 12 | int write(lua_State* lua); 13 | 14 | protected: 15 | Connection* connection() const override; 16 | 17 | private: 18 | unique_ptr _connection; 19 | }; 20 | 21 | InternetConnection::InternetConnection() 22 | : _needs_connection(false) 23 | , _needs_data(false) 24 | { 25 | add("finishConnect", &InternetConnection::finishConnect); 26 | add("read", &InternetConnection::read); 27 | add("close", &InternetConnection::close); 28 | } 29 | 30 | void InternetConnection::setOnClose(InternetConnectionEventSet::OnClosedCallback cb) 31 | { 32 | handler.onClosed = cb; 33 | } 34 | 35 | void InternetConnection::dispose() 36 | { 37 | _close(); 38 | if (handler.onClosed) 39 | handler.onClosed(this); 40 | } 41 | 42 | int InternetConnection::finishConnect(lua_State* lua) 43 | { 44 | _needs_connection = connection()->state() < ConnectionState::Ready; 45 | return ValuePack::ret(lua, !_needs_connection); 46 | } 47 | 48 | int InternetConnection::close(lua_State* lua) 49 | { 50 | _close(); 51 | return 0; 52 | } 53 | 54 | void InternetConnection::_close() 55 | { 56 | connection()->close(); 57 | } 58 | 59 | bool InternetConnection::update() 60 | { 61 | bool updated = false; 62 | if (_needs_connection) 63 | { 64 | if (connection()->state() >= ConnectionState::Ready) 65 | { 66 | _needs_connection = false; 67 | updated = true; 68 | } 69 | } 70 | if (_needs_data) 71 | { 72 | if (connection()->state() == ConnectionState::Ready) 73 | { 74 | auto avail = connection()->bytes_available(); 75 | if (connection()->preload(avail + 1)) 76 | { 77 | _needs_data = false; 78 | updated = true; 79 | } 80 | } 81 | } 82 | 83 | return updated; 84 | } 85 | 86 | int InternetConnection::read(lua_State* lua) 87 | { 88 | _needs_data = true; 89 | if (!connection()->can_read()) 90 | return ValuePack::ret(lua, Value::nil, "not connected"); 91 | 92 | LUA_NUMBER default_n = INT_MAX; 93 | ssize_t n = static_cast(Value::checkArg(lua, 1, &default_n)); 94 | 95 | connection()->preload(n); 96 | n = std::min(n, connection()->bytes_available()); 97 | vector buffer; 98 | connection()->back_insert(&buffer, 0, n); 99 | connection()->move(buffer.size()); 100 | 101 | if (buffer.size() == 0 && connection()->state() == ConnectionState::Finished) 102 | { 103 | connection()->close(); 104 | return ValuePack::ret(lua, Value::nil); 105 | } 106 | 107 | return ValuePack::ret(lua, buffer); 108 | } 109 | 110 | ///////////////////////////////////////////////////////////////////////////////////////////////// 111 | 112 | TcpObject::TcpObject(const string& addr, int port) 113 | { 114 | add("write", &TcpObject::write); 115 | this->name("TcpObject"); 116 | 117 | _connection.reset(new Connection(addr, port)); 118 | } 119 | 120 | int TcpObject::write(lua_State* lua) 121 | { 122 | if (!_connection->can_write()) 123 | return ValuePack::ret(lua, Value::nil, "not connected"); 124 | 125 | vector buffer = Value::checkArg>(lua, 1); 126 | _connection->write(buffer); 127 | return ValuePack::ret(lua, buffer.size()); 128 | } 129 | 130 | Connection* TcpObject::connection() const 131 | { 132 | return _connection.get(); 133 | } 134 | 135 | ///////////////////////////////////////////////////////////////////////////////////////////////// 136 | 137 | HttpAddress::HttpAddress(string url) 138 | : raw(url) 139 | , valid(false) 140 | { 141 | size_t protocol_end = url.find("://"); 142 | if (protocol_end == string::npos) 143 | return; 144 | 145 | string protocol = url.substr(0, protocol_end); 146 | if (protocol == "http") 147 | { 148 | this->https = false; 149 | this->port = 80; 150 | } 151 | else if (protocol == "https") 152 | { 153 | this->https = true; 154 | this->port = 443; 155 | } 156 | else 157 | return; 158 | 159 | url = url.substr(protocol_end + 3); // remove protocol and :// 160 | 161 | size_t params_index = url.find("?"); 162 | if (params_index != string::npos) 163 | { 164 | this->params = url.substr(params_index + 1); 165 | url = url.substr(0, params_index); 166 | } 167 | 168 | this->hostname = url.substr(0, url.find("/")); 169 | 170 | if (this->hostname.empty()) 171 | return; 172 | 173 | if (url.size() > this->hostname.size()) 174 | this->path = url.substr(this->hostname.size() + 1); 175 | 176 | // remove port from hostname if it exists 177 | size_t port_index = this->hostname.find(":"); 178 | if (port_index != string::npos) 179 | { 180 | string port_text = this->hostname.substr(port_index + 1); 181 | this->hostname = this->hostname.substr(0, port_index); 182 | stringstream ss; 183 | ss << port_text; 184 | ss >> this->port; 185 | if (!ss || !ss.eof()) 186 | { 187 | // invalid port number 188 | return; 189 | } 190 | } 191 | 192 | this->valid = true; 193 | } 194 | 195 | InternetConnection::HttpGenRegistry& InternetConnection::http_gen() 196 | { 197 | static HttpGenRegistry s_http = nullptr; 198 | return s_http; 199 | } 200 | 201 | InternetConnection* InternetConnection::openTcp(UserDataAllocator allocator, const InternetConnectionEventSet& eventSet, const TcpConstructionParameters& args) 202 | { 203 | UserData* pAlloc = allocator(sizeof(TcpObject)); 204 | auto pConn = new (pAlloc) TcpObject(args.addr, args.port); 205 | pConn->setOnClose(eventSet.onClosed); 206 | return pConn; 207 | } 208 | 209 | InternetConnection* InternetConnection::openHttp(UserDataAllocator allocator, const InternetConnectionEventSet& eventSet, const HttpConstructionParameters& args) 210 | { 211 | if (InternetConnection::http_gen() == nullptr) 212 | return nullptr; 213 | InternetConnection* pConn = InternetConnection::http_gen()(allocator, args); 214 | pConn->setOnClose(eventSet.onClosed); 215 | return pConn; 216 | } 217 | -------------------------------------------------------------------------------- /components/drive.cpp: -------------------------------------------------------------------------------- 1 | #include "drive.h" 2 | #include "model/client.h" 3 | #include "drivers/fs_utils.h" 4 | #include "model/host.h" 5 | #include "model/log.h" 6 | using Logging::lout; 7 | 8 | #include 9 | #include 10 | 11 | bool Drive::s_registered = Host::registerComponentType("drive"); 12 | 13 | Drive::Drive() 14 | { 15 | add("writeByte", &Drive::writeByte, ""); 16 | add("readByte", &Drive::readByte, ""); 17 | add("setLabel", &Drive::setLabel, ""); 18 | add("getSectorSize", &Drive::getSectorSize, ""); 19 | add("getPlatterCount", &Drive::getPlatterCount, ""); 20 | add("getLabel", &Drive::getLabel, ""); 21 | add("writeSector", &Drive::writeSector, ""); 22 | add("getCapacity", &Drive::getCapacity, ""); 23 | add("readSector", &Drive::readSector, ""); 24 | } 25 | 26 | Drive::~Drive() 27 | { 28 | std::ofstream writer(_hostPath); 29 | writer.write(_buffer.data(), _buffer.size()); 30 | writer.close(); 31 | } 32 | 33 | int Drive::writeByte(lua_State* lua) 34 | { 35 | int offset = Value::checkArg(lua, 1); 36 | int byte = Value::checkArg(lua, 2); 37 | int sector = offsetToSector(offset); 38 | validateSector(lua, sector); 39 | std::vector data; 40 | data.push_back(byte); 41 | write(offset, data); 42 | return ValuePack::ret(lua); 43 | } 44 | 45 | int Drive::readByte(lua_State* lua) 46 | { 47 | int offset = Value::checkArg(lua, 1); 48 | int sector = offsetToSector(offset); 49 | validateSector(lua, sector); 50 | 51 | auto data = read(offset, 1); 52 | assert(data.size() == 1); 53 | return ValuePack::ret(lua, static_cast(data.at(0))); 54 | } 55 | 56 | int Drive::setLabel(lua_State* lua) 57 | { 58 | string new_label = Value::checkArg(lua, 1); 59 | int stack = getLabel(lua); 60 | update(ConfigIndex::Label, new_label); 61 | return stack; 62 | } 63 | 64 | int Drive::getSectorSize(lua_State* lua) 65 | { 66 | return ValuePack::ret(lua, getSectorSize()); 67 | } 68 | 69 | int Drive::getPlatterCount(lua_State* lua) 70 | { 71 | int count; 72 | switch (_tier) 73 | { 74 | case 1: count = 2; break; 75 | case 2: count = 4; break; 76 | case 3: count = 6; break; 77 | default: count = 0; 78 | } 79 | 80 | return ValuePack::ret(lua, count); 81 | } 82 | 83 | int Drive::getLabel(lua_State* lua) 84 | { 85 | return ValuePack::ret(lua, config().get(ConfigIndex::Label)); 86 | } 87 | 88 | int Drive::writeSector(lua_State* lua) 89 | { 90 | int sector = Value::checkArg(lua, 1) - 1; 91 | std::vector data = Value::checkArg>(lua, 2); 92 | validateSector(lua, sector); 93 | data.resize(getSectorSize()); 94 | int offset = sectorToOffset(sector); 95 | write(offset, data); 96 | return ValuePack::ret(lua); 97 | } 98 | 99 | int Drive::getCapacity(lua_State* lua) 100 | { 101 | return ValuePack::ret(lua, getCapacity()); 102 | } 103 | 104 | int Drive::readSector(lua_State* lua) 105 | { 106 | int sector = Value::checkArg(lua, 1) - 1; 107 | validateSector(lua, sector); 108 | int offset = sectorToOffset(sector); 109 | std::vector data = read(offset, getSectorSize()); 110 | assert(data.size() == static_cast(getSectorSize())); 111 | return ValuePack::ret(lua, data); 112 | } 113 | 114 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 115 | 116 | bool Drive::onInitialize() 117 | { 118 | // the drive is always stored in the vm root dir using its component address 119 | _hostPath = client()->envPath() + "/" + address(); 120 | Value tier = config().get(ConfigIndex::Tier).Or(3); 121 | if (tier.type() != "number") 122 | { 123 | return false; 124 | } 125 | _tier = tier.toNumber(); 126 | 127 | if (fs_utils::exists(_hostPath)) 128 | { 129 | fs_utils::read(_hostPath, _buffer); 130 | _buffer.resize(getCapacity()); 131 | } 132 | else 133 | { 134 | _buffer.resize(getCapacity()); 135 | std::ofstream file(_hostPath, std::ios_base::app | std::ios_base::out); 136 | if (!file) 137 | { 138 | lout << "Failed to initialize drive[" << address() << "] file" << std::endl; 139 | return false; 140 | } 141 | file.close(); 142 | } 143 | 144 | return true; 145 | } 146 | 147 | int Drive::getSectorSize() 148 | { 149 | return 512; 150 | } 151 | 152 | int Drive::getCapacity() 153 | { 154 | int kbs; 155 | switch (_tier) 156 | { 157 | case 1: kbs = 1024; break; 158 | case 2: kbs = 2048; break; 159 | case 3: kbs = 4096; break; 160 | default: kbs = 0; 161 | } 162 | return kbs * 1024; 163 | } 164 | 165 | int Drive::offsetToSector(int offset) 166 | { 167 | return offset / getSectorSize(); 168 | } 169 | 170 | int Drive::sectorToOffset(int sector) 171 | { 172 | return sector * getSectorSize(); 173 | } 174 | 175 | int Drive::validateSector(lua_State* lua, int sector) 176 | { 177 | int offset = sectorToOffset(sector); 178 | if (sector < 0 || offset >= getCapacity()) 179 | { 180 | return luaL_error(lua, "invalid offset, not in a usable sector"); 181 | } 182 | 183 | return 0; 184 | } 185 | 186 | std::vector Drive::read(int offset, int size) 187 | { 188 | assert(offset >= 0); 189 | assert(size >= 0); 190 | if (size == 0) return {}; 191 | 192 | if (static_cast(offset) >= _buffer.size()) 193 | { 194 | return {}; 195 | } 196 | 197 | size_t expectedAvail = static_cast(offset + size); 198 | if (expectedAvail > _buffer.size()) 199 | { 200 | size = _buffer.size() - offset; 201 | } 202 | 203 | std::vector buffer; 204 | buffer.resize(size); 205 | ::memcpy(buffer.data(), _buffer.data() + offset, buffer.size()); 206 | return buffer; 207 | } 208 | 209 | void Drive::write(int offset, std::vector data) 210 | { 211 | assert(offset >= 0); 212 | size_t uoffset = static_cast(offset); 213 | if (uoffset >= _buffer.size()) 214 | { 215 | lout << "bad drive write beyond buffer" << std::endl; 216 | return; 217 | } 218 | 219 | size_t limit = _buffer.size() - uoffset; 220 | size_t write_size = std::min(limit, data.size()); 221 | 222 | ::memcpy(_buffer.data() + uoffset, data.data(), write_size); 223 | } 224 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "components/computer.h" 2 | #include "drivers/fs_utils.h" 3 | #include "io/frame.h" 4 | #include "model/client.h" 5 | #include "model/host.h" 6 | #include "model/log.h" 7 | #include 8 | #include 9 | 10 | using std::cerr; 11 | 12 | void usage() 13 | { 14 | cerr << "ocvm [ENV_PATH] [OPTIONS]\n" 15 | " ENV_PATH (optional) VM env path. Default ./tmp\n" 16 | "OPTIONS\n" 17 | " --frame=TYPE Term emulator type. Can be 'ansi' (default) or 'basic'.\n" 18 | " --log-allocs[=PATH] Enable logging mallocs and stacks.\n" 19 | " Optional custom path, default stack.log\n" 20 | " --bios=PATH Path to custom eeprom bios code\n" 21 | " --machine=PATH Path to custom machine.lua\n" 22 | " --fonts=PATH Path to custom fonts.hex\n"; 23 | ::exit(1); 24 | } 25 | 26 | struct Args 27 | { 28 | vector indexed; 29 | map named; 30 | 31 | enum 32 | { 33 | LogAllocKey = 0, 34 | FrameKey, 35 | BiosKey, 36 | MachineKey, 37 | FontsKey 38 | }; 39 | 40 | const string keys[FontsKey + 1] = { 41 | "log-allocs", 42 | "frame", 43 | "bios", 44 | "machine", 45 | "fonts" 46 | }; 47 | 48 | string get(int n) const 49 | { 50 | n--; 51 | if (n < 0 || n >= static_cast(indexed.size())) 52 | return ""; 53 | return indexed.at(n); 54 | } 55 | 56 | string get(string name) const 57 | { 58 | const auto& it = named.find(name); 59 | if (it == named.end()) 60 | return ""; 61 | return it->second; 62 | } 63 | 64 | bool valid_key(const string& key) 65 | { 66 | if (key == "help") // no error message, but report usage 67 | return false; 68 | 69 | for (size_t i = 0; i < sizeof(keys); i++) 70 | { 71 | if (key == keys[i]) 72 | return true; 73 | } 74 | 75 | cerr << "bad argument [" << key << "]\n"; 76 | return false; 77 | } 78 | 79 | string defaults(const string& key) 80 | { 81 | if (key == keys[LogAllocKey]) 82 | return "stack.log"; 83 | return ""; 84 | } 85 | 86 | string client_env_path() const 87 | { 88 | string value = get(1); 89 | return value.empty() ? "tmp" : value; 90 | } 91 | 92 | string frame_type() const 93 | { 94 | string value = get(keys[Args::FrameKey]); 95 | return value.empty() ? "ansi" : value; 96 | } 97 | 98 | string stack_log() const 99 | { 100 | return get(keys[Args::LogAllocKey]); 101 | } 102 | 103 | string bios_path() const 104 | { 105 | string value = get(keys[Args::BiosKey]); 106 | return value.empty() ? fs_utils::make_proc_path("system/bios.lua") : value; 107 | } 108 | 109 | string machine_path() const 110 | { 111 | string value = get(keys[Args::MachineKey]); 112 | return value.empty() ? fs_utils::make_proc_path("system/machine.lua") : value; 113 | } 114 | 115 | string fonts_path() const 116 | { 117 | string value = get(keys[Args::FontsKey]); 118 | return value.empty() ? fs_utils::make_proc_path("system/font.hex") : value; 119 | } 120 | }; 121 | 122 | bool valid_arg_index(size_t size) 123 | { 124 | static size_t expected = 1; 125 | if (size > expected) 126 | { 127 | cerr << "too many arguments, expected " << expected << endl; 128 | } 129 | return size <= 1; 130 | } 131 | 132 | Args load_args(int argc, char** argv) 133 | { 134 | Args args; 135 | if (argc >= 1) 136 | fs_utils::set_prog_name(argv[0]); 137 | 138 | // start from 1, argv[0] is the prog name 139 | for (int i = 1; i < argc; i++) 140 | { 141 | string t = argv[i]; 142 | if (t.find("--") == 0) 143 | { 144 | string key = t.substr(2); 145 | string value; 146 | size_t equal_index = key.find("="); 147 | if (equal_index != string::npos) 148 | { 149 | value = key.substr(equal_index + 1); 150 | key = key.substr(0, equal_index); 151 | 152 | if (!args.valid_key(key)) 153 | { 154 | usage(); 155 | } 156 | 157 | args.named[key] = value; 158 | } 159 | else 160 | { 161 | string def = args.defaults(key); 162 | if (def.empty()) 163 | usage(); 164 | args.named[key] = def; 165 | } 166 | } 167 | else if (t.find("-") == 0) 168 | usage(); 169 | else 170 | { 171 | args.indexed.push_back(t); 172 | if (!valid_arg_index(args.indexed.size())) 173 | { 174 | usage(); 175 | } 176 | } 177 | } 178 | return args; 179 | } 180 | 181 | void prepareMachineDirectory(string path) 182 | { 183 | string client_env_path = fs_utils::make_pwd_path(path); 184 | // make the env path if it doesn't already exist 185 | if (!fs_utils::mkdir(client_env_path)) 186 | { 187 | std::cerr << "could not create virtual machine path: " << client_env_path << std::endl; 188 | ::exit(1); 189 | } 190 | 191 | Logger::context({ client_env_path }); 192 | } 193 | 194 | string runVirtualMachine(const Args& args) 195 | { 196 | string clientShutdownMessage; 197 | 198 | prepareMachineDirectory(args.client_env_path()); 199 | 200 | // init host config 201 | // // prepares component factories such as screen, keyboard, and filesystem 202 | Host host(args.frame_type()); 203 | host.stackLog(args.stack_log()); 204 | host.biosPath(args.bios_path()); 205 | host.machinePath(args.machine_path()); 206 | host.fontsPath(args.fonts_path()); 207 | 208 | RunState run; 209 | 210 | do 211 | { 212 | // init client config 213 | Client client(&host, Logger::context().path); 214 | // init lua environment 215 | // // creates instances of host components 216 | if (!client.load()) 217 | break; 218 | 219 | do 220 | { 221 | run = client.run(); 222 | } while (run == RunState::Continue); 223 | 224 | clientShutdownMessage = client.getAllCrashText(); 225 | } while (run == RunState::Reboot); 226 | 227 | return clientShutdownMessage; 228 | } 229 | 230 | int main(int argc, char** argv) 231 | { 232 | auto args = load_args(argc, argv); 233 | 234 | string result = runVirtualMachine(args); 235 | std::cout << result; 236 | 237 | return !result.empty(); 238 | } 239 | -------------------------------------------------------------------------------- /drivers/connection.cpp: -------------------------------------------------------------------------------- 1 | #include "connection.h" 2 | 3 | // c includes for sockets 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | using std::string; 13 | using std::stringstream; 14 | using std::vector; 15 | 16 | void Connection::async_open(Connection* pc) 17 | { 18 | int status; 19 | addrinfo hints{}; 20 | hints.ai_family = AF_UNSPEC; 21 | hints.ai_socktype = SOCK_STREAM; 22 | hints.ai_flags = AI_PASSIVE; 23 | addrinfo* server_info = nullptr; 24 | 25 | string port_text; 26 | { 27 | stringstream ss; 28 | ss << pc->_port; 29 | port_text = ss.str(); 30 | } 31 | 32 | if ((status = ::getaddrinfo(pc->_host.c_str(), port_text.c_str(), &hints, &server_info)) != 0) 33 | { 34 | pc->_state = ConnectionState::Failed; 35 | return; 36 | } 37 | 38 | int id = -1; 39 | 40 | // now connect to the server 41 | for (auto pServer = server_info; pServer; pServer = pServer->ai_next) 42 | { 43 | if ((id = ::socket(pServer->ai_family, pServer->ai_socktype, pServer->ai_protocol)) == -1) 44 | { 45 | // modem failed: bad socket 46 | } 47 | else if (::connect(id, pServer->ai_addr, pServer->ai_addrlen) == -1) 48 | { 49 | } 50 | else if (!set_nonblocking(id)) 51 | { 52 | // failed to switch socket to non blocking 53 | } 54 | else 55 | { 56 | break; 57 | } 58 | 59 | ::close(id); 60 | id = -1; 61 | } 62 | 63 | freeaddrinfo(server_info); 64 | 65 | if (id == -1) 66 | { 67 | pc->_state = ConnectionState::Failed; 68 | return; 69 | } 70 | 71 | pc->_id = id; 72 | pc->_client_side = true; 73 | pc->_state = ConnectionState::Ready; 74 | } 75 | 76 | Connection::Connection(int id) 77 | : _id(id) 78 | , _host("") 79 | , _port(-1) 80 | , _state(ConnectionState::Ready) 81 | //,_connection_thread, 82 | { 83 | } 84 | 85 | Connection::Connection(const string& host, int system_port) 86 | : _id(-1) 87 | , _host(host) 88 | , _port(system_port) 89 | , _state(ConnectionState::Starting) 90 | , _connection_thread(Connection::async_open, this) 91 | { 92 | } 93 | 94 | void Connection::close() 95 | { 96 | if (_connection_thread.joinable()) 97 | _connection_thread.join(); 98 | ::close(_id); 99 | _state = ConnectionState::Closed; 100 | } 101 | 102 | Connection::~Connection() 103 | { 104 | close(); 105 | } 106 | 107 | bool Connection::write(const vector& vec) 108 | { 109 | if (state() != ConnectionState::Ready) 110 | return false; 111 | 112 | return ::send(_id, vec.data(), vec.size(), MSG_NOSIGNAL) != -1; 113 | } 114 | 115 | string Connection::label() const 116 | { 117 | stringstream ss; 118 | if (_client_side) 119 | ss << "Client->Server "; 120 | else 121 | ss << "Server->Client "; 122 | ss << "Connection[" << _id << "]"; 123 | return ss.str(); 124 | } 125 | 126 | ConnectionState Connection::state() const 127 | { 128 | return _state; 129 | } 130 | 131 | bool set_nonblocking(int id) 132 | { 133 | int flags; 134 | if ((flags = fcntl(id, F_GETFL, 0)) < 0) 135 | { 136 | return false; 137 | } 138 | 139 | if (fcntl(id, F_SETFL, flags | O_NONBLOCK) < 0) 140 | { 141 | return false; 142 | } 143 | 144 | return true; 145 | } 146 | 147 | ssize_t Connection::bytes_available() const 148 | { 149 | return _buffer_size; 150 | } 151 | 152 | bool Connection::preload(ssize_t bytes) 153 | { 154 | if (_state != ConnectionState::Ready) 155 | return bytes <= _buffer_size; 156 | 157 | if (bytes > Connection::max_buffer_size) 158 | { 159 | if (_buffer_size == Connection::max_buffer_size) 160 | { 161 | // buffer overflow, cannot read more from socket 162 | _state = ConnectionState::Failed; 163 | return false; 164 | } 165 | bytes = Connection::max_buffer_size; 166 | } 167 | 168 | while (_buffer_size < bytes) 169 | { 170 | ssize_t bytes_received = ::read(_id, _internal_buffer + _buffer_size, bytes - _buffer_size); 171 | if (bytes_received <= 0) // not ready or closed or failed or interrupted 172 | { 173 | if (bytes_received == 0 || (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) 174 | { 175 | // if (errno == 11) 176 | // disconnected 177 | // else 178 | // read failed 179 | 180 | _state = ConnectionState::Finished; 181 | 182 | ::close(_id); 183 | _id = -1; 184 | } 185 | return false; 186 | } 187 | _buffer_size += bytes_received; 188 | } 189 | return true; 190 | } 191 | 192 | bool Connection::back_insert(vector* pOut, ssize_t offset, ssize_t bytes) 193 | { 194 | if (!preload(offset + bytes)) 195 | return false; 196 | 197 | std::copy(_internal_buffer + offset, _internal_buffer + offset + bytes, std::back_inserter(*pOut)); 198 | return true; 199 | } 200 | 201 | bool Connection::move(ssize_t bytes) 202 | { 203 | if (_buffer_size < bytes) 204 | return false; 205 | 206 | if (_buffer_size > bytes) 207 | ::memmove(_internal_buffer, _internal_buffer + bytes, _buffer_size - bytes); 208 | 209 | _buffer_size -= bytes; 210 | 211 | return true; 212 | } 213 | 214 | bool Connection::can_read() const 215 | { 216 | return _state == ConnectionState::Ready || _state == ConnectionState::Finished; 217 | } 218 | 219 | bool Connection::can_write() const 220 | { 221 | return _state == ConnectionState::Ready; 222 | } 223 | 224 | bool Connection::readyNextPacket(std::vector* buffer, bool keepPacketSize) 225 | { 226 | buffer->clear(); 227 | 228 | // read next packet from server 229 | constexpr ssize_t header_size = sizeof(int32_t); 230 | if (!back_insert(buffer, 0, header_size)) 231 | return false; 232 | 233 | int32_t packet_size = 0; 234 | char* p = reinterpret_cast(&packet_size); 235 | p[0] = buffer->at(0); 236 | p[1] = buffer->at(1); 237 | p[2] = buffer->at(2); 238 | p[3] = buffer->at(3); 239 | 240 | ssize_t end = header_size + packet_size; 241 | if (Connection::max_buffer_size < end) 242 | { 243 | // modem likely bad packet, size reported: packet_size 244 | move(header_size); 245 | return false; 246 | } 247 | 248 | if (!keepPacketSize) 249 | { 250 | buffer->clear(); 251 | } 252 | 253 | if (!back_insert(buffer, header_size, packet_size)) 254 | return false; 255 | 256 | move(end); 257 | 258 | // modem packet completed: ${end} bytes 259 | return true; 260 | } 261 | -------------------------------------------------------------------------------- /color/color_map.cpp: -------------------------------------------------------------------------------- 1 | #include "color_map.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static int monochrome_color = 0xffffff; 8 | 9 | enum CShift 10 | { 11 | RShift32 = 16, 12 | GShift32 = 8, 13 | BShift32 = 0 14 | }; 15 | 16 | enum CBits 17 | { 18 | Reds = 6, 19 | Greens = 8, 20 | Blues = 5 21 | }; 22 | 23 | class ColorFormat 24 | { 25 | public: 26 | virtual int inflate(const ColorState& state, int value) const = 0; 27 | virtual int deflate(const ColorState& state, const Color& color) const = 0; 28 | virtual void initialize_palette(ColorState& state) const = 0; 29 | }; 30 | 31 | class ColorFormat_1 : public ColorFormat 32 | { 33 | public: 34 | int inflate(const ColorState&, int value) const override 35 | { 36 | if (value == 0) 37 | return 0; 38 | else 39 | return monochrome_color; 40 | } 41 | 42 | int deflate(const ColorState& state, const Color& color) const override 43 | { 44 | return color.rgb == 0 ? 0 : 1; 45 | } 46 | 47 | void initialize_palette(ColorState& state) const override 48 | { 49 | } 50 | } cf_1; 51 | 52 | class ColorFormat_4 : public ColorFormat{ 53 | public : 54 | int inflate(const ColorState& state, int value) const override{ 55 | int index = std::max(0, std::min((int)ColorState::PALETTE_SIZE, value)); 56 | return state.palette[index]; 57 | } 58 | 59 | static inline constexpr bool isFromPalette(int value) 60 | { 61 | return value >= 0 && value <= (int)ColorState::PALETTE_SIZE; 62 | } 63 | 64 | static inline constexpr int delta(int a, int b) 65 | { 66 | int rA = (a >> CShift::RShift32) & 0xff; 67 | int gA = (a >> CShift::GShift32) & 0xff; 68 | int bA = (a >> CShift::BShift32) & 0xff; 69 | 70 | int rB = (b >> CShift::RShift32) & 0xff; 71 | int gB = (b >> CShift::GShift32) & 0xff; 72 | int bB = (b >> CShift::BShift32) & 0xff; 73 | 74 | int dr = rA - rB; 75 | int dg = gA - gB; 76 | int db = bA - bB; 77 | 78 | return 0.2126 * dr * dr + 0.7152 * dg * dg + 0.0722 * db * db; 79 | } 80 | 81 | int deflate(const ColorState& state, const Color& color) const override 82 | { 83 | if (color.paletted) 84 | { 85 | return std::max(0, color.rgb) % ColorState::PALETTE_SIZE; 86 | } 87 | 88 | // search for palette entry with smallest distance from color 89 | int min_distance = std::numeric_limits::max(); 90 | size_t best_index = 0; 91 | 92 | for (size_t i = 0; i < ColorState::PALETTE_SIZE && min_distance > 0; i++) 93 | { 94 | const int& palette_entry = state.palette[i]; 95 | int distance = delta(palette_entry, color.rgb); 96 | if (distance < min_distance) 97 | { 98 | min_distance = distance; 99 | best_index = i; 100 | } 101 | } 102 | 103 | return best_index; 104 | } 105 | 106 | void initialize_palette(ColorState& state) const override 107 | { 108 | static const int palette[ColorState::PALETTE_SIZE]{ 109 | 0xFFFFFF, 0xFFCC33, 0xCC66CC, 0x6699FF, 110 | 0xFFFF33, 0x33CC33, 0xFF6699, 0x333333, 111 | 0xCCCCCC, 0x336699, 0x9933CC, 0x333399, 112 | 0x663300, 0x336600, 0xFF3333, 0x000000 113 | }; 114 | memcpy(state.palette, palette, sizeof(int) * ColorState::PALETTE_SIZE); 115 | } 116 | 117 | protected: 118 | } 119 | cf_4; 120 | 121 | class ColorFormat_8 : public ColorFormat_4 122 | { 123 | public: 124 | ColorFormat_8() 125 | { 126 | } 127 | 128 | void initialize_palette(ColorState& state) const override 129 | { 130 | // Initialize palette to grayscale, excluding black and white, because 131 | // those are already contained in the normal color cube. 132 | for (size_t i = 0; i < ColorState::PALETTE_SIZE; i++) 133 | { 134 | int shade = 0xff * (i + 1) / (ColorState::PALETTE_SIZE + 1); 135 | state.palette[i] = 136 | (shade << CShift::RShift32) | 137 | (shade << CShift::GShift32) | 138 | (shade << CShift::BShift32); 139 | } 140 | } 141 | 142 | int inflate(const ColorState& state, int value) const override 143 | { 144 | if (isFromPalette(value)) 145 | { 146 | return ColorFormat_4::inflate(state, value); 147 | } 148 | int index = value - (int)ColorState::PALETTE_SIZE; 149 | int idxB = index % CBits::Blues; 150 | int idxG = (index / CBits::Blues) % CBits::Greens; 151 | int idxR = (index / CBits::Blues / CBits::Greens) % CBits::Reds; 152 | int r = (int)(idxR * 0xFF / (CBits::Reds - 1.0) + 0.5); 153 | int g = (int)(idxG * 0xFF / (CBits::Greens - 1.0) + 0.5); 154 | int b = (int)(idxB * 0xFF / (CBits::Blues - 1.0) + 0.5); 155 | return (r << RShift32) | 156 | (g << GShift32) | 157 | (b << BShift32); 158 | } 159 | 160 | int deflate(const ColorState& state, const Color& color) const override 161 | { 162 | int paletteIndex = ColorFormat_4::deflate(state, color) & 0xFF; 163 | if (color.paletted) 164 | { 165 | return paletteIndex; 166 | } 167 | 168 | int deflated = ColorMap::deflate(color.rgb); 169 | if (delta(inflate(state, deflated), color.rgb) <= delta(inflate(state, paletteIndex), color.rgb)) 170 | { 171 | return deflated; 172 | } 173 | 174 | return paletteIndex; 175 | } 176 | } cf_8; 177 | 178 | const ColorFormat* toFormater(EDepthType depth) 179 | { 180 | const ColorFormat* pf = nullptr; 181 | switch (depth) 182 | { 183 | case EDepthType::_1: 184 | pf = &cf_1; 185 | break; 186 | case EDepthType::_4: 187 | pf = &cf_4; 188 | break; 189 | case EDepthType::_8: 190 | pf = &cf_8; 191 | break; 192 | } 193 | return pf; 194 | } 195 | 196 | int ColorMap::inflate(const ColorState& state, int value) 197 | { 198 | const ColorFormat* pf = toFormater(state.depth); 199 | return pf->inflate(state, value); 200 | } 201 | 202 | int ColorMap::deflate(const ColorState& state, const Color& color) 203 | { 204 | const ColorFormat* pf = toFormater(state.depth); 205 | return pf->deflate(state, color); 206 | } 207 | 208 | int ColorMap::deflate(int rgb) 209 | { 210 | int r = (rgb >> CShift::RShift32) & 0xff; 211 | int g = (rgb >> CShift::GShift32) & 0xff; 212 | int b = (rgb >> CShift::BShift32) & 0xff; 213 | 214 | int idxR = (int)(r * (CBits::Reds - 1.0) / 0xFF + 0.5); 215 | int idxG = (int)(g * (CBits::Greens - 1.0) / 0xFF + 0.5); 216 | int idxB = (int)(b * (CBits::Blues - 1.0) / 0xFF + 0.5); 217 | int deflated = (ColorState::PALETTE_SIZE + idxR * CBits::Greens * CBits::Blues + idxG * CBits::Blues + idxB) & 0xFF; 218 | return deflated; 219 | } 220 | 221 | void ColorMap::set_monochrome(int rgb) 222 | { 223 | monochrome_color = rgb; 224 | } 225 | 226 | void ColorMap::initialize_color_state(ColorState& state, EDepthType depth) 227 | { 228 | const ColorFormat* pf = toFormater(depth); 229 | state.depth = depth; 230 | pf->initialize_palette(state); 231 | } 232 | -------------------------------------------------------------------------------- /drivers/internet_http.cpp: -------------------------------------------------------------------------------- 1 | #include "internet_http.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | using std::stringstream; 9 | 10 | static bool SetHttpGenerator() 11 | { 12 | InternetConnection::HttpGenRegistry& http_gen = InternetConnection::http_gen(); 13 | http_gen = [](UserDataAllocator allocator, const HttpConstructionParameters& args) { 14 | return new (allocator(sizeof(HttpObject))) HttpObject(args.addr, args.post, args.header); 15 | }; 16 | return true; 17 | } 18 | 19 | bool HttpObject::s_registered = SetHttpGenerator(); 20 | 21 | ///////////////////////////////////////////////////////// 22 | 23 | static string trim(const string& value) 24 | { 25 | size_t first = value.find_first_not_of(" \t\n\r"); 26 | if (first == string::npos) 27 | return value; 28 | string tvalue = value.substr(first); 29 | size_t last = tvalue.find_last_not_of(" \t\n\r"); 30 | if (last != string::npos) 31 | tvalue = tvalue.substr(0, last + 1); 32 | 33 | return tvalue; 34 | } 35 | 36 | static string escape(const string& text) 37 | { 38 | string result; 39 | 40 | size_t last = 0; 41 | while (true) 42 | { 43 | size_t next = text.find("'", last); 44 | result += text.substr(last, next); 45 | if (next == string::npos) 46 | { 47 | break; 48 | } 49 | result += "'\\''"; 50 | last = next + 1; 51 | } 52 | 53 | return "'" + result + "'"; 54 | } 55 | 56 | ////////////////// PIPED COMMAND //////////////////////// 57 | 58 | PipedCommand::PipedCommand() 59 | : _child_id(-1) 60 | { 61 | } 62 | 63 | PipedCommand::~PipedCommand() 64 | { 65 | this->close(); 66 | } 67 | 68 | bool PipedCommand::open(const string& command, const vector& args) 69 | { 70 | int pids[3][2]{ { -1, -1 }, { -1, -1 }, { -1, -1 } }; 71 | for (int i = 0; i < 3; i++) 72 | { 73 | if (::pipe(pids[i])) 74 | return false; 75 | } 76 | 77 | _child_id = ::fork(); 78 | if (_child_id == -1) 79 | { 80 | return false; 81 | } 82 | 83 | if (_child_id) // parent 84 | { 85 | int stdin = pids[STDIN_FILENO][1]; 86 | ::close(pids[STDIN_FILENO][0]); 87 | set_nonblocking(stdin); 88 | 89 | int stdout = pids[STDOUT_FILENO][0]; 90 | ::close(pids[STDOUT_FILENO][1]); 91 | set_nonblocking(stdout); 92 | 93 | int stderr = pids[STDERR_FILENO][0]; 94 | ::close(pids[STDERR_FILENO][1]); 95 | set_nonblocking(stderr); 96 | 97 | _stdin.reset(new Connection(stdin)); 98 | _stdout.reset(new Connection(stdout)); 99 | _stderr.reset(new Connection(stderr)); 100 | 101 | // success 102 | return true; 103 | } 104 | 105 | ::dup2(pids[STDIN_FILENO][0], STDIN_FILENO); 106 | ::close(pids[STDIN_FILENO][1]); 107 | 108 | ::dup2(pids[STDOUT_FILENO][1], STDOUT_FILENO); 109 | ::close(pids[STDOUT_FILENO][0]); 110 | 111 | ::dup2(pids[STDERR_FILENO][1], STDERR_FILENO); 112 | ::close(pids[STDERR_FILENO][0]); 113 | 114 | const char** vargs = new const char*[args.size() + 2]; 115 | vargs[0] = command.data(); 116 | for (size_t i = 0; i < args.size(); i++) 117 | { 118 | const auto& arg = args.at(i); 119 | vargs[i + 1] = arg.data(); 120 | } 121 | vargs[args.size() + 1] = nullptr; 122 | 123 | ::execvp(command.data(), const_cast(vargs)); 124 | delete[] vargs; 125 | ::_exit(EXIT_FAILURE); 126 | return false; 127 | } 128 | 129 | Connection* PipedCommand::stdin() const 130 | { 131 | return _stdin.get(); 132 | } 133 | 134 | Connection* PipedCommand::stdout() const 135 | { 136 | return _stdout.get(); 137 | } 138 | 139 | Connection* PipedCommand::stderr() const 140 | { 141 | return _stderr.get(); 142 | } 143 | 144 | int PipedCommand::id() const 145 | { 146 | return _child_id; 147 | } 148 | 149 | void PipedCommand::close() 150 | { 151 | ::close(_child_id); 152 | } 153 | 154 | ///////////////////////////////////////////////////////////////////////////////////////////////// 155 | 156 | HttpObject::HttpObject(const HttpAddress& addr, const string& post, const map& header) 157 | : _response_ready(false) 158 | { 159 | add("response", &HttpObject::response); 160 | 161 | this->name("HttpObject"); 162 | 163 | vector args{ "-q", "-S", "--no-check-certificate", "-O", "/proc/self/fd/1" }; 164 | if (post.size()) 165 | { 166 | args.push_back("--post-data=" + escape(post)); 167 | args.push_back("--method=POST"); 168 | } 169 | for (const auto& header_pair : header) 170 | { 171 | args.push_back("--header=" + escape(header_pair.first + ": " + header_pair.second)); 172 | } 173 | if (!header.empty() && header.find("Host") == header.end()) 174 | { 175 | args.push_back("--header=" + escape("Host: " + addr.hostname)); 176 | } 177 | if (addr.valid) 178 | { 179 | args.push_back(addr.raw); 180 | // http request: addr.raw 181 | _cmd.open("wget", args); 182 | } 183 | } 184 | 185 | int HttpObject::response(lua_State* lua) 186 | { 187 | if (!_response_ready) 188 | return ValuePack::ret(lua, Value::nil); 189 | 190 | return _response.push(lua); 191 | } 192 | 193 | bool HttpObject::update() 194 | { 195 | if (!_response_ready) 196 | { 197 | Connection* resp = _cmd.stderr(); 198 | if (!resp->can_read()) 199 | { 200 | _response_ready = true; 201 | _response.clear(); 202 | _response.push_back(Value::nil); 203 | _response.push_back("connectioned closed"); 204 | } 205 | else 206 | { 207 | bool response_finished = resp->state() == ConnectionState::Finished; 208 | size_t prev_buffer_size = resp->bytes_available(); 209 | resp->preload(prev_buffer_size + 128); 210 | size_t buffer_size = resp->bytes_available(); 211 | 212 | _response_ready = response_finished && prev_buffer_size == buffer_size; 213 | 214 | vector buffer; 215 | resp->back_insert(&buffer, 0, buffer_size); 216 | size_t from = 0; 217 | 218 | while (from < buffer.size()) 219 | { 220 | static const vector nl{ '\n' }; 221 | auto line_iterator = std::search(std::begin(buffer) + from, std::end(buffer), std::begin(nl), std::end(nl)); 222 | 223 | if (line_iterator == buffer.end()) 224 | { 225 | break; 226 | } 227 | 228 | stringstream ss; 229 | string line(buffer.begin() + from, line_iterator); 230 | resp->move(line.size() + 1); 231 | from = (line_iterator - buffer.begin()) + 1; 232 | 233 | ss << line; 234 | 235 | string key; 236 | string value; 237 | if (_response.size() == 0) 238 | { 239 | // HTTP/1.1 200 OK 240 | ss >> key; 241 | key = trim(key); 242 | int code; 243 | string message; 244 | ss >> code; 245 | std::getline(ss, message); 246 | if (!ss || !ss.eof()) 247 | { 248 | // failed to parse 249 | _response_ready = true; 250 | _response.push_back(Value::nil); 251 | _response.push_back("failed to parse http response"); 252 | resp->close(); 253 | break; 254 | } 255 | 256 | message = trim(message); 257 | _response.push_back(code); 258 | _response.push_back(message); 259 | } 260 | else 261 | { 262 | // Data: Mon, 19 May 2014 12:46:36 GMT 263 | std::getline(ss, key, ':'); 264 | key = trim(key); 265 | std::getline(ss, value); 266 | value = trim(value); 267 | if (_response.size() < 3) 268 | { 269 | _response.push_back(Value::table()); 270 | } 271 | _response.at(2).set(key, value); 272 | } 273 | } 274 | } 275 | } 276 | 277 | return InternetConnection::update(); 278 | } 279 | 280 | Connection* HttpObject::connection() const 281 | { 282 | return _cmd.stdout(); 283 | } 284 | -------------------------------------------------------------------------------- /drivers/kb_drv.cpp: -------------------------------------------------------------------------------- 1 | #include "kb_drv.h" 2 | #include "drivers/kb_data.h" 3 | #include "term_buffer.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | static KBData kb_data; 10 | 11 | class KeyboardTerminalDriverCommon : public KeyboardTerminalDriver 12 | { 13 | public: 14 | KeyboardTerminalDriverCommon() 15 | { 16 | _modifier_state = 0; 17 | } 18 | 19 | protected: 20 | void mark(bool bPressed, _Code keycode, vector* pOut) 21 | { 22 | // filter out some events 23 | switch (keycode) 24 | { 25 | case 42 | 0x80: // PRINT SCREEN (comes in a pair of double bytes, 42,55 -- each are pressed and unpressed) 26 | case 46 | 0x80: // FN+F6 (SPEAKER VOLUME DOWN) (double byte) 27 | case 48 | 0x80: // FN+F7 (SPEAKER VOLUME UP) (double byte) 28 | case 55 | 0x80: // PRINT SCREEN (comes in a pair of double bytes, 42,55 -- each are pressed and unpressed) 29 | case 76 | 0x80: // FN+F9 (DISPLAY BACKLIGHT DECREASE) (double byte) 30 | case 84 | 0x80: // FN+F10 (DISPLAY BACKLIGHT INCREASE) (double byte) 31 | case 86 | 0x80: // FN+F4 (DISPLAY) (double byte) 32 | case 95 | 0x80: // FN+F3 (SLEEP) (double byte) 33 | return; 34 | case 219: // WINDOWS 35 | case 221: // MENU 36 | keycode = 0; 37 | break; 38 | } 39 | 40 | KeyEvent ke; 41 | ke.bPressed = bPressed; 42 | ke.keycode = keycode; 43 | 44 | update_modifier(bPressed, ke.keycode); 45 | ke.keysym = kb_data.lookup(ke.keycode, static_cast(_modifier_state)); 46 | 47 | ke.bShift = (_modifier_state & (_Mod)ModBit::Shift); 48 | ke.bCaps = (_modifier_state & (_Mod)ModBit::Caps); 49 | ke.bControl = (_modifier_state & (_Mod)ModBit::Control); 50 | ke.bAlt = (_modifier_state & (_Mod)ModBit::Alt); 51 | ke.bNumLock = (_modifier_state & (_Mod)ModBit::NumLock); 52 | 53 | // unusual in-game shifted keycodes 54 | if (_modifier_state & (_Mod)ModBit::Shift) 55 | { 56 | switch (keycode) // keycodes shift 57 | { 58 | case 3: 59 | ke.keycode = 145; 60 | break; // 2 61 | case 7: 62 | ke.keycode = 144; 63 | break; // 6 64 | case 12: 65 | ke.keycode = 147; 66 | break; // - 67 | case 39: 68 | ke.keycode = 146; 69 | break; // ; 70 | } 71 | } 72 | 73 | pOut->push_back(ke); 74 | } 75 | 76 | void update_modifier(bool bPressed, _Code keycode) 77 | { 78 | const auto& modifier_set_iterator = kb_data.modifiers.find(keycode); 79 | if (modifier_set_iterator != kb_data.modifiers.end()) 80 | { 81 | const auto& mod_key_tuple = modifier_set_iterator->second; 82 | 83 | unsigned int mod_index = std::get<0>(mod_key_tuple); // shift(0), lock(1), ctrl(2), etc 84 | unsigned int nth_code = std::get<1>(mod_key_tuple); // the nth code in the group 85 | 86 | std::bitset<8> mod_bits = _mod_groups[mod_index]; 87 | mod_bits.set(nth_code, bPressed); 88 | _mod_groups[mod_index] = static_cast(mod_bits.to_ulong()); 89 | 90 | std::bitset<8> state_bits = _modifier_state; 91 | state_bits.set(mod_index, mod_bits.any()); 92 | _modifier_state = static_cast(state_bits.to_ulong()); 93 | } 94 | } 95 | 96 | vector idle() override 97 | { 98 | return {}; 99 | } 100 | 101 | _Mod _modifier_state; 102 | 103 | private: 104 | unsigned char _mod_groups[8]{}; // 8 mod keys, 8 possible locations of those keys 105 | }; 106 | 107 | class KeyboardLocalRawTtyDriver : public KeyboardTerminalDriverCommon 108 | { 109 | public: 110 | vector parse(TermBuffer* buffer) override 111 | { 112 | if (buffer->size() == 0 || buffer->hasMouseCode()) 113 | return {}; 114 | 115 | bool released; 116 | unsigned int keycode = buffer->get(); 117 | 118 | switch (keycode) 119 | { 120 | case 0xE0: // double byte 121 | keycode = buffer->get(); 122 | released = keycode & 0x80; 123 | keycode |= 0x80; // add press indicator 124 | break; 125 | case 0xE1: // triple byte 126 | keycode = buffer->get(); // 29(released) or 29+0x80[157](pressed) 127 | released = keycode & 0x80; 128 | // NUMLK is a double byte 0xE0, 69 (| x80) 129 | // PAUSE is a triple byte 0xE1, 29 (| x80), 69 (| 0x80) 130 | // because triple byte press state is encoded in the 2nd byte 131 | // the third byte should retain 0x80 132 | keycode = buffer->get() | 0x80; 133 | break; 134 | default: 135 | released = keycode & 0x80; 136 | keycode &= 0x7F; // remove pressed indicator 137 | break; 138 | } 139 | 140 | vector vke; 141 | mark(!released, keycode, &vke); 142 | return vke; 143 | } 144 | }; 145 | 146 | class KeyboardPtyDriver : public KeyboardTerminalDriverCommon 147 | { 148 | inline void wakeup(bool activity) 149 | { 150 | if (activity) 151 | { 152 | _last_idle = std::chrono::system_clock::now(); 153 | } 154 | } 155 | 156 | public: 157 | vector parse(TermBuffer* buffer) override 158 | { 159 | if (buffer->size() == 0 || buffer->hasMouseCode()) 160 | return {}; 161 | 162 | vector events; 163 | 164 | _Mod mod; 165 | _Code code; 166 | if (!kb_data.lookup(buffer, &code, &mod)) 167 | { 168 | if (buffer->size()) // insert? 169 | { 170 | KeyEvent ke; 171 | while (buffer->size()) 172 | ke.insert.push_back(buffer->get()); 173 | events.push_back(ke); 174 | } 175 | wakeup(!events.empty()); 176 | return events; 177 | } 178 | 179 | if (mod != _modifier_state) 180 | { 181 | // send key release 182 | // ~new (0101) & old (1001) = 0001 183 | _Mod released = ~mod & _modifier_state; 184 | auto codes = kb_data.getModCodes(released); 185 | for (auto modCode : codes) 186 | mark(false, modCode, &events); 187 | 188 | // send key press 189 | // new (1010) & ~old (0110) = 0010 190 | _Mod pressed = mod & ~_modifier_state; 191 | codes = kb_data.getModCodes(pressed); 192 | for (auto modCode : codes) 193 | mark(true, modCode, &events); 194 | 195 | _modifier_state = mod; 196 | } 197 | 198 | // keep history of last N pressed keys 199 | if (_pressedCodesCache.find(code) == _pressedCodesCache.end()) 200 | { 201 | _pressedCodesCache.insert(code); 202 | } 203 | 204 | mark(true, code, &events); 205 | wakeup(!events.empty()); 206 | return events; 207 | } 208 | 209 | vector idle() override 210 | { 211 | if (_pressedCodesCache.empty()) 212 | return {}; 213 | 214 | constexpr std::chrono::milliseconds idle_timeout{ 500 }; 215 | std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); 216 | auto time_elapsed = std::chrono::duration_cast(now - _last_idle); 217 | 218 | vector events; 219 | if (time_elapsed > idle_timeout) 220 | { 221 | // send key release 222 | // ~new (0101) & old (1001) = 0001 223 | _Mod released = ~0 & _modifier_state; 224 | auto codes = kb_data.getModCodes(released); 225 | for (auto modCode : codes) 226 | mark(false, modCode, &events); 227 | 228 | _modifier_state = 0; 229 | 230 | // mark all keys as released 231 | for (const auto& code : _pressedCodesCache) 232 | { 233 | mark(false, code, &events); 234 | } 235 | _pressedCodesCache.clear(); 236 | } 237 | 238 | return events; 239 | } 240 | 241 | private: 242 | std::set<_Code> _pressedCodesCache; 243 | std::chrono::system_clock::time_point _last_idle = std::chrono::system_clock::now(); 244 | }; 245 | 246 | // static 247 | std::unique_ptr KeyboardTerminalDriver::create(bool bMaster) 248 | { 249 | if (bMaster) 250 | return std::make_unique(); 251 | else 252 | return std::make_unique(); 253 | } 254 | -------------------------------------------------------------------------------- /haiku/filesystem.cpp: -------------------------------------------------------------------------------- 1 | #include "filesystem.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace haiku 13 | { 14 | namespace filesystem 15 | { 16 | std::string name(const std::string& path) 17 | { 18 | if (!path.empty()) 19 | { 20 | auto last_slash = path.find_last_of("/"); 21 | if (last_slash != std::string::npos) 22 | { 23 | std::string after_slash = path.substr(last_slash + 1); 24 | if (after_slash.empty()) 25 | { 26 | // path ended in slash, such as /a/b/c/ 27 | return haiku::filesystem::name(path.substr(0, last_slash)); 28 | } 29 | // part after last slash not empty, and thus is name 30 | return after_slash; 31 | } 32 | // no slash, path is whole name 33 | } 34 | 35 | return path; 36 | } 37 | 38 | std::string ensure_slash(const std::string& path) 39 | { 40 | if (path.empty() || path.at(path.size() - 1) == '/') 41 | return path; 42 | return path + "/"; 43 | } 44 | 45 | bool copy_file(const std::string& from, const std::string& to, std::error_code& ec) 46 | { 47 | ec.assign(1, std::system_category()); 48 | std::ifstream in(from); 49 | if (!in) 50 | return false; 51 | 52 | std::ofstream out(to); 53 | if (!out) 54 | return false; 55 | 56 | out << in.rdbuf(); 57 | in.close(); 58 | out.close(); 59 | 60 | ec.assign(0, std::system_category()); 61 | return true; 62 | } 63 | 64 | void copy(const std::string& from, const std::string& to, copy_options options, std::error_code& ec) noexcept 65 | { 66 | if (!exists(from, ec)) 67 | return; 68 | 69 | bool from_is_dir = haiku::filesystem::is_directory(from, ec); 70 | 71 | std::string to_target = to; 72 | if (exists(to, ec)) 73 | { 74 | if (is_directory(to, ec)) 75 | { 76 | to_target = ensure_slash(to) + name(from); 77 | } 78 | else 79 | { 80 | if (from_is_dir) 81 | { 82 | ec.assign(1, std::system_category()); 83 | return; // cannot copy dir to file 84 | } 85 | } 86 | } 87 | 88 | if (from_is_dir) 89 | { 90 | haiku::filesystem::create_directories(to_target, ec); 91 | if (ec.value() != 0) 92 | return; 93 | 94 | for (const auto& ele : directory_iterator(from, ec)) 95 | { 96 | std::string from_file = ensure_slash(from) + ele.path(); 97 | std::string to_file_target = ensure_slash(to_target) + ele.path(); 98 | haiku::filesystem::copy(from_file, to_file_target, options, ec); 99 | if (ec.value() != 0) 100 | return; 101 | } 102 | } 103 | else 104 | { 105 | copy_file(from, to_target, ec); 106 | } 107 | } 108 | 109 | void rename(const std::string& from, const std::string& to, std::error_code& ec) noexcept 110 | { 111 | ec.assign(1, std::system_category()); 112 | if (haiku::filesystem::exists(to, ec) || !haiku::filesystem::exists(from, ec)) 113 | return; 114 | 115 | haiku::filesystem::copy(from, to, copy_options::skip_existing, ec); 116 | if (ec.value() != 0) 117 | return; 118 | 119 | if (!haiku::filesystem::remove(from, ec)) 120 | ec.assign(1, std::system_category()); 121 | } 122 | 123 | std::vector path_segments(const std::string& path) 124 | { 125 | std::vector vec; 126 | 127 | size_t last = 0; 128 | while (last < path.size()) 129 | { 130 | size_t next = path.find("/", last); 131 | size_t len = (next == std::string::npos ? path.size() : next) - last; 132 | std::string part = path.substr(last, len); 133 | vec.push_back(part); 134 | last += len + 1; 135 | } 136 | 137 | return vec; 138 | } 139 | 140 | void create_directories(const std::string& path, std::error_code& ec) noexcept 141 | { 142 | auto segments = path_segments(path); 143 | std::string next; 144 | 145 | for (const auto& part : segments) 146 | { 147 | next += part + "/"; 148 | DIR* dir = ::opendir(next.c_str()); 149 | if (dir) 150 | { 151 | ::closedir(dir); 152 | } 153 | else 154 | { 155 | if (::mkdir(next.c_str(), 0755) == -1) 156 | { 157 | ec.assign(1, std::system_category()); 158 | return; 159 | } 160 | } 161 | } 162 | ec.assign(0, std::system_category()); 163 | } 164 | 165 | bool exists(const std::string& path, std::error_code& ec) noexcept 166 | { 167 | std::ifstream ifs; 168 | ifs.open(path); 169 | return static_cast(ifs) || is_directory(path, ec); 170 | } 171 | 172 | bool is_directory(const std::string& path, std::error_code& ec) noexcept 173 | { 174 | auto* dir = ::opendir(path.c_str()); 175 | if (dir) 176 | { 177 | ::closedir(dir); 178 | return true; 179 | } 180 | ec.assign(1, std::system_category()); 181 | return false; 182 | } 183 | 184 | size_t file_size(const std::string& path, std::error_code& ec) noexcept 185 | { 186 | ec.assign(1, std::system_category()); 187 | std::ifstream ifs; 188 | ifs.open(path); 189 | if (ifs) 190 | { 191 | ifs.seekg(0, std::ios_base::end); 192 | size_t pos = static_cast(ifs.tellg()); 193 | ifs.close(); 194 | ec.assign(0, std::system_category()); 195 | return pos; 196 | } 197 | 198 | return 0; 199 | } 200 | 201 | file_time_type last_write_time(const std::string& path, std::error_code& ec) noexcept 202 | { 203 | struct ::stat attr; 204 | ::stat(path.c_str(), &attr); 205 | file_time_type ft(std::chrono::seconds(attr.st_mtime)); 206 | return ft; 207 | } 208 | 209 | file_status symlink_status(const std::string& path, std::error_code& ec) noexcept 210 | { 211 | if (is_directory(path, ec)) 212 | { 213 | return file_status(file_type::directory); 214 | } 215 | else if (exists(path, ec)) 216 | { 217 | return file_status(file_type::regular); 218 | } 219 | return file_status(file_type::none); 220 | } 221 | 222 | bool remove(const std::string& path, std::error_code& ec) noexcept 223 | { 224 | std::string cmd = "rm -rf "; 225 | cmd += path; 226 | 227 | int result = ::system(cmd.c_str()); 228 | ec.assign(result, std::system_category()); 229 | if (result != 0) 230 | { 231 | ec.assign(1, std::system_category()); 232 | return false; 233 | } 234 | 235 | return true; 236 | } 237 | 238 | std::string current_path(std::error_code& ec) noexcept 239 | { 240 | char cwd[PATH_MAX]; 241 | if (getcwd(cwd, sizeof(cwd)) != nullptr) 242 | return cwd; 243 | else 244 | return ""; 245 | } 246 | 247 | directory_iterator::directory_iterator(const std::string& path, std::error_code& ec) noexcept 248 | { 249 | _root = path; 250 | DIR* dir = opendir(path.c_str()); 251 | if (dir) 252 | { 253 | dirent* ent = nullptr; 254 | while ((ent = readdir(dir))) 255 | { 256 | std::string name = ent->d_name; 257 | if (name != "." && name != "..") 258 | _entries.emplace(name); 259 | } 260 | closedir(dir); 261 | } 262 | } 263 | 264 | const directory_iterator::directory_entry& directory_iterator::operator*() const 265 | { 266 | return _entries.front(); 267 | } 268 | 269 | bool directory_iterator::operator!=(const directory_iterator& rhs) const 270 | { 271 | if (_entries.size() != rhs._entries.size()) 272 | return true; 273 | 274 | if (_entries.size() == 0) 275 | return false; // both are empty, so they must be equal 276 | 277 | if (_root != rhs._root) 278 | return true; 279 | 280 | return _entries.front().path() == rhs._entries.front().path(); 281 | } 282 | 283 | directory_iterator& directory_iterator::operator++() 284 | { 285 | _entries.pop(); 286 | return *this; 287 | } 288 | 289 | directory_iterator directory_iterator::begin() noexcept 290 | { 291 | return *this; 292 | } 293 | 294 | directory_iterator directory_iterator::end() noexcept 295 | { 296 | return directory_iterator(); 297 | } 298 | 299 | file_type file_status::type() const 300 | { 301 | return _type; 302 | } 303 | }; 304 | }; 305 | -------------------------------------------------------------------------------- /drivers/fs_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "drivers/fs_utils.h" 2 | 3 | #include "model/log.h" 4 | #include 5 | #include 6 | using std::cerr; 7 | using std::error_code; 8 | using std::ifstream; 9 | using std::ofstream; 10 | 11 | #ifdef __APPLE__ 12 | #include 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #ifndef __HAIKU__ 20 | #include 21 | namespace fs = std::experimental::filesystem; 22 | #include 23 | #else 24 | #include "haiku/filesystem.h" 25 | namespace fs = haiku::filesystem; 26 | #include "haiku/wordexp.h" 27 | #endif 28 | 29 | static std::string g_prog_name; 30 | 31 | static string handle_exception(std::exception& exp) 32 | { 33 | return exp.what(); 34 | } 35 | 36 | static string handle_exception(std::exception_ptr&& p) 37 | { 38 | try 39 | { 40 | if (p) 41 | std::rethrow_exception(p); 42 | } 43 | catch (std::exception& exp) 44 | { 45 | return handle_exception(exp); 46 | } 47 | 48 | return "unknown exception"; 49 | } 50 | 51 | string resolve(const string& path) 52 | { 53 | string result = path; 54 | if (path.size() > 0 && path.find("~") == 0) 55 | { 56 | ::wordexp_t exp_result; 57 | if (::wordexp(path.c_str(), &exp_result, 0) == 0) 58 | { 59 | if (exp_result.we_wordc == 1) 60 | { 61 | result = std::string(exp_result.we_wordv[0]); 62 | } 63 | ::wordfree(&exp_result); 64 | } 65 | } 66 | 67 | return result; 68 | } 69 | 70 | bool fs_utils::run_safely(function func, function onError) 71 | { 72 | string exception_message; 73 | try 74 | { 75 | func(); 76 | return true; 77 | } 78 | catch (...) 79 | { 80 | exception_message = handle_exception(std::current_exception()); 81 | } 82 | 83 | if (onError) 84 | onError(exception_message); 85 | 86 | return false; 87 | } 88 | 89 | bool fs_utils::read(const string& path, vector& outData) 90 | { 91 | ifstream file; 92 | file.open(resolve(path)); 93 | if (!file) 94 | return false; 95 | 96 | std::filebuf* pbuf = file.rdbuf(); 97 | std::size_t buffer_size = pbuf->pubseekoff(0, file.end, file.in); 98 | pbuf->pubseekpos(0, file.in); 99 | 100 | outData.resize(buffer_size); 101 | pbuf->sgetn(outData.data(), buffer_size); 102 | 103 | file.close(); 104 | return true; 105 | } 106 | 107 | bool fs_utils::read(const string& path, string* pOutData) 108 | { 109 | auto resolved = resolve(path); 110 | // read without p is just open test 111 | if (!pOutData) 112 | { 113 | return fs::exists(resolved); 114 | } 115 | 116 | ifstream file; 117 | file.open(resolved); 118 | if (!file) 119 | return false; 120 | 121 | *pOutData = static_cast(stringstream() << file.rdbuf()).str(); 122 | 123 | file.close(); 124 | return true; 125 | } 126 | 127 | bool fs_utils::copy(const string& src, const string& dst) 128 | { 129 | string resolved_src = resolve(src); 130 | string resolved_dst = resolve(dst); 131 | if (!fs_utils::exists(resolved_src)) 132 | { 133 | return false; 134 | } 135 | 136 | error_code ec; 137 | fs::copy(resolved_src, resolved_dst, fs::copy_options::recursive, ec); 138 | return ec.value() == 0; 139 | } 140 | 141 | bool fs_utils::write(const vector& data, const string& dst) 142 | { 143 | ofstream file; 144 | file.open(resolve(dst)); 145 | if (!file) 146 | return false; 147 | 148 | file.write(data.data(), data.size()); 149 | file.close(); 150 | 151 | return true; 152 | } 153 | 154 | bool fs_utils::write(const string& data, const string& dst) 155 | { 156 | vector buffer{ data.begin(), data.end() }; 157 | return fs_utils::write(buffer, dst); 158 | } 159 | 160 | bool fs_utils::mkdir(const string& path) 161 | { 162 | error_code ec; 163 | fs::create_directories(path, ec); 164 | return ec.value() == 0; 165 | } 166 | 167 | bool fs_utils::exists(const string& path) 168 | { 169 | error_code ec; 170 | bool result = fs::exists(path, ec); 171 | return result && ec.value() == 0; 172 | } 173 | 174 | vector fs_utils::list(const string& path) 175 | { 176 | vector result; 177 | if (!fs_utils::exists(path)) 178 | return result; 179 | 180 | error_code ec; 181 | for (const auto& ele : fs::directory_iterator(path, ec)) 182 | { 183 | if (ec.value() != 0) 184 | return {}; 185 | 186 | result.push_back(ele.path()); 187 | } 188 | 189 | return result; 190 | } 191 | 192 | bool fs_utils::isDirectory(const string& path) 193 | { 194 | error_code ec; 195 | bool result = fs::is_directory(path, ec); 196 | return result && ec.value() == 0; 197 | } 198 | 199 | size_t fs_utils::size(const string& path, bool recursive) 200 | { 201 | error_code ec; 202 | size_t result = 0; 203 | if (!fs_utils::isDirectory(path)) 204 | { 205 | result = fs::file_size(path, ec); 206 | if (ec.value() != 0) 207 | result = 0; 208 | } 209 | return result; 210 | } 211 | 212 | uint64_t fs_utils::lastModified(const string& filepath) 213 | { 214 | error_code ec; 215 | uint64_t result = 0; 216 | 217 | auto ftime = fs::last_write_time(filepath, ec); 218 | if (ec.value() != 0) 219 | return 0; 220 | 221 | std::time_t cftime = decltype(ftime)::clock::to_time_t(ftime); 222 | result = static_cast(cftime); 223 | 224 | return result; 225 | } 226 | 227 | bool fs_utils::remove(const string& path) 228 | { 229 | bool success = true; 230 | error_code ec; 231 | auto info = fs::symlink_status(path, ec); 232 | if (ec.value() == 0 && info.type() == fs::file_type::directory) 233 | for (fs::directory_iterator d(path, ec), end; ec.value() == 0 && d != end; ++d) 234 | success = success && fs_utils::remove(d->path()); 235 | if (ec.value()) 236 | return -1; 237 | success = success && fs::remove(path, ec); 238 | return success && ec.value() == 0; 239 | } 240 | 241 | bool fs_utils::rename(const string& from, const string& to) 242 | { 243 | error_code ec; 244 | fs::rename(from, to, ec); 245 | return ec.value() == 0; 246 | } 247 | 248 | bool fs_utils::resize(const string& path, int size) 249 | { 250 | if (size < 0) 251 | { 252 | return false; 253 | } 254 | if (static_cast(size) > std::numeric_limits::max()) 255 | { 256 | return false; 257 | } 258 | 259 | error_code ec; 260 | fs::resize_file(path, size, ec); 261 | return ec.value() == 0; 262 | } 263 | 264 | void fs_utils::set_prog_name(const string& prog_name) 265 | { 266 | g_prog_name = prog_name; 267 | } 268 | 269 | string proc_root() 270 | { 271 | static string path; 272 | if (path.empty()) 273 | { 274 | constexpr ssize_t size = 1024; 275 | char buf[size]{ 0 }; 276 | 277 | #ifdef __APPLE__ 278 | uint32_t len = size; 279 | if (_NSGetExecutablePath(buf, &len) != 0) 280 | { 281 | cerr << "proc path too long\n"; 282 | ::exit(1); 283 | } 284 | #elif __HAIKU__ 285 | path = g_prog_name; 286 | if (g_prog_name.size() > 0) 287 | { 288 | if (g_prog_name.at(0) != '/') 289 | { 290 | std::error_code ec; 291 | auto pwd = fs::current_path(ec); 292 | size_t last_index = static_cast(pwd.size() - 1); 293 | if (pwd.size() > 0 && pwd.at(last_index) != '/') 294 | { 295 | pwd += "/"; 296 | } 297 | path = pwd + path; 298 | } 299 | } 300 | auto len = path.size(); 301 | auto reduced = len < size ? len : size; 302 | ::memcpy(buf, path.data(), reduced); 303 | #else 304 | ssize_t len = ::readlink("/proc/self/exe", buf, size); 305 | if (len >= size) // yikes, abort 306 | { 307 | cerr << "proc path too long\n"; 308 | ::exit(1); 309 | } 310 | #endif 311 | 312 | buf[len] = 0; 313 | path = buf; 314 | 315 | // remove proc file name 316 | size_t last_slash = path.find_last_of("/"); 317 | if (last_slash == string::npos) 318 | { 319 | cerr << "proc path [" << path << "] had no dir slash, unexpected\n"; 320 | ::exit(1); 321 | } 322 | path = path.substr(0, last_slash + 1); 323 | } 324 | return path; 325 | } 326 | 327 | string pwd() 328 | { 329 | error_code ec; 330 | string path = fs::current_path(ec); 331 | return path + "/"; 332 | } 333 | 334 | string fs_utils::make_proc_path(const string& given_path) 335 | { 336 | static string path = proc_root(); 337 | if (given_path.substr(0, 1) != "/") 338 | { 339 | return path + given_path; 340 | } 341 | return given_path; 342 | } 343 | 344 | string fs_utils::make_pwd_path(const string& given_path) 345 | { 346 | static string pwd_path = pwd(); 347 | if (given_path.substr(0, 1) != "/") 348 | { 349 | return pwd_path + given_path; 350 | } 351 | return given_path; 352 | } 353 | 354 | string fs_utils::filename(const string& path) 355 | { 356 | return fs::canonical(path).filename(); 357 | } 358 | -------------------------------------------------------------------------------- /model/client.cpp: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | #include "components/component.h" 3 | #include "components/computer.h" 4 | #include "host.h" 5 | 6 | #include "config.h" 7 | #include "drivers/fs_utils.h" 8 | 9 | #include "apis/os.h" 10 | #include "apis/system.h" 11 | #include "apis/unicode.h" 12 | #include "apis/userdata.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | using Logging::lout; 20 | 21 | Client::Client(Host* host, const string& env_path) 22 | : LuaProxy("component") 23 | , _computer(nullptr) 24 | , _config(nullptr) 25 | , _env_path(env_path) 26 | , _host(host) 27 | { 28 | add("list", &Client::component_list); 29 | add("invoke", &Client::component_invoke); 30 | add("methods", &Client::component_methods); 31 | add("type", &Client::component_type); 32 | add("slot", &Client::component_slot); 33 | add("doc", &Client::component_doc); 34 | } 35 | 36 | Client::~Client() 37 | { 38 | close(); 39 | } 40 | 41 | Host* Client::host() const 42 | { 43 | return _host; 44 | } 45 | 46 | bool Client::load() 47 | { 48 | if (_config) 49 | { 50 | lout << "Client is either already loaded or did not close properly"; 51 | return false; 52 | } 53 | 54 | _config.reset(new Config()); 55 | 56 | if (!_config->load(envPath(), "client")) 57 | { 58 | lout << "failed to load client config\n"; 59 | return false; 60 | } 61 | 62 | if (!createComponents()) 63 | return false; 64 | lout << "components loaded: " << _components.size() << "\n"; 65 | 66 | if (!loadLuaComponentApi()) 67 | { 68 | lout << "failed to load lua component api\n"; 69 | return false; 70 | } 71 | 72 | if (!postInit()) 73 | return false; 74 | lout << "components post initialized\n"; 75 | 76 | return true; 77 | } 78 | 79 | bool Client::createComponents() 80 | { 81 | // load components from config 82 | for (const auto& section : _config->keys()) 83 | { 84 | auto& section_data = _config->get(section); 85 | if (section == "components") 86 | { 87 | int count = section_data.len(); 88 | for (int index = 1; index <= count; index++) 89 | { 90 | if (!section_data.contains(index)) 91 | continue; 92 | Value& component_config = section_data.get(index); 93 | string key = component_config.get(1).toString(); 94 | lout << key << ": "; 95 | auto pc = _host->create(key); 96 | if (!pc) 97 | { 98 | lout << "skipping component [" << key << "] no driver found\n"; 99 | } 100 | else if (!pc->initialize(this, component_config)) 101 | { 102 | stringstream ss; 103 | ss << "failed to initialize: " << key << endl; 104 | lout << ss.str(); 105 | std::cerr << ss.str(); 106 | return false; 107 | } 108 | else 109 | { 110 | _components.push_back(std::move(pc)); 111 | lout << "ready\n"; 112 | } 113 | } 114 | } 115 | else if (section == "system") 116 | { 117 | SystemApi::configure(section_data); 118 | } 119 | } 120 | return true; 121 | } 122 | 123 | bool Client::postInit() 124 | { 125 | // find fs with no source content, that is the tmp fs 126 | for (auto* pc : components()) 127 | { 128 | if (!pc->postInit()) 129 | { 130 | lout << pc->type() << "[" << pc->address() << "] failed to postInit\n"; 131 | return false; 132 | } 133 | // the vm boot handles component_added for us 134 | // _computer->pushSignal(ValuePack({"component_added", pc->address(), pc->type()})); 135 | } 136 | 137 | return true; 138 | } 139 | 140 | bool Client::loadLuaComponentApi() 141 | { 142 | // computer required 143 | if (!_computer) 144 | { 145 | lout << "emulation requires exactly one computer component\n"; 146 | return false; 147 | } 148 | 149 | _computer->stackLog(_host->stackLog()); 150 | _computer->newlib(this); 151 | _computer->newlib(OSApi::get()); 152 | _computer->newlib(SystemApi::get()); 153 | _computer->newlib(UnicodeApi::get()); 154 | _computer->newlib(UserDataApi::get()); 155 | return true; 156 | } 157 | 158 | void Client::close() 159 | { 160 | if (_config) 161 | { 162 | _config->save(); 163 | _config.reset(); 164 | } 165 | 166 | // some components detach from each other during dtor 167 | // and to find each other, they may use component.list 168 | // but there is no need to when dtoring the client (this) 169 | vector> comp_copy; 170 | for (auto& pc : _components) 171 | comp_copy.push_back(std::move(pc)); 172 | _components.clear(); 173 | 174 | // now the screen should be closed, we can report crash info 175 | std::cerr << _crash; 176 | } 177 | 178 | vector Client::components(string filter, bool exact) const 179 | { 180 | vector result; 181 | 182 | for (auto& pc : _components) 183 | { 184 | string type = pc->type(); 185 | if (type.find(filter) == 0) 186 | { 187 | if (!exact || type == filter) 188 | { 189 | result.push_back(pc.get()); 190 | } 191 | } 192 | } 193 | 194 | return result; 195 | } 196 | 197 | Component* Client::component(const string& address) const 198 | { 199 | for (auto& pc : _components) 200 | { 201 | if (pc->address() == address) 202 | { 203 | return pc.get(); 204 | } 205 | } 206 | return nullptr; 207 | } 208 | 209 | int Client::component_list(lua_State* lua) 210 | { 211 | static const string default_filter = ""; 212 | static const bool default_exact = false; 213 | 214 | string filter = Value::checkArg(lua, 1, &default_filter); 215 | bool exact = Value::checkArg(lua, 2, &default_exact); 216 | if (lua_type(lua, 1) == LUA_TNIL) 217 | exact = false; 218 | 219 | Value result = Value::table(); 220 | for (auto* pc : components(filter, exact)) 221 | { 222 | result.set(pc->address(), pc->type()); 223 | } 224 | 225 | return ValuePack::ret(lua, result); 226 | } 227 | 228 | int Client::component_invoke(lua_State* lua) 229 | { 230 | // for logging, this is called via LuaProxy because all method calls are dispatched there first 231 | // LuaProxy::invoke has already logged much about this call, but is waiting to log the result 232 | 233 | // we remove address and the method name from the stack so that invoked methods can expect their args to start at 1 234 | string address = Value::checkArg(lua, 1); 235 | lua_remove(lua, 1); 236 | string methodName = Value::checkArg(lua, 1); 237 | lua_remove(lua, 1); 238 | 239 | Component* pc = component(address); 240 | if (!pc) 241 | return ValuePack::ret(lua, Value::nil, "no such component " + address); 242 | 243 | int stacked = pc->invoke(methodName, lua); 244 | lua_pushboolean(lua, true); 245 | lua_insert(lua, 1); 246 | return stacked + 1; 247 | } 248 | 249 | int Client::component_methods(lua_State* lua) 250 | { 251 | string address = Value::checkArg(lua, 1); 252 | 253 | Component* pc = component(address); 254 | if (!pc) 255 | return ValuePack::ret(lua, Value::nil, "no such component"); 256 | 257 | Value mpack = Value::table(); 258 | Value info = Value::table(); 259 | info.set("direct", true); 260 | for (const auto& luaMethod : pc->methods()) 261 | { 262 | mpack.set(std::get<0>(luaMethod), info); 263 | } 264 | return ValuePack::ret(lua, mpack); 265 | } 266 | 267 | int Client::component_type(lua_State* lua) 268 | { 269 | string address = Value::checkArg(lua, 1); 270 | Component* pc = component(address); 271 | if (!pc) 272 | return ValuePack::ret(lua, Value::nil, "no such component"); 273 | 274 | return ValuePack::ret(lua, pc->type()); 275 | } 276 | 277 | int Client::component_slot(lua_State* lua) 278 | { 279 | string address = Value::checkArg(lua, 1); 280 | Component* pc = component(address); 281 | if (!pc) 282 | return ValuePack::ret(lua, Value::nil, "no such component"); 283 | 284 | return ValuePack::ret(lua, pc->slot()); 285 | } 286 | 287 | int Client::component_doc(lua_State* lua) 288 | { 289 | string address = Value::checkArg(lua, 1); 290 | string methodName = Value::checkArg(lua, 2); 291 | Component* pc = component(address); 292 | if (!pc) 293 | return ValuePack::ret(lua, Value::nil, "no such component"); 294 | 295 | return ValuePack::ret(lua, pc->doc(methodName)); 296 | } 297 | 298 | const string& Client::envPath() const 299 | { 300 | return _env_path; 301 | } 302 | 303 | void Client::computer(Computer* c) 304 | { 305 | _computer = c; 306 | } 307 | 308 | Computer* Client::computer() const 309 | { 310 | return _computer; 311 | } 312 | 313 | RunState Client::run() 314 | { 315 | for (auto& pc : _components) 316 | { 317 | auto state = pc->update(); 318 | if (state != RunState::Continue) 319 | { 320 | return state; 321 | } 322 | } 323 | 324 | return RunState::Continue; 325 | } 326 | 327 | void Client::pushSignal(const ValuePack& pack) 328 | { 329 | _computer->pushSignal(pack); 330 | } 331 | 332 | bool Client::add_component(Value& component_config) 333 | { 334 | if (component_config.len() == 0) 335 | return false; 336 | 337 | string type = component_config.get(1).toString(); 338 | 339 | auto pc = _host->create(type); 340 | if (!pc || !pc->initialize(this, component_config)) 341 | { 342 | return false; 343 | } 344 | 345 | if (!pc->postInit()) 346 | { 347 | lout << pc->type() << "[" << pc->address() << "] failed to postInit\n"; 348 | return false; 349 | } 350 | 351 | auto addr = pc->address(); 352 | _components.push_back(std::move(pc)); 353 | _computer->pushSignal(ValuePack({ "component_added", addr, type })); 354 | 355 | return true; 356 | } 357 | 358 | bool Client::remove_component(const string& address) 359 | { 360 | for (auto it = _components.begin(); it != _components.end(); it++) 361 | { 362 | auto& pc = *it; 363 | if (pc->address() == address) 364 | { 365 | _computer->pushSignal(ValuePack({ "component_removed", pc->address(), pc->type() })); 366 | _components.erase(it); 367 | return true; 368 | } 369 | } 370 | 371 | return false; 372 | } 373 | 374 | void Client::appendCrashText(const string& report) 375 | { 376 | lout << "crash: " << report << endl; 377 | _crash += report; 378 | _crash += "\n"; 379 | } 380 | 381 | string Client::getAllCrashText() const 382 | { 383 | return _crash; 384 | } 385 | --------------------------------------------------------------------------------