commit 18255ce4cd7b2ffc019e35dff8c5639a4286ff86 Author: PwLDev <64767383+PwLDev@users.noreply.github.com> Date: Tue Apr 28 13:11:03 2026 -0700 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7d2d4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Node +node_modules/ + +# Milsko +deps/ +*.tar.gz + +# Build +build/ +dist/ +prebuild/ + +# IDE +.vscode + +# macOS +.DS_Store \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..02d76a3 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "none", + "semi": true, + "singleQuote": false, + "tabWidth": 4, + "useTabs": false +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cbed6da --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2026, Pyrite development team +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README-es.md b/README-es.md new file mode 100644 index 0000000..f53ca67 --- /dev/null +++ b/README-es.md @@ -0,0 +1,21 @@ +

+ Milsko for Node.js logo +

+ +> Crea GUIs de escritorio nativas y multi-plataforma con Node.js. + +[English](README.md) • [Español](README-es.md) • [GitHub](https://github.com/pyrite-dev/milsko-node.git) • [Fishy Forgejo](https://forgejo.nishi.boats/pyrite-dev/milsko-node.git) • [Website](https://milsko.nishi.boats) + +Milsko es un conjunto de herramientas para GUIs ligero y multiplataforma diseñado para ser totalmente retrocompatible con sistemas operativos más antiguos, a la vez siendo relativamente moderno. + +Este paquete contiene enlaces orientados a objectos para crear y empaquetar aplicaciones GUI nativas utilizando Node.js. + +## Instalación + +Usa el administrador de paquetes de tu preferencia (también es compatible con Bun). Contiene binarios precompilados por defecto. + +```sh +npm install milsko +``` + +Si no hay binarios precompilados disponibles para tu plataforma o te gustaría compilarlo desde fuente, puedes usar `npm install milsko --build-from-source` o equivalente. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2507257 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +

+ Milsko for Node.js logo +

+ +> Build native cross-platform desktop GUIs with Node.js. + +[English](README.md) • [Español](README-es.md) • [GitHub](https://github.com/pyrite-dev/milsko-node.git) • [Fishy Forgejo](https://forgejo.nishi.boats/pyrite-dev/milsko-node.git) • [Website](https://milsko.nishi.boats) + +Milsko is a lightweight cross-platform GUI toolkit designed to be fully compatible with older OSes while also being reasonably modern. + +This package provides OOP-oriented bindings to create and package fully native GUI appliations with Node.js. + +## Installation +Use your preferred package manager (also compatible with Bun). Prebuilt binaries are included by default. + +```sh +npm install milsko +``` + +If none precompiled binaries are available for your platform or if you'd like to build from source, you can use `npm install milsko --build-from-source` or equivalent. diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..c23034a --- /dev/null +++ b/binding.gyp @@ -0,0 +1,79 @@ +{ + "targets": [ + { + "target_name": "milsko", + "sources": ["src/milsko.cpp"], + "include_dirs": ["= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + + "isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], + + "lru-cache": ["lru-cache@11.3.3", "", {}, "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ=="], + + "make-fetch-happen": ["make-fetch-happen@15.0.5", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg=="], + + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@5.0.2", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "optionalDependencies": { "iconv-lite": "^0.7.2" } }, "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@2.0.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="], + + "node-addon-api": ["node-addon-api@8.7.0", "", {}, "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA=="], + + "node-gyp": ["node-gyp@12.2.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^15.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "which": "^6.0.0" }, "bin": "bin/node-gyp.js" }, "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + + "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": "bin/nopt.js" }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], + + "npm-run-path": ["npm-run-path@3.1.0", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "prebuildify": ["prebuildify@6.0.1", "", { "dependencies": { "minimist": "^1.2.5", "mkdirp-classic": "^0.5.3", "node-abi": "^3.3.0", "npm-run-path": "^3.1.0", "pump": "^3.0.0", "tar-fs": "^2.1.0" }, "bin": "bin.js" }, "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw=="], + + "prettier": ["prettier@3.8.2", "", { "bin": "bin/prettier.cjs" }, "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q=="], + + "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "rimraf": ["rimraf@6.1.3", "", { "dependencies": { "glob": "^13.0.3", "package-json-from-dist": "^1.0.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="], + + "tar": ["tar@7.5.13", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], + + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + } +} diff --git a/lib/constants.ts b/lib/constants.ts new file mode 100644 index 0000000..5995f1f --- /dev/null +++ b/lib/constants.ts @@ -0,0 +1,10 @@ +export const MwDEFAULT: number = 268435455; +export const MwMB_ICONMASK: number = 15; +export const MwMB_BUTTONMASK: number = 240; +export const MwMB_BUTTONOK: number = 16; +export const MwMB_BUTTONCANCEL: number = 32; +export const MwMB_BUTTONYES: number = 64; +export const MwMB_BUTTONNO: number = 128; +export const MwKEY_FLAG: number = 2147483648; +export const MwKEY_CONTROL_FLAG: number = 3221225472; +export const MwKEY_ALT_FLAG: number = 2684354560; diff --git a/lib/enums.ts b/lib/enums.ts new file mode 100644 index 0000000..4001f47 --- /dev/null +++ b/lib/enums.ts @@ -0,0 +1,71 @@ +export enum MwDIRECTION { + MwNORTH, + MwSOUTH, + MwEAST, + MwWEST +} + +export enum MwORIENTATION { + MwVERTICAL, + MwHORIZONTAL +} + +export enum MwALIGNMENT { + MwALIGNMENT_CENTER, + MwALIGNMENT_BEGINNING, + MwALIGNMENT_END +} + +export enum MwMB_ICON { + MwMB_ICONWARNING = 1, + MwMB_ICONINFO, + MwMB_ICONNOTE, + MwMB_ICONQUESTION, + MwMB_ICONNEWS, + MwMB_ICONERROR, + MwMB_ICONCLOCK +} + +export enum MwCOORDINATE { + MwCOORDINATE_GLOBAL, + MwCOORDINATE_LOCAL +} + +export enum MwCLIPBOARD { + MwCLIPBOARD_MAIN, + MwCLIPBOARD_PRIMARY +} + +export enum MwMOUSE { + MwMOUSE_LEFT = 1, + MwMOUSE_MIDDLE, + MwMOUSE_RIGHT, + MwMOUSE_WHEELUP, + MwMOUSE_WHEELDOWN +} + +export enum MwKEY { + MwKEY_BACKSPACE = 2147483649, + MwKEY_LEFT, + MwKEY_RIGHT, + MwKEY_UP, + MwKEY_DOWN, + MwKEY_ENTER, + MwKEY_ESCAPE, + MwKEY_LEFTSHIFT, + MwKEY_RIGHTSHIFT, + MwKEY_ALT, + MwKEY_CONTROL +} + +export enum MwVULKANFIELD { + MwVULKANFIELD_GETINSTANCEPROCADDR, + MwVULKANFIELD_INSTANCE, + MwVULKANFIELD_SURFACE, + MwVULKANFIELD_PHYSICALDEVICE, + MwVULKANFIELD_LOGICALDEVICE, + MwVULKANFIELD_GRAPHICSQUEUEINDEX, + MwVULKANFIELD_PRESENTQUEUEINDEX, + MwVULKANFIELD_GRAPHICSQUEUE, + MwVULKANFIELD_PRESENTQUEUE +} diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/lib/structs.ts b/lib/structs.ts new file mode 100644 index 0000000..5affa0a --- /dev/null +++ b/lib/structs.ts @@ -0,0 +1,32 @@ +export interface MwPoint { + x: number; + y: number; +} + +export interface MwRect { + x: number; + y: number; + width: number; + height: number; +} + +export interface MwSizeHints { + minWidth: number; + minHeight: number; + maxWidth: number; + maxHeight: number; +} + +export interface MwRGB { + red: number; + green: number; + blue: number; +} + +export interface MwMouse {} + +export interface MwVulkanConfig { + apiVersion: number; + vkVersion: number; + validationLayers: number; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ec83c8f --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "milsko", + "version": "1.0.1", + "description": "Lightweight and cross-platform native GUI toolkit", + "main": "lib/index.js", + "type": "module", + "upstream": "v1.0a", + "scripts": { + "bindgen": "node scripts/bindgen.js && prettier --write lib/", + "download": "node scripts/download.js", + "install": "node scripts/install.js", + "update": "rimraf deps/milsko.tar.gz deps/milsko/ && node scripts/download.js" + }, + "repository": { + "type": "git", + "url": "git+https://forgejo.nishi.boats/pyrite-dev/milsko-node.git" + }, + "keywords": [ + "milsko", + "ui", + "gui", + "desktop", + "app" + ], + "author": "PwLDev", + "license": "BSD-3-Clause", + "bugs": { + "url": "https://forgejo.nishi.boats/pyrite-dev/milsko-node/issues" + }, + "homepage": "https://forgejo.nishi.boats/pyrite-dev/milsko-node#readme", + "engines": { + "node": ">= 18" + }, + "devDependencies": { + "@types/node": "^25.6.0", + "fast-xml-parser": "^5.6.0", + "node-gyp": "^12.2.0", + "prebuildify": "^6.0.1", + "prettier": "^3.8.2", + "rimraf": "^6.1.3", + "typescript": "^6.0.2" + }, + "dependencies": { + "node-addon-api": "^8.7.0", + "node-gyp-build": "^4.8.4", + "tar": "^7.5.13" + }, + "files": [ + "README.md", + "package.json", + "binding.gyp", + "dist", + "scripts", + "prebuilds" + ] +} diff --git a/scripts/bindgen.js b/scripts/bindgen.js new file mode 100644 index 0000000..b45f444 --- /dev/null +++ b/scripts/bindgen.js @@ -0,0 +1,163 @@ +import { XMLParser } from "fast-xml-parser"; +import { createWriteStream } from "node:fs"; +import { createRequire } from "node:module"; + +const { upstream } = createRequire(import.meta.url)("../package.json"); +const source = + "https://forgejo.nishi.boats/api/v1/repos/pyrite-dev/milsko/tags"; + +// Out files +const structsOut = createWriteStream("lib/structs.ts"); +const enumsOut = createWriteStream("lib/enums.ts"); +const constantsOut = createWriteStream("lib/constants.ts"); + +function toCamelCase(input) { + const split = input.split("_"); + for (let i = 0; i < split.length; i++) { + const firstChar = + i <= 0 + ? split[i].charAt(0).toLowerCase() + : split[i].charAt(0).toUpperCase(); + + split[i] = firstChar + split[i].slice(1); + } + + return split.join(""); +} + +function typeToTypescript(type) { + switch (type) { + case "integer": + return "number"; + case "short": + return "number"; + case "string": + return "string"; + default: + "any"; + } +} + +function scanStructs(root) { + const list = root["structs"]["struct"]; + + for (const struct of list) { + console.log(struct); + const structName = struct["@_name"]; + structsOut.write("export interface " + structName + " {\n"); + + for (const [type, children] of Object.entries(struct)) { + // attributes start with @_ + if (!type.startsWith("@_") && Array.isArray(children)) { + for (const child of children) { + const attributeName = toCamelCase(child["@_name"]); + structsOut.write( + attributeName + ": " + typeToTypescript(type) + ";\n" + ); + } + } + } + + structsOut.write("}\n\n"); + } + + structsOut.end(); +} + +function scanEnumerations(root) { + const list = root["enumerations"]["enumeration"]; + + for (const enumeration of list) { + const enumName = enumeration["@_name"]; + enumsOut.write("export enum " + enumName + " {\n"); + + for (const [type, children] of Object.entries(enumeration)) { + // attributes start with @_ + if (!type.startsWith("@_")) { + for (const child of children) { + const attributeName = child["@_name"]; + const attributeValue = child["#text"]; + + enumsOut.write(attributeName); + if (attributeValue) enumsOut.write(" = " + attributeValue); + + enumsOut.write(",\n"); + } + } + } + + enumsOut.write("}\n\n"); + } + + enumsOut.end(); +} + +function scanConstants(root) { + const list = root["constants"]; + + for (const [type, children] of Object.entries(list)) { + for (const child of children) { + const constName = child["@_name"]; + const constValue = child["#text"]; + + constantsOut.write( + "export const " + + constName + + ": " + + typeToTypescript(type) + + " = " + + constValue + + ";\n" + ); + } + } + + constantsOut.end(); +} + +async function bindgen() { + const parser = new XMLParser({ + attributeNamePrefix: "@_", + ignoreAttributes: false, + parseAttributeValue: true + }); + + const response = await fetch(source); + if (!response.ok) { + throw new Error( + "Failed to fetch the Milsko repository tags, try running npm run install or npm run update again (" + + response.status + + ")" + ); + } + + const tags = await response.json(); + const match = tags.find((k) => k.name == upstream); + + if (!match) { + throw new Error( + "Could not find a tag that matches the upstream version " + upstream + ); + } + + const commitHash = match["id"] + const specURL = "https://forgejo.nishi.boats/pyrite-dev/milsko/raw/commit/" + + commitHash + "/milsko.xml"; + + const spac = await fetch(specURL); + if (!spac.ok) { + throw new Error( + "Failed to fetch the XML API specification with status code " + + spac.status + ); + } + + const raw = await spac.text(); + const root = parser.parse(raw)["milsko"]; + + scanStructs(root); + scanEnumerations(root); + scanConstants(root); +} + +bindgen(); diff --git a/scripts/download.js b/scripts/download.js new file mode 100644 index 0000000..20eedc2 --- /dev/null +++ b/scripts/download.js @@ -0,0 +1,59 @@ +import { execSync } from "node:child_process"; +import { createWriteStream, mkdirSync } from "node:fs"; +import { createRequire } from "node:module"; +import { join, resolve } from "node:path"; +import { Readable } from "node:stream"; +import { pipeline } from "node:stream/promises"; +import { t, extract as tarExtract } from "tar"; + +const extractDir = resolve("deps"); +const fileName = "milsko.tar.gz"; +const source = + "https://forgejo.nishi.boats/api/v1/repos/pyrite-dev/milsko/tags"; + +const require = createRequire(import.meta.url); +const { upstream } = require("../package.json"); + +export async function download() { + console.log("[Mw] Fetching Milsko release tags from", source); + + const response = await fetch(source); + if (!response.ok) { + throw new Error( + "Failed to fetch the Milsko repository tags, try running npm run install or npm run update again (" + + response.status + + ")" + ); + } + + const tags = await response.json(); + const match = tags.find((k) => k.name == upstream); + + if (!match) { + throw new Error( + "Could not find a tag that matches the upstream version " + upstream + ); + } + + const tarUrl = match["tarball_url"]; + console.log("[Mw] Fetching tarball from " + tarUrl); + + const tarball = await fetch(tarUrl); + + const filePath = join(extractDir, fileName); + mkdirSync(extractDir, { recursive: true }); // mkdir -p + + const tarStream = Readable.fromWeb(tarball.body); + const fileStream = createWriteStream(filePath); + + await pipeline(tarStream, fileStream); + await tarExtract({ + file: filePath, + cwd: extractDir + }); + + console.log("[Mw] Milsko library successfully installed!"); +} + +if (import.meta.main) + download(); \ No newline at end of file diff --git a/scripts/install.js b/scripts/install.js new file mode 100644 index 0000000..c85fd38 --- /dev/null +++ b/scripts/install.js @@ -0,0 +1,31 @@ +import { execSync } from "node:child_process" +import { createRequire } from "node:module"; +import { download } from "./download"; + +const require = createRequire(import.meta.url); +const forceBuild = process.env["npm_config_build_from_source"] == "true"; + +function hasPrebuilt() { + let prebuilt = false; + + try { + require("node-gyp-build")(".."); + prebuilt = true; + } catch (error) { + prebuilt = false; + } + + return prebuilt; +} + +async function install() { + console.log("[Mw] Downloading Milsko library..."); + await download(); + + console.log("[Mw] Building from source..."); + execSync("npx node-gyp rebuild", { stdio: "inherit" }); +} + +if (forceBuild || !hasPrebuilt()) { + install(); +} \ No newline at end of file diff --git a/src/classes.cpp b/src/classes.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/classes.hpp b/src/classes.hpp new file mode 100644 index 0000000..e899c83 --- /dev/null +++ b/src/classes.hpp @@ -0,0 +1,4 @@ +#pragma once +#include + +Napi::Object ClassesInit(Napi::Env env, Napi::Object exports); diff --git a/src/milsko.cpp b/src/milsko.cpp new file mode 100644 index 0000000..59b2577 --- /dev/null +++ b/src/milsko.cpp @@ -0,0 +1,13 @@ +#include "widget.hpp" +#include +#include + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + MwLibraryInit(); + + MwBaseWidget::Init(env, exports); + + return exports; +} + +NODE_API_MODULE(milsko, Init); \ No newline at end of file diff --git a/src/widget.cpp b/src/widget.cpp new file mode 100644 index 0000000..e802c30 --- /dev/null +++ b/src/widget.cpp @@ -0,0 +1,368 @@ +#include "widget.hpp" + +Napi::FunctionReference MwBaseWidget::constructor; + +Napi::Object MwBaseWidget::Init(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + Napi::Function func = DefineClass(env, "MwBaseWidget", + { + InstanceMethod("setArea", &SetArea), + InstanceMethod("move", &Move), + InstanceMethod("resize", &Resize), + InstanceMethod("step", &Step), + InstanceMethod("loop", &Loop), + InstanceMethod("show", &Show), + InstanceMethod("hideCursor", &HideCursor), + InstanceMethod("grabPointer", &GrabPointer), + InstanceMethod("getClipboard", &GetClipboard), + InstanceMethod("addTickList", &AddTickList), + InstanceMethod("forceRender", &ForceRender), + InstanceMethod("reparent", &Reparent), + InstanceMethod("add", &Add), + InstanceMethod("remove", &Remove), + InstanceAccessor("area", &GetArea, nullptr), + InstanceAccessor("isPending", &IsPending, nullptr), + InstanceAccessor("parent", &GetParent, nullptr), + InstanceAccessor("children", &GetChildren, nullptr), + InstanceAccessor("name", &GetName, nullptr), + InstanceAccessor("cursorCoord", &GetCursorCoord, nullptr), + InstanceAccessor("screenSize", &GetScreenSize, nullptr), + InstanceAccessor("coordinateType", &GetCoordinateType, nullptr), + }); + + constructor = Napi::Persistent(func); + constructor.SuppressDestruct(); + + exports.Set("MwBaseWidget", func); + return exports; +}; + +MwBaseWidget::MwBaseWidget(const Napi::CallbackInfo &info): +Napi::ObjectWrap(info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + + handle = MwCreateWidget(MwWindowClass, "window", nullptr, 0, 0, 300, 300); +}; + +MwBaseWidget::~MwBaseWidget() { + // call destructors for each children + if (children.size() > 0) { + for (const auto &child : children) { + delete child; + } + } + + if (handle) + MwDestroyWidget(handle); +}; + +Napi::Value MwBaseWidget::GetArea(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + Napi::Object area = Napi::Object::New(env); + + area.Set("x", Napi::Number::New(env, MwGetInteger(handle, MwNx))); + area.Set("y", Napi::Number::New(env, MwGetInteger(handle, MwNy))); + area.Set("width", Napi::Number::New(env, MwGetInteger(handle, MwNwidth))); + area.Set("height", Napi::Number::New(env, MwGetInteger(handle, MwNheight))); + + return area; +} + +Napi::Value MwBaseWidget::SetArea(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() > 0 && info[0].IsObject()) { + const Napi::Object rect = info[0].ToObject(); + + MwVaApply(handle, + MwNx, rect.Has("x") ? rect.Get("x").As().Uint32Value() : 0, + MwNy, rect.Has("y") ? rect.Get("y").As().Uint32Value() : 0, + MwNwidth, rect.Has("width") ? rect.Get("width").As().Uint32Value() : 0, + MwNheight, rect.Has("height") ? rect.Get("height").As().Uint32Value() : 0, + nullptr + ); + } else { + Napi::TypeError::New(env, "Expected an object type of MwRect as argument") + .ThrowAsJavaScriptException(); + } + + return info.This(); +} + +Napi::Value MwBaseWidget::Move(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() > 1 && info[0].IsNumber() && info[1].IsNumber()) { + int x = info[0].As().Uint32Value(); + int y = info[1].As().Uint32Value(); + + MwVaApply(handle, + MwNx, x, + MwNy, y, + nullptr + ); + } else { + Napi::TypeError::New(env, "Expected two number arguments (x, y)") + .ThrowAsJavaScriptException(); + } + + return Napi::Value(); +} + +Napi::Value MwBaseWidget::Resize(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() > 1 && info[0].IsNumber() && info[1].IsNumber()) { + int width = info[0].As().Uint32Value(); + int height = info[1].As().Uint32Value(); + + MwVaApply(handle, + MwNwidth, width, + MwNheight, height, + nullptr + ); + } else { + Napi::TypeError::New(env, "Expected two number arguments (width, height)") + .ThrowAsJavaScriptException(); + } + + return Napi::Value(); +} + +Napi::Value MwBaseWidget::Step(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + int step = MwStep(handle); + + return Napi::Boolean::New(env, step == 0); +} + +Napi::Value MwBaseWidget::IsPending(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + bool pending = MwPending(handle); + + return Napi::Boolean::New(env, pending); +} + +Napi::Value MwBaseWidget::Loop(const Napi::CallbackInfo &info) { + MwLoop(handle); + return Napi::Value(); +} + +Napi::Value MwBaseWidget::Show(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + bool toggle = true; + + if (info.Length() > 0 && info[0].IsBoolean()) { + toggle = info[0].As().Value(); + } + + MwShow(handle, static_cast(toggle)); + return Napi::Value(); +} + +Napi::Value MwBaseWidget::HideCursor(const Napi::CallbackInfo &info) { + MwHideCursor(handle); + return Napi::Value(); +} + +Napi::Value MwBaseWidget::GrabPointer(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + bool toggle = true; + + if (info.Length() > 0 && info[0].IsBoolean()) { + toggle = info[0].As().Value(); + } + + MwGrabPointer(handle, static_cast(toggle)); + return Napi::Value(); +} + +Napi::Value MwBaseWidget::AddTickList(const Napi::CallbackInfo &info) { + MwAddTickList(handle); + return Napi::Value(); +} + +Napi::Value MwBaseWidget::ForceRender(const Napi::CallbackInfo &info) { + MwForceRender(handle); + return Napi::Value(); +} + +Napi::Value MwBaseWidget::Reparent(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() < 1 || !info[0].IsObject() || !info[0].IsNull()) { + Napi::TypeError::New(env, "Expected an instance of a widget class as argument") + .ThrowAsJavaScriptException(); + return env.Null(); + } + + MwBaseWidget *newParent = nullptr; + + if (info[0].IsObject()) { + Napi::Object wrapped = info[0].As(); + const bool isInstance = wrapped.InstanceOf(constructor.Value()); + + if (!isInstance) { + Napi::TypeError::New(env, "Provided argument is not an instance of a widget") + .ThrowAsJavaScriptException(); + return env.Null(); + } + + newParent = MwBaseWidget::Unwrap(wrapped); + } + + if (parent) { + // if the current parent has this widget as a child, disown :( + for (size_t i = 0; i < parent->children.size(); i++) { + if (parent->children[i] == this) { + parent->children.erase(parent->children.begin() + i); + break; + } + } + } + + MwReparent( + handle, + newParent ? newParent->handle : nullptr + ); + + parent = newParent; + newParent->children.push_back(this); + + return Napi::Value(); +} + +Napi::Value MwBaseWidget::Add(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() < 1 || !info[0].IsObject()) { + Napi::TypeError::New(env, "Expected an instance of a widget class as argument") + .ThrowAsJavaScriptException(); + return env.Undefined(); + } + + Napi::Object wrapped = info[0].As(); + const bool isInstance = wrapped.InstanceOf(constructor.Value()); + + if (isInstance) { + MwBaseWidget *child = MwBaseWidget::Unwrap(wrapped); + + if (child == this) { + Napi::Error::New(env, "Cannot add widget as child of itself") + .ThrowAsJavaScriptException(); + return env.Null(); + } + + if (child->parent) { + // if the current parent has this widget as a child, disown :( + for (int i = 0; i < child->parent->children.size(); i++) { + if (child->parent->children[i] == this) { + child->parent->children.erase(child->parent->children.begin() + i); + break; + } + } + } + + MwReparent( + child->handle, + handle + ); + + child->parent = this; + children.push_back(child); + } else { + Napi::TypeError::New(env, "Provided argument is not an instance of a widget") + .ThrowAsJavaScriptException(); + return env.Null(); + } + + return Napi::Value(); +} + +Napi::Value MwBaseWidget::Remove(const Napi::CallbackInfo &info) { + if (parent) { + for (size_t i = 0; i < parent->children.size(); i++) { + if (parent->children[i] == this) { + parent->children.erase(parent->children.begin() + i); + break; + } + } + } + + MwReparent(handle, nullptr); + return Napi::Value(); +} + +Napi::Value MwBaseWidget::GetParent(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (!parent) + return env.Null(); + + return parent->Value(); +} + +Napi::Value MwBaseWidget::GetChildren(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + Napi::Array result = Napi::Array::New(env, children.size()); + + for (size_t i = 0; i < children.size(); i++) { + result.Set(i, children[i]->Value()); + } + + return result; +} + +Napi::Value MwBaseWidget::GetName(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + const std::string name = MwGetName(handle); + + return Napi::String::New(env, name); +} + +Napi::Value MwBaseWidget::GetCursorCoord(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + Napi::Object coord = Napi::Object::New(env); + MwPoint point; + + MwGetCursorCoord(handle, &point); + coord.Set("x", Napi::Number::New(env, point.x)); + coord.Set("y", Napi::Number::New(env, point.y)); + + return coord; +} + +Napi::Value MwBaseWidget::GetScreenSize(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + Napi::Object size = Napi::Object::New(env); + MwRect rect; + + MwGetScreenSize(handle, &rect); + size.Set("x", Napi::Number::New(env, rect.x)); + size.Set("y", Napi::Number::New(env, rect.y)); + size.Set("width", Napi::Number::New(env, rect.width)); + size.Set("height", Napi::Number::New(env, rect.height)); + + return size; +} + +Napi::Value MwBaseWidget::GetCoordinateType(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + int coordType = MwGetCoordinateType(handle); + + return Napi::Number::New(env, coordType); +} + +Napi::Value MwBaseWidget::GetClipboard(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() > 0 && info[0].IsNumber()) { + int type = info[0].As().Uint32Value(); + MwGetClipboard(handle, type); + } else { + Napi::TypeError::New(env, "Expected a clipboard type number argument.") + .ThrowAsJavaScriptException(); + } + + return env.Undefined(); +} diff --git a/src/widget.hpp b/src/widget.hpp new file mode 100644 index 0000000..cc47ebf --- /dev/null +++ b/src/widget.hpp @@ -0,0 +1,42 @@ +#pragma once +#include +#include +#include + +class MwBaseWidget : public Napi::ObjectWrap { +public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + MwBaseWidget(const Napi::CallbackInfo &info); + ~MwBaseWidget(); + +protected: + static Napi::FunctionReference constructor; + + MwWidget handle; + MwBaseWidget *parent; + std::vector children; + + Napi::Value SetArea(const Napi::CallbackInfo &info); + Napi::Value Move(const Napi::CallbackInfo &info); + Napi::Value Resize(const Napi::CallbackInfo &info); + Napi::Value Step(const Napi::CallbackInfo &info); + Napi::Value Loop(const Napi::CallbackInfo &info); + Napi::Value Show(const Napi::CallbackInfo &info); + Napi::Value HideCursor(const Napi::CallbackInfo &info); + Napi::Value GrabPointer(const Napi::CallbackInfo &info); + Napi::Value AddTickList(const Napi::CallbackInfo &info); + Napi::Value ForceRender(const Napi::CallbackInfo &info); + Napi::Value Reparent(const Napi::CallbackInfo &info); + Napi::Value Add(const Napi::CallbackInfo &info); + Napi::Value Remove(const Napi::CallbackInfo &info); + Napi::Value GetClipboard(const Napi::CallbackInfo &info); + + Napi::Value GetArea(const Napi::CallbackInfo &info); + Napi::Value IsPending(const Napi::CallbackInfo &info); + Napi::Value GetParent(const Napi::CallbackInfo &info); + Napi::Value GetChildren(const Napi::CallbackInfo &info); + Napi::Value GetName(const Napi::CallbackInfo &info); + Napi::Value GetCursorCoord(const Napi::CallbackInfo &info); + Napi::Value GetScreenSize(const Napi::CallbackInfo &info); + Napi::Value GetCoordinateType(const Napi::CallbackInfo &info); +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8b49187 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "rootDir": "./lib", + "outDir": "./dist", + "module": "es2020", + "target": "esnext", + "types": ["node"], + "allowJs": true, + "declaration": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noImplicitReturns": true, + "strict": true, + "verbatimModuleSyntax": true, + "noUncheckedSideEffectImports": true, + "skipLibCheck": true + }, + "exclude": ["scripts"] +}