initial commit

This commit is contained in:
PwLDev 2026-04-28 13:11:03 -07:00
commit 18255ce4cd
21 changed files with 1244 additions and 0 deletions

17
.gitignore vendored Normal file
View file

@ -0,0 +1,17 @@
# Node
node_modules/
# Milsko
deps/
*.tar.gz
# Build
build/
dist/
prebuild/
# IDE
.vscode
# macOS
.DS_Store

7
.prettierrc Normal file
View file

@ -0,0 +1,7 @@
{
"trailingComma": "none",
"semi": true,
"singleQuote": false,
"tabWidth": 4,
"useTabs": false
}

24
LICENSE Normal file
View file

@ -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 <organization> 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 <COPYRIGHT HOLDER> 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.

21
README-es.md Normal file
View file

@ -0,0 +1,21 @@
<h1 align="center" title="Milsko for Node.js">
<img src="https://forgejo.nishi.boats/pyrite-dev/milsko/raw/branch/master/resource/logo/banner-nodejs.png" alt="Milsko for Node.js logo">
</h1>
> 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.

20
README.md Normal file
View file

@ -0,0 +1,20 @@
<h1 align="center" title="Milsko for Node.js">
<img src="https://forgejo.nishi.boats/pyrite-dev/milsko/raw/branch/master/resource/logo/banner-nodejs.png" alt="Milsko for Node.js logo">
</h1>
> 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.

79
binding.gyp Normal file
View file

@ -0,0 +1,79 @@
{
"targets": [
{
"target_name": "milsko",
"sources": ["src/milsko.cpp"],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"dependencies": [
"libMw",
"<!(node -p \"require('node-addon-api').gyp\")",
],
"cflags!": ["-fno-exceptions"],
"cflags_cc!": ["-fno-exceptions"],
"xcode_settings": {
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
"CLANG_CXX_LIBRARY": "libc++",
"MACOSX_DEPLOYMENT_TARGET": "10.4"
}
},
{
"target_name": "libMw",
"type": "shared_library",
"sources": [
"deps/milsko/src/*.c",
"deps/milsko/src/cursor/*.c",
"deps/milsko/src/icon/*.c",
"deps/milsko/src/widget/*.c",
"deps/milsko/src/text/*.c",
"deps/milsko/src/text/font/*.c",
"deps/milsko/src/dialog/*.c",
"deps/milsko/src/abstract/*.c"
"deps/milsko/external/*.c",
"deps/milsko/external/libz/src/*.c",
"deps/milsko/external/stb_image.c",
"deps/milsko/external/stb_truetype.c",
],
"include_dirs": [
"deps/milsko/include"
"deps/milsko/external"
"deps/milsko/external/libz/include"
],
"defines": [
"USE_STB_IMAGE",
"USE_STB_TRUETYPE"
],
"conditions": [
# [
# "OS=='mac'",
# {
# "link_settings": {
# "ldflags": ["-framework Cocoa -lobjc"]
# },
# "sources": [
# "deps/milsko/src/backend/nococoa.m"
# ],
# "defines": ["USE_COCOA"]
# }
# ],
[
"OS=='win'",
{
"link_settings": {
"ldflags": ["-static-libgcc"]
},
"sources": [
"deps/milsko/src/backend/gdi.c"
],
"defines": ["USE_GDI"]
},
{
"sources": [
"deps/milsko/src/backend/x11.c",
],
"defines": ["USE_X11"]
}
],
]
}
]
}

208
bun.lock Normal file
View file

@ -0,0 +1,208 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "milsko",
"dependencies": {
"node-addon-api": "^8.7.0",
"node-gyp-build": "^4.8.4",
"tar": "^7.5.13",
},
"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",
},
},
},
"packages": {
"@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
"@nodable/entities": ["@nodable/entities@1.1.0", "", {}, "sha512-bidpxmTBP0pOsxULw6XlxzQpTgrAGLDHGBK/JuWhPDL6ZV0GZ/PmN9CA9do6e+A9lYI6qx6ikJUtJYRxup141g=="],
"@npmcli/agent": ["@npmcli/agent@4.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA=="],
"@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="],
"@npmcli/redact": ["@npmcli/redact@4.0.0", "", {}, "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="],
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
"brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
"exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
"fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
"fast-xml-parser": ["fast-xml-parser@5.6.0", "", { "dependencies": { "@nodable/entities": "^1.1.0", "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-5G+uaEBbOm9M4dgMOV3K/rBzfUNGqGqoUTaYJM3hBwM8t71w07gxLQZoTsjkY8FtfjabqgQHEkeIySBDYeBmJw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
"fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="],
"glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 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=="],
}
}

10
lib/constants.ts Normal file
View file

@ -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;

71
lib/enums.ts Normal file
View file

@ -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
}

0
lib/index.ts Normal file
View file

32
lib/structs.ts Normal file
View file

@ -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;
}

56
package.json Normal file
View file

@ -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"
]
}

163
scripts/bindgen.js Normal file
View file

@ -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();

59
scripts/download.js Normal file
View file

@ -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();

31
scripts/install.js Normal file
View file

@ -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();
}

0
src/classes.cpp Normal file
View file

4
src/classes.hpp Normal file
View file

@ -0,0 +1,4 @@
#pragma once
#include <napi.h>
Napi::Object ClassesInit(Napi::Env env, Napi::Object exports);

13
src/milsko.cpp Normal file
View file

@ -0,0 +1,13 @@
#include "widget.hpp"
#include <Mw/Milsko.h>
#include <napi.h>
Napi::Object Init(Napi::Env env, Napi::Object exports) {
MwLibraryInit();
MwBaseWidget::Init(env, exports);
return exports;
}
NODE_API_MODULE(milsko, Init);

368
src/widget.cpp Normal file
View file

@ -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<MwBaseWidget>(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<Napi::Number>().Uint32Value() : 0,
MwNy, rect.Has("y") ? rect.Get("y").As<Napi::Number>().Uint32Value() : 0,
MwNwidth, rect.Has("width") ? rect.Get("width").As<Napi::Number>().Uint32Value() : 0,
MwNheight, rect.Has("height") ? rect.Get("height").As<Napi::Number>().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<Napi::Number>().Uint32Value();
int y = info[1].As<Napi::Number>().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<Napi::Number>().Uint32Value();
int height = info[1].As<Napi::Number>().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<Napi::Boolean>().Value();
}
MwShow(handle, static_cast<int>(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<Napi::Boolean>().Value();
}
MwGrabPointer(handle, static_cast<int>(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<Napi::Object>();
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<Napi::Object>();
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<Napi::Number>().Uint32Value();
MwGetClipboard(handle, type);
} else {
Napi::TypeError::New(env, "Expected a clipboard type number argument.")
.ThrowAsJavaScriptException();
}
return env.Undefined();
}

42
src/widget.hpp Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include <Mw/Milsko.h>
#include <napi.h>
#include <vector>
class MwBaseWidget : public Napi::ObjectWrap<MwBaseWidget> {
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<MwBaseWidget *> 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);
};

19
tsconfig.json Normal file
View file

@ -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"]
}