From 82919a690ebcd966e9a6850aac08799abc94c2d0 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Wed, 25 May 2022 02:24:35 +0530 Subject: [PATCH] Garbage collection bugs fixed - Objects that weren't pushed to PKVM's temp reference were fixed - vm->bytes_allocated was modified after the re-calculation of garbage collection fixed. --- src/core/compiler.c | 3 + src/core/public.c | 55 +- src/core/value.c | 54 +- src/core/vm.c | 40 +- src/core/vm.h | 4 + src/libs/ext_term.c | 259 -------- src/libs/libs.c | 8 - src/libs/thirdparty/term/LICENSE | 21 - src/libs/thirdparty/term/term.h | 1027 ------------------------------ 9 files changed, 112 insertions(+), 1359 deletions(-) delete mode 100644 src/libs/ext_term.c delete mode 100644 src/libs/thirdparty/term/LICENSE delete mode 100644 src/libs/thirdparty/term/term.h diff --git a/src/core/compiler.c b/src/core/compiler.c index 4b30d55..55ab6e1 100644 --- a/src/core/compiler.c +++ b/src/core/compiler.c @@ -2734,9 +2734,12 @@ static void compileFunction(Compiler* compiler, FuncType fn_type) { if (compiler->parser.has_syntax_error) return; + // The function will register itself in the owner's constant pool and it's + // GC root so we don't need to push it to temp references. int fn_index; Function* func = newFunction(compiler->parser.vm, name, name_length, compiler->module, false, NULL, &fn_index); + func->is_method = (fn_type == FUNC_METHOD || fn_type == FUNC_CONSTRUCTOR); checkMaxConstantsReached(compiler, fn_index); diff --git a/src/core/public.c b/src/core/public.c index bad082f..5ed4124 100644 --- a/src/core/public.c +++ b/src/core/public.c @@ -124,6 +124,7 @@ PKVM* pkNewVM(PkConfiguration* config) { vm->working_set = (Object**)vm->config.realloc_fn( NULL, sizeof(Object*) * vm->working_set_capacity, NULL); vm->next_gc = INITIAL_GC_SIZE; + vm->collecting_garbage = false; vm->min_heap_size = MIN_HEAP_SIZE; vm->heap_fill_percent = HEAP_FILL_PERCENT; @@ -179,7 +180,12 @@ void pkSetUserData(PKVM* vm, void* user_data) { PkHandle* pkNewModule(PKVM* vm, const char* name) { CHECK_ARG_NULL(name); Module* module = newModuleInternal(vm, name); - return vmNewHandle(vm, VAR_OBJ(module)); + + vmPushTempRef(vm, &module->_super); // module. + PkHandle* handle = vmNewHandle(vm, VAR_OBJ(module)); + vmPopTempRef(vm); // module. + + return handle; } void pkRegisterModule(PKVM* vm, PkHandle* module) { @@ -219,7 +225,10 @@ PkHandle* pkNewClass(PKVM* vm, const char* name, class_->new_fn = new_fn; class_->delete_fn = delete_fn; - return vmNewHandle(vm, VAR_OBJ(class_)); + vmPushTempRef(vm, &class_->_super); // class_. + PkHandle* handle = vmNewHandle(vm, VAR_OBJ(class_)); + vmPopTempRef(vm); // class_. + return handle; } void pkClassAddMethod(PKVM* vm, PkHandle* cls, @@ -237,6 +246,8 @@ void pkClassAddMethod(PKVM* vm, PkHandle* cls, Function* fn = newFunction(vm, name, (int)strlen(name), class_->owner, true, NULL, NULL); + vmPushTempRef(vm, &fn->_super); // fn. + fn->arity = arity; fn->is_method = true; fn->native = fptr; @@ -246,15 +257,19 @@ void pkClassAddMethod(PKVM* vm, PkHandle* cls, // won't be garbage collected (class handle has reference to the module). Closure* method = newClosure(vm, fn); + vmPopTempRef(vm); // fn. + vmPushTempRef(vm, &method->_super); // method. + { + if (strcmp(name, CTOR_NAME) == 0) { + class_->ctor = method; - if (strcmp(name, CTOR_NAME) == 0) { - class_->ctor = method; - - } else { - vmPushTempRef(vm, &method->_super); // method. - pkClosureBufferWrite(&class_->methods, vm, method); - vmPopTempRef(vm); // method. + } else { + vmPushTempRef(vm, &method->_super); // method. + pkClosureBufferWrite(&class_->methods, vm, method); + vmPopTempRef(vm); // method. + } } + vmPopTempRef(vm); // method. } void pkReleaseHandle(PKVM* vm, PkHandle* handle) { @@ -766,7 +781,11 @@ bool pkSetAttribute(PKVM* vm, int instance, const char* name, int value) { VALIDATE_SLOT_INDEX(instance); VALIDATE_SLOT_INDEX(value); - varSetAttrib(vm, SLOT(instance), newString(vm, name), SLOT(value)); + String* sname = newString(vm, name); + vmPushTempRef(vm, &sname->_super); // sname. + varSetAttrib(vm, SLOT(instance), sname, SLOT(value)); + vmPopTempRef(vm); // sname. + return !VM_HAS_ERROR(vm); } @@ -777,12 +796,19 @@ bool pkGetAttribute(PKVM* vm, int instance, const char* name, VALIDATE_SLOT_INDEX(instance); VALIDATE_SLOT_INDEX(index); - SET_SLOT(index, varGetAttrib(vm, SLOT(instance), newString(vm, name))); + String* sname = newString(vm, name); + vmPushTempRef(vm, &sname->_super); // sname. + SET_SLOT(index, varGetAttrib(vm, SLOT(instance), sname)); + vmPopTempRef(vm); // sname. + return !VM_HAS_ERROR(vm); } static Var _newInstance(PKVM* vm, Class* cls, int argc, Var* argv) { Var instance = preConstructSelf(vm, cls); + if (VM_HAS_ERROR(vm)) return VAR_NULL; + + if (IS_OBJ(instance)) vmPushTempRef(vm, AS_OBJ(instance)); // instance. Closure* ctor = cls->ctor; while (ctor == NULL) { @@ -792,6 +818,7 @@ static Var _newInstance(PKVM* vm, Class* cls, int argc, Var* argv) { } if (ctor != NULL) vmCallMethod(vm, instance, ctor, argc, argv, NULL); + if (IS_OBJ(instance)) vmPopTempRef(vm); // instance. return instance; } @@ -861,8 +888,12 @@ bool pkCallMethod(PKVM* vm, int instance, const char* method, if (ret >= 0) VALIDATE_SLOT_INDEX(ret); bool is_method = false; - Var callable = getMethod(vm, SLOT(instance), newString(vm, method), + String* smethod = newString(vm, method); + vmPushTempRef(vm, &smethod->_super); // smethod. + Var callable = getMethod(vm, SLOT(instance), smethod, &is_method); + vmPopTempRef(vm); // smethod. + if (VM_HAS_ERROR(vm)) return false; // Calls a class == construct. diff --git a/src/core/value.c b/src/core/value.c index 7f3dc40..2b78862 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -149,7 +149,9 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) { markObject(vm, &func->owner->_super); - if (!func->is_native) { + // If a garbage collection is triggered when allocating a name string + // for this function, it's [fn] property will be NULL. + if (!func->is_native && func->fn != NULL) { Fn* fn = func->fn; vm->bytes_allocated += sizeof(Fn); @@ -293,14 +295,14 @@ String* newStringVaArgs(PKVM* vm, const char* fmt, va_list args) { List* newList(PKVM* vm, uint32_t size) { List* list = ALLOCATE(vm, List); - vmPushTempRef(vm, &list->_super); + vmPushTempRef(vm, &list->_super); // list. varInitObject(&list->_super, vm, OBJ_LIST); pkVarBufferInit(&list->elements); if (size > 0) { pkVarBufferFill(&list->elements, vm, VAR_NULL, size); list->elements.count = 0; } - vmPopTempRef(vm); + vmPopTempRef(vm); // list. return list; } @@ -323,13 +325,9 @@ Range* newRange(PKVM* vm, double from, double to) { Module* newModule(PKVM* vm) { Module* module = ALLOCATE(vm, Module); + memset(module, 0, sizeof(Module)); varInitObject(&module->_super, vm, OBJ_MODULE); - module->path = NULL; - module->name = NULL; - module->initialized = false; - module->body = NULL; - pkVarBufferInit(&module->globals); pkUintBufferInit(&module->global_names); pkVarBufferInit(&module->constants); @@ -343,6 +341,7 @@ Function* newFunction(PKVM* vm, const char* name, int length, int* fn_index) { Function* func = ALLOCATE(vm, Function); + memset(func, 0, sizeof(Function)); varInitObject(&func->_super, vm, OBJ_FUNC); vmPushTempRef(vm, &func->_super); // func @@ -388,10 +387,7 @@ Closure* newClosure(PKVM* vm, Function* fn) { varInitObject(&closure->_super, vm, OBJ_CLOSURE); closure->fn = fn; - - for (int i = 0; i < fn->upvalue_count; i++) { - closure->upvalues[i] = NULL; - } + memset(closure->upvalues, 0, sizeof(Upvalue*) * fn->upvalue_count); return closure; } @@ -410,9 +406,10 @@ Fiber* newFiber(PKVM* vm, Closure* closure) { ASSERT(closure == NULL || closure->fn->arity >= -1, OOPS); Fiber* fiber = ALLOCATE(vm, Fiber); - ASSERT(fiber != NULL, "Out of memory"); - // Not sure why this memset is needed here. If it doesn't then remove it. + // If a garbage collection is triggered here, and the fiber isn't fully + // constructed -> it's fields are not intialized yet, would cause a crash + // so we need to memset here. memset(fiber, 0, sizeof(Fiber)); varInitObject(&fiber->_super, vm, OBJ_FIBER); @@ -476,6 +473,12 @@ Class* newClass(PKVM* vm, const char* name, int length, const char* docstring, int* cls_index) { Class* cls = ALLOCATE(vm, Class); + + // If the garbage collection trigged bellow while allocating for + // [cls->name] or other properties, the calss is in the root (temp ref) + // and it's property [cls->name] is un initialized, which cause a crash. + memset(cls, 0, sizeof(Class)); + varInitObject(&cls->_super, vm, OBJ_CLASS); vmPushTempRef(vm, &cls->_super); // class. @@ -483,12 +486,7 @@ Class* newClass(PKVM* vm, const char* name, int length, pkClosureBufferInit(&cls->methods); cls->class_of = PK_INSTANCE; - cls->owner = NULL; cls->super_class = super; - cls->docstring = NULL; - cls->ctor = NULL; - cls->new_fn = NULL; - cls->delete_fn = NULL; // Builtin types doesn't belongs to a module. if (module != NULL) { @@ -510,36 +508,38 @@ Instance* newInstance(PKVM* vm, Class* cls) { "class with newInstance() function."); Instance* inst = ALLOCATE(vm, Instance); + memset(inst, 0, sizeof(Instance)); varInitObject(&inst->_super, vm, OBJ_INST); + vmPushTempRef(vm, &inst->_super); // inst. inst->cls = cls; - inst->attribs = newMap(vm); - if (cls->new_fn != NULL) { inst->native = cls->new_fn(vm); } else { inst->native = NULL; } + inst->attribs = newMap(vm); + vmPopTempRef(vm); // inst. return inst; } List* rangeAsList(PKVM* vm, Range* self) { - List* list; + if (self->from < self->to) { - list = newList(vm, (uint32_t)(self->to - self->from)); + List* list = newList(vm, (uint32_t)(self->to - self->from)); + vmPushTempRef(vm, &list->_super); // list. for (double i = self->from; i < self->to; i++) { pkVarBufferWrite(&list->elements, vm, VAR_NUM(i)); } - return list; + vmPopTempRef(vm); // list. - } else { - list = newList(vm, 0); + return list; } - return list; + return newList(vm, 0); } String* stringLower(PKVM* vm, String* self) { diff --git a/src/core/vm.c b/src/core/vm.c index 0956c97..e0d9826 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -27,10 +27,24 @@ void* vmRealloc(PKVM* vm, void* memory, size_t old_size, size_t new_size) { // Track the total allocated memory of the VM to trigger the GC. // if vmRealloc is called for freeing, the old_size would be 0 since // deallocated bytes are traced by garbage collector. - vm->bytes_allocated += new_size - old_size; + // + // If we're running a garbage collection, VM's byte allocated will be + // re-calculated at vmCollectGarbage() which is equal to the remaining + // objects after clening the garbage. And clearing a garbage will + // recursively invoke this function so we shouldn't modify it. + if (!vm->collecting_garbage) { + vm->bytes_allocated += new_size - old_size; + } + + // If we're garbage collecting no new allocation is allowed. + ASSERT(!vm->collecting_garbage || new_size == 0, + "No new allocation is allowed while garbage collection is running."); if (new_size > 0 && vm->bytes_allocated > vm->next_gc) { + ASSERT(vm->collecting_garbage == false, OOPS); + vm->collecting_garbage = true; vmCollectGarbage(vm); + vm->collecting_garbage = false; } #if TRACE_MEMORY @@ -99,10 +113,6 @@ Module* vmGetModule(PKVM* vm, String* key) { void vmCollectGarbage(PKVM* vm) { - // Reset VM's bytes_allocated value and count it again so that we don't - // required to know the size of each object that'll be freeing. - vm->bytes_allocated = 0; - // Mark builtin functions. for (int i = 0; i < vm->builtins_count; i++) { markObject(vm, &vm->builtins_funcs[i]->_super); @@ -139,11 +149,23 @@ void vmCollectGarbage(PKVM* vm) { markObject(vm, &vm->fiber->_super); } + // Reset VM's bytes_allocated value and count it again so that we don't + // required to know the size of each object that'll be freeing. + vm->bytes_allocated = 0; + // Pop the marked objects from the working set and push all of it's // referenced objects. This will repeat till no more objects left in the // working set. popMarkedObjects(vm); + // Now [vm->bytes_allocated] is equal to the number of bytes allocated for + // the root objects which are marked above. Since we're garbage collecting + // freeObject() shouldn't modify vm->bytes_allocated. We ensure this by + // copying the value to [bytes_allocated] and check after freeing. +#ifdef DEBUG + size_t bytes_allocated = vm->bytes_allocated; +#endif + // Now sweep all the un-marked objects in then link list and remove them // from the chain. @@ -166,6 +188,10 @@ void vmCollectGarbage(PKVM* vm) { } } +#ifdef DEBUG + ASSERT(bytes_allocated = vm->bytes_allocated, OOPS); +#endif + // Next GC heap size will be change depends on the byte we've left with now, // and the [heap_fill_percent]. vm->next_gc = vm->bytes_allocated + ( @@ -937,7 +963,9 @@ L_vm_main_loop: ASSERT_INDEX(index, module->constants.count); ASSERT(IS_OBJ_TYPE(module->constants.data[index], OBJ_FUNC), OOPS); Function* fn = (Function*)AS_OBJ(module->constants.data[index]); + Closure* closure = newClosure(vm, fn); + vmPushTempRef(vm, &closure->_super); // closure. // Capture the vaupes. for (int i = 0; i < fn->upvalue_count; i++) { @@ -954,6 +982,8 @@ L_vm_main_loop: } PUSH(VAR_OBJ(closure)); + vmPopTempRef(vm); // closure. + DISPATCH(); } diff --git a/src/core/vm.h b/src/core/vm.h index bc29d87..605bc0e 100644 --- a/src/core/vm.h +++ b/src/core/vm.h @@ -69,6 +69,10 @@ struct PKVM { // The number of bytes that'll trigger the next GC. size_t next_gc; + // True if PKVM is running a garbage collection, and no new allocation is + // allowed in this phase. + bool collecting_garbage; + // Minimum size the heap could get. size_t min_heap_size; diff --git a/src/libs/ext_term.c b/src/libs/ext_term.c deleted file mode 100644 index 429b7c5..0000000 --- a/src/libs/ext_term.c +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (c) 2020-2022 Thakee Nathees - * Copyright (c) 2021-2022 Pocketlang Contributors - * Distributed Under The MIT License - */ - -#ifndef PK_AMALGAMATED -#include "libs.h" -#endif - -#define TERM_IMPLEMENT -#include "thirdparty/term/term.h" //<< AMALG_INLINE >> -#undef TERM_IMPLEMENT - -// A reference to the event class, to check is instance of. -static PkHandle* _cls_term_event = NULL; - -static void _setSlotVector(PKVM* vm, int slot, int tmp, double x, double y) { - - if (!pkImportModule(vm, "types", slot)) return; - if (!pkGetAttribute(vm, slot, "Vector", slot)) return; - if (!pkNewInstance(vm, slot, slot, 0, 0)) return; - - pkSetSlotNumber(vm, tmp, x); - if (!pkSetAttribute(vm, slot, "x", tmp)) return; - pkSetSlotNumber(vm, tmp, y); - if (!pkSetAttribute(vm, slot, "y", tmp)) return; -} - -void* _termEventNew(PKVM* vm) { - term_Event* event = pkRealloc(vm, NULL, sizeof(term_Event)); - event->type = TERM_ET_UNKNOWN; - return event; -} - -void _termEventDelete(PKVM* vm, void* event) { - pkRealloc(vm, event, 0); -} - -void _termEventGetter(PKVM* vm) { - const char* name; - if (!pkValidateSlotString(vm, 1, &name, NULL)) return; - - term_Event* event = pkGetSelf(vm); - - if (strcmp(name, "type") == 0) { - pkSetSlotNumber(vm, 0, (double)event->type); - - } else if (strcmp(name, "keycode") == 0) { - pkSetSlotNumber(vm, 0, (double)event->key.code); - - } else if (strcmp(name, "ascii") == 0) { - pkSetSlotNumber(vm, 0, (double)event->key.ascii); - - } else if (strcmp(name, "modifiers") == 0) { - if (event->type == TERM_ET_KEY_DOWN) { - pkSetSlotNumber(vm, 0, (double)event->key.modifiers); - } else { - pkSetSlotNumber(vm, 0, (double)event->mouse.modifiers); - } - - } else if (strcmp(name, "button") == 0) { - pkSetSlotNumber(vm, 0, (double)event->mouse.button); - - } else if (strcmp(name, "pos") == 0) { - pkReserveSlots(vm, 2); - _setSlotVector(vm, 0, 1, event->mouse.pos.x, event->mouse.pos.y); - - } else if (strcmp(name, "scroll") == 0) { - pkSetSlotBool(vm, 0, event->mouse.scroll); - } - -} - -void _registerEnums(PKVM* vm, PkHandle* term) { - pkReserveSlots(vm, 1); - pkSetSlotHandle(vm, 0, term); - - pkSetSlotNumber(vm, 1, TERM_KEY_UNKNOWN); pkSetAttribute(vm, 0, "KEY_UNKNOWN", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_0); pkSetAttribute(vm, 0, "KEY_0", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_1); pkSetAttribute(vm, 0, "KEY_1", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_2); pkSetAttribute(vm, 0, "KEY_2", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_3); pkSetAttribute(vm, 0, "KEY_3", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_4); pkSetAttribute(vm, 0, "KEY_4", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_5); pkSetAttribute(vm, 0, "KEY_5", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_6); pkSetAttribute(vm, 0, "KEY_6", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_7); pkSetAttribute(vm, 0, "KEY_7", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_8); pkSetAttribute(vm, 0, "KEY_8", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_9); pkSetAttribute(vm, 0, "KEY_9", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_A); pkSetAttribute(vm, 0, "KEY_A", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_B); pkSetAttribute(vm, 0, "KEY_B", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_C); pkSetAttribute(vm, 0, "KEY_C", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_D); pkSetAttribute(vm, 0, "KEY_D", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_E); pkSetAttribute(vm, 0, "KEY_E", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F); pkSetAttribute(vm, 0, "KEY_F", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_G); pkSetAttribute(vm, 0, "KEY_G", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_H); pkSetAttribute(vm, 0, "KEY_H", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_I); pkSetAttribute(vm, 0, "KEY_I", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_J); pkSetAttribute(vm, 0, "KEY_J", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_K); pkSetAttribute(vm, 0, "KEY_K", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_L); pkSetAttribute(vm, 0, "KEY_L", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_M); pkSetAttribute(vm, 0, "KEY_M", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_N); pkSetAttribute(vm, 0, "KEY_N", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_O); pkSetAttribute(vm, 0, "KEY_O", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_P); pkSetAttribute(vm, 0, "KEY_P", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_Q); pkSetAttribute(vm, 0, "KEY_Q", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_R); pkSetAttribute(vm, 0, "KEY_R", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_S); pkSetAttribute(vm, 0, "KEY_S", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_T); pkSetAttribute(vm, 0, "KEY_T", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_U); pkSetAttribute(vm, 0, "KEY_U", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_V); pkSetAttribute(vm, 0, "KEY_V", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_W); pkSetAttribute(vm, 0, "KEY_W", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_X); pkSetAttribute(vm, 0, "KEY_X", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_Y); pkSetAttribute(vm, 0, "KEY_Y", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_Z); pkSetAttribute(vm, 0, "KEY_Z", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_ESC); pkSetAttribute(vm, 0, "KEY_ESC", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_ENTER); pkSetAttribute(vm, 0, "KEY_ENTER", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_SPACE); pkSetAttribute(vm, 0, "KEY_SPACE", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_HOME); pkSetAttribute(vm, 0, "KEY_HOME", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_END); pkSetAttribute(vm, 0, "KEY_END", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_PAGEUP); pkSetAttribute(vm, 0, "KEY_PAGEUP", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_PAGEDOWN); pkSetAttribute(vm, 0, "KEY_PAGEDOWN", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_LEFT); pkSetAttribute(vm, 0, "KEY_LEFT", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_UP); pkSetAttribute(vm, 0, "KEY_UP", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_RIGHT); pkSetAttribute(vm, 0, "KEY_RIGHT", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_DOWN); pkSetAttribute(vm, 0, "KEY_DOWN", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_INSERT); pkSetAttribute(vm, 0, "KEY_INSERT", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_DELETE); pkSetAttribute(vm, 0, "KEY_DELETE", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_BACKSPACE); pkSetAttribute(vm, 0, "KEY_BACKSPACE", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_TAB); pkSetAttribute(vm, 0, "KEY_TAB", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F1); pkSetAttribute(vm, 0, "KEY_F1", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F2); pkSetAttribute(vm, 0, "KEY_F2", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F3); pkSetAttribute(vm, 0, "KEY_F3", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F4); pkSetAttribute(vm, 0, "KEY_F4", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F5); pkSetAttribute(vm, 0, "KEY_F5", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F6); pkSetAttribute(vm, 0, "KEY_F6", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F7); pkSetAttribute(vm, 0, "KEY_F7", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F8); pkSetAttribute(vm, 0, "KEY_F8", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F9); pkSetAttribute(vm, 0, "KEY_F9", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F10); pkSetAttribute(vm, 0, "KEY_F10", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F11); pkSetAttribute(vm, 0, "KEY_F11", 1); - pkSetSlotNumber(vm, 1, TERM_KEY_F12); pkSetAttribute(vm, 0, "KEY_F12", 1); - - pkSetSlotNumber(vm, 1, TERM_MB_UNKNOWN); pkSetAttribute(vm, 0, "BUTTON_UNKNOWN", 1); - pkSetSlotNumber(vm, 1, TERM_MB_LEFT); pkSetAttribute(vm, 0, "BUTTON_LEFT", 1); - pkSetSlotNumber(vm, 1, TERM_MB_MIDDLE); pkSetAttribute(vm, 0, "BUTTON_MIDDLE", 1); - pkSetSlotNumber(vm, 1, TERM_MB_RIGHT); pkSetAttribute(vm, 0, "BUTTON_RIGHT", 1); - - pkSetSlotNumber(vm, 1, TERM_MD_NONE); pkSetAttribute(vm, 0, "MD_NONE", 1); - pkSetSlotNumber(vm, 1, TERM_MD_CTRL); pkSetAttribute(vm, 0, "MD_CTRL", 1); - pkSetSlotNumber(vm, 1, TERM_MD_ALT); pkSetAttribute(vm, 0, "MD_ALT", 1); - pkSetSlotNumber(vm, 1, TERM_MD_SHIFT); pkSetAttribute(vm, 0, "MD_SHIFT", 1); - - pkSetSlotNumber(vm, 1, TERM_ET_UNKNOWN); pkSetAttribute(vm, 0, "EVENT_UNKNOWN", 1); - pkSetSlotNumber(vm, 1, TERM_ET_KEY_DOWN); pkSetAttribute(vm, 0, "EVENT_KEY_DOWN", 1); - pkSetSlotNumber(vm, 1, TERM_ET_RESIZE); pkSetAttribute(vm, 0, "EVENT_RESIZE", 1); - pkSetSlotNumber(vm, 1, TERM_ET_DOUBLE_CLICK); pkSetAttribute(vm, 0, "EVENT_DOUBLE_CLICK", 1); - pkSetSlotNumber(vm, 1, TERM_ET_MOUSE_DOWN); pkSetAttribute(vm, 0, "EVENT_MOUSE_DOWN", 1); - pkSetSlotNumber(vm, 1, TERM_ET_MOUSE_UP); pkSetAttribute(vm, 0, "EVENT_MOUSE_UP", 1); - pkSetSlotNumber(vm, 1, TERM_ET_MOUSE_MOVE); pkSetAttribute(vm, 0, "EVENT_MOUSE_MOVE", 1); - pkSetSlotNumber(vm, 1, TERM_ET_MOUSE_DRAG); pkSetAttribute(vm, 0, "EVENT_MOUSE_DRAG", 1); - pkSetSlotNumber(vm, 1, TERM_ET_MOUSE_SCROLL); pkSetAttribute(vm, 0, "EVENT_MOUSE_SCROLL", 1); - -} - - -void _termInit(PKVM* vm) { - bool capture_events; - if (!pkValidateSlotBool(vm, 1, &capture_events)) return; - term_init(capture_events); -} - -void _termCleanup(PKVM* vm) { - term_cleanup(); -} - -void _termIsatty(PKVM* vm) { - pkSetSlotBool(vm, 0, term_isatty()); -} - -void _termNewScreenBuffer(PKVM* vm) { - term_new_screen_buffer(); -} - -void _termRestoreScreenBuffer(PKVM* vm) { - term_restore_screen_buffer(); -} - -void _termGetSize(PKVM* vm) { - pkReserveSlots(vm, 2); - term_Vec size = term_getsize(); - _setSlotVector(vm, 0, 1, size.x, size.y); -} - -void _termGetPosition(PKVM* vm) { - pkReserveSlots(vm, 2); - term_Vec pos = term_getposition(); - _setSlotVector(vm, 0, 1, pos.x, pos.y); -} - -void _termSetPosition(PKVM* vm) { - double x, y; - - int argc = pkGetArgc(vm); - if (!pkCheckArgcRange(vm, argc, 1, 2)) return; - - if (argc == 1) { - pkReserveSlots(vm, 3); - if (!pkGetAttribute(vm, 1, "x", 2)) return; - if (!pkValidateSlotNumber(vm, 2, &x)) return; - - if (!pkGetAttribute(vm, 1, "y", 2)) return; - if (!pkValidateSlotNumber(vm, 2, &y)) return; - } else { - if (!pkValidateSlotNumber(vm, 1, &x)) return; - if (!pkValidateSlotNumber(vm, 2, &y)) return; - } - - term_Vec pos = term_vec((int)x, (int)y); - term_setposition(pos); -} - -void _termReadEvent(PKVM* vm) { - pkReserveSlots(vm, 3); - pkSetSlotHandle(vm, 2, _cls_term_event); - if (!pkValidateSlotInstanceOf(vm, 1, 2)) return; - - term_Event* event = pkGetSlotNativeInstance(vm, 1); - pkSetSlotBool(vm, 0, term_read_event(event)); -} - -/*****************************************************************************/ -/* MODULE REGISTER */ -/*****************************************************************************/ - -void registerModuleTerm(PKVM* vm) { - PkHandle* term = pkNewModule(vm, "term"); - - _registerEnums(vm, term); - pkModuleAddFunction(vm, term, "init", _termInit, 1); - pkModuleAddFunction(vm, term, "cleanup", _termCleanup, 0); - pkModuleAddFunction(vm, term, "isatty", _termIsatty, 0); - pkModuleAddFunction(vm, term, "new_screen_buffer", _termNewScreenBuffer, 0); - pkModuleAddFunction(vm, term, "restore_screen_buffer", _termRestoreScreenBuffer, 0); - pkModuleAddFunction(vm, term, "getsize", _termGetSize, 0); - pkModuleAddFunction(vm, term, "getposition", _termGetPosition, 0); - pkModuleAddFunction(vm, term, "setposition", _termSetPosition, -1); - pkModuleAddFunction(vm, term, "read_event", _termReadEvent, 1); - - _cls_term_event = pkNewClass(vm, "Event", NULL, term, _termEventNew, _termEventDelete); - pkClassAddMethod(vm, _cls_term_event, "@getter", _termEventGetter, 1); - - pkRegisterModule(vm, term); - pkReleaseHandle(vm, term); -} - -void cleanupModuleTerm(PKVM* vm) { - if (_cls_term_event) pkReleaseHandle(vm, _cls_term_event); -} diff --git a/src/libs/libs.c b/src/libs/libs.c index 5a1fd68..b22501f 100644 --- a/src/libs/libs.c +++ b/src/libs/libs.c @@ -17,11 +17,6 @@ void registerModuleIO(PKVM* vm); void registerModulePath(PKVM* vm); void registerModuleDummy(PKVM* vm); -// Additional libraries. - -void registerModuleTerm(PKVM* vm); -void cleanupModuleTerm(PKVM* vm); - // Registers the modules. void registerLibs(PKVM* vm) { registerModuleMath(vm); @@ -30,11 +25,8 @@ void registerLibs(PKVM* vm) { registerModuleIO(vm); registerModulePath(vm); registerModuleDummy(vm); - - registerModuleTerm(vm); } // Cleanup the modules. void cleanupLibs(PKVM* vm) { - cleanupModuleTerm(vm); } diff --git a/src/libs/thirdparty/term/LICENSE b/src/libs/thirdparty/term/LICENSE deleted file mode 100644 index 5ac7fe5..0000000 --- a/src/libs/thirdparty/term/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Thakee Nathees - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. diff --git a/src/libs/thirdparty/term/term.h b/src/libs/thirdparty/term/term.h deleted file mode 100644 index 6ad90c3..0000000 --- a/src/libs/thirdparty/term/term.h +++ /dev/null @@ -1,1027 +0,0 @@ -/*****************************************************************************/ -/* PUBLIC API */ -/*****************************************************************************/ - -/* - * Note that at this point *nix stdout are buffered and windows aren't. - * - * *nix systems doesn't support double click at this point, but it's in my - * TODO. Contributions are wellcome. - * - * Window resize events can be enabled by setting NO_WINDOW_RESIZE_EVENT - * macro value to 1 and recompile, however you have to provide a event - * callback for resize events in *nix systems. - * - */ - -#include - -/* - * A generic Vector type to pass size, position data around. - */ -typedef struct { - int x; - int y; -} term_Vec; - -/* A macro function to create a vector. */ -#define term_vec(x, y) (term_Vec) { (x), (y) } - - -/* - * List of keycodes. Note that not all possible keys are listed - * here however they can be retrieved by from the key event's - * ascii value. - */ -typedef enum { - - TERM_KEY_UNKNOWN = 0, - - TERM_KEY_0 = '0', - TERM_KEY_1 = '1', - TERM_KEY_2 = '2', - TERM_KEY_3 = '3', - TERM_KEY_4 = '4', - TERM_KEY_5 = '5', - TERM_KEY_6 = '6', - TERM_KEY_7 = '7', - TERM_KEY_8 = '8', - TERM_KEY_9 = '9', - - TERM_KEY_A = 'A', - TERM_KEY_B = 'B', - TERM_KEY_C = 'C', - TERM_KEY_D = 'D', - TERM_KEY_E = 'E', - TERM_KEY_F = 'F', - TERM_KEY_G = 'G', - TERM_KEY_H = 'H', - TERM_KEY_I = 'I', - TERM_KEY_J = 'J', - TERM_KEY_K = 'K', - TERM_KEY_L = 'L', - TERM_KEY_M = 'M', - TERM_KEY_N = 'N', - TERM_KEY_O = 'O', - TERM_KEY_P = 'P', - TERM_KEY_Q = 'Q', - TERM_KEY_R = 'R', - TERM_KEY_S = 'S', - TERM_KEY_T = 'T', - TERM_KEY_U = 'U', - TERM_KEY_V = 'V', - TERM_KEY_W = 'W', - TERM_KEY_X = 'X', - TERM_KEY_Y = 'Y', - TERM_KEY_Z = 'Z', - - TERM_KEY_ESC, - TERM_KEY_ENTER, - TERM_KEY_SPACE, - TERM_KEY_HOME, - TERM_KEY_END, - TERM_KEY_PAGEUP, - TERM_KEY_PAGEDOWN, - TERM_KEY_LEFT, - TERM_KEY_UP, - TERM_KEY_RIGHT, - TERM_KEY_DOWN, - TERM_KEY_INSERT, - TERM_KEY_DELETE, - TERM_KEY_BACKSPACE, - TERM_KEY_TAB, - - TERM_KEY_F1, - TERM_KEY_F2, - TERM_KEY_F3, - TERM_KEY_F4, - TERM_KEY_F5, - TERM_KEY_F6, - TERM_KEY_F7, - TERM_KEY_F8, - TERM_KEY_F9, - TERM_KEY_F10, - TERM_KEY_F11, - TERM_KEY_F12, - -} term_KeyCode; - - -/* - * Event type. - */ -typedef enum { - TERM_ET_UNKNOWN = 0, - TERM_ET_KEY_DOWN, - TERM_ET_DOUBLE_CLICK, - TERM_ET_MOUSE_DOWN, - TERM_ET_MOUSE_UP, - TERM_ET_MOUSE_MOVE, - TERM_ET_MOUSE_DRAG, - TERM_ET_MOUSE_SCROLL, - #if ! NO_WINDOW_RESIZE_EVENT - TERM_ET_RESIZE, - #endif -} term_EventType; - - -/* - * Key event modifier flags, that contain the ctrl, alt, shift key state. - */ -typedef enum { - TERM_MD_NONE = 0x0, - TERM_MD_CTRL = (1 << 1), - TERM_MD_ALT = (1 << 2), - TERM_MD_SHIFT = (1 << 3), -} term_Modifiers; - - -/* - * Mouse button event's button index. - */ -typedef enum { - TERM_MB_UNKNOWN = 0, - TERM_MB_LEFT = 1, - TERM_MB_MIDDLE = 2, - TERM_MB_RIGHT = 3, -} term_MouseBtn; - - -/* - * Key event. - */ -typedef struct { - term_KeyCode code; - char ascii; - term_Modifiers modifiers; -} term_EventKey; - - -/* - * Mouse event. - */ -typedef struct { - term_MouseBtn button; - term_Vec pos; - bool scroll; /* If true down otherwise up. */ - term_Modifiers modifiers; -} term_EventMouse; - -/* - * Event. - */ -typedef struct { - term_EventType type; - union { - term_EventKey key; - term_EventMouse mouse; -#if ! NO_WINDOW_RESIZE_EVENT - term_Vec resize; -#endif - }; -} term_Event; - - -/* Returns true if both stdin and stdout are tty like device. */ -bool term_isatty(); - - -/* - * Initialize the terminal. On windows it'll enable the virtual terminal - * processing, *nix it'll enter non-canonical mode, and won't echo the - * characters that are inputted. - * - * @param capture_events: If true it'll enable input events processing. - */ -void term_init(bool capture_events); - - -/* Cleans up all the internals. */ -void term_cleanup(void); - - -/* - * Reads an event. You should initialize terminal with capture_events - * to enable reading events. - * - * @param event: an event pointer that'll updated after reading an event. - * - * @return If an event has been read it'll return true. - */ -bool term_read_event(term_Event* event); - - -/* Create an alternative screen buffer. */ -void term_new_screen_buffer(); - - -/* - * Restore the screen buffer after switching to an alternative buffer with - * term_new_screen_buffer(). This will also clean entier screen and place - * the cursor at (0, 0). - */ -void term_restore_screen_buffer(); - - -/* Returns the screen size. */ -term_Vec term_getsize(); - - -/* Returns the cursor position in a zero based index coordinate. */ -term_Vec term_getposition(); - - -/* Sets the cursor position in a zero based index coordinate. */ -void term_setposition(term_Vec pos); - - -/*****************************************************************************/ -/* INTERNAL HEADERS AND MACROS */ -/*****************************************************************************/ - -#ifdef TERM_IMPLEMENT - -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 - #define TERM_SYS_WIN -#else - #define TERM_SYS_NIX -#endif - -/* Platform specific includes */ -#if defined(TERM_SYS_WIN) - #include -#elif defined(TERM_SYS_NIX) - #include - #include - #include -#endif - -#if defined(_MSC_VER) || (defined(TERM_SYS_WIN) && defined(__TINYC__)) - #include - #define read _read - #define fileno _fileno - #define isatty _isatty -#else - #include -#endif - -/* - * On *nix window resize are signals, not events so we cannot use readEvent() - * function to read. However if you want to handle them define this as 0 and - * add a callback to read resize events. On windows nothing has to be done. - */ -#define NO_WINDOW_RESIZE_EVENT 1 - -/* - * In older version of windows some terminal attributes are not defined - * So I'm defining everyting here if they're not already. - * - * Note that I have no idea which macros are not defined and not, I'm just - * re-defining every this looks suspicious. - */ -#ifdef TERM_SYS_WIN - - #ifndef ENABLE_VIRTUALINAL_PROCESSING - #define ENABLE_VIRTUALINAL_PROCESSING 0x0004 - #endif - - #ifndef ENABLE_EXTENDED_FLAGS - #define ENABLE_EXTENDED_FLAGS 0x0080 - #endif - - #ifndef FROM_LEFT_1ST_BUTTON_PRESSED - #define FROM_LEFT_1ST_BUTTON_PRESSED 0x0001 - #endif - - #ifndef RIGHTMOST_BUTTON_PRESSED - #define RIGHTMOST_BUTTON_PRESSED 0x0002 - #endif - - #ifndef FROM_LEFT_2ND_BUTTON_PRESSED - #define FROM_LEFT_2ND_BUTTON_PRESSED 0x0004 - #endif - - #ifndef MOUSE_MOVED - #define MOUSE_MOVED 0x0001 - #endif - - #ifndef DOUBLE_CLICK - #define DOUBLE_CLICK 0x0002 - #endif - - #ifndef MOUSE_WHEELED - #define MOUSE_WHEELED 0x0004 - #endif - - #ifndef LEFT_ALT_PRESSED - #define LEFT_ALT_PRESSED 0x0002 - #endif - - #ifndef RIGHT_ALT_PRESSED - #define RIGHT_ALT_PRESSED 0x0001 - #endif - - #ifndef RIGHT_CTRL_PRESSED - #define RIGHT_CTRL_PRESSED 0x0004 - #endif - - #ifndef LEFT_CTRL_PRESSED - #define LEFT_CTRL_PRESSED 0x0008 - #endif - - #ifndef SHIFT_PRESSED - #define SHIFT_PRESSED 0x0010 - #endif - -#endif /* TERM_SYS_WIN */ - -/* Input read buffer size. */ -#define INPUT_BUFF_SZ 256 - - -/* Returns predicate (a <= c <= b). */ -#define BETWEEN(a, c, b) ((a) <= (c) && (c) <= (b)) - -/*****************************************************************************/ -/* TYPEDEFINES AND DECLARATIONS */ -/*****************************************************************************/ - - -#define _veceq(v1, v2) (((v1.x) == (v2).x) && ((v1).y == (v2).y)) - -typedef struct { - -#if defined(TERM_SYS_WIN) - DWORD outmode, inmode; /* Backup modes. */ - HANDLE h_stdout, h_stdin; /* Handles. */ - -#elif defined(TERM_SYS_NIX) - struct termios tios; /* Backup modes. */ - uint8_t buff[INPUT_BUFF_SZ]; /* Input buffer. */ - int32_t buffc; /* Buffer element count. */ -#endif - - term_Vec screensize; - term_Vec mousepos; - - bool capture_events; - bool initialized; - -} term_Ctx; - - -static term_Ctx _ctx; - -static void _init(); -static void _cleanup(); -static term_Vec _getsize(); - -#ifdef TERM_SYS_NIX -static void _handle_resize(int sig); -#endif - -static bool _read_event(term_Event* event); - - -/*****************************************************************************/ -/* IMPLEMENTATIONS */ -/*****************************************************************************/ - - -bool term_isatty() { - return (!!isatty(fileno(stdout))) && (!!isatty(fileno(stdin))); -} - - -void term_init(bool capture_events) { - memset(&_ctx, 0, sizeof(term_Ctx)); - _ctx.capture_events = capture_events; - - _init(); - - _ctx.screensize = _getsize(); - _ctx.initialized = true; - atexit(term_cleanup); -} - - -void term_cleanup(void) { - assert(_ctx.initialized); - _cleanup(); -} - - -bool term_read_event(term_Event* event) { - return _read_event(event); -} - - -#if defined(TERM_SYS_WIN) -static void _init() { - _ctx.h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - GetConsoleMode(_ctx.h_stdout, &_ctx.outmode); - - _ctx.h_stdin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(_ctx.h_stdin, &_ctx.inmode); - - DWORD outmode = (_ctx.outmode | ENABLE_VIRTUALINAL_PROCESSING); - SetConsoleMode(_ctx.h_stdout, outmode); - - if (_ctx.capture_events) { - DWORD inmode = ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT; - SetConsoleMode(_ctx.h_stdin, inmode); - } -} - - -static void _cleanup() { - SetConsoleMode(_ctx.h_stdout, _ctx.outmode); - SetConsoleMode(_ctx.h_stdin, _ctx.inmode); -} - - -#elif defined(TERM_SYS_NIX) -static void _init() { - tcgetattr(fileno(stdin), &_ctx.tios); - - struct termios raw = _ctx.tios; - - /* - * ECHO : It won't print character as we type. - * ICANON : Disable canonical mode, inputs will - * be byte by byte instead of line by line. - * ISIG : Disable Ctrl+C, Ctrl+Z - * IXON : Disable Ctrl+S, Ctrl+Q - * IEXTEN : Disable Ctrl+V - * ICRNL : Fix Ctrl+M. It won't convert '\r' into '\n' anymore. - * OPOST : It won't convert '\n' into '\r\n' anymore. - * BRKINT : Disable break condition that'll send a SIGINT. - */ - raw.c_lflag &= ~(ECHO | ICANON); - if (_ctx.capture_events) { - /*raw.c_oflag &= ~(OPOST);*/ - raw.c_iflag &= ~(IXON | ICRNL | BRKINT); - raw.c_lflag &= ~(ISIG | IEXTEN); - } - - /* - * VMIN : Minimum number of bytes should be read before return from read(). - * VTIME : Maximum amount of time to be wait before read() returns. - * 1 unit is 100 of a second (ie. vtime = n => 1/100 s) - * - * However on windows running WSL, VTIME won't work, it'll wait till an - * input is read. - */ - raw.c_cc[VMIN] = 0; - raw.c_cc[VTIME] = 1; - - tcsetattr(fileno(stdin), TCSAFLUSH, &raw); - - /* Enable mouse events. */ - if (_ctx.capture_events) { - fprintf(stdout, "\x1b[?1003h\x1b[?1006h"); - fflush(stdout); - } - - /* Handle resize events. */ - signal(SIGWINCH, _handle_resize); - -} - - -static void _cleanup() { - - /* Disable mouse events. */ - if (_ctx.capture_events) { - fprintf(stdout, "\x1b[?1003l\x1b[?1006l\x1b[?25h"); - } - - tcsetattr(fileno(stdin), TCSAFLUSH, &_ctx.tios); -} - - -void _handle_resize(int sig) { - term_Vec newsize = _getsize(); - if (!_veceq(_ctx.screensize, newsize)) { - return; - } - _ctx.screensize = newsize; - -#if !NO_WINDOW_RESIZE_EVENT - term_Event event; - memset(&event, 0, sizeof(term_Event)); - event.type = TERM_ET_RESIZE; - event.resize = _ctx.screensize; - - #error "Dispatch the event with a callback or something." - your_callback(&event); -#endif /* NO_WINDOW_RESIZE_EVENT */ -} - -#endif /* TERM_SYS_NIX */ - - -void term_new_screen_buffer() { - fprintf(stdout, "\x1b[?1049h"); -} - - -void term_restore_screen_buffer() { - fprintf(stdout, "\x1b[H\x1b[J"); /* Clear screen and go to (0, 0). */ - fprintf(stdout, "\x1b[?1049l"); -} - - -term_Vec term_getposition() { - term_Vec pos; - - #if defined(TERM_SYS_WIN) - CONSOLE_SCREEN_BUFFER_INFO binfo; - GetConsoleScreenBufferInfo(_ctx.h_stdout, &binfo); - pos.x = binfo.dwCursorPosition.X; - pos.y = binfo.dwCursorPosition.Y; - return pos; - - #elif defined(TERM_SYS_NIX) - - struct termios tio; - if (tcgetattr(fileno(stdin), &tio) != 0) { - assert(false && "tcgetattr(stdin) failed."); - } - - tcsetattr(fileno(stdin), TCSANOW, &tio); - assert(((tio.c_lflag & (ICANON | ECHO)) == 0) - && "Did you forget to call term_init()"); - - - /* - * Request cursor position. stdin will be in the for of ESC[n;mR - * here where n is the row and m is the column. (1 based). - */ - fprintf(stdout, "\x1b[6n"); - - if (getchar() != '\x1b' || getchar() != '[') { - assert(false && "getchar() failed in getposition()"); - } - - pos.x = 0; pos.y = 0; - int* p = &pos.y; - - while (true) { - int c = getchar(); - - if (c == EOF) { - assert(false && "getchar() failed in getposition()"); - } - - if (c == ';') p = &pos.x; - if (c == 'R') break; - - - if (BETWEEN('0', c, '9')) { - *p = *p * 10 + (c - '0'); - } - } - - /* Since column, row numbers are 1 based substract 1 for 0 based. */ - pos.x--; pos.y--; - - #endif /* TERM_SYS_NIX */ - - return pos; -} - - -void term_setposition(term_Vec pos) { - fprintf(stdout, "\x1b[%i;%iH", pos.y + 1, pos.x + 1); -} - - -static term_Vec _getsize() { - term_Vec size; - -#if defined(TERM_SYS_WIN) - CONSOLE_SCREEN_BUFFER_INFO binfo; - GetConsoleScreenBufferInfo(_ctx.h_stdout, &binfo); - size.x = binfo.srWindow.Right - binfo.srWindow.Left + 1; - size.y = binfo.srWindow.Bottom - binfo.srWindow.Top + 1; - -#elif defined(TERM_SYS_NIX) - struct winsize wsize; - ioctl(fileno(stdout), TIOCGWINSZ, &wsize); - size.x = wsize.ws_col; - size.y = wsize.ws_row; -#endif - - return size; -} - - -term_Vec term_getsize() { - return _ctx.screensize; -} - - -/*****************************************************************************/ -/* INPUT PROCESSING */ -/*****************************************************************************/ - -#if defined(TERM_SYS_WIN) - - -/* - * Convert windows virtual keys to term_KeyCodes. Returns false if the key - * should be ignored as an event (ex: shift, ctrl, ...). - */ -static bool _toTermKeyCode(WORD vk, term_KeyCode * kc) { - - if (0x30 <= vk && vk <= 0x39) { *kc = (term_KeyCode)TERM_KEY_0 + (vk - 0x30); return true; } - if (0x60 <= vk && vk <= 0x69) { *kc = (term_KeyCode)TERM_KEY_0 + (vk - 0x60); return true; } - if (0x41 <= vk && vk <= 0x5a) { *kc = (term_KeyCode)TERM_KEY_A + (vk - 0x41); return true; } - if (0x70 <= vk && vk <= 0x7b) { *kc = (term_KeyCode)TERM_KEY_F1 + (vk - 0x70); return true; } - - /* - * Note that shift, ctrl, alt keys are returned as TERM_KEY_UNKNOWN - *since *nix systems don't support them. - */ - switch (vk) { - case VK_BACK: *kc = TERM_KEY_BACKSPACE; break; - case VK_TAB: *kc = TERM_KEY_TAB; break; - case VK_RETURN: *kc = TERM_KEY_ENTER; break; - case VK_ESCAPE: *kc = TERM_KEY_ESC; break; - case VK_SPACE: *kc = TERM_KEY_SPACE; break; - case VK_PRIOR: *kc = TERM_KEY_PAGEUP; break; - case VK_NEXT: *kc = TERM_KEY_PAGEDOWN; break; - case VK_END: *kc = TERM_KEY_END; break; - case VK_HOME: *kc = TERM_KEY_HOME; break; - case VK_LEFT: *kc = TERM_KEY_LEFT; break; - case VK_RIGHT: *kc = TERM_KEY_RIGHT; break; - case VK_UP: *kc = TERM_KEY_UP; break; - case VK_DOWN: *kc = TERM_KEY_DOWN; break; - case VK_INSERT: *kc = TERM_KEY_INSERT; break; - case VK_DELETE: *kc = TERM_KEY_DELETE; break; - - case VK_SHIFT: - case VK_CONTROL: - case VK_MENU: - case VK_PAUSE: - case VK_CAPITAL: /* Capslock. */ - return false; - } - - return true; -} - - -static bool _read_event(term_Event* event) { - memset(event, 0, sizeof(term_Event)); - event->type = TERM_ET_UNKNOWN; - - DWORD count; - if (!GetNumberOfConsoleInputEvents(_ctx.h_stdin, &count)) { - /* TODO: error handle api ("GetNumberOfConsoleInputEvents() failed."). */ - return false; - } - - if (count == 0) return false; - - INPUT_RECORD ir; - if (!ReadConsoleInput(_ctx.h_stdin, &ir, 1, &count)) { - /* TODO: error handle api ("ReadConsoleInput() failed."). */ - return false; - } - - switch (ir.EventType) { - case KEY_EVENT: { - KEY_EVENT_RECORD* ker = &ir.Event.KeyEvent; - - /* Key up event not available in *nix systems. So we're ignoring here as well. */ - if (!ker->bKeyDown) return false; - - if (!_toTermKeyCode(ker->wVirtualKeyCode, &event->key.code)) return false; - - event->type = TERM_ET_KEY_DOWN; - event->key.ascii = ker->uChar.AsciiChar; - - if ((ker->dwControlKeyState & LEFT_ALT_PRESSED) || (ker->dwControlKeyState & RIGHT_ALT_PRESSED)) - event->key.modifiers |= TERM_MD_ALT; - if ((ker->dwControlKeyState & LEFT_CTRL_PRESSED) || (ker->dwControlKeyState & RIGHT_CTRL_PRESSED)) - event->key.modifiers |= TERM_MD_CTRL; - if (ker->dwControlKeyState & SHIFT_PRESSED) - event->key.modifiers |= TERM_MD_SHIFT; - - } break; - - case MOUSE_EVENT: { - - MOUSE_EVENT_RECORD* mer = &ir.Event.MouseEvent; - - static DWORD last_state = 0; /* Last state to compare if a new button pressed. */ - bool pressed = mer->dwButtonState; /* If any state is on pressed will be true. */ - - /* Xor will give != 0 if any button changed. */ - DWORD change = last_state ^ mer->dwButtonState; - - if (change != 0) { - /* - * What if the mouse doesn't have the middle button and right - * button is the second button?. - */ - if (change & FROM_LEFT_1ST_BUTTON_PRESSED) { - pressed = mer->dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED; - event->mouse.button = TERM_MB_LEFT; - - } else if (change & RIGHTMOST_BUTTON_PRESSED) { - pressed = mer->dwButtonState & RIGHTMOST_BUTTON_PRESSED; - event->mouse.button = TERM_MB_RIGHT; - - } else if (change & FROM_LEFT_2ND_BUTTON_PRESSED) { - pressed = mer->dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED; - event->mouse.button = TERM_MB_MIDDLE; - } - } - last_state = mer->dwButtonState; - - event->mouse.pos.x = mer->dwMousePosition.X; - event->mouse.pos.y = mer->dwMousePosition.Y; - - if (mer->dwEventFlags == 0) { - event->type = (pressed) ? TERM_ET_MOUSE_DOWN : TERM_ET_MOUSE_UP; - - } else if (mer->dwEventFlags & MOUSE_MOVED) { - if (_veceq(_ctx.mousepos, event->mouse.pos)) { - return false; - } - event->type = (pressed) ? TERM_ET_MOUSE_DRAG : TERM_ET_MOUSE_MOVE; - - } else if (mer->dwEventFlags & MOUSE_WHEELED) { - event->type = TERM_ET_MOUSE_SCROLL; - event->mouse.scroll = (mer->dwButtonState & 0xFF000000) ? true : false; - - } else if (mer->dwEventFlags & DOUBLE_CLICK) { - event->type = TERM_ET_DOUBLE_CLICK; - } - - _ctx.mousepos.x = event->mouse.pos.x; - _ctx.mousepos.y = event->mouse.pos.y; - - if ((mer->dwControlKeyState & LEFT_ALT_PRESSED) || (mer->dwControlKeyState & RIGHT_ALT_PRESSED)) - event->mouse.modifiers |= TERM_MD_ALT; - if ((mer->dwControlKeyState & LEFT_CTRL_PRESSED) || (mer->dwControlKeyState & RIGHT_CTRL_PRESSED)) - event->mouse.modifiers |= TERM_MD_CTRL; - if (mer->dwControlKeyState & SHIFT_PRESSED) - event->mouse.modifiers |= TERM_MD_SHIFT; - - } break; - - case WINDOW_BUFFER_SIZE_EVENT: { - #if ! NO_WINDOW_RESIZE_EVENT - WINDOW_BUFFER_SIZE_RECORD* wbs = &ir.Event.WindowBufferSizeEvent; - event->type = TERM_ET_RESIZE; - term_Vec newsize = term_vec(wbs->dwSize.X, wbs->dwSize.Y); - if (_ctx.screensize.x == newsize.x && _ctx.screensize.y == newsize.y) return false; - _ctx.screensize = newsize; - event->resize = _ctx.screensize; - #else - return false; - #endif - } break; - - /* Not handling as it's not available in *nix. */ - case MENU_EVENT: - case FOCUS_EVENT: - return false; - } - - return event->type != TERM_ET_UNKNOWN; -} - -#elif defined(TERM_SYS_NIX) - -/* Returns the length of an escape sequence. */ -static int _escape_length(const char* buff, uint32_t size) { - int length = 0; - - while (length < size) { - char c = buff[length++]; - - if (BETWEEN('a', c, 'z') || BETWEEN('A', c, 'Z') || (c == '~')) { - if (c == 'O' && length < size) { - c = buff[length]; - if (BETWEEN('A', c, 'D') || BETWEEN('P', c, 'S') || c == 'F' || c == 'H') { - return length + 1; - } - } - - return length; - - } else if (c == '\x1b') { - return length; - } - } - - return length; -} - - -static void _key_event(char c, term_Event* event) { - event->type = TERM_ET_KEY_DOWN; - event->key.ascii = c; - - /* Note: Ctrl+M and both reads as '\r'. */ - if (c == '\r') { event->key.code = TERM_KEY_ENTER; return; } - if (c == 127) { event->key.code = TERM_KEY_BACKSPACE; return; } - if (c == 9) { event->key.code = TERM_KEY_TAB; return; } - if (c == 32) { event->key.code = TERM_KEY_SPACE; return; } - - event->key.code = (term_KeyCode) c; - - /* Ctrl + key */ - if (1 <= c && c <= 26) { - event->key.modifiers |= TERM_MD_CTRL; - event->key.code = (term_KeyCode)('A' + (c - 1)); - - } else if (BETWEEN('a', c, 'z') || BETWEEN('A', c, 'Z') || BETWEEN('0', c, '9')) { - event->key.code = (term_KeyCode) toupper(c); - if (BETWEEN('A', c, 'Z')) event->key.modifiers |= TERM_MD_SHIFT; - } -} - - -/* Reference: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking */ -static void _mouse_event(const char* buff, uint32_t count, term_Event * event) { - - /* buff = cb ; cx ; cy m|M */ - - const char* c = buff; - if (!(BETWEEN('0', *c, '9'))) return; - - int cb = 0, cx = 0, cy = 0; - char m; - - while (BETWEEN('0', *c, '9')) cb = cb * 10 + (*c++ - '0'); - if (*c++ != ';') return; - while (BETWEEN('0', *c, '9')) cx = cx * 10 + (*c++ - '0'); - if (*c++ != ';') return; - while (BETWEEN('0', *c, '9')) cy = cy * 10 + (*c++ - '0'); - - m = *c++; - if (m != 'm' && m != 'M') return; - - /* - * low two bits = button information. - * next three bits = modifiers. - * next bits = event type. - */ - int low = cb & 0b11; - int high = (cb & 0b11100) >> 2; - int type = cb >> 5; - - /* Note that the modifiers won't work/incorrect in WSL. */ - if (high & 0b001) event->mouse.modifiers |= TERM_MD_SHIFT; - if (high & 0b010) event->mouse.modifiers |= TERM_MD_ALT; - if (high & 0b100) event->mouse.modifiers |= TERM_MD_CTRL; - - event->mouse.pos.x = cx - 1; - event->mouse.pos.y = cy - 1; - - switch (type) { - case 0: { - event->type = (m == 'M') ? TERM_ET_MOUSE_DOWN : TERM_ET_MOUSE_UP; - event->mouse.button = (term_MouseBtn)(low + 1); - } break; - - case 1: { - if (low == 0b11) { /* Mouse move. */ - event->type = TERM_ET_MOUSE_MOVE; - - } else { /* Drag. */ - event->type = TERM_ET_MOUSE_DRAG; - event->mouse.button = (term_MouseBtn)(low + 1); - } - } break; - - case 2: { /* Scroll. */ - event->type = TERM_ET_MOUSE_SCROLL; - - if (low == 0) event->mouse.scroll = false; - else if (low == 1) event->mouse.scroll = true; - - } break; - } -} - - -void _parse_escape_sequence(const char* buff, uint32_t count, term_Event* event) { - assert(buff[0] == '\x1b'); - - if (count == 1) { - event->type = TERM_ET_KEY_DOWN; - event->key.ascii = *buff; - event->key.code = TERM_KEY_ESC; - return; - } - - if (count == 2) { - _key_event(buff[1], event); - event->key.modifiers |= TERM_MD_ALT; - return; - } - - - #define _SET_KEY(keycode) \ - do { \ - event->type = TERM_ET_KEY_DOWN; \ - event->key.code = keycode; \ - } while (false) - - #define _MATCH(str) \ - ((count >= strlen(str) + 1) && (strncmp(buff + 1, (str), strlen(str)) == 0)) - - if (_MATCH("[<")) _mouse_event(buff + 3, count - 3, event); - - else if (_MATCH("[A") || _MATCH("OA")) _SET_KEY(TERM_KEY_UP); - else if (_MATCH("[B") || _MATCH("OB")) _SET_KEY(TERM_KEY_DOWN); - else if (_MATCH("[C") || _MATCH("OC")) _SET_KEY(TERM_KEY_RIGHT); - else if (_MATCH("[D") || _MATCH("OD")) _SET_KEY(TERM_KEY_LEFT); - else if (_MATCH("[5~") || _MATCH("[[5~")) _SET_KEY(TERM_KEY_PAGEUP); - else if (_MATCH("[6~") || _MATCH("[[6~")) _SET_KEY(TERM_KEY_PAGEDOWN); - - else if (_MATCH("[H") || _MATCH("OH") || _MATCH("[1~") || _MATCH("[[7~")) _SET_KEY(TERM_KEY_HOME); - else if (_MATCH("[F") || _MATCH("OF") || _MATCH("[4~") || _MATCH("[[8~")) _SET_KEY(TERM_KEY_END); - - else if (_MATCH("[2~")) _SET_KEY(TERM_KEY_INSERT); - else if (_MATCH("[3~")) _SET_KEY(TERM_KEY_DELETE); - - else if (_MATCH("OP") || _MATCH("[11~")) _SET_KEY(TERM_KEY_F1); - else if (_MATCH("OQ") || _MATCH("[12~")) _SET_KEY(TERM_KEY_F2); - else if (_MATCH("OR") || _MATCH("[13~")) _SET_KEY(TERM_KEY_F3); - else if (_MATCH("OS") || _MATCH("[14~")) _SET_KEY(TERM_KEY_F4); - - else if (_MATCH("[15~")) _SET_KEY(TERM_KEY_F5); - else if (_MATCH("[17~")) _SET_KEY(TERM_KEY_F6); - else if (_MATCH("[18~")) _SET_KEY(TERM_KEY_F7); - else if (_MATCH("[19~")) _SET_KEY(TERM_KEY_F8); - else if (_MATCH("[20~")) _SET_KEY(TERM_KEY_F9); - else if (_MATCH("[21~")) _SET_KEY(TERM_KEY_F10); - else if (_MATCH("[23~")) _SET_KEY(TERM_KEY_F11); - else if (_MATCH("[24~")) _SET_KEY(TERM_KEY_F12); - - #undef _MATCH - #undef _SET_KEY -} - - -static void _buff_shift(uint32_t length) { - assert(_ctx.buff != NULL); - if (length < _ctx.buffc) { - memmove(_ctx.buff, _ctx.buff + length, _ctx.buffc - length); - _ctx.buffc -= length; - } else { - _ctx.buffc = 0; - } -} - - -static bool _read_event(term_Event* event) { - - memset(event, 0, sizeof(term_Event)); - event->type = TERM_ET_UNKNOWN; - - int count = read(fileno(stdin), _ctx.buff + _ctx.buffc, INPUT_BUFF_SZ - _ctx.buffc); - if (count == 0) return false; - - _ctx.buffc += count; - - int event_length = 1; /* Num of character for the event in the buffer. */ - - if (*_ctx.buff == '\x1b') { - event_length = _escape_length(_ctx.buff + 1, _ctx.buffc - 1) + 1; - _parse_escape_sequence(_ctx.buff, event_length, event); - if (event->type == TERM_ET_MOUSE_MOVE) { - if (_veceq(_ctx.mousepos, event->mouse.pos)) { - _buff_shift(event_length); - return false; - } - _ctx.mousepos = event->mouse.pos; - } - - } else { - _key_event(_ctx.buff[0], event); - } - - _buff_shift(event_length); - - return event->type != TERM_ET_UNKNOWN; -} - -#endif /* TERM_SYS_NIX */ - -#endif /* TERM_IMPLEMENT */