diff --git a/project.json b/project.json index 73282da..ec744aa 100644 --- a/project.json +++ b/project.json @@ -15,6 +15,6 @@ } }, "safe": true, - "opt": "O1", + "opt": "O0", "debug-info": "full" } diff --git a/resources/style.css b/resources/style.css index 37e98d8..01c3a9c 100644 --- a/resources/style.css +++ b/resources/style.css @@ -9,7 +9,6 @@ div { separator { bg: #fbf1c7ff; size: 1; - padding: 0 5 0 5; } button { diff --git a/src/conf.c3 b/src/conf.c3 index 7e609f3..108a163 100644 --- a/src/conf.c3 +++ b/src/conf.c3 @@ -5,9 +5,17 @@ import std::os::env; import std::core::mem::allocator; import std::collections::object; import std::collections::list; +import std::encoding::json; +struct ConfigFile { + Path path; + String name; +} + alias StrList = list::List{String}; +alias ConfFList = list::List{ConfigFile}; + fn Path? String.to_expanded_path(&str, Allocator allocator) { @@ -46,40 +54,75 @@ fn Path? String.to_expanded_path(&str, Allocator allocator) }; } -fn StrList get_qemu_cmdline(Allocator allocator, Object* conf) +fn StrList? get_qemu_cmdline(Allocator allocator, Object* conf) { - // parse the disk path - String disk = conf.get_string("disk")!!; - Path disk_path = disk.to_expanded_path(mem)!!; - defer disk_path.free(); - - // compose the command line for the vm StrList cmd; - cmd.init(allocator); + @pool() { + // parse the disk path + String disk = conf.get_string("disk")!; + Path disk_path = disk.to_expanded_path(tmem)!; - // first the executable - cmd.push(conf.get_string("qemu"))!!; - // then the drive - cmd.push("-drive"); - cmd.push(string::format(allocator, "file=%s", disk_path.str_view())); - //cmd.push(string::format(allocator, "file=%s,format=%s", disk_path.str_view(), disk_path.extension()))!!; - // memory - cmd.push("-m"); - cmd.push(conf.get_string("memory"))!!; - // processors - cmd.push("-smp"); - cmd.push(conf.get_string("processors") ?? "1"); - // then all the parameters - Object* parameters = conf.get("parameters")!!; - for (usz i = 0; i < parameters.get_len(); i++) { - Object* p = parameters.get_at(i); - if (p.is_string()) { - cmd.push(string::format(allocator, "-%s", p.s)); - } else if (p.is_indexable()) { - cmd.push(string::format(allocator, "-%s", p.get_string_at(0)))!!; - cmd.push(p.get_string_at(1))!!; + // compose the command line for the vm + cmd.init(allocator); + + // first the executable + cmd.push(conf.get_string("qemu"))!!; + // then the drive + cmd.push("-drive"); + cmd.push(string::format(allocator, "file=%s", disk_path.str_view())); + // memory + cmd.push("-m"); + cmd.push(conf.get_string("memory"))!!; + // processors + cmd.push("-smp"); + cmd.push(conf.get_string("processors") ?? "1"); + // then all the parameters + Object* parameters = conf.get("parameters")!!; + for (usz i = 0; i < parameters.get_len(); i++) { + Object* p = parameters.get_at(i); + if (p.is_string()) { + cmd.push(string::format(allocator, "-%s", p.s)); + } else if (p.is_indexable()) { + cmd.push(string::format(allocator, "-%s", p.get_string_at(0)))!!; + cmd.push(p.get_string_at(1))!!; + } } - } + }; return cmd; } + +fn ConfFList? get_config_list(Allocator allocator) +{ + ConfFList l; + l.init(allocator); + @pool() { + // find the right config directory + Path conf_dir = env::get_config_dir(tmem).append(tmem, "simple-qemu-manager")!; + + if (!file::exists(conf_dir.str_view()) || !file::is_dir(conf_dir.str_view())) { + path::mkdir(conf_dir, recursive:true)!; + return l; + } + + // list all json files + PathList pl = path::ls(tmem, conf_dir)!; + foreach (fname : pl) @pool() { + if ((fname.extension() ?? "") != "json") continue; + Path fpath = conf_dir.append(tmem, fname.str_view())!; + + File cf = file::open_path(fpath, "r")!; + defer (void)cf.close(); + + Object* c = json::parse(tmem, &cf)!; + ConfigFile e = { + .path = path::new(allocator, fpath.str_view(), fpath.env)!, + .name = string::format(allocator, "%s", c.get_string("name"))!, + }; + l.push(e); + }; + + }; + + return l; +} diff --git a/src/main.c3 b/src/main.c3 index e13b210..7fce951 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -28,7 +28,7 @@ fn int main(String[] args) Object* conf = json::parse(mem, &file)!!; defer conf.free(); - // UI initialization + // ---------------------------------------------- UI initialization ---------------------------------------------- // ArenaAllocator arena; char[] arena_mem = mem::new_array(char, 1024*1024); defer (void)mem::free(arena_mem); @@ -59,9 +59,9 @@ fn int main(String[] args) ui.import_style_from_file(STYLESHEET_PATH); ren::pre(ren.win); - // End UI initialization + // -------------------------------------------- End UI initialization -------------------------------------------- // - StrList cmd = conf::get_qemu_cmdline(mem, conf); + StrList cmd = conf::get_qemu_cmdline(mem, conf)!!; defer cmd.free(); String vm_name = conf.get_string("name")!!; String vm_disk = conf.get_string("disk").to_expanded_path(tmem).str_view()!!; @@ -71,6 +71,11 @@ fn int main(String[] args) SubProcess vm_proc; defer (void)vm_proc.join(); + ConfFList conf_list = conf::get_config_list(tmem)!!; + foreach (cf : conf_list) { + io::printn(cf); + } + bool quit; Clock sleep_clock; while (!quit) { @@ -84,10 +89,23 @@ fn int main(String[] args) if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true; ui.@div(ugui::@grow(), ugui::@grow(), COLUMN) { - ui.@div(ugui::@grow(), ugui::@fit(20)) { + ui.@div(ugui::@grow(), ugui::@fit(20), ROW, LEFT) { ui.text("Machine:")!!; ui.separator(ugui::@grow(), ugui::@grow())!!; ui.text(vm_name)!!; + + static bool popup; + static Point popup_pos; + if (ui.button("v")!!.mouse_release) { + popup = true; + popup_pos = ui.input.mouse.pos; + } + ui.@popup(&popup, popup_pos, ugui::@fit(), ugui::@fit(), COLUMN) { + foreach (idx, c : conf_list) { + ui.text(c.name, idx)!!; + } + }!!; + }!!; ui.hor_line()!!; ui.@div(ugui::@grow(), ugui::@grow(), COLUMN, scroll_y: true) { diff --git a/src/vm.c3 b/src/vm.c3 new file mode 100644 index 0000000..8ff9144 --- /dev/null +++ b/src/vm.c3 @@ -0,0 +1,90 @@ +import std::io; +import std::io::path; +import std::io::file; +import std::os::process; +import std::collections::object; +import std::encoding::json; +import conf; + + +struct VirtualMachineDesc { + Path config_path; + Object* config; + String name; + StrList cmdline; + Path disk_path; + SubProcess process; +} + + +fn VirtualMachineDesc? new_from_path(Allocator allocator, Path path) +{ + VirtualMachineDesc desc; + desc.config_path = path::new(allocator, path.str_view(), path.env)!; + + File file = file::open_path(path, "r")!; + defer (void)file.close(); + + desc.config = json::parse(allocator, &file)!; + desc.name = desc.config.get_string("name")!; + desc.disk_path = desc.config.get_string("disk").to_expanded_path(allocator)!; + + desc.cmdline = conf::get_qemu_cmdline(allocator, desc.config)!; + + return desc; +} + + +<* @param[&in] desc *> +fn bool VirtualMachineDesc.is_initialized(&desc) +{ + return desc.config_path.str_view() != "" + && desc.config != null + && desc.name != "" + && desc.disk_path.str_view() != "" + && desc.cmdline.len() != 0; +} + + +<* @param[&inout] desc *> +fn void VirtualMachineDesc.free(&desc) +{ + if (!desc.is_initialized()) return; + (void)desc.stop(); + desc.config_path.free(); + desc.config.free(); + // desc.name.free() name is just a string view into desc.config + desc.disk_path.free(); + desc.cmdline.free(); + + *desc = {}; +} + + +<* @param[&inout] desc *> +fn void? VirtualMachineDesc.start(&desc) +{ + if (!desc.is_initialized()) return; + if ((desc.process.is_running() ?? false)) return; + + desc.process = process::create(desc.cmdline.array_view(), {.inherit_stdio=true, .inherit_environment=true})!; +} + + +<* @param[&inout] desc *> +fn void? VirtualMachineDesc.stop(&desc) +{ + if (!desc.is_initialized()) return; + if (!(desc.process.is_running() ?? false)) return; + + desc.process.terminate()!; + desc.process.join()!; +} + + +<* @param[&in] desc *> +fn bool VirtualMachineDesc.is_running(&desc) +{ + if (!desc.is_initialized()) return false; + return desc.process.is_running() ?? false; +}