commit 8cf674c9e51fc32e7a757a7a9a5261efbe0513a8 Author: LGram16 Date: Tue Dec 9 20:35:38 2025 +0900 Refactor: Rename NanoKVM to BatchuKVM and update server URL diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2fe22c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +*.log +pnpm-debug.log* + +web/node_modules +web/dist +web/web + +.DS_Store +*.local +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.idea +.vscode + +NanoKVM-Server + +support/sg2002/kvm_system/build +support/sg2002/kvm_system/dist +support/sg2002/kvm_system/CMakeLists.txt + +support/sg2002/kvm_vision_test/build +support/sg2002/kvm_vision_test/dist +support/sg2002/kvm_vision_test/CMakeLists.txt + +support/sg2002/additional/original +kvmapp/server/dl_lib +kvmapp/kvm_system/kvm_system diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6250493 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,212 @@ +## 2.3.0 [0e30a26](https://github.com/sipeed/NanoKVM/commit/0e30a26db42cbc08416662b49a88a5d16ad93424) (2025-11-26) + +### Features + +* Added an EDID editor in the terminal with a built-in 1080P EDID template +* Added German virtual keyboard and German clipboard support (thanks to [@Alexander-Ger-Reich](https://github.com/Alexander-Ger-Reich)) +* Resetting the HID also resets the USB hardware +* Added support for deleting ISO images + +### Refactoring & Improvements + +* Video (H.264 WebRTC): Refactored the module to significantly reduce video latency +* Video (H.264 Direct): Refactored to optimize data transmission and support data parsing even when the page is in the background +* Video (MJPEG): Refactored and ensure correct data length transmission +* Added locking before resetting USB to prevent HID deadlock (thanks to [@scpcom](https://github.com/scpcom)) +* Optimized HID writing logic +* Made HDMI toggle state persist across reboots (thanks to [@tpretz](https://github.com/tpretz)) +* Enhanced the paste feature with browser clipboard API integration (thanks to [@patrickpilon](https://github.com/patrickpilon)) +* Set the inquiry string for the virtual storage device (thanks to [@scpcom](https://github.com/scpcom)) +* Hostname now updates `/etc/hosts` simultaneously and takes effect immediately (thanks to [@scpcom](https://github.com/scpcom)) +* Improved compatibility for static IP configuration on Windows +* Optimized the web page title update logic +* Added a confirmation dialog when uninstalling Tailscale +* Improved UI for Clipboard, Image Mounting, and settings pages + +### Bug Fixes + +* Tailscale: Fixed an issue where Tailscale would turn the device into a router +* Tailscale: Fixed an issue where Tailscale would disable IPv6 +* Tailscale: Added a swap memory option to prevent Tailscale Out-Of-Memory errors +* Tailscale: Fixed Tailscale accept-dns configuration (thanks to [@lazydba247](https://github.com/lazydba247)) +* Fixed an issue where certain modifier keys were not recognized +* Fixed vertical mouse drift when the page is zoomed in or out + +### Localization + +* Update Korean translation (thanks to [@xenix4845](https://github.com/xenix4845)) +* Update Traditional Chinese translation (thanks to [@protonchang](https://github.com/protonchang)) +* Add Brazilian Portuguese translation (thanks to [@chiconws](https://github.com/chiconws) and [@Luccas-LF](https://github.com/Luccas-LF)) +* Add Swedish translation (thanks to [@acidflash](https://github.com/acidflash)) +* Add Catalan translation (thanks to [@Zagur](https://github.com/Zagur)) +* Add Turkish translation (thanks to [@Keylem](https://github.com/Keylem)) + +### Security + +* Implemented a delay after failed login attempts to mitigate brute-force attacks +* Upgraded dependencies to fix known security vulnerabilities + +## 2.2.9 [c77981c](https://github.com/sipeed/NanoKVM/commit/c77981cc0ceebd8f6705b6c5d8c3cf4edf4f6717) (2025-06-13) + +* fix(security): resolve parameter injection in serial port terminal + +## 2.2.8 [01e28f1](https://github.com/sipeed/NanoKVM/commit/01e28f10ae8b581d484bb6077ddfe7bbe4e57919) (2025-05-22) + +* feat: add AZERTY virtual keyboard Layout (thanks to [@felix068](https://github.com/felix068)) +* feat: add support for enabling/disabling HDMI output (PCIe version only) (thanks to [@tpretz](https://github.com/tpretz)) +* feat: add support for custom mouse wheel speed +* fix: prevent direct H.264 stream buffer overflow and replay issues +* perf: improve keyboard paste performance (thanks to [@ethanperrine](https://github.com/ethanperrine)) +* Localization + * update Korean translation (thanks to [@kmw0410](https://github.com/kmw0410)) + * update Ukrainian translation (thanks to [@arbdevml](https://github.com/arbdevml)) + * update Russian translation (thanks to [@arbdevml](https://github.com/arbdevml)) + +## 2.2.7 [e18ec22](https://github.com/sipeed/NanoKVM/commit/e18ec2219d22886529575d1fdaad5c320e05f5b2) (2025-05-08) + +* feat: add HTTPS support +* feat: support direct H.264 streaming over HTTP +* Localization + * update Russian translation (thanks to [polyzium](https://github.com/polyzium)) + * update Dutch translation (thanks to [LeonStraathof](https://github.com/LeonStraathof)) + * update Ukrainian translation (thanks to [click0](https://github.com/click0)) + * update German translation (thanks to [3limin4tor](https://github.com/3limin4tor)) + +## 2.2.6 [c83dc55](https://github.com/sipeed/NanoKVM/commit/c83dc5565c9dbed22336661a8832edbd93a06d11) (2025-04-17) + +* feat: add mouse jiggler to prevent system sleep +* feat: add support for swap memory +* feat: add support for customizing the device hostname +* feat: add support for customizing the web page title +* feat: add support for assigning custom names to Wake-on-LAN MAC addresses +* feat: add confirmation prompts for power operations +* feat: add logo to the login page (thanks to [S33G](https://github.com/S33G)) +* fix: fix possible privacy issues with MIC drivers. [Repair Record](https://github.com/sipeed/NanoKVM/commit/f9244b36df090a05cd59ba11ea4fd01e9b638995) +* fix: fix iptables rule that could interfere with SSH connections (thanks to [scpcom](https://github.com/scpcom)) +* fix: fix the static IP gateway configuration might not apply correctly (thanks to [xitation](https://github.com/xitation)) +* perf: optimized OLED display handling and sleep logic +* perf: improve H.264 streaming reliability by adding a heartbeat mechanism +* perf: set the minimum screen size to 640x480 +* perf: add display of both wired and wireless IPv4 addresses in the settings page +* perf: update the Thai language translations (thanks to [ChokunPlayZ](https://github.com/ChokunPlayZ)) +* chore: bump axios to 1.8.4 +* chore: bump golang.org/x/net to v0.39.0 +* chore: bump github.com/golang-jwt/jwt/v5 to to v5.2.2 + +## 2.2.5 [3286cc2](https://github.com/sipeed/NanoKVM/commit/3286cc2f85a14133d65935cb476c833dcf151459) (2025-03-26) + +* fix: server crash caused by MJPEG frame detection error +* feat: add HID-Only mode +* feat: support preview updates +* perf: improve image reading performance by optimizing screen parameters + +## 2.2.4 [1bf986d](https://github.com/sipeed/NanoKVM/commit/1bf986d41b34d568c1ffee5df90ce61b6b08456b) (2025-03-21) + +* fix: resolve USB initialization issue +* fix: correct abnormal updates in certain models +* perf: add version restrictions for production testing + +## 2.2.3 [6ef83cb](https://github.com/sipeed/NanoKVM/commit/6ef83cb22fcd77f721d32c97c85d12f2bfc3035a) (2025-03-21) + +* feat: add support for setting H.264 GOP +* fix: resolve deadlock caused by HDMI resolution errors +* perf: merge H.264 SPS and PPS into I-frame +* perf: refactor MJPEG frame detection +* perf: added more configuration options to the serial port terminal (thanks to [@mekaris](https://github.com/mekaris)) +* perf: improve the `update-nanokvm.py` script (thanks to [@reishoku](https://github.com/reishoku)) +* perf: disable mDNS by default in new products +* perf: update log timestamp to millisecond precision +* chore: bump Go to 1.23 +* chore: bump `golang.org/x/net` to v0.37.0 + +## 2.2.2 [58d5ab2](https://github.com/sipeed/NanoKVM/commit/58d5ab2d37244b1e1a68b925a5c23c324c489ad3) (2025-03-11) + +* feat: add watchdog for NanoKVM-Server +* feat: add support for UE chip +* feat: support system reboot +* feat: support enable/disable mDNS +* fix: resolve UE chip cannot start server +* perf: refactor automatic resolution detection +* perf: add output prompt for unsupported resolutions +* perf: add lock to kvmv_read_img +* perf: configurable VENC automatic recycling feature +* perf: add maximum limit for vi +* perf: add tooltips to menu bar (thanks to [@S33G](https://github.com/S33G)) +* perf: menu bar is now draggable (thanks to [@forumi0721](https://github.com/forumi0721)) +* perf: image list support auto-refresh (thanks to [@forumi0721](https://github.com/forumi0721)) +* perf: update translations + +## 2.2.1 [b5e48a0](https://github.com/sipeed/NanoKVM/commit/b5e48a07e82df3aedd60442342ae50b95684a697) (2025-02-21) + +* fix: mounted image were not being detected correctly +* perf: add support for CD-ROM mode when mounting image (thanks to [@scpcom](https://github.com/scpcom)) +* perf: add a loading state during login +* perf: add changelog link in settings +* perf: update translation and cleanup the code (thanks to [@ChokunPlayZ](https://github.com/ChokunPlayZ) [@Stoufiler](https://github.com/Stoufiler) [@polyzium](https://github.com/polyzium) [@Jonher937](https://github.com/Jonher937) [@S33G](https://github.com/S33G)) + +## 2.2.0 [0dbf8c0](https://github.com/sipeed/NanoKVM/commit/0dbf8c007f2d0183d0f0601c3da6d3c3fccd8b31) (2025-02-17) + +NanoKVM [Image v1.4.0](https://github.com/sipeed/NanoKVM/releases/tag/v1.4.0) has been released! + +> Please refer to the [wiki](https://wiki.sipeed.com/hardware/en/kvm/NanoKVM/system/introduction.html) for more details about the image and application. + +* fix: improve password update notification logic (thanks to [@li20034](https://github.com/li20034)) +* perf: increase update wait time to 10s (from 6s) +* perf: update Korean translation (thanks to [@forumi0721](https://github.com/forumi0721)) +* perf: update Traditional Chinese translation (thanks to [@protonchang](https://github.com/protonchang)) +* refactor: update `libkvm.so` and `libkvm_mmf.so` libraries + +## 2.1.6 [6eb4a4e](https://github.com/sipeed/NanoKVM/commit/6eb4a4ea6254f465a47f9881d13934c686649061) (2025-02-14) + +* feat: support downloading image from online URL (thanks to [@Itxaka](https://github.com/Itxaka)) +* feat: add keyboard shortcut `Ctrl+Alt+Del` (thanks to [@CaffeeLake](https://github.com/CaffeeLake)) +* fix: fix the CSRF issue +* perf: add an option to configure custom ICE servers (thanks to [@VMFortress](https://github.com/VMFortress)) +* perf: removed unnecessary modifications to DNS configuration +* perf: add an SSH enable/disable toggle in the web UI +* perf: add a Tailscale enable/disable toggle in the web UI +* perf: download Tailscale installation package from the official source +* perf: automatic enable/disable GOMEMLIMIT on tailscale start/stop +* perf: add JWT configuration + * secretKey: customize secret key. If empty, generated a random 64-byte secret key by default + * refreshTokenDuration: customize token expiration time + * revokeTokensOnLogout: invalidate all JWT tokens on logout +* perf: implement secure password storage using bcrypt hashing +* perf: implement integrity checks for online updates +* refactor: refactor HDMI module and remove the dependency `libmaixcam_lib.so` +* refactor: web terminal use pty instead of SSH +* refactor: move Tailscale APIs from the `network` module to the `extensions` module + +## 2.1.5 [85f6447](https://github.com/sipeed/NanoKVM/commit/85f6447a16cc2591c6459b7d3dfda4d4cb75e98c) (2025-01-14) + +* feat: add HDMI reset for NanoKVM-PCIe +* fix: remove unnecessary lock acquisition during HID reset +* refactor: refactor Tailscale + +## 2.1.4 [d7ca7c4](https://github.com/sipeed/NanoKVM/commit/d7ca7c453d821ad099bf79b463969419041279cb) (2025-01-10) + +* feat: support configuring OLED sleep settings +* feat: support setting the `GOMEMLIMIT` environment variable +* fix: fix Wi-Fi configuration +* perf: password changes now update both the web user and the system root user +* perf: add MAC address verification for Wake-on-LAN +* refactor: a lot update to web UI +* refactor: refactor Tailscale + +## 2.1.3 [26078fe](https://github.com/sipeed/NanoKVM/commit/26078fe46e43d4543d7b09901b4992e4fbe4f01f) (2024-12-27) + +* feat: add API to retrieve Wi-Fi information +* fix: fix keyboard modifier keys +* fix: update keyboard and mouse HID codes +* fix: update hardware version information + +## 2.1.2 [5a39562](https://github.com/sipeed/NanoKVM/commit/5a39562f2d32695933f4e7e86866136236cc9903) (2024-12-04) + +* feat: add hardware version to configuration +* feat: add Wi-Fi configuration support for NanoKVM-PCIe +* perf: update web UI +* chore: add dependency libraries + +## 2.1.1 [74a303b](https://github.com/sipeed/NanoKVM/commit/74a303bd5cbb58f9d8ddd81abaaf4919dbbfb71b) (2024-11-06) + +* feat: support h264 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f9ec10b --- /dev/null +++ b/README.md @@ -0,0 +1,139 @@ +NanoKVM +====== + +
+ +![](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM_3.png) + +

+ 🚀 Quick Start + | + 🛠️ Hardware Details + | + 💾 Firmware Releases +

+ +English | [中文](./README_ZH.md) | [日本語](./README_JA.md) | [한국어](./README_KO.md) + +
+ +## 🌟 What is NanoKVM? + +NanoKVM is a series of compact, open-source IP-KVM devices. Built upon the LicheeRV Nano (RISC-V), NanoKVM allows you to remotely access and control computers as if you were sitting right in front of them – perfect for managing servers, embedded systems, or any headless machine. + +## 📦 Product Family + +We offer several NanoKVM versions to suit your needs: + +* **NanoKVM-Cube Lite:** A barebones kit for DIY enthusiasts and enterprise users needing bulk deployment. +* **NanoKVM-Cube Full:** A complete package with a sleek case, accessories, and a pre-flashed system SD card. Ready to use out-of-the-box, ideal for individual users. +* **NanoKVM-PCIe:** A unique form factor with a PCIe bracket for internal chassis mounting, drawing power directly from a PCIe slot. And add optional WiFi and PoE functions. + +### 🚀 NanoKVM-Pro: The 4K Powerhouse + +Based on popular demand, we've upgraded NanoKVM to the **NanoKVM-Pro**: + +* **Resolution Upgrade:** From 1080P to stunning **4K@30fps / 2K@60fps**. +* **Network Upgrade:** Blazing fast **GbE + PoE + WiFi 6** (up from 100M Ethernet). +* **Latency Upgrade:** Encoder accelerated latency reduced from 100-150ms to **50-100ms**. + +> The NanoKVM-Pro is now available for pre-sale! [Click here for product details](https://sipeed.com/nanokvm/pro). + +
+ +![NanoKVM Product Family](https://cdn.sipeed.com/public/nanokvm-products.jpg) + +
+ +## 🛠️ Technical Specifications + +| Product | NanoKVM-Pro | NanoKVM (Cube/PCIe) | GxxKVM | JxxKVM | +|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- |------------------------------------ | +| Core | AX631 2xA53 1.5G | SG2002 1xC906 1.0G | RV1126 4xA7 1.5G | RV1106 1xA7 1.2G | +| Memory & Storage | 1G LPDDR4X + 32G eMMC | 256M DDR3 + 32G microSD | 1G DDR3 + 8G eMMc | 256M DDR3 + 16G eMMC | +| System | NanoKVM / PiKVM | NanoKVM | GxxKVM | JxxKVM | +| Resolution | 4K@30fps / 2K@60fps | 1080P@60fps | 4K@30fps / 2K@60fps | 1080P@60fps | +| HDMI Loopout | 4K loopout | x | x | x | +| Video Encoding | MJPEG / H.264 / H.265 | MJPEG / H264 | MJPEG / H264 | MJPEG / H264 | +| Audio Transmit | ✓ | x | ✓ | x | +| UEFI / BIOS | ✓ | ✓ | ✓ | ✓ | +| Emulated USB Keyboard & Mouse | ✓ | ✓ | ✓ | ✓ | +| Emulated USB ISO | ✓ | ✓ | ✓ | ✓ | +| IPMI | ✓ | ✓ | ✓ | x | +| Wake-on-LAN | ✓ | ✓ | ✓ | ✓ | +| Web Terminal | ✓ | ✓ | ✓ | ✓ | +| Serial Terminal | 3 channels | 2 channels | x | 1 channel | +| Custom Scripts | ✓ | ✓ | x | x | +| Storage | 32G eMMC 300MB/s | 32G MicroSD 12MB/s | 8G eMMC 120MB/s | 8G eMMC 60MB/s | +| Ethernet | 1000M | 100M | 1000M | 100M | +| PoE | Optional | Optional | x | x | +| WiFi | Optional WiFi6 | Optional WiFi6 | x | x | +| ATX Power Control | ✓ | ✓ | Extra $15 | Extra $10 | +| Display | 1.47" 320x172 LCD / 0.96" 128x64 OLED | 0.96" 128x64 OLED | - | 1.68" 280x240 | +| More Features | Sync LED Strip / Smart Assistant | - | - | - | +| Power Consumption | 0.4A@5V | 0.2A@5V | 0.4A@5V | 0.2A@5V | +| Power Input | USB-C or PoE | USB-C | USB-C | USB-C | +| Dimensions | 65x65x26mm | 40x36x36mm | 80x60x17.5mm | 60x43x(24~31)mm | +| Price | ~~$79~~ $69 ATX / ~~$89~~ $79 Desk | $25 Lite / $50 Full(with ATX) | $89 no ATX / $102 with ATX | $69 no ATX / $79 with ATX | + +## 📂 Project Structure + +``` shell +├── kvmapp # APP update package +│ ├── jpg_stream # Compatible for direct updates from very old versions +│ ├── kvm_new_app # Triggers necessary components for kvm_system update +│ ├── kvm_system # kvm_system application +│ ├── server # NanoKVM front-end and back-end applications +│ └── system # Necessary system components +├── web # NanoKVM front-end +├── server # NanoKVM back-end +├── support # Auxiliary functions (image subsystem, system status, system updates, screen, keys, etc.) +│ ├── sg2002 # NanoKVM-Lite/Full/PCIe +│ └── h618 # NanoKVM-Pro +├── ... +``` + +## 🔩 Hardware Platform (NanoKVM Cube/PCIe) + +NanoKVM is based on Sipeed [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html),you can find specifications, schematics, and dimensional drawings [here](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano). + +The NanoKVM Lite is constructed by the LicheeRV Nano and HDMItoCSI board, and the NanoKVM FULL adds the NanoKVM-A/B board and shell to the NanoKVM Lite. The HDMItoCSI board is used to convert the HDMI signal; NanoKVM-A, including OLED, ATX control output (USB Type-C interface), auxiliary power supply and ATX power on/off and reset buttons; The NanoKVM-B is connected to the plate at one end and the computer at the other end is connected to the computer ATX-Pin, which is used to remotely control the power of the computer. + +The NanoKVM image is built on LicheeRV Nano SDK and MaixCDK, and is compatible with materials that use the LicheeRV Nano, opposite the KVM software cannot be used with the LicheeRV Nano or other SG2002 products. If you would like to build an HDMI input application on LicheeRV Nano or MaixCam, please contact us for technical support. + +Note: Out of the 256MB memory in SG2002, 158MB is currently allocated for the multimedia subsystem, which NanoKVM will use for video image acquisition and processing. + +* [NanoKVM-A Schematic](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_A_30111.pdf) +* [NanoKVM-B Schematic](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_B_30131.pdf) +* [NanoKVM img](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM) + +![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) + +## 🤝 Contributing + +We welcome contributions! Here's how you can help: + +1. Fork the repository. +2. Create a feature branch. +3. Commit your changes. +4. Push to the branch. +5. Open a Pull Request. + +Please keep your pull requests small and focused to facilitate easier review and merging. + +> 🎁 **Contributors who submit high-quality Pull Requests may receive a NanoKVM Cube, PCIe, or Pro as a token of our appreciation!** + +## 🛒 Where to Buy + +* [Aliexpress(global except USA&Russia)](https://www.aliexpress.com/item/1005007369816019.html) +* [淘宝](https://item.taobao.com/item.htm?id=811206560480) +* [Preorder (any other country that not support in Aliexpress or Taobao)](https://sipeed.com/nanokvm) + +## 💬 Community & Support + +* [MaixHub Discussion](https://maixhub.com/discussion/nanokvm) +* QQ group: 703230713 + +## 📜 License + +This project is licensed under the GPL-3.0 License - see the LICENSE file for details. diff --git a/README_JA.md b/README_JA.md new file mode 100644 index 0000000..b880048 --- /dev/null +++ b/README_JA.md @@ -0,0 +1,129 @@ +NanoKVM +====== + +
+ +![](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM_3.png) + +

+ クイックスタート + | + ハードウェア +

+ +[English](./README.md) | [中文](./README_ZH.md) | 日本語 | [한국어](./README_KO.md) + +
+ +> NanoKVM は [RISC-V](https://en.wikipedia.org/wiki/RISC-V) を搭載しています! +> 使用中に問題や提案がある場合は、ここで issue を提出するか、[MaixHub Discussion](https://maixhub.com/discussion/nanokvm) でお知らせください。 + +## オープンソース & コントリビューション + +### 2025.02.19 更新 + +NanoKVM のすべてのコンポーネントはオープンソース化されており、[front-end](https://github.com/sipeed/NanoKVM/tree/main/web)、[back-end](https://github.com/sipeed/NanoKVM/tree/main/server)、[kvm_vision](https://github.com/sipeed/NanoKVM/tree/main/vision/components/kvm)、[kvm_mmf](https://github.com/sipeed/NanoKVM/tree/main/vision/components/kvm_mmf)、[kvm_system](https://github.com/sipeed/NanoKVM/tree/main/support)、[kvmapp update package](https://github.com/sipeed/NanoKVM-System/tree/main/kvmapp)、[system sdk](https://github.com/sipeed/LicheeRV-Nano-Build/tree/NanoKVM)、[packaging methods](https://github.com/sipeed/LicheeRV-Nano-Build/blob/NanoKVM/kvm/NanoKVM_img.sh)が含まれます。 + +### 2025.02.14 更新 + +最新のアプリケーションバージョン v2.1.6 には、多くのセキュリティ強化とバグ修正が追加されているため、一般のユーザーはこのバージョン以降へのアップグレードをお勧めします。 + +### 2025.02.05 更新 + +セキュリティを懸念しているユーザーは、この issue をお読みください: 、セキュリティに関するすべての懸念事項について説明されています。 +GitHub は、オープンで透明性のあるプラットフォームとして、いわゆる「バックドア」の懸念事項を議論する場を提供しています。オープンソースであることにより、製品のセキュリティがさらに強化されます! + +### 2024.10.18 更新 + +10/08にバックエンドコードをオープンソース化しました。すぐにPRを提出してくれたCivilに感謝します。最新のNanoKVM-PCIeを報酬として提供します! +10月中旬のバッチはテストとパッケージングを開始しており、10/01以前のほとんどの注文は次週と次々週に出荷されます。 +10/01以降、アリエクスプレスの出荷日は12月に変更されました。これは保守的な納期です。10月の注文は11/15〜12/15の間に発送される予定です。 + +## 紹介 + +Lichee NanoKVMは、LicheeRV NanoをベースにしたIP-KVM製品で、LicheeRV Nanoの極小サイズと強力な機能を継承しています。 +Lichee NanoKVMは2つのバージョンがあります: +NanoKVM Liteは基本構成で、一定のDIY能力を持つ個人ユーザーや大量のニーズを持つ企業ユーザーに適しています。 +NanoKVM Fullは完全版で、洗練されたケースと完全なアクセサリーを備え、起動時にすぐに使用できるシステムミラーカードが内蔵されており、個人ユーザーに推奨されます。 + +![NanoKVM 分解画像](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_1.jpg) + +## 技術仕様 + +| 製品 | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 | +|----------------------- |-------------------------------------- |---------------------------------- |----------------------------------- | +| 計算ユニット | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) | +| 解像度 | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps | +| ビデオエンコーディング | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 | +| ビデオ遅延 | 90~230ms | 90~230ms | 100~230ms | +| UEFI/BIOS | ✓ | ✓ | ✓ | +| 仮想HID | ✓ | ✓ | ✓ | +| 仮想CD-ROM | ✓ | ✓ | ✓ | +| IPMI | ✓ | ✓ | ✓ | +| Wake-on-LAN | ✓ | ✓ | ✓ | +| ETH | 100M/10M | 100M/10M | 1000M/100M/10M | +| ATX電源制御 | なし、ユーザーが自分で接続 | USBインターフェースIO制御ボード | RJ-45インターフェースIO制御ボード | +| OLED | なし、ユーザーが自分で接続 | 128x64 0.96" 白 | 128x32 0.91" 白 | +| UART | 2 | 2 | 1 | +| TFカード | なし | ✓ | ✓ | +| 拡張 | なし | PoE | WiFi/LTE | +| 消費電力 | 0.2A@5V | 0.2A@5V | ピーク時 2.6A@5V | +| 電源入力 | PC USBで給電可能 | PC USBまたは補助電源 | DC 5V 3A電源が必要 | +| 冷却 | 静音ファンレス | 静音ファンレス | ファン冷却 | +| サイズ | 23x37x15mm ~1/30 PiKVM V4サイズ | 40x36x36mm ~1/7 PiKVM V4サイズ | 120x68x44mm | + +![NanoKVM PCB ピン配置](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) + +## NanoKVM ハードウェアプラットフォーム + +NanoKVMはSipeed [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html)に基づいており、仕様、回路図、寸法図は[こちら](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano)で確認できます。 + +NanoKVM LiteはLicheeRV NanoとHDMItoCSIボードで構成され、NanoKVM FULLはNanoKVM LiteにNanoKVM-A/Bボードとシェルを追加しています。HDMItoCSIボードはHDMI信号を変換するために使用されます。NanoKVM-AにはOLED、ATX制御出力(USB Type-Cインターフェース)、補助電源供給およびATX電源オン/オフおよびリセットボタンが含まれています。NanoKVM-Bは一端がAボードに接続され、他端がコンピュータのATXピンに接続され、コンピュータの電源をリモートで制御します。 + +NanoKVMイメージはLicheeRV Nano SDKおよびMaixCDKに基づいて構築されており、LicheeRV Nanoを使用する資料と互換性があります。逆に、KVMソフトウェアはLicheeRV Nanoや他のSG2002製品では使用できません。LicheeRV NanoやMaixCamでHDMI入力アプリケーションを構築したい場合は、技術サポートを受けるためにお問い合わせください。 + +注:SG2002の256MBメモリのうち、現在158MBがマルチメディアサブシステムに割り当てられており、NanoKVMはビデオ画像の取得と処理にこのメモリを使用します。 + ++ [NanoKVM-A 回路図](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_A_30111.pdf) ++ [NanoKVM-B 回路図](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_HDMI_MIPI_31011.pdf) ++ [NanoKVM イメージダウンロード](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM) + +## ロードマップ + +### Q1 2025 + +**改善:** + +- [x] HDMI モジュールをリファクタリングし、libmaixcam_lib.so の依存関係を削除 +- [x] SSH をデフォルトで無効にし、Web UI に有効/無効の切り替えを追加 +- [x] Tailscale をデフォルトで無効にし、Web UI に有効/無効の切り替えを追加 +- [x] 設定ファイルに JWT 設定オプションを追加 +- [x] bcrypt を利用したパスワードストレージのセキュリティ保護 + +**配布:** + +- [x] GitHub 経由でのアプリケーション配布 +- [x] オンラインでの更新の際に整合性チェックを実施 +- [ ] オフラインでの更新をサポート + +**バグ修正:** + +- [x] DNS の問題を修正 +- [x] CSRF の脆弱性を修正 + +**機能** + +- [ ] [79](https://github.com/sipeed/NanoKVM/issues/79) Zerotier をサポート +- [ ] [99](https://github.com/sipeed/NanoKVM/issues/99) WireGuard をサポート +- [ ] [249](https://github.com/sipeed/NanoKVM/issues/249) Mouse Jiggler を追加 + +## 購入方法 + +* [Aliexpress(グローバル、米国とロシアを除く)](https://www.aliexpress.com/item/1005007369816019.html) +* [淘宝](https://item.taobao.com/item.htm?id=811206560480) +* [予約注文 (淘宝とアリエクスプレスがサポートされていない国)](https://sipeed.com/nanokvm) + +## コミュニティ + +* [MaixHub Discussion](https://maixhub.com/discussion/nanokvm) +* QQグループ: 703230713 diff --git a/README_KO.md b/README_KO.md new file mode 100644 index 0000000..8860573 --- /dev/null +++ b/README_KO.md @@ -0,0 +1,139 @@ +NanoKVM +====== + +
+ +![](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM_3.png) + +

+ 🚀 빠른 시작 + | + 🛠️ 하드웨어 정보 + | + 💾 펌웨어 릴리즈 +

+ +[English](./README.md) | [中文](./README_ZH.md) | [日本語](./README_JA.md) | 한국어 + +
+ +## 🌟 NanoKVM이 무엇인가요? + +NanoKVM은 소형 오픈 소스 IP-KVM 장치 시리즈입니다. LicheeRV Nano(RISC-V)를 기반으로 구축된 NanoKVM을 사용하면 마치 컴퓨터 앞에 바로 앉아있는 것처럼 원격으로 컴퓨터에 접근하고 제어할 수 있어 서버, 임베디드 시스템 또는 모든 헤드리스 머신을 관리하는 데 완벽합니다. + +## 📦 제품군 + +다양한 요구를 충족할 수 있도록 여러 NanoKVM 버전을 제공합니다: + +* **NanoKVM-Cube Lite:** DIY 사용자 및 대량 배포가 필요한 기업용을 위한 최소 구성 키트입니다. +* **NanoKVM-Cube Full:** 세련된 케이스, 액세서리, 사전 플래시된 시스템 SD 카드가 포함된 완전 구성 패키지입니다. 개봉 후 즉시 사용할 수 있어 개인 사용자에게 적합합니다. +* **NanoKVM-PCIe:** 독특한 폼팩터로, PCIe 브래킷을 통해 섀시 내부에 장착하며, 전원인 PCIe 슬롯에서 직접 공급받습니다. 선택적으로 WiFi와 PoE 기능을 추가할 수 있습니다. + +### 🚀 NanoKVM-Pro: 4K를 위한 파워 머신 + +대중적인 수요를 기반으로, NanoKVM을 **NanoKVM-Pro**로 업그레이드했습니다: + +* **해상도 업그레이드:** 1080P에서 놀라운 **4K@30fps / 2K@60fps**까지. +* **네트워크 업그레이드:** 초고속 **GbE + PoE + WiFi 6** (100M 이더넷 대비 향상). +* **지연 시간 업그레이드:** 인코더 가속 지연시간이 100-150ms에서 **50-100ms**로 감소. + +> NanoKVM-Pro가 예약 판매 중입니다! [제품 세부 정보는 여기를 클릭하세요](https://sipeed.com/nanokvm/pro). + +
+ +![NanoKVM Product Family](https://cdn.sipeed.com/public/nanokvm-products.jpg) + +
+ +## 🛠️ 기술 사양 + +| 제품 | NanoKVM-Pro | NanoKVM (Cube/PCIe) | GxxKVM | JxxKVM | +|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- |------------------------------------ | +| 코어 | AX631 2xA53 1.5G | SG2002 1xC906 1.0G | RV1126 4xA7 1.5G | RV1106 1xA7 1.2G | +| 메모리 & 저장 공간 | 1G LPDDR4X + 32G eMMC | 256M DDR3 + 32G microSD | 1G DDR3 + 8G eMMc | 256M DDR3 + 16G eMMC | +| 시스템 | NanoKVM / PiKVM | NanoKVM | GxxKVM | JxxKVM | +| 해상도 | 4K@30fps / 2K@60fps | 1080P@60fps | 4K@30fps / 2K@60fps | 1080P@60fps | +| HDMI 루프아웃 | 4K 루프아웃 | x | x | x | +| 영상 인코딩 | MJPEG / H.264 / H.265 | MJPEG / H264 | MJPEG / H264 | MJPEG / H264 | +| 오디오 전송 | ✓ | x | ✓ | x | +| UEFI / BIOS | ✓ | ✓ | ✓ | ✓ | +| USB 키보드 & 마우스 에뮬레이션 | ✓ | ✓ | ✓ | ✓ | +| USB ISO 에뮬레이션 | ✓ | ✓ | ✓ | ✓ | +| IPMI | ✓ | ✓ | ✓ | x | +| Wake-on-LAN | ✓ | ✓ | ✓ | ✓ | +| 웹 터미널 | ✓ | ✓ | ✓ | ✓ | +| 시리얼 터미널 | 3 채널 | 2 채널 | x | 1 채널 | +| 사용자 정의 스크립트 | ✓ | ✓ | x | x | +| 저장 공간 | 32G eMMC 300MB/s | 32G MicroSD 12MB/s | 8G eMMC 120MB/s | 8G eMMC 60MB/s | +| 이더넷 | 1000M | 100M | 1000M | 100M | +| PoE | 옵션 | 옵션 | x | x | +| WiFi | WiFi6 옵션 | WiFi6 옵션 | x | x | +| ATX 전원 조작 | ✓ | ✓ | $15 추가 | $10 추가 | +| 디스플레이 | 1.47" 320x172 LCD / 0.96" 128x64 OLED | 0.96" 128x64 OLED | - | 1.68" 280x240 | +| 기타 기능 | LED 줄 동기화 / 스마트 어시스턴트 | - | - | - | +| 전력 소비량 | 0.4A@5V | 0.2A@5V | 0.4A@5V | 0.2A@5V | +| 전원 입력 | USB-C 또는 PoE | USB-C | USB-C | USB-C | +| 크기 | 65x65x26mm | 40x36x36mm | 80x60x17.5mm | 60x43x(24~31)mm | +| 가격 | ~~$79~~ $69 ATX / ~~$89~~ $79 Desk | $25 Lite / $50 Full(with ATX) | ATX 미사용 시 $89 / ATX 사용 시 $102 | ATX 미사용 시 $69 / ATX 사용 시 $79 | + +## 📂 프로젝트 구조 + +``` shell +├── kvmapp # APP 업데이트 패키지 +│ ├── jpg_stream # 매우 오래된 버전에서도 직접 업데이트 가능하도록 호환됨 +│ ├── kvm_new_app # kvm_system 업데이트에 필요한 구성 요소를 트리거함 +│ ├── kvm_system # kvm_system 애플리케이션 +│ ├── server # NanoKVM 프론트 엔드와 백엔드 애플리케이션 +│ └── system # 필요한 시스템 구성 요소 +├── web # NanoKVM 프론트엔드 +├── server # NanoKVM 백엔드 +├── support # 보조 기능 (이미지 하위 시스템, 시스템 상태, 시스템 업데이트, 화면, 키, 기타.) +│ ├── sg2002 # NanoKVM-Lite/Full/PCIe +│ └── h618 # NanoKVM-Pro +├── ... +``` + +## 🔩 하드웨어 플랫폼 (NanoKVM Cube/PCIe) + +NanoKVM은 Sipeed [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html)를 기반으로 하며,[여기](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano)에서 사양, 도식, 치수, 도면은 여기에서 찾을 수 있습니다. + +NanoKVM Lite는 LicheeRV Nano와 HDMItoCSI 보드로 구성되어 있으며, NanoKVM FULL은 NanoKVM Lite에 NasnoKVM-A/B 보드와 케이스를 추가한 구성입니다. HDMItoCSI 보드는 HDMI 신호를 변환하는 역할을 하고 NanoKVM-A는 OLED, ATX 전원 출력 (USB Type-C 인터페이스), 보조 전원 공급 장치, ATX 전원 켜기/끄기 및 리셋 버튼을 포함합니다. NanoKVM-B는 한쪽 끝이 컴퓨터와 연결되고 다른 한쪽 끝은 컴퓨터의 ATX 핀에 연결되어 컴퓨터의 전원을 원격으로 제어하는 데 사용됩니다. + +NanoKVM 이미지는 LicheeRV Nano SDK와 MaixCDK로 구축되었으며, LicheeRV Nano를 사용하는 자재와 호환됩니다. 반면에 KVM 소프트웨어는 LicheeRV Nano나 다른 SG2002 제품과 함께 사용할 수 없습니다. 만약 LicheeRV Nano나 MaixCam에서 HDMI 입력 애플리케이션을 구축하고 싶으시다면, 기술 지원에 문의해 주세요. + +참고: SG2002의 256MB 메모리 중에 158MB는 멀티미디어 하위 폴더에 할당되어 있으며, NanoKVM은 비디오 이미지 획득과 처리에 사용됩니다. + +* [NanoKVM-A 도식](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_A_30111.pdf) +* [NanoKVM-B 도식](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_B_30131.pdf) +* [NanoKVM 이미지](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM) + +![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) + +## 🤝 기여 + +여러분의 기여를 환영합니다! 도움을 줄 수 있는 방법은 다음과 같습니다: + +1. 레포지토리를 포크하세요 +2. 기능 브랜치를 만드세요 +3. 변경한 것을 커밋하세요 +4. 브랜치로 푸시하세요 +5. 풀 리퀘스트를 생성하세요 + +Please keep your pull requests small and focused to facilitate easier review and merging. + +> 🎁 **고품질 풀 리퀘스트를 제출한 기여자는 감사의 표시로 NanoKVM Cube, PCIe, 또는 Pro를 받을 수 있습니다!** + +## 🛒 어디서 구매하나요? + +* [Aliexpress(미국&러시아를 제외한 전 세계)](https://www.aliexpress.com/item/1005007369816019.html) +* [淘宝](https://item.taobao.com/item.htm?id=811206560480) +* [사전 주문 (Aliexpress나 Taobao를 지원하지 않는 국가)](https://sipeed.com/nanokvm) + +## 💬 커뮤니티 & 지원 + +* [MaixHub Discussion](https://maixhub.com/discussion/nanokvm) +* QQ group: 703230713 + +## 📜 라이센스 + +이 프로젝트는 GPL-3.0 라이센스에 따라 라이센스가 부여됩니다. 자세한 사항은 LICENSE 파일을 참조하세요. diff --git a/README_ZH.md b/README_ZH.md new file mode 100644 index 0000000..fe2ea06 --- /dev/null +++ b/README_ZH.md @@ -0,0 +1,139 @@ +NanoKVM +====== + +
+ +![](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM_3.png) + +

+ 🚀 快速开始 + | + 🛠️ 硬件信息 + | + 💾 固件发布 +

+ +[English](./README.md) | 中文 | [日本語](./README_JA.md) | [한국어](./README_KO.md) + +
+ +## 🌟 NanoKVM 是什么? + +NanoKVM 是一系列紧凑型开源 IP-KVM 设备。NanoKVM 基于 LicheeRV Nano (RISC-V) 构建,让您能够身临其境般的远程访问和控制计算机,是管理服务器、嵌入式系统或任何无头设备的理想之选。 + +## 📦 产品系列 + +我们提供多种 NanoKVM 版本以满足您的需求: + +* **NanoKVM-Cube Lite**:为 DIY 爱好者和需要批量部署的企业用户提供的准系统套件。 +* **NanoKVM-Cube Full**:包含外壳、配件以及预装系统 SD 卡的完整套装。开箱即用,非常适合个人用户。 +* **NanoKVM-PCIe**:独特的外形设计,配备 PCIe 支架,可安装在机箱内部,并直接从 PCIe 插槽供电。可选配 WiFi 和 PoE 功能。 + +### 🚀 NanoKVM-Pro + +根据大众的需求,我们将 NanoKVM 升级到了 **NanoKVM-Pro**: + +* **分辨率升级**:从 1080P 升级到 **4K@30fps / 2K@60fps**。 +* **网络升级**:从百兆网口升级到超快的 **GbE + WiFi 6**。 +* **延迟升级**:编码器加速延迟从 100-150 毫秒减少到 **50-100 毫秒**。 + +> NanoKVM-Pro 现已开启预售![点击此处查看产品详情](https://sipeed.com/nanokvm/pro)。 + +
+ +![NanoKVM Product Family](https://cdn.sipeed.com/public/nanokvm-products.jpg) + +
+ +## 🛠️ 技术规格 + +| 产品 | NanoKVM-Pro | NanoKVM (Cube/PCIe) | GxxKVM | JxxKVM | +|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- |------------------------------------ | +| 核心 | AX631 2xA53 1.5G | SG2002 1xC906 1.0G | RV1126 4xA7 1.5G | RV1106 1xA7 1.2G | +| 内存&存储 | 1G LPDDR4X + 32G eMMC | 256M DDR3 + 32G microSD | 1G DDR3 + 8G eMMc | 256M DDR3 + 16G eMMC | +| 系统 | NanoKVM / PiKVM | NanoKVM | GxxKVM | JxxKVM | +| 分辨率 | 4K@30fps / 2K@60fps | 1080P@60fps | 4K@30fps / 2K@60fps | 1080P@60fps | +| HDMI 环出 | 4K 环出 | x | x | x | +| 视频编码 | MJPEG / H.264 / H.265 | MJPEG / H264 | MJPEG / H264 | MJPEG / H264 | +| 音频输出 | ✓ | x | ✓ | x | +| UEFI / BIOS | ✓ | ✓ | ✓ | ✓ | +| 模拟 USB 键鼠 | ✓ | ✓ | ✓ | ✓ | +| 模拟 USB ISO | ✓ | ✓ | ✓ | ✓ | +| IPMI | ✓ | ✓ | ✓ | x | +| 局域网唤醒 | ✓ | ✓ | ✓ | ✓ | +| 网页终端 | ✓ | ✓ | ✓ | ✓ | +| 串口终端 | 3 channels | 2 channels | x | 1 channel | +| 用户脚本 | ✓ | ✓ | x | x | +| 存储 | 32G eMMC 300MB/s | 32G MicroSD 12MB/s | 8G eMMC 120MB/s | 8G eMMC 60MB/s | +| 网口 | 1000M | 100M | 1000M | 100M | +| PoE | 可选 | 可选 | x | x | +| WiFi | 可选 WiFi6 | 可选 WiFi6 | x | x | +| ATX 电源控制 | ✓ | ✓ | ✓ | ✓ | +| 显示屏 | 1.47" 320x172 LCD / 0.96" 128x64 OLED | 0.96" 128x64 OLED | - | 1.68" 280x240 | +| 特色功能 | LED 同步灯带 / 智能助手 | - | - | - | +| 功耗 | 0.4A@5V | 0.2A@5V | 0.4A@5V | 0.2A@5V | +| 电源输入 | USB-C or PoE | USB-C | USB-C | USB-C | +| 尺寸 | 65x65x26mm | 40x36x36mm | 80x60x17.5mm | 60x43x(24~31)mm | +| 价格 | ~~$79~~ $69 ATX / ~~$89~~ $79 Desk | $25 Lite / $50 Full(带ATX) | $89 无ATX / $102 带ATX | $69 无ATX / $79 带ATX | + +## 📂 项目结构 + +``` shell +├── kvmapp # APP更新包 +│ ├── jpg_stream # 兼容从非常老的版本中直接更新 +│ ├── kvm_new_app # 触发 kvm_system 更新必要组件 +│ ├── kvm_system # kvm_system 应用 +│ ├── server # NanoKVM 前后端应用 +│ └── system # 必要系统组件 +├── server # NanoKVM 后端 +├── support # 辅助功能(图像子系统、系统状态、系统更新、屏幕、按键……) +│ ├── sg2002 # NanoKVM-Lite/Full/PCIe +│ └── h618 # NanoKVM-Pro +├── web # NanoKVM 前端 +├── LICENSE +├── README_JA.md +├── README.md +├── README_ZH.md +└── CHANGELOG.md +``` + +## 🔩 硬件平台(NanoKVM Cube/PCIe) + +NanoKVM 基于 Sipeed [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html) 核心板搭建,这部分硬件的规格书、原理图、尺寸图等均可在这里找到:[点击这里](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano) + +NanoKVM Lite 由 LicheeRV Nano E 和 HDMItoCSI 小板构成,NanoKVM FULL 在 NanoKVM Lite 基础上增加 NanoKVM-A/B 板和外壳。HDMItoCSI板用于转换HDMI信号;NanoKVM-A 包含 OLED、ATX控制输出(TypeC接口形式)、辅助供电(TypeC接口)以及ATX开关机、复位按键;NanoKVM-B 一端连接A板,一端连接电脑ATX针脚,用于电脑的远程开关机。 + +NanoKVM 镜像在LicheeRV Nano SDK 和 MaixCDK 基础上构建,可以兼容使用 LicheeRV Nano 的资料,反之LicheeRV Nano 或其他 SG2002 产品无法使用KVM软件。如果您想在 LicheeRV Nano 或 MaixCam 上构建 HDMI输入相关应用,请与我们联系,以获得技术支持。 + +注: SG2002的256MB内存中, 目前划分105MB用于多媒体子系统, NanoKVM会在视频图像采集和处理中使用这部分内存. + +* [NanoKVM-A 原理图](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_A_30111.pdf) +* [NanoKVM-B 原理图](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_HDMI_MIPI_31011.pdf) +* [NanoKVM 镜像下载](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM) + +![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) + +## 🤝 贡献代码 + +我们十分欢迎任何人来贡献代码!您可以通过以下方式提供帮助: + +1. Fork 本仓库. +2. 创建一个分支. +3. 提交你的代码. +4. 推送代码到分支. +5. 创建一个PR. + +请保持你的 PR 尽量简单且集中,以便于更轻松地审查和合并。 + +> 🎁 **提交高质量 PR 的贡献者可能会收到 NanoKVM Cube、PCIe 或 Pro 作为感谢礼物!** + +## 🛒 购买渠道 + +* [Aliexpress(全球,除了美国和俄罗斯)](https://www.aliexpress.com/item/1005007369816019.html) +* [淘宝](https://item.taobao.com/item.htm?id=811206560480) +* [Preorder (其它不支持淘宝和速卖通的国家)](https://sipeed.com/nanokvm) + +## 💬 社区 + +* [MaixHub Discussion](https://maixhub.com/discussion/nanokvm) +* QQ group: 703230713 diff --git a/kvmapp/jpg_stream/S95nanokvm b/kvmapp/jpg_stream/S95nanokvm new file mode 100644 index 0000000..056508f --- /dev/null +++ b/kvmapp/jpg_stream/S95nanokvm @@ -0,0 +1,91 @@ +#!/bin/sh +# nanokvm Rev2.1 + +case "$1" in + start) + echo -n kvm > /boot/hostname.prefix + cp /mnt/data/sensor_cfg.ini.LT /mnt/data/sensor_cfg.ini + + str_value=$(cat /sys/class/cvi-base/base_uid | awk '{print $2}') + first_uint=$(echo $str_value | cut -d'_' -f1) + second_uint=$(echo $str_value | cut -d'_' -f2) + result="$first_uint$second_uint" + echo $result > /device_key + + # if [ ! -e /etc/kvm/hw ] + # then + + # fi + + # echo 504 > /sys/class/gpio/export # pwr led + # echo 505 > /sys/class/gpio/export # hdd led + # echo 503 > /sys/class/gpio/export # pwr key + # echo 507 > /sys/class/gpio/export # rst key + + # echo in > /sys/class/gpio/gpio504/direction # pwr led + # echo in > /sys/class/gpio/gpio505/direction # hdd led + # echo out > /sys/class/gpio/gpio503/direction # pwr key + # echo out > /sys/class/gpio/gpio507/direction # rst key + + iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT + iptables -A OUTPUT -o eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT + iptables -A OUTPUT -o eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT + iptables -A INPUT -i eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT + iptables -A OUTPUT -o eth0 -p tcp --sport 8000 -m state --state ESTABLISHED -j DROP + + cp -r /kvmapp/kvm_system /tmp/ + /tmp/kvm_system/kvm_system & + + # if [ -e /kvmapp/kvm_stream ] + # then + # cp -r /kvmapp/kvm_stream /tmp/ + # /tmp/kvm_stream/kvm_stream & + # fi + + cp -r /kvmapp/server /tmp/ + /tmp/server/NanoKVM-Server & + ;; + stop) + killall kvm_system + # killall kvm_stream + killall NanoKVM-Server + rm -r /tmp/kvm_system + # rm -r /tmp/kvm_stream + rm -r /tmp/server + echo "OK" + ;; + # restart_stream) + # killall kvm_stream + # rm -r /tmp/kvm_stream + # if [ -e /kvmapp/kvm_stream ] + # then + # cp -r /kvmapp/kvm_stream /tmp/ + # /tmp/kvm_stream/kvm_stream & + # echo "OK" + # fi + # ;; + restart) + killall kvm_system + # killall kvm_stream + killall NanoKVM-Server + rm -r /tmp/kvm_system + # rm -r /tmp/kvm_stream + rm -r /tmp/server + + cp -r /kvmapp/kvm_system /tmp/ + /tmp/kvm_system/kvm_system & + + # if [ -e /kvmapp/kvm_stream ] + # then + # cp -r /kvmapp/kvm_stream /tmp/ + # /tmp/kvm_stream/kvm_stream & + # fi + + cp -r /kvmapp/server /tmp/ + /tmp/server/NanoKVM-Server & + + sync + + echo "OK" + ;; +esac \ No newline at end of file diff --git a/kvmapp/jpg_stream/jpg_stream b/kvmapp/jpg_stream/jpg_stream new file mode 100644 index 0000000..1a0030f --- /dev/null +++ b/kvmapp/jpg_stream/jpg_stream @@ -0,0 +1,5 @@ +rm /etc/init.d/S95webkvm +cp /kvmapp/jpg_stream/S95nanokvm /etc/init.d/ +cp /kvmapp/jpg_stream/dl_lib/libmaixcam_lib.so /kvmapp/kvm_system/dl_lib +rm -r /kvmapp/jpg_stream +/etc/init.d/S95nanokvm restart diff --git a/kvmapp/kvm_new_app b/kvmapp/kvm_new_app new file mode 100644 index 0000000..e69de29 diff --git a/kvmapp/kvm_system/kvm_stream b/kvmapp/kvm_system/kvm_stream new file mode 100644 index 0000000..e69de29 diff --git a/kvmapp/system/init.d/S00kmod b/kvmapp/system/init.d/S00kmod new file mode 100644 index 0000000..83e9a8e --- /dev/null +++ b/kvmapp/system/init.d/S00kmod @@ -0,0 +1,32 @@ +#!/bin/sh + +if [ "$1" = "start" ] +then + . /etc/profile + printf "load kernel module: " + cd /mnt/system/ko/ + insmod soph_sys.ko + insmod soph_base.ko + insmod soph_rtos_cmdqu.ko + insmod soph_fast_image.ko + insmod soph_mipi_rx.ko + insmod soph_snsr_i2c.ko + insmod soph_vi.ko + insmod soph_vpss.ko + insmod soph_dwa.ko + insmod soph_vo.ko + insmod soph_rgn.ko + insmod soph_wdt.ko + insmod soph_clock_cooling.ko + insmod soph_tpu.ko + insmod soph_vcodec.ko + insmod soph_jpeg.ko + insmod soph_vc_driver.ko MaxVencChnNum=9 MaxVdecChnNum=9 + insmod soph_rtc.ko + insmod soph_ive.ko + insmod soph_mon.ko + insmod soph_pwm.ko + insmod soph_wiegand.ko + echo "OK" + exit 0 +fi diff --git a/kvmapp/system/init.d/S01fs b/kvmapp/system/init.d/S01fs new file mode 100644 index 0000000..3391d66 --- /dev/null +++ b/kvmapp/system/init.d/S01fs @@ -0,0 +1,42 @@ +#!/bin/sh + +if [ "$1" = "start" ] +then + # use all sdcard free space for data + parted -s /dev/mmcblk0 "resizepart 2 -0" + echo "yes + 8192MB + " | parted ---pretend-input-tty /dev/mmcblk0 "resizepart 2 8192MB" + # resize data filesystem + (resize2fs /dev/mmcblk0p2) & + + + . /etc/profile + printf "mounting filesystem : " + mkdir -p /boot + mount -t vfat /dev/mmcblk0p1 /boot + mount -t configfs configfs /sys/kernel/config + mount -t debugfs debugfs /sys/kernel/debug + + if [ -e /boot/usb.disk0 ] + then + if [ ! -e /etc/kvm.disk0 ] + then + touch /etc/kvm.disk0 + # use all sdcard free space for data + parted -s /dev/mmcblk0 "mkpart primary 8193MB 100%" + sleep 1 + # resize data filesystem + (mkfs.exfat /dev/mmcblk0p3) & + sleep 1 + fi + fi + + if [ -e /dev/mmcblk0p3 ] + then + mkdir -p /data + mount /dev/mmcblk0p3 /data + fi + + echo "OK" +fi diff --git a/kvmapp/system/init.d/S03usbdev b/kvmapp/system/init.d/S03usbdev new file mode 100644 index 0000000..f6cd9cf --- /dev/null +++ b/kvmapp/system/init.d/S03usbdev @@ -0,0 +1,152 @@ +#!/bin/sh +# kvmhwd Rev2.1 + +start_usb_dev(){ + . /etc/profile + echo "usb mode: device" + cd /sys/kernel/config/usb_gadget + + mkdir g0 + cd g0 + + # echo 0x3346 > idVendor + # echo 0x1009 > idProduct + if [ -e /boot/usb.vid ] + then + cat /boot/usb.vid > idVendor + else + echo 0x3346 > idVendor + fi + if [ -e /boot/usb.pid ] + then + cat /boot/usb.pid > idProduct + else + echo 0x1009 > idProduct + fi + mkdir strings/0x409 + echo '0123456789ABCDEF' > strings/0x409/serialnumber + echo 'sipeed' > strings/0x409/manufacturer + echo 'NanoKVM' > strings/0x409/product + + mkdir configs/c.1 + echo 0xE0 > configs/c.1/bmAttributes + echo 120 > configs/c.1/MaxPower + mkdir configs/c.1/strings/0x409 + echo "NanoKVM" > configs/c.1/strings/0x409/configuration + + if [ -e /boot/usb.ncm ] + then + mkdir functions/ncm.usb0 + ln -s functions/ncm.usb0 configs/c.1/ + else + if [ -e /boot/usb.rndis0 ] + then + mkdir functions/rndis.usb0 + ln -s functions/rndis.usb0 configs/c.1/ + fi + fi + + if [ ! -e /boot/disable_hid ] + then + # keyboard + mkdir functions/hid.GS0 + if [ -e /boot/BIOS ] + then + echo 1 > functions/hid.GS0/subclass + fi + if [ ! -e /boot/usb.notwakeup ] + then + echo 1 > functions/hid.GS0/wakeup_on_write + fi + echo 1 > functions/hid.GS0/protocol + echo 8 > functions/hid.GS0/report_length + echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.GS0/report_desc + ln -s functions/hid.GS0 configs/c.1 + + # mouse + mkdir functions/hid.GS1 + if [ -e /boot/BIOS ] + then + echo 1 > functions/hid.GS1/subclass + fi + if [ ! -e /boot/usb.notwakeup ] + then + echo 1 > functions/hid.GS1/wakeup_on_write + fi + echo 2 > functions/hid.GS1/protocol + echo 4 > functions/hid.GS1/report_length + echo -ne \\x5\\x1\\x9\\x2\\xa1\\x1\\x9\\x1\\xa1\\x0\\x5\\x9\\x19\\x1\\x29\\x3\\x15\\x0\\x25\\x1\\x95\\x3\\x75\\x1\\x81\\x2\\x95\\x1\\x75\\x5\\x81\\x3\\x5\\x1\\x9\\x30\\x9\\x31\\x9\\x38\\x15\\x81\\x25\\x7f\\x75\\x8\\x95\\x3\\x81\\x6\\xc0\\xc0 > functions/hid.GS1/report_desc + ln -s functions/hid.GS1 configs/c.1 + + # touchpad + mkdir functions/hid.GS2 + if [ -e /boot/BIOS ] + then + echo 1 > functions/hid.GS2/subclass + fi + if [ ! -e /boot/usb.notwakeup ] + then + echo 1 > functions/hid.GS2/wakeup_on_write + fi + echo 2 > functions/hid.GS2/protocol + echo 6 > functions/hid.GS2/report_length + echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x03\\x15\\x00\\x25\\x01\\x95\\x03\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x05\\x81\\x01\\x05\\x01\\x09\\x30\\x09\\x31\\x15\\x00\\x26\\xff\\x7f\\x35\\x00\\x46\\xff\\x7f\\x75\\x10\\x95\\x02\\x81\\x02\\x05\\x01\\x09\\x38\\x15\\x81\\x25\\x7f\\x35\\x00\\x45\\x00\\x75\\x08\\x95\\x01\\x81\\x06\\xc0\\xc0 > functions/hid.GS2/report_desc + ln -s functions/hid.GS2 configs/c.1 + fi + + if [ -e /boot/usb.disk0 ] + then + mkdir functions/mass_storage.disk0 + ln -s functions/mass_storage.disk0 configs/c.1/ + echo 1 > functions/mass_storage.disk0/lun.0/removable + if [ -e /boot/usb.disk0.ro ] + then + echo 1 > functions/mass_storage.disk0/lun.0/ro + echo 0 > functions/mass_storage.disk0/lun.0/cdrom + fi + echo "NanoKVM USB Mass Storage0520" > functions/mass_storage.disk0/lun.0/inquiry_string + disk=$(cat /boot/usb.disk0) + if [ -z "${disk}" ] + then + # if [ ! -e /mnt/usbdisk.img ] + # then + # fallocate -l 8G /mnt/usbdisk.img + # mkfs.vfat /mnt/usbdisk.img + # fi + echo /dev/mmcblk0p3 > functions/mass_storage.disk0/lun.0/file + else + cat /boot/usb.disk0 > functions/mass_storage.disk0/lun.0/file + fi + fi + + ls /sys/class/udc/ | cat > UDC + echo device > /proc/cviusb/otg_role +} + +start_usb_host(){ + echo '' > /sys/kernel/config/usb_gadget/g0/UDC + echo host > /proc/cviusb/otg_role +} + +restart_usb_dev(){ + echo > /sys/kernel/config/usb_gadget/g0/UDC + sleep 1 + ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC + echo "USB Restart OK!" +} + +case "$1" in + start) + start_usb_dev + ;; + restart) + restart_usb_dev + ;; + stop) + start_usb_host + ;; + stop_start) + start_usb_host + start_usb_dev + ;; +esac diff --git a/kvmapp/system/init.d/S03usbhid b/kvmapp/system/init.d/S03usbhid new file mode 100644 index 0000000..a8bcc45 --- /dev/null +++ b/kvmapp/system/init.d/S03usbhid @@ -0,0 +1,105 @@ +#!/bin/sh +# kvmhwd hid only + +start_usb_dev(){ + . /etc/profile + echo "usb mode: device" + cd /sys/kernel/config/usb_gadget + + mkdir g0 + cd g0 + + echo 0x0101 > bcdUSB + echo 0x0623 > bcdDevice + # echo 0x3346 > idVendor + # echo 0x1009 > idProduct + if [ -e /boot/usb.vid ] + then + cat /boot/usb.vid > idVendor + else + echo 0x3346 > idVendor + fi + if [ -e /boot/usb.pid ] + then + cat /boot/usb.pid > idProduct + else + echo 0x1009 > idProduct + fi + mkdir strings/0x409 + # echo '0' > strings/0x409/serialnumber + echo 'sipeed' > strings/0x409/manufacturer + echo 'NanoKVM' > strings/0x409/product + + mkdir configs/c.1 + echo 0xA0 > configs/c.1/bmAttributes + echo 200 > configs/c.1/MaxPower + mkdir configs/c.1/strings/0x409 + echo "NanoKVM" > configs/c.1/strings/0x409/configuration + + # keyboard + mkdir functions/hid.GS0 + echo 1 > functions/hid.GS0/subclass + if [ ! -e /boot/usb.notwakeup ] + then + echo 1 > functions/hid.GS0/wakeup_on_write + fi + echo 1 > functions/hid.GS0/protocol + echo 8 > functions/hid.GS0/report_length + echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.GS0/report_desc + ln -s functions/hid.GS0 configs/c.1 + + # mouse + mkdir functions/hid.GS1 + echo 1 > functions/hid.GS1/subclass + if [ ! -e /boot/usb.notwakeup ] + then + echo 1 > functions/hid.GS1/wakeup_on_write + fi + echo 2 > functions/hid.GS1/protocol + echo 4 > functions/hid.GS1/report_length + echo -ne \\x5\\x1\\x9\\x2\\xa1\\x1\\x9\\x1\\xa1\\x0\\x5\\x9\\x19\\x1\\x29\\x3\\x15\\x0\\x25\\x1\\x95\\x3\\x75\\x1\\x81\\x2\\x95\\x1\\x75\\x5\\x81\\x3\\x5\\x1\\x9\\x30\\x9\\x31\\x9\\x38\\x15\\x81\\x25\\x7f\\x75\\x8\\x95\\x3\\x81\\x6\\xc0\\xc0 > functions/hid.GS1/report_desc + ln -s functions/hid.GS1 configs/c.1 + + # touchpad + mkdir functions/hid.GS2 + echo 1 > functions/hid.GS2/subclass + if [ ! -e /boot/usb.notwakeup ] + then + echo 1 > functions/hid.GS2/wakeup_on_write + fi + echo 2 > functions/hid.GS2/protocol + echo 6 > functions/hid.GS2/report_length + echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x03\\x15\\x00\\x25\\x01\\x95\\x03\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x05\\x81\\x01\\x05\\x01\\x09\\x30\\x09\\x31\\x15\\x00\\x26\\xff\\x7f\\x35\\x00\\x46\\xff\\x7f\\x75\\x10\\x95\\x02\\x81\\x02\\x05\\x01\\x09\\x38\\x15\\x81\\x25\\x7f\\x35\\x00\\x45\\x00\\x75\\x08\\x95\\x01\\x81\\x06\\xc0\\xc0 > functions/hid.GS2/report_desc + ln -s functions/hid.GS2 configs/c.1 + + ls /sys/class/udc/ | cat > UDC + echo device > /proc/cviusb/otg_role +} + +start_usb_host(){ + echo '' > /sys/kernel/config/usb_gadget/g0/UDC + echo host > /proc/cviusb/otg_role +} + +restart_usb_dev(){ + echo > /sys/kernel/config/usb_gadget/g0/UDC + sleep 1 + ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC + echo "USB Restart OK!" +} + +case "$1" in + start) + start_usb_dev + ;; + restart) + restart_usb_dev + ;; + stop) + start_usb_host + ;; + stop_start) + start_usb_host + start_usb_dev + ;; +esac diff --git a/kvmapp/system/init.d/S15kvmhwd b/kvmapp/system/init.d/S15kvmhwd new file mode 100644 index 0000000..e2e2575 --- /dev/null +++ b/kvmapp/system/init.d/S15kvmhwd @@ -0,0 +1,234 @@ +#!/bin/sh +# kvmhwd Rev2.4 + +Alpha_OLED_RST_Pin=371 +Beta_OLED_RST_Pin=502 +Beta_OLED_SCL=595 +Beta_OLED_SDA=507 +WiFi_EN_Pin=506 +Alpha_PWR_LED_Pin=504 +Alpha_HDD_LED_Pin=505 +Alpha_PWR_KEY_Pin=503 +Alpha_RST_KEY_Pin=507 +Beta_PWR_LED_Pin=504 +Beta_PWR_KEY_Pin=503 +Beta_RST_KEY_Pin=505 +PCIe_HDMI_RST_Pin=451 + +init_alpha_hw(){ + devmem 0x030010D0 32 0x2 # I2C1_SCL + devmem 0x030010DC 32 0x2 # I2C1_SDA + devmem 0x030010D4 32 0x3 # OLED_RST + devmem 0x0300103C 32 0x3 # GPIOA15 + devmem 0x03001050 32 0x3 # GPIOA22 + devmem 0x0300105C 32 0x3 # GPIOA23 + devmem 0x03001060 32 0x3 # GPIOA24 + devmem 0x03001054 32 0x3 # GPIOA25 + devmem 0x03001058 32 0x3 # GPIOA27 + + devmem 0x03001068 32 0x6 # GPIOA 18 UART1 RX + devmem 0x03001064 32 0x6 # GPIOA 19 UART1 TX + devmem 0x03001070 32 0x2 # GPIOA 28 UART2 TX + devmem 0x03001074 32 0x2 # GPIOA 29 UART2 RX + + echo ${Alpha_OLED_RST_Pin} > /sys/class/gpio/export # OLED_RST + echo out > /sys/class/gpio/gpio${Alpha_OLED_RST_Pin}/direction + echo 1 > /sys/class/gpio/gpio${Alpha_OLED_RST_Pin}/value + + echo ${Alpha_PWR_LED_Pin} > /sys/class/gpio/export # pwr led + echo ${Alpha_HDD_LED_Pin} > /sys/class/gpio/export # hdd led + echo ${Alpha_PWR_KEY_Pin} > /sys/class/gpio/export # pwr key + echo ${Alpha_RST_KEY_Pin} > /sys/class/gpio/export # rst key + + echo in > /sys/class/gpio/gpio${Alpha_PWR_LED_Pin}/direction # pwr led + echo in > /sys/class/gpio/gpio${Alpha_HDD_LED_Pin}/direction # hdd led + echo out > /sys/class/gpio/gpio${Alpha_PWR_KEY_Pin}/direction # pwr key + echo out > /sys/class/gpio/gpio${Alpha_RST_KEY_Pin}/direction # rst key + + rmmod /mnt/system/ko/i2c-gpio.ko + rmmod /mnt/system/ko/i2c-algo-bit.ko + + # rm /etc/init.d/S25wifimod + # rm /etc/init.d/S30wifi +} + +init_beta_pcie_hw(){ + devmem 0x0300103C 32 0x3 # GPIOA15 + devmem 0x03001050 32 0x3 # GPIOA22 + devmem 0x0300105C 32 0x3 # GPIOA23 + devmem 0x03001060 32 0x3 # GPIOA24 + devmem 0x03001054 32 0x3 # GPIOA25 + devmem 0x03001058 32 0x3 # GPIOA27 + + devmem 0x030010E4 32 0x0 # SDIO CLK + devmem 0x030010E0 32 0x0 # SDIO CMD + devmem 0x030010DC 32 0x0 # SDIO D0 + devmem 0x030010D8 32 0x0 # SDIO D1 + devmem 0x030010D4 32 0x0 # SDIO D2 + devmem 0x030010D0 32 0x0 # SDIO D3 + + devmem 0x03001068 32 0x6 # GPIOA 18 UART1 RX + devmem 0x03001064 32 0x6 # GPIOA 19 UART1 TX + devmem 0x03001070 32 0x2 # GPIOA 28 UART2 TX + devmem 0x03001074 32 0x2 # GPIOA 29 UART2 RX + + echo ${Beta_OLED_RST_Pin} > /sys/class/gpio/export # Beta OLED_RST + echo out > /sys/class/gpio/gpio${Beta_OLED_RST_Pin}/direction + echo 1 > /sys/class/gpio/gpio${Beta_OLED_RST_Pin}/value + + echo ${Beta_PWR_LED_Pin} > /sys/class/gpio/export # pwr led + echo ${Beta_PWR_KEY_Pin} > /sys/class/gpio/export # pwr key + echo ${Beta_RST_KEY_Pin} > /sys/class/gpio/export # rst key + echo ${PCIe_HDMI_RST_Pin} > /sys/class/gpio/export # hdmi rst key + + echo in > /sys/class/gpio/gpio${Beta_PWR_LED_Pin}/direction # pwr led + echo out > /sys/class/gpio/gpio${Beta_PWR_KEY_Pin}/direction # pwr key + echo out > /sys/class/gpio/gpio${Beta_RST_KEY_Pin}/direction # rst key + echo out > /sys/class/gpio/gpio${PCIe_HDMI_RST_Pin}/direction # rst key + + echo 1 > /sys/class/gpio/gpio${PCIe_HDMI_RST_Pin}/value # rst key + + rmmod /mnt/system/ko/i2c-gpio.ko + rmmod /mnt/system/ko/i2c-algo-bit.ko + insmod /mnt/system/ko/i2c-algo-bit.ko + insmod /mnt/system/ko/i2c-gpio.ko +} + +kvm_hw_detect(){ + if [ ! -e /etc/kvm/hdmi_version ] + then + rm /etc/kvm/hw + fi + if [ -e /etc/kvm/hw ] + then + echo "/etc/kvm/hw exist" + else + echo "/etc/kvm/hw not exist" + if [ -d "/etc/kvm/" ] + then + echo "/etc/kvm/ exist" + else + mkdir /etc/kvm/ + fi + devmem 0x0300104C 32 0x3 # GPIOA26 / WiFi_EN + echo ${WiFi_EN_Pin} > /sys/class/gpio/export # WiFi_EN + echo out > /sys/class/gpio/gpio${WiFi_EN_Pin}/direction + echo 0 > /sys/class/gpio/gpio${WiFi_EN_Pin}/value + + devmem 0x0300103C 32 0x3 # GPIOA15 + devmem 0x03001050 32 0x3 # GPIOA22 + devmem 0x0300105C 32 0x3 # GPIOA23 + devmem 0x03001060 32 0x3 # GPIOA24 + devmem 0x03001054 32 0x3 # GPIOA25 + devmem 0x03001058 32 0x3 # GPIOA27 + + devmem 0x030010D0 32 0x2 # I2C1_SCL + devmem 0x030010DC 32 0x2 # I2C1_SDA + devmem 0x030010D4 32 0x3 # OLED_RST + echo ${Alpha_OLED_RST_Pin} > /sys/class/gpio/export # OLED_RST + echo out > /sys/class/gpio/gpio${Alpha_OLED_RST_Pin}/direction + echo 1 > /sys/class/gpio/gpio${Alpha_OLED_RST_Pin}/value + + kvm_tmp=$(i2cdetect -ry 1 0x3d 0x3d | grep 3d) + if [ -n "$kvm_tmp" ] + then + # alpha hw + echo "alpha" > /etc/kvm/hw + else + # beta/pcie hw + echo ${Alpha_OLED_RST_Pin} > /sys/class/gpio/unexport # OLED_RST + echo ${Beta_OLED_RST_Pin} > /sys/class/gpio/export # Beta_OLED_RST_Pin + echo out > /sys/class/gpio/gpio${Beta_OLED_RST_Pin}/direction + echo 1 > /sys/class/gpio/gpio${Beta_OLED_RST_Pin}/value + + rmmod /mnt/system/ko/i2c-gpio.ko + rmmod /mnt/system/ko/i2c-algo-bit.ko + insmod /mnt/system/ko/i2c-algo-bit.ko + insmod /mnt/system/ko/i2c-gpio.ko + + kvm_tmp=$(i2cdetect -ry 4 0x2c 0x2c | grep 2c) + if [ -n "$kvm_tmp" ] + then + echo "c" > /etc/kvm/hdmi_version + else + kvm_tmp1=$(i2cdetect -ry 4 0x2b 0x2b | grep 2b) + if [ -n "$kvm_tmp1" ] + then + echo "ux" > /etc/kvm/hdmi_version + else + echo "ue" > /etc/kvm/hdmi_version + fi + fi + + kvm_tmp=$(i2cdetect -ry 5 0x3c 0x3c | grep 3c) + if [ -n "$kvm_tmp" ] + then + echo "pcie" > /etc/kvm/hw + sync + reboot + else + echo "beta" > /etc/kvm/hw + # rm /etc/init.d/S25wifimod + # rm /etc/init.d/S30wifi + sync + fi + + echo 1 > /sys/class/gpio/gpio${WiFi_EN_Pin}/value + echo ${WiFi_EN_Pin} > /sys/class/gpio/unexport # WiFi_EN + fi + fi + sync +} + +kvm_hw_init(){ + FIND_FILE="/etc/kvm/hw" + if [ `grep -c "alpha" $FIND_FILE` -ne '0' ] + then + echo "hw = alpha!" + init_alpha_hw + fi + if [ `grep -c "beta" $FIND_FILE` -ne '0' ] + then + echo "hw = beta!" + init_beta_pcie_hw + fi + if [ `grep -c "pcie" $FIND_FILE` -ne '0' ] + then + echo "hw = pcie!" + init_beta_pcie_hw + fi +} + +kvm_hdmi_version_detect(){ + hdmi_tmp=$(i2cdetect -ry 4 0x2c 0x2c | grep 2c) + if [ -n "$hdmi_tmp" ] + then + echo "c" > /etc/kvm/hdmi_version + else + hdmi_tmp1=$(i2cdetect -ry 4 0x2b 0x2b | grep 2b) + if [ -n "$hdmi_tmp1" ] + then + echo "ux" > /etc/kvm/hdmi_version + else + echo "ue" > /etc/kvm/hdmi_version + fi + fi +} + +case "$1" in + start) + kvm_hw_detect + kvm_hw_init + ;; + re-detect) + rm /etc/kvm/hw + kvm_hw_detect + kvm_hw_init + ;; + re-init) + kvm_hw_init + ;; + get_hdmi_version) + kvm_hdmi_version_detect + ;; +esac diff --git a/kvmapp/system/init.d/S30eth b/kvmapp/system/init.d/S30eth new file mode 100644 index 0000000..604a673 --- /dev/null +++ b/kvmapp/system/init.d/S30eth @@ -0,0 +1,67 @@ +#!/bin/sh + +RESERVE_INET="192.168.90.1/22" + +clean_line() { + echo "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/\r//g' +} + +calc_gw() { + addr="$1" + netid="$2" + IFS=. read a b c d <>24)&255 )).$(( (gwnum>>16)&255 )).$(( (gwnum>>8)&255 )).$(( gwnum&255 ))" +} + +start() { + if [ -e /boot/eth.nodhcp ]; then + while IFS= read -r line || [ -n "$line" ]; do + line=$(clean_line "$line") + [ -z "$line" ] && continue + set -- $line + inet="$1" + gw="$2" + [ -z "$inet" ] && continue + addr=${inet%/*} + netid=${inet#*/} + [ -z "$gw" ] && gw=$(calc_gw "$addr" "$netid") + arping -Dqc2 -Ieth0 "$addr" || continue + ip a add "$inet" brd + dev eth0 + ip r add default via "$gw" dev eth0 + echo -e "nameserver $gw" >> /etc/resolv.conf + break + done < /boot/eth.nodhcp + + ip a show dev eth0 | grep inet > /dev/null || { + udhcpc -i eth0 -t 3 -T 1 -A 5 -b -p /run/udhcpc.eth0.pid &>/dev/null + ip a show dev eth0 | grep inet > /dev/null + } || { + inet=$RESERVE_INET + addr=${inet%/*} + ip a add "$inet" brd + dev eth0 + } || exit 1 + else + udhcpc -i eth0 -t 10 -T 1 -A 5 -b -p /run/udhcpc.eth0.pid & + fi +} + +stop() { + [ ! -e "/run/udhcpc.eth0.pid" ] && exit 1 + kill "$(cat /run/udhcpc.eth0.pid)" + rm /run/udhcpc.eth0.pid +} + +case "$1" in + start) start ;; + stop) stop ;; + restart|reload) + $0 stop + $0 start + ;; + *) exit 1 ;; +esac \ No newline at end of file diff --git a/kvmapp/system/init.d/S30wifi b/kvmapp/system/init.d/S30wifi new file mode 100644 index 0000000..93bea60 --- /dev/null +++ b/kvmapp/system/init.d/S30wifi @@ -0,0 +1,175 @@ +#!/bin/sh + +. /etc/profile + +gen_hostapd_conf() { + ssid="${1}" + pass="${2}" + echo "ctrl_interface=/var/run/hostapd" + echo "ctrl_interface_group=0" + echo "ssid=${ssid}" + echo "hw_mode=g" + echo "channel=1" + echo "beacon_int=100" + echo "dtim_period=2" + echo "max_num_sta=255" + echo "rts_threshold=-1" + echo "fragm_threshold=-1" + echo "macaddr_acl=0" + echo "auth_algs=3" + echo "wpa=2" + echo "wpa_passphrase=${pass}" + echo "ieee80211n=1" +} + +gen_udhcpd_conf() { + interface=${1} + ipv4_prefix=${2} + echo "start ${ipv4_prefix}.100" + echo "end ${ipv4_prefix}.200" + echo "interface ${interface}" + echo "pidfile /var/run/udhcpd.${interface}.pid" + echo "lease_file /var/lib/misc/udhcpd.${interface}.leases" + echo "option subnet 255.255.255.0" + echo "option lease 864000" +} + +gen_dnsmasq_conf() { + ipv4_prefix=${1} + echo "bind-interfaces" + echo "interface=wlan0" + echo "address=/config.kvm/${ipv4_prefix}.1" + echo "no-hosts" + echo "dhcp-range=${ipv4_prefix}.2,${ipv4_prefix}.254" + echo "dhcp-option=6,${ipv4_prefix}.1" + echo "dhcp-option=3,${ipv4_prefix}.1" + echo "dhcp-authoritative" +} + +start() { + echo "wifi mode: sta" + ssid="" + pass="" + if [ -e /boot/wifi.ssid ] && [ -e /boot/wifi.pass ]; then + echo "Updating WiFi credentials from /boot to /etc/kvm/" + + rm -f /etc/kvm/wifi.ssid /etc/kvm/wifi.pass + + mv /boot/wifi.ssid /etc/kvm/wifi.ssid + mv /boot/wifi.pass /etc/kvm/wifi.pass + + chown root:root /etc/kvm/wifi.ssid /etc/kvm/wifi.pass + chmod 644 /etc/kvm/wifi.ssid /etc/kvm/wifi.pass + fi + if [ -e /etc/kvm/wifi.ssid ] + then + ssid=`cat /etc/kvm/wifi.ssid` + fi + if [ -e /etc/kvm/wifi.pass ] + then + pass=`cat /etc/kvm/wifi.pass` + fi + echo "ctrl_interface=/var/run/wpa_supplicant" > /etc/wpa_supplicant.conf + wpa_passphrase "$ssid" "$pass" >> /etc/wpa_supplicant.conf + wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf + if [ ! -e /boot/wifi.nodhcp ] + then + (udhcpc -i wlan0 -t 10 -T 1 -A 5 -b -p /run/udhcpc.wlan0.pid) & + fi +} + +ap_start() { + echo "wifi mode: ap" + id2=$(printf "%d" 0x$(sha512sum /sys/class/cvi-base/base_uid | head -c 2)) + id3=$(printf "%d" 0x$(sha512sum /sys/class/cvi-base/base_uid | head -c 4 | tail -c 2)) + if [ "$id2" = "$id3" ] + then + id2=$((id2 + 1)) + fi + if [ "$id2" -ge 255 ] + then + id2=253 + fi + if [ "$id3" -ge 255 ] + then + id3=254 + fi + ssid="" + pass="" + if [ -e /kvmapp/kvm/ap.ssid ] + then + ssid=`cat /kvmapp/kvm/ap.ssid` + fi + if [ -e /kvmapp/kvm/ap.pass ] + then + pass=`cat /kvmapp/kvm/ap.pass` + fi + gen_hostapd_conf "$ssid" "$pass" > /etc/hostapd.conf + ipv4_prefix=10.$id3.$id2 + if [ ! -e /etc/udhcpd.wlan0.conf ] + then + gen_udhcpd_conf wlan0 "${ipv4_prefix}" > /etc/udhcpd.wlan0.conf + fi + # gen_dnsmasq_conf "${ipv4_prefix}" > /etc/dnsmasq.conf + + # iptables --policy INPUT ACCEPT + # iptables --policy FORWARD ACCEPT + # iptables --policy OUTPUT ACCEPT + # iptables -F + # iptables -t nat -F + # iptables -t nat -A PREROUTING -i wlan0 -p udp --dport 53 -j DNAT --to ${ipv4_prefix}.1 + + ifconfig wlan0 up + ip route del default || true + ip add flush dev wlan0 + ip addr add $ipv4_prefix.1/24 dev wlan0 + # dnsmasq --pid-file=/tmp/dnsmasq.run -C /etc/dnsmasq.conf + hostapd -B -i wlan0 /etc/hostapd.conf + udhcpd -S /etc/udhcpd.wlan0.conf +} + +stop() { + ps -ef|grep hostapd|grep -v grep|awk '{print $1}'|xargs kill -2 || true + ps -ef|grep "udhcpd -S /etc/udhcpd.wlan0.conf" |grep -v grep|awk '{print $1}'|xargs kill -2 || true + killall wpa_supplicant || true + if [ -e /run/udhcpc.wlan0.pid ] + then + kill `cat /run/udhcpc.wlan0.pid` || true + rm -f /run/udhcpc.wlan0.pid + fi + if [ -e /var/run/udhcpd.wlan0.pid ] + then + kill `cat /var/run/udhcpd.wlan0.pid` || true + rm -f /var/run/udhcpd.wlan0.pid + fi + if [ -e /tmp/dnsmasq.run ] + then + kill `cat /tmp/dnsmasq.run` || true + rm -f /tmp/dnsmasq.run + fi + # if [ -e /etc/wpa_supplicant.conf] + # then + # echo > /etc/wpa_supplicant.conf + # fi + airmon-ng stop wlan0mon || true +} + +restart() { + stop + start +} + +if [ "${1}" = "start" ] +then + start +elif [ "${1}" = "stop" ] +then + stop +elif [ "${1}" = "ap" ] +then + stop + ap_start +elif [ "${1}" = "restart" ] +then + restart +fi diff --git a/kvmapp/system/init.d/S50avahi-daemon b/kvmapp/system/init.d/S50avahi-daemon new file mode 100644 index 0000000..81ac513 --- /dev/null +++ b/kvmapp/system/init.d/S50avahi-daemon @@ -0,0 +1,20 @@ +#!/bin/sh +# +# avahi-daemon init script + +DAEMON=/usr/sbin/avahi-daemon +case "$1" in + start) + $DAEMON -c || $DAEMON -D + ;; + stop) + $DAEMON -c && $DAEMON -k + ;; + reload) + $DAEMON -c && $DAEMON -r + ;; + *) + echo "Usage: S50avahi-daemon {start|stop|reload}" >&2 + exit 1 + ;; +esac diff --git a/kvmapp/system/init.d/S50ssdpd b/kvmapp/system/init.d/S50ssdpd new file mode 100644 index 0000000..480769b --- /dev/null +++ b/kvmapp/system/init.d/S50ssdpd @@ -0,0 +1,49 @@ +#!/bin/sh + +DAEMON=ssdpd +PIDFILE=/var/run/$DAEMON.pid +CFGFILE=/etc/default/$DAEMON + +DAEMON_ARGS="" + +# Read configuration variable file if it is present +# shellcheck source=/dev/null +[ -r "$CFGFILE" ] && . "$CFGFILE" + +# shellcheck disable=SC2086 +start() { + printf 'Starting %s: ' "$DAEMON" + if start-stop-daemon -S -q -p "$PIDFILE" -x "$DAEMON" -- $DAEMON_ARGS; then + echo "OK" + else + echo "FAIL" + fi +} + +stop() { + printf 'Stopping %s: ' "$DAEMON" + if start-stop-daemon -K -q -p "$PIDFILE" -x "$DAEMON"; then + echo "OK" + else + echo "FAIL" + fi +} + +restart() { + stop + start +} + +case "$1" in + start|stop|restart) + "$1" + ;; + reload) + restart + ;; + *) + echo "Usage: $0 {start|stop|restart|reload}" + exit 1 +esac + +exit $? \ No newline at end of file diff --git a/kvmapp/system/init.d/S50sshd b/kvmapp/system/init.d/S50sshd new file mode 100644 index 0000000..33439d4 --- /dev/null +++ b/kvmapp/system/init.d/S50sshd @@ -0,0 +1,73 @@ +#!/bin/sh +# +# sshd Starts sshd. +# + +# Ensure required binaries exist +[ -x /usr/bin/ssh-keygen ] || exit 0 +[ -x /usr/sbin/sshd ] || exit 1 + +PIDFILE="/var/run/sshd.pid" + +umask 077 + +startssh() { + # Generate SSH keys if they do not exist + [ ! -f /etc/ssh/ssh_host_rsa_key ] && /usr/bin/ssh-keygen -A + + printf "Starting sshd: " + /usr/sbin/sshd + touch /var/lock/sshd + echo "OK" +} + +start() { + if [ -e /etc/kvm/ssh_stop ]; then + if [ -e /boot/start_ssh_once ]; then + rm -f /boot/start_ssh_once + startssh + else + echo "SSH does not start" + exit 0 + fi + else + startssh + fi +} + +stop() { + printf "Stopping sshd: " + killall sshd 2>/dev/null + rm -f /var/lock/sshd + echo "OK" +} + +restart() { + stop + start +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart|reload) + restart + ;; + permanent_on) + rm -f /etc/kvm/ssh_stop + start + ;; + permanent_off) + touch /etc/kvm/ssh_stop + stop + ;; + *) + echo "Usage: $0 {start|stop|restart|permanent_on|permanent_off}" + exit 1 +esac + +exit 0 \ No newline at end of file diff --git a/kvmapp/system/init.d/S80dnsmasq b/kvmapp/system/init.d/S80dnsmasq new file mode 100644 index 0000000..175daf9 --- /dev/null +++ b/kvmapp/system/init.d/S80dnsmasq @@ -0,0 +1,29 @@ +#!/bin/sh + +DAEMON="dnsmasq" +PIDFILE="/var/run/$DAEMON.pid" + +[ -f /etc/dnsmasq.conf ] || exit 0 + +case "$1" in + start) + printf "Starting dnsmasq: " + start-stop-daemon -S -p "$PIDFILE" -x "/usr/sbin/$DAEMON" -- \ + --pid-file="$PIDFILE" + [ $? = 0 ] && echo "OK" || echo "FAIL" + ;; + stop) + printf "Stopping dnsmasq: " + start-stop-daemon -K -q -p "$PIDFILE" -x "/usr/sbin/$DAEMON" + [ $? = 0 ] && echo "OK" || echo "FAIL" + ;; + restart|reload) + $0 stop + $0 start + ;; + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 +esac + +exit 0 diff --git a/kvmapp/system/init.d/S95nanokvm b/kvmapp/system/init.d/S95nanokvm new file mode 100644 index 0000000..a40cc17 --- /dev/null +++ b/kvmapp/system/init.d/S95nanokvm @@ -0,0 +1,69 @@ +#!/bin/sh +# nanokvm Rev3.1 + +case "$1" in + start) + echo -n kvm > /boot/hostname.prefix + + # Copy sensor config if the file exists + [ -f /mnt/data/sensor_cfg.ini.LT ] && cp /mnt/data/sensor_cfg.ini.LT /mnt/data/sensor_cfg.ini + + # Generate unique device key + if [ -f /sys/class/cvi-base/base_uid ]; then + str_value=$(awk '{print $2}' /sys/class/cvi-base/base_uid) + first_uint=$(echo "$str_value" | cut -d'_' -f1) + second_uint=$(echo "$str_value" | cut -d'_' -f2) + echo "$first_uint$second_uint" > /device_key + fi + + # Set iptables rules (skip if already present) + iptables -C INPUT -i eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT 2>/dev/null || \ + iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT + + iptables -C OUTPUT -o eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT 2>/dev/null || \ + iptables -A OUTPUT -o eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT + + iptables -C INPUT -i eth0 -p tcp --sport 22 -m state --state NEW,ESTABLISHED -j ACCEPT 2>/dev/null || \ + iptables -A INPUT -i eth0 -p tcp --sport 22 -m state --state NEW,ESTABLISHED -j ACCEPT + + iptables -C OUTPUT -o eth0 -p tcp --dport 22 -m state --state ESTABLISHED -j ACCEPT 2>/dev/null || \ + iptables -A OUTPUT -o eth0 -p tcp --dport 22 -m state --state ESTABLISHED -j ACCEPT + + iptables -C OUTPUT -o eth0 -p tcp --sport 8000 -m state --state ESTABLISHED -j DROP 2>/dev/null || \ + iptables -A OUTPUT -o eth0 -p tcp --sport 8000 -m state --state ESTABLISHED -j DROP + + # Start services + cp -r /kvmapp/kvm_system /tmp/ + /tmp/kvm_system/kvm_system & + + cp -r /kvmapp/server /tmp/ + /tmp/server/NanoKVM-Server & + ;; + + stop) + killall kvm_system + killall NanoKVM-Server + rm -r /tmp/kvm_system /tmp/server + echo "OK" + ;; + + restart) + killall kvm_system + killall NanoKVM-Server + rm -r /tmp/kvm_system /tmp/server + + cp -r /kvmapp/kvm_system /tmp/ + /tmp/kvm_system/kvm_system & + + cp -r /kvmapp/server /tmp/ + /tmp/server/NanoKVM-Server & + + sync + echo "OK" + ;; + + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 + ;; +esac \ No newline at end of file diff --git a/kvmapp/system/init.d/S98tailscaled b/kvmapp/system/init.d/S98tailscaled new file mode 100644 index 0000000..3c34d64 --- /dev/null +++ b/kvmapp/system/init.d/S98tailscaled @@ -0,0 +1,120 @@ +#!/bin/sh + +DAEMON="tailscaled" +PIDFILE="/var/run/$DAEMON.pid" + +# Set the port to listen on for incoming VPN packets. +# Remote nodes will automatically be informed about the new port number, +# but you might want to configure this in order to set external firewall +# settings. +PORT="41641" + +# Extra flags you might want to pass to tailscaled. +FLAGS="" + +# You need tailscaled at /usr/sbin to server, and tailscale at /usr/bin to operate +# STATIC version needed. Download page at https://pkgs.tailscale.com/stable/#static +PKG_URL_LATEST="https://pkgs.tailscale.com/stable/tailscale_latest_riscv64.tgz" +[ ! -x /usr/sbin/$DAEMON ] && + echo "/usr/sbin/$DAEMON not found, please download it from $PKG_URL_LATEST" && + echo "Then unpack it, copy $DAEMON to /usr/sbin and copy tailscale to /usr/bin" && + exit 1 +VERSION=$(/usr/sbin/$DAEMON --version|sed -n '1p'|xargs echo -n) + +[ -x /usr/bin/tailscale ] || echo "/usr/bin/tailscale not found, your installation of tailscale may be broken" + +# just for those need forwarding +[ ! -f /etc/sysctl.d/99-tailscale.conf ] && + mkdir -p /etc/sysctl.d/ && + echo "missing /etc/sysctl.d/99-tailscale.conf, try make it below:" && + (tee /etc/sysctl.d/99-tailscale.conf </dev/null + [ $? = 0 ] && echo "OK" || echo "FAIL" + ;; + restart|reload) + $0 stop + $0 start + ;; + doc) + cat < None: + if pathlib.Path(temporary).exists(): + shutil.rmtree(temporary) + pathlib.Path(temporary).mkdir() + print(f"Created temporary directory {temporary}") + + +def read(file: str) -> str: + return pathlib.Path(file).read_text().replace("\n", "") + + +def download_firmware() -> None: + print("Downloading firmware...") + + now = int(time.time()) + url = f"https://cdn.sipeed.com/nanokvm/latest.zip?n={now}" + print(f"Downloading firmware from {url}") + + response = requests.get(url) + + if response.status_code != 200: + raise Exception(f"Failed to download firmware, status: {response.status_code}") + + content_type = response.headers.get("content-type") + if content_type != "application/zip": + raise Exception(f"Failed to download firmware, content_type: {content_type}") + + zip_file = f"{temporary}/latest.zip" + with open(zip_file, "wb") as f: + for chunk in response.iter_content(chunk_size=1024): + f.write(chunk) + + with zipfile.ZipFile(zip_file, "r") as f: + f.extractall(temporary) + + print("Completed downloading firmware.") + + +def update() -> None: + backup_dir = pathlib.Path("/root/old") + firmware_dir = pathlib.Path("/kvmapp") + + if backup_dir.exists(): + shutil.rmtree(backup_dir) + + if firmware_dir.exists(): + firmware_dir.rename(backup_dir) + + pathlib.Path(f"{temporary}/latest").rename(firmware_dir) + + +def change_permissions() -> None: + for root, dirs, files in os.walk("/kvmapp"): + os.chmod(root, 0o755) + + for file in files: + file_path = os.path.join(root, file) + os.chmod(file_path, 0o755) + + print("change permissions done") + + +def main() -> None: + try: + print("Stopping nanokvm service...") + os.system("/etc/init.d/S95nanokvm stop") + + print("Staring update...") + + mkdir() + download_firmware() + update() + change_permissions() + + version = read("/kvmapp/version") + print(f"Successfully updated to version: {version}") + print("restart service\nthe nanokvm will reboot") + except Exception as e: + print(f"update failed\n{e}") + finally: + shutil.rmtree(temporary) + os.system("/etc/init.d/S95nanokvm restart") + + +if __name__ == "__main__": + main() diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..3b927bc --- /dev/null +++ b/server/README.md @@ -0,0 +1,99 @@ +# NanoKVM Server + +This is the backend server implementation for NanoKVM. + +For detailed documentation, please visit our [Wiki](https://wiki.sipeed.com/nanokvm). + +## Structure + +```shell +server +├── common // Common utility components +├── config // Server configuration +├── dl_lib // Shared object libraries +├── include // Header files for shared objects +├── logger // Logging system +├── middleware // Server middleware components +├── proto // API request/response definitions +├── router // API route handlers +├── service // Core service implementations +├── utils // Utility functions +└── main.go +``` + +## Configuration + +The configuration file path is `/etc/kvm/server.yaml`. + +```yaml +proto: http +port: + http: 80 + https: 443 +cert: + crt: server.crt + key: server.key + +# Log level (debug/info/warn/error) +# Note: Use 'info' or 'error' in production, 'debug' only for development +logger: + level: info + file: stdout + +# Authentication setting (enable/disable) +# Note: Only disable authentication in development environment +authentication: enable + +jwt: + # JWT secret key. If left empty, a random 64-byte key will be generated automatically. + secretKey: "" + # JWT token expiration time in seconds. Default: 2678400 (31 days) + refreshTokenDuration: 2678400 + # Invalidate all JWT tokens when the user logs out. Default: true + revokeTokensOnLogout: true + +# Address for custom STUN server +# Note: You can disable the STUN service by setting it to 'disable' (e.g., in a LAN environment) +stun: stun.l.google.com:19302 + +# Address and authentication for custom TURN server +turn: + turnAddr: example_addr + turnUser: example_user + turnCred: example_cred +``` + +## Compile & Deploy + +Note: Use Linux operating system (x86-64). This build process is not compatible with ARM, Windows or macOS. + +1. Install the Toolchain + 1. Download the toolchain from the following link: [Download Link](https://sophon-file.sophon.cn/sophon-prod-s3/drive/23/03/07/16/host-tools.tar.gz). + 2. Extract the file and add the `host-tools/gcc/riscv64-linux-musl-x86_64/bin` directory to your PATH environment variable. + 3. Run `riscv64-unknown-linux-musl-gcc -v`. If there is version information in the output, the installation is successful. + +2. Compile the Project + 1. Run `cd server` from the project root directory. + 2. Run `go mod tidy` to install Go dependencies. + 3. (Optional) If you compiled `libkvm.so` yourself, you need to modify its RPATH by `patchelf --add-rpath \$ORIGIN ./dl_lib/libkvm.so`. + 4. Run `CGO_ENABLED=1 GOOS=linux GOARCH=riscv64 CC=riscv64-unknown-linux-musl-gcc CGO_CFLAGS="-mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d" go build` to compile the project. + 5. After compilation, an executable file named `NanoKVM-Server` will be generated. + +3. Modify RPATH + 1. Run `sudo apt install patchelf` or `pip install patchelf` to install patchelf. + 2. Run `patchelf --version`. Ensure the version is 0.14 or higher`. + 3. Run `patchelf --add-rpath \$ORIGIN/dl_lib NanoKVM-Server` to modify the RPATH of the executable file. + +4. Deploy the Application + 1. File uploads requires SSH. Please enable it in the Web Settings: `Settings > SSH`; + 2. Replace the original file in the NanoKVM `/kvmapp/server/` directory with the newly compiled `NanoKVM-Server`. + 3. Restart the service on NanoKVM by executing `/etc/init.d/S95nanokvm restart`. + +## Manually Update + +> File uploads requires SSH. Please enable it in the Web Settings: `Settings > SSH`; + +1. Download the latest application from [GitHub](https://github.com/sipeed/NanoKVM/releases); +2. Unzip the downloaded file and rename the unzipped folder to `kvmapp`; +3. Back up the existing `/kvmapp` directory on your NanoKVM, then replace it with the new `kvmapp` folder; +4. Run `/etc/init.d/S95nanokvm restart` on your NanoKVM to restart the service. diff --git a/server/README_JA.md b/server/README_JA.md new file mode 100644 index 0000000..55a6f40 --- /dev/null +++ b/server/README_JA.md @@ -0,0 +1,97 @@ +# NanoKVM サーバー + +これは NanoKVM のバックエンドサーバーの実装です。 + +詳細なドキュメントについては、[Wiki](https://wiki.sipeed.com/nanokvm) を参照してください。 + +## 構造 + +```shell +server +├── common // 共通ユーティリティコンポーネント +├── config // サーバー設定 +├── dl_lib // 共有オブジェクトライブラリ +├── include // 共有オブジェクトのヘッダーファイル +├── logger // ロギングシステム +├── middleware // サーバーミドルウェアコンポーネント +├── proto // API リクエスト/レスポンス定義 +├── router // API ルートハンドラ +├── service // コアサービスの実装 +├── utils // ユーティリティ関数 +└── main.go +``` + +## 設定 + +設定ファイルのパスは `/etc/kvm/server.yaml` です。 + +```yaml +proto: http +port: + http: 80 + https: 443 +cert: + crt: server.crt + key: server.key + +# ログレベル (debug/info/warn/error) +# 注意: 本番環境では 'info' または 'error' を使用し、'debug' は開発環境でのみ使用してください +logger: + level: info + file: stdout + +# 認証設定 (enable/disable) +# 注意: 認証を無効にするのは開発環境でのみ行ってください +authentication: enable + +jwt: + # JWT 秘密鍵の設定。 空のままにすると、サーバー起動時にランダムな 64 バイトの鍵が自動的に生成されます。 + secretKey: "" + # JWT トークンの有効期限(秒単位)。 デフォルト: 2678400 (31 日) + refreshTokenDuration: 2678400 + # ユーザーがログアウトすると、すべての JWT トークンが無効になります。 デフォルト: true + revokeTokensOnLogout: true + +# カスタム STUN サーバーのアドレス +stun: stun.l.google.com:19302 + +# カスタム TURN サーバーのアドレスと認証情報 +turn: + turnAddr: turn.cloudflare.com:3478 + turnUser: example_user + turnCred: example_cred +``` + +## コンパイルとデプロイ + +注意: Linux オペレーティングシステム (x86-64) を使用してください。このビルドプロセスは ARM、Windows、macOS では互換性がありません。 + +1. ツールチェーンのインストール + 1. 以下のリンクからツールチェーンをダウンロードします: [ダウンロードリンク](https://sophon-file.sophon.cn/sophon-prod-s3/drive/23/03/07/16/host-tools.tar.gz)。 + 2. ファイルを解凍し、`host-tools/gcc/riscv64-linux-musl-x86_64/bin` ディレクトリを PATH 環境変数に追加します。 + 3. `riscv64-unknown-linux-musl-gcc -v` を実行します。バージョン情報が表示されれば、インストールは成功です。 + +2. プロジェクトのコンパイル + 1. プロジェクトのルートディレクトリから `cd server` を実行します。 + 2. `go mod tidy` を実行して Go の依存関係をインストールします。 + 3. `CGO_ENABLED=1 GOOS=linux GOARCH=riscv64 CC=riscv64-unknown-linux-musl-gcc CGO_CFLAGS="-mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d" go build` を実行してプロジェクトをコンパイルします。 + 4. コンパイルが完了すると、`NanoKVM-Server` という名前の実行ファイルが生成されます。 + +3. RPATH の変更 + 1. `sudo apt install patchelf` または `pip install patchelf` を実行して patchelf をインストールします。 + 2. `patchelf --version` を実行します。バージョンが 0.14 以上であることを確認します。 + 3. `patchelf --add-rpath \$ORIGIN/dl_lib NanoKVM-Server` を実行して、実行ファイルの RPATH を変更します。 + +4. アプリケーションのデプロイ + 1. デプロイ前に、ブラウザでアプリケーションを最新バージョンに更新します。手順は[こちら](https://wiki.sipeed.com/hardware/en/kvm/NanoKVM/system/updating.html)を参照してください。 + 2. コンパイルして生成された `NanoKVM-Server` ファイルを使用して、NanoKVM の `/kvmapp/server/` ディレクトリ内の元のファイルを置き換えます。 + 3. NanoKVM で `/etc/init.d/S95nanokvm restart` を実行してサービスを再起動します。 + +## 手動更新 + +> ファイルのアップロードには SSH が必要です。Web 設定で有効にしてください: `設定 > SSH` + +1. [GitHub](https://github.com/sipeed/NanoKVM/releases) から最新のアプリケーションをダウンロードします。 +2. ダウンロードしたファイルを解凍し、解凍したフォルダーの名前を `kvmapp` に変更します。 +3. NanoKVM 上の既存の `/kvmapp` ディレクトリをバックアップし、新しい `kvmapp` フォルダーに置き換えます。 +4. NanoKVM で `/etc/init.d/S95nanokvm restart` を実行してサービスを再起動します。 diff --git a/server/README_ZH.md b/server/README_ZH.md new file mode 100644 index 0000000..ad8ec12 --- /dev/null +++ b/server/README_ZH.md @@ -0,0 +1,96 @@ +# NanoKVM Server + +NanoKVM 后端服务的代码。更多文档请参考 [Wiki](https://wiki.sipeed.com/nanokvm) 。 + +## 目录结构 + +```shell +server +├── common // 公用组件 +├── config // 服务配置 +├── dl_lib // so 文件 +├── include // 头文件 +├── logger // 服务日志 +├── middleware // 中间件 +├── proto // api 请求响应参数 +├── router // api 路由 +├── service // api 处理逻辑 +├── utils // 工具函数 +└── main.go +``` + +## 配置文件 + +配置文件路径为 `/etc/kvm/server.yaml`。 + +```yaml +proto: http +port: + http: 80 + https: 443 +cert: + crt: server.crt + key: server.key + +# 日志级别(debug/info/warn/error) +# 注意:在生产环境中使用 info 或 error。debug 模式仅在开发环境中使用。 +logger: + level: info + file: stdout + +# 鉴权设置(enable/disable) +# 注意:生产环境中请勿使用 disable。 +authentication: enable + +jwt: + # jwt 密钥。设置为空则使用随机生成的64位密钥 + secretKey: "" + # jwt token 过期时间(单位:秒),默认为2678400(31天) + refreshTokenDuration: 2678400 + # 在帐号登出时是否使所有 jwt token 失效。默认为 true + revokeTokensOnLogout: true + +# 自定义 STUN 服务器的地址 +# 注意:可以设置为“disable”来禁用 STUN 服务(例如在局域网环境中使用时) +stun: stun.l.google.com:19302 + +turn: + turnAddr: example_addr + turnUser: example_user + turnCred: example_cred +``` + +## 编译部署 + +**注意:请使用 Linux 操作系统(x86-64)。该工具链无法在 ARM、Windows 或 macOS 下使用。** + +1. 安装工具链 + 1. 下载工具链:[下载地址](https://sophon-file.sophon.cn/sophon-prod-s3/drive/23/03/07/16/host-tools.tar.gz); + 2. 解压下载文件,然后将 `host-tools/gcc/riscv64-linux-musl-x86_64/bin` 目录加入到环境变量; + 3. 执行 `riscv64-unknown-linux-musl-gcc -v`,如果显示版本信息则安装成功。 + +2. 编译 + 1. 在项目根目录下执行 `cd server` 进入 server 目录; + 2. 执行 `go mod tidy` 安装 Go 依赖包; + 3. (可选)如果您手动编译了 `libkvm.so`,则需要通过 `patchelf --add-rpath \$ORIGIN ./dl_lib/libkvm.so` 修改其 RPATH 属性。 + 4. 执行 `CGO_ENABLED=1 GOOS=linux GOARCH=riscv64 CC=riscv64-unknown-linux-musl-gcc CGO_CFLAGS="-mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d" go build` 进行编译; + 5. 编译完成后,会生成可执行文件 `NanoKVM-Server`。 + +3. 修改 RPATH + 1. 执行 `sudo apt install patchelf` 或 `pip install patchelf` 安装 patchelf; + 2. 执行 `patchelf --version`,确保版本大于等于 0.14; + 3. 执行 `patchelf --add-rpath \$ORIGIN/dl_lib NanoKVM-Server` 修改可执行文件的 RPATH 属性。 + +4. 部署 + 1. 上传文件需要启用 SSH 功能。请在 Web `设置 - SSH` 中检查 SSH 是否已经启用; + 2. 使用编译生成的 `NanoKVM-Server` 文件,替换 NanoKVM 中 `/kvmapp/server/` 目录下的原始文件; + 3. 在 NanoKVM 中执行 `/etc/init.d/S95nanokvm restart` 重启服务。 + +## 手动更新 + +> 请确保已经在 Web 界面的 `设置 - SSH` 中启用了 SSH 功能,以便上传文件。 + +1. 从 [GitHub](https://github.com/sipeed/NanoKVM/releases) 下载最新的应用安装包; +2. 解压缩下载的安装包,并将解压后的文件夹重命名为 `kvmapp`; +3. 备份 NanoKVM 系统中的 `/kvmapp` 目录,然后用解压后的 `kvmapp` 文件夹替换现有目录。 +4. 在 NanoKVM 中执行 `/etc/init.d/S95nanokvm restart` 重启服务。 diff --git a/server/build.sh b/server/build.sh new file mode 100644 index 0000000..a21ab6f --- /dev/null +++ b/server/build.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -e + +# Configuration Variables +BINARY_NAME="NanoKVM-Server" +CC_COMPILER="riscv64-unknown-linux-musl-gcc" +CGO_CFLAGS_OPTS="-mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d" + +# Define colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Helper function to check if a command exists +check_dependency() { + if ! command -v "$1" &> /dev/null; then + echo -e "${RED}[ERROR] Required command '$1' not found.${NC}" + echo "Please install it or ensure it is in your PATH." + exit 1 + fi +} + +# ------------------------------------------------------------------------------ +# Step 1: Check Prerequisites +# ------------------------------------------------------------------------------ +echo -e "${YELLOW}[INFO] Checking build environment...${NC}" + +check_dependency "go" +check_dependency "patchelf" +check_dependency "$CC_COMPILER" + +echo -e "${GREEN}[OK] All dependencies found.${NC}" + +# ------------------------------------------------------------------------------ +# Step 2: Build the Binary +# ------------------------------------------------------------------------------ +echo -e "${YELLOW}[INFO] Starting cross-compilation for RISC-V 64-bit (BoringCrypto enabled)...${NC}" + +export CGO_ENABLED=1 +export GOOS=linux +export GOARCH=riscv64 +# export GOEXPERIMENT=boringcrypto +export CC="$CC_COMPILER" +export CGO_CFLAGS="$CGO_CFLAGS_OPTS" + +go build -o "$BINARY_NAME" -v + +if [ -f "$BINARY_NAME" ]; then + echo -e "${GREEN}[SUCCESS] Binary '$BINARY_NAME' created successfully.${NC}" +else + echo -e "${RED}[ERROR] Build failed. Binary not found.${NC}" + exit 1 +fi + +# ------------------------------------------------------------------------------ +# Step 3: Patch RPATH +# ------------------------------------------------------------------------------ +echo -e "${YELLOW}[INFO] Patching RPATH with patchelf...${NC}" + +patchelf --add-rpath '$ORIGIN/dl_lib' "$BINARY_NAME" + +echo -e "${GREEN}[DONE] Build script completed successfully!${NC}" diff --git a/server/common/kvm_vision.go b/server/common/kvm_vision.go new file mode 100644 index 0000000..99d3a95 --- /dev/null +++ b/server/common/kvm_vision.go @@ -0,0 +1,111 @@ +package common + +/* + #cgo CFLAGS: -I../include + #cgo LDFLAGS: -L../dl_lib -lkvm + #include "kvm_vision.h" +*/ +import "C" +import ( + "sync" + "unsafe" + + log "github.com/sirupsen/logrus" +) + +var ( + kvmVision *KvmVision + kvmVisionOnce sync.Once +) + +type KvmVision struct{} + +func GetKvmVision() *KvmVision { + kvmVisionOnce.Do(func() { + kvmVision = &KvmVision{} + + logLevel := C.uint8_t(0) + C.kvmv_init(logLevel) + log.Debugf("kvm vision initialized") + }) + + return kvmVision +} + +func (k *KvmVision) ReadMjpeg(width uint16, height uint16, quality uint16) (data []byte, result int) { + var ( + kvmData *C.uint8_t + dataSize C.uint32_t + ) + + result = int(C.kvmv_read_img( + C.uint16_t(width), + C.uint16_t(height), + C.uint8_t(0), + C.uint16_t(quality), + &kvmData, + &dataSize, + )) + if result < 0 { + log.Errorf("failed to read kvm image: %v", result) + return + } + defer C.free_kvmv_data(&kvmData) + + data = C.GoBytes(unsafe.Pointer(kvmData), C.int(dataSize)) + return +} + +func (k *KvmVision) ReadH264(width uint16, height uint16, bitRate uint16) (data []byte, result int) { + var ( + kvmData *C.uint8_t + dataSize C.uint32_t + ) + + result = int(C.kvmv_read_img( + C.uint16_t(width), + C.uint16_t(height), + C.uint8_t(1), + C.uint16_t(bitRate), + &kvmData, + &dataSize, + )) + if result < 0 { + log.Errorf("failed to read kvm image: %v", result) + return + } + defer C.free_kvmv_data(&kvmData) + + data = C.GoBytes(unsafe.Pointer(kvmData), C.int(dataSize)) + return +} + +func (k *KvmVision) SetHDMI(enable bool) int { + hdmiEnable := C.uint8_t(0) + if enable { + hdmiEnable = C.uint8_t(1) + } + + result := int(C.kvmv_hdmi_control(hdmiEnable)) + if result < 0 { + log.Errorf("failed to set hdmi to %t", enable) + return result + } + + return result +} + +func (k *KvmVision) SetGop(gop uint8) { + _gop := C.uint8_t(gop) + C.set_h264_gop(_gop) +} + +func (k *KvmVision) SetFrameDetect(frame uint8) { + _frame := C.uint8_t(frame) + C.set_frame_detact(_frame) +} + +func (k *KvmVision) Close() { + C.kvmv_deinit() + log.Debugf("stop kvm vision...") +} diff --git a/server/common/screen.go b/server/common/screen.go new file mode 100644 index 0000000..2f116c9 --- /dev/null +++ b/server/common/screen.go @@ -0,0 +1,105 @@ +package common + +import "sync" + +type Screen struct { + Width uint16 + Height uint16 + FPS int + Quality uint16 + BitRate uint16 + GOP uint8 +} + +var ( + screen *Screen + screenOnce sync.Once +) + +// ResolutionMap height to width +var ResolutionMap = map[uint16]uint16{ + 1080: 1920, + 720: 1280, + 600: 800, + 480: 640, + 0: 0, +} + +var QualityMap = map[uint16]bool{ + 100: true, + 80: true, + 60: true, + 50: true, +} + +var BitRateMap = map[uint16]bool{ + 5000: true, + 3000: true, + 2000: true, + 1000: true, +} + +func GetScreen() *Screen { + screenOnce.Do(func() { + screen = &Screen{ + Width: 0, + Height: 0, + Quality: 80, + FPS: 30, + BitRate: 3000, + GOP: 30, + } + }) + + return screen +} + +func SetScreen(key string, value int) { + switch key { + case "resolution": + height := uint16(value) + if width, ok := ResolutionMap[height]; ok { + screen.Width = width + screen.Height = height + } + + case "quality": + if value > 100 { + screen.BitRate = uint16(value) + } else { + screen.Quality = uint16(value) + } + + case "fps": + screen.FPS = validateFPS(value) + + case "gop": + screen.GOP = uint8(value) + } +} + +func CheckScreen() { + if _, ok := ResolutionMap[screen.Height]; !ok { + screen.Width = 1920 + screen.Height = 1080 + } + + if _, ok := QualityMap[screen.Quality]; !ok { + screen.Quality = 80 + } + + if _, ok := BitRateMap[screen.BitRate]; !ok { + screen.BitRate = 3000 + } +} + +func validateFPS(fps int) int { + if fps > 60 { + return 60 + } + if fps < 10 { + return 10 + } + + return fps +} diff --git a/server/config/config.go b/server/config/config.go new file mode 100644 index 0000000..ab982b7 --- /dev/null +++ b/server/config/config.go @@ -0,0 +1,122 @@ +package config + +import ( + "bytes" + "errors" + "log" + "os" + "sync" + + "github.com/spf13/viper" + "gopkg.in/yaml.v3" +) + +var ( + instance Config + once sync.Once +) + +func GetInstance() *Config { + once.Do(initialize) + + return &instance +} + +func initialize() { + if err := readByFile(); err != nil { + if errors.As(err, &viper.ConfigFileNotFoundError{}) { + create() + } + + if err = readByDefault(); err != nil { + log.Fatalf("Failed to read default configuration!") + } + + log.Println("using default configuration") + } + + if err := validate(); err != nil { + log.Fatalf("Failed to validate configuration!") + } + + if err := viper.Unmarshal(&instance); err != nil { + log.Fatalf("Failed to parse configuration: %s", err) + } + + checkDefaultValue() + + if instance.Authentication == "disable" { + log.Println("NOTICE: Authentication is disabled! Please ensure your service is secure!") + } + + log.Println("config loaded successfully") +} + +func readByFile() error { + viper.SetConfigName("server") + viper.SetConfigType("yaml") + viper.AddConfigPath("/etc/kvm/") + + return viper.ReadInConfig() +} + +func readByDefault() error { + data, err := yaml.Marshal(defaultConfig) + if err != nil { + log.Printf("failed to marshal default config: %s", err) + return err + } + + return viper.ReadConfig(bytes.NewBuffer(data)) +} + +// Create configuration file. +func create() { + var ( + file *os.File + data []byte + err error + ) + + _ = os.MkdirAll("/etc/kvm", 0o644) + + file, err = os.OpenFile("/etc/kvm/server.yaml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644) + if err != nil { + log.Printf("open config failed: %s", err) + return + } + defer func() { + _ = file.Close() + }() + + if data, err = yaml.Marshal(defaultConfig); err != nil { + log.Printf("failed to marshal default config: %s", err) + return + } + + if _, err = file.Write(data); err != nil { + log.Printf("failed to save config: %s", err) + return + } + + if err = file.Sync(); err != nil { + log.Printf("failed to sync config: %s", err) + return + } + + log.Println("create file /etc/kvm/server.yaml with default configuration") +} + +// Validate the configuration. This is to ensure compatibility with earlier versions. +func validate() error { + if viper.GetInt("port.http") > 0 && viper.GetInt("port.https") > 0 { + return nil + } + + _ = os.Remove("/etc/kvm/server.yaml") + log.Println("delete empty configuration file") + + create() + + return readByDefault() +} diff --git a/server/config/default.go b/server/config/default.go new file mode 100644 index 0000000..a841e9e --- /dev/null +++ b/server/config/default.go @@ -0,0 +1,50 @@ +package config + +var defaultConfig = &Config{ + Proto: "http", + Port: Port{ + Http: 80, + Https: 443, + }, + Cert: Cert{ + Crt: "server.crt", + Key: "server.key", + }, + Logger: Logger{ + Level: "info", + File: "stdout", + }, + JWT: JWT{ + SecretKey: "", + RefreshTokenDuration: 2678400, + RevokeTokensOnLogout: true, + }, + Stun: "stun.l.google.com:19302", + Turn: Turn{ + TurnAddr: "", + TurnUser: "", + TurnCred: "", + }, + Authentication: "enable", +} + +func checkDefaultValue() { + if instance.JWT.SecretKey == "" { + instance.JWT.SecretKey = generateRandomSecretKey() + instance.JWT.RevokeTokensOnLogout = true + } + + if instance.JWT.RefreshTokenDuration == 0 { + instance.JWT.RefreshTokenDuration = 2678400 + } + + if instance.Stun == "" { + instance.Stun = "stun.l.google.com:19302" + } + + if instance.Authentication == "" { + instance.Authentication = "enable" + } + + instance.Hardware = getHardware() +} diff --git a/server/config/file.go b/server/config/file.go new file mode 100644 index 0000000..1eaa8d3 --- /dev/null +++ b/server/config/file.go @@ -0,0 +1,45 @@ +package config + +import ( + "os" + + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" +) + +const ConfigurationFile = "/etc/kvm/server.yaml" + +func Read() (*Config, error) { + data, err := os.ReadFile(ConfigurationFile) + if err != nil { + log.Errorf("failed to read config: %v", err) + return nil, err + } + + var conf Config + + if err := yaml.Unmarshal(data, &conf); err != nil { + log.Fatalf("failed to unmarshal config: %v", err) + return nil, err + } + + log.Debugf("read %s successfully", ConfigurationFile) + return &conf, nil +} + +func Write(conf *Config) error { + data, err := yaml.Marshal(&conf) + if err != nil { + log.Errorf("failed to marshal config: %v", err) + return err + } + + err = os.WriteFile(ConfigurationFile, data, 0644) + if err != nil { + log.Errorf("failed to write config: %v", err) + return err + } + + log.Debugf("write to %s successfully", ConfigurationFile) + return nil +} diff --git a/server/config/hardware.go b/server/config/hardware.go new file mode 100644 index 0000000..bea3872 --- /dev/null +++ b/server/config/hardware.go @@ -0,0 +1,95 @@ +package config + +import ( + "os" + "strings" + + log "github.com/sirupsen/logrus" +) + +type HWVersion int + +const ( + HWVersionAlpha HWVersion = iota + HWVersionBeta + HWVersionPcie + + HWVersionFile = "/etc/kvm/hw" +) + +var HWAlpha = Hardware{ + Version: HWVersionAlpha, + GPIOReset: "/sys/class/gpio/gpio507/value", + GPIOPower: "/sys/class/gpio/gpio503/value", + GPIOPowerLED: "/sys/class/gpio/gpio504/value", + GPIOHDDLed: "/sys/class/gpio/gpio505/value", +} + +var HWBeta = Hardware{ + Version: HWVersionBeta, + GPIOReset: "/sys/class/gpio/gpio505/value", + GPIOPower: "/sys/class/gpio/gpio503/value", + GPIOPowerLED: "/sys/class/gpio/gpio504/value", + GPIOHDDLed: "", +} + +var HWPcie = Hardware{ + Version: HWVersionPcie, + GPIOReset: "/sys/class/gpio/gpio505/value", + GPIOPower: "/sys/class/gpio/gpio503/value", + GPIOPowerLED: "/sys/class/gpio/gpio504/value", + GPIOHDDLed: "", +} + +func (h HWVersion) String() string { + switch h { + case HWVersionAlpha: + return "Alpha" + case HWVersionBeta: + return "Beta" + case HWVersionPcie: + return "PCIE" + default: + return "Unknown" + } +} + +func GetHwVersion() HWVersion { + content, err := os.ReadFile(HWVersionFile) + if err != nil { + return HWVersionAlpha + } + + version := strings.ReplaceAll(string(content), "\n", "") + switch version { + case "alpha": + return HWVersionAlpha + case "beta": + return HWVersionBeta + case "pcie": + return HWVersionPcie + default: + return HWVersionAlpha + } +} + +func getHardware() (h Hardware) { + version := GetHwVersion() + + switch version { + case HWVersionAlpha: + h = HWAlpha + + case HWVersionBeta: + h = HWBeta + + case HWVersionPcie: + h = HWPcie + + default: + h = HWAlpha + log.Errorf("Unsupported hardware version: %s", version) + } + + return +} diff --git a/server/config/jwt.go b/server/config/jwt.go new file mode 100644 index 0000000..9edd85b --- /dev/null +++ b/server/config/jwt.go @@ -0,0 +1,28 @@ +package config + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "time" +) + +// RegenerateSecretKey regenerate secret key when logout +func RegenerateSecretKey() { + if instance.JWT.RevokeTokensOnLogout { + instance.JWT.SecretKey = generateRandomSecretKey() + } +} + +// Generate random string for secret key. +func generateRandomSecretKey() string { + b := make([]byte, 64) + _, err := rand.Read(b) + if err != nil { + currentTime := time.Now().UnixNano() + timeString := fmt.Sprintf("%d", currentTime) + return fmt.Sprintf("%064s", timeString) + } + + return base64.URLEncoding.EncodeToString(b) +} diff --git a/server/config/types.go b/server/config/types.go new file mode 100644 index 0000000..2f8f069 --- /dev/null +++ b/server/config/types.go @@ -0,0 +1,49 @@ +package config + +type Config struct { + Proto string `yaml:"proto"` + Port Port `yaml:"port"` + Cert Cert `yaml:"cert"` + Logger Logger `yaml:"logger"` + Authentication string `yaml:"authentication"` + JWT JWT `yaml:"jwt"` + Stun string `yaml:"stun"` + Turn Turn `yaml:"turn"` + + Hardware Hardware `yaml:"-"` +} + +type Logger struct { + Level string `yaml:"level"` + File string `yaml:"file"` +} + +type Port struct { + Http int `yaml:"http"` + Https int `yaml:"https"` +} + +type Cert struct { + Crt string `yaml:"crt"` + Key string `yaml:"key"` +} + +type JWT struct { + SecretKey string `yaml:"secretKey"` + RefreshTokenDuration uint64 `yaml:"refreshTokenDuration"` + RevokeTokensOnLogout bool `yaml:"revokeTokensOnLogout"` +} + +type Turn struct { + TurnAddr string `yaml:"turnAddr"` + TurnUser string `yaml:"turnUser"` + TurnCred string `yaml:"turnCred"` +} + +type Hardware struct { + Version HWVersion `yaml:"-"` + GPIOReset string `yaml:"-"` + GPIOPower string `yaml:"-"` + GPIOPowerLED string `yaml:"-"` + GPIOHDDLed string `yaml:"-"` +} diff --git a/server/dl_lib/libaaccomm2.so b/server/dl_lib/libaaccomm2.so new file mode 100644 index 0000000..4818c55 Binary files /dev/null and b/server/dl_lib/libaaccomm2.so differ diff --git a/server/dl_lib/libaacdec2.so b/server/dl_lib/libaacdec2.so new file mode 100644 index 0000000..2e9653b Binary files /dev/null and b/server/dl_lib/libaacdec2.so differ diff --git a/server/dl_lib/libaacenc2.so b/server/dl_lib/libaacenc2.so new file mode 100644 index 0000000..68f9e40 Binary files /dev/null and b/server/dl_lib/libaacenc2.so differ diff --git a/server/dl_lib/libaacsbrdec2.so b/server/dl_lib/libaacsbrdec2.so new file mode 100644 index 0000000..8b7ac66 Binary files /dev/null and b/server/dl_lib/libaacsbrdec2.so differ diff --git a/server/dl_lib/libaacsbrenc2.so b/server/dl_lib/libaacsbrenc2.so new file mode 100644 index 0000000..d8b2a8e Binary files /dev/null and b/server/dl_lib/libaacsbrenc2.so differ diff --git a/server/dl_lib/libae.so b/server/dl_lib/libae.so new file mode 100644 index 0000000..7b3b72b Binary files /dev/null and b/server/dl_lib/libae.so differ diff --git a/server/dl_lib/libaf.so b/server/dl_lib/libaf.so new file mode 100644 index 0000000..882ecf3 Binary files /dev/null and b/server/dl_lib/libaf.so differ diff --git a/server/dl_lib/libawb.so b/server/dl_lib/libawb.so new file mode 100644 index 0000000..5e00f3a Binary files /dev/null and b/server/dl_lib/libawb.so differ diff --git a/server/dl_lib/libcli.so b/server/dl_lib/libcli.so new file mode 100644 index 0000000..abc9aae Binary files /dev/null and b/server/dl_lib/libcli.so differ diff --git a/server/dl_lib/libcvi_RES1.so b/server/dl_lib/libcvi_RES1.so new file mode 100644 index 0000000..d1b4d5b Binary files /dev/null and b/server/dl_lib/libcvi_RES1.so differ diff --git a/server/dl_lib/libcvi_VoiceEngine.so b/server/dl_lib/libcvi_VoiceEngine.so new file mode 100644 index 0000000..cd26e5d Binary files /dev/null and b/server/dl_lib/libcvi_VoiceEngine.so differ diff --git a/server/dl_lib/libcvi_audio.so b/server/dl_lib/libcvi_audio.so new file mode 100644 index 0000000..6efbfc3 Binary files /dev/null and b/server/dl_lib/libcvi_audio.so differ diff --git a/server/dl_lib/libcvi_bin.so b/server/dl_lib/libcvi_bin.so new file mode 100644 index 0000000..0414a13 Binary files /dev/null and b/server/dl_lib/libcvi_bin.so differ diff --git a/server/dl_lib/libcvi_bin_isp.so b/server/dl_lib/libcvi_bin_isp.so new file mode 100644 index 0000000..5b26a29 Binary files /dev/null and b/server/dl_lib/libcvi_bin_isp.so differ diff --git a/server/dl_lib/libcvi_ispd2.so b/server/dl_lib/libcvi_ispd2.so new file mode 100644 index 0000000..800a49b Binary files /dev/null and b/server/dl_lib/libcvi_ispd2.so differ diff --git a/server/dl_lib/libcvi_ive.so b/server/dl_lib/libcvi_ive.so new file mode 100644 index 0000000..2f56399 Binary files /dev/null and b/server/dl_lib/libcvi_ive.so differ diff --git a/server/dl_lib/libcvi_ssp.so b/server/dl_lib/libcvi_ssp.so new file mode 100644 index 0000000..a8c03c3 Binary files /dev/null and b/server/dl_lib/libcvi_ssp.so differ diff --git a/server/dl_lib/libcvi_vqe.so b/server/dl_lib/libcvi_vqe.so new file mode 100644 index 0000000..c46e91a Binary files /dev/null and b/server/dl_lib/libcvi_vqe.so differ diff --git a/server/dl_lib/libdnvqe.so b/server/dl_lib/libdnvqe.so new file mode 100644 index 0000000..817abe6 Binary files /dev/null and b/server/dl_lib/libdnvqe.so differ diff --git a/server/dl_lib/libini.so b/server/dl_lib/libini.so new file mode 100644 index 0000000..5d3d8bc Binary files /dev/null and b/server/dl_lib/libini.so differ diff --git a/server/dl_lib/libisp.so b/server/dl_lib/libisp.so new file mode 100644 index 0000000..ff652ba Binary files /dev/null and b/server/dl_lib/libisp.so differ diff --git a/server/dl_lib/libisp_algo.so b/server/dl_lib/libisp_algo.so new file mode 100644 index 0000000..e90d61c Binary files /dev/null and b/server/dl_lib/libisp_algo.so differ diff --git a/server/dl_lib/libjson-c.so.5 b/server/dl_lib/libjson-c.so.5 new file mode 100644 index 0000000..c1a7526 Binary files /dev/null and b/server/dl_lib/libjson-c.so.5 differ diff --git a/server/dl_lib/libkvm.so b/server/dl_lib/libkvm.so new file mode 100644 index 0000000..a3a89bb Binary files /dev/null and b/server/dl_lib/libkvm.so differ diff --git a/server/dl_lib/libkvm_mmf.so b/server/dl_lib/libkvm_mmf.so new file mode 100644 index 0000000..fc56b31 Binary files /dev/null and b/server/dl_lib/libkvm_mmf.so differ diff --git a/server/dl_lib/libmipi_tx.so b/server/dl_lib/libmipi_tx.so new file mode 100644 index 0000000..f11c05f Binary files /dev/null and b/server/dl_lib/libmipi_tx.so differ diff --git a/server/dl_lib/libmisc.so b/server/dl_lib/libmisc.so new file mode 100644 index 0000000..2d97541 Binary files /dev/null and b/server/dl_lib/libmisc.so differ diff --git a/server/dl_lib/libopencv_core.so.409 b/server/dl_lib/libopencv_core.so.409 new file mode 100644 index 0000000..05342c4 Binary files /dev/null and b/server/dl_lib/libopencv_core.so.409 differ diff --git a/server/dl_lib/libopencv_highgui.so.409 b/server/dl_lib/libopencv_highgui.so.409 new file mode 100644 index 0000000..e08b1f4 Binary files /dev/null and b/server/dl_lib/libopencv_highgui.so.409 differ diff --git a/server/dl_lib/libopencv_imgcodecs.so.409 b/server/dl_lib/libopencv_imgcodecs.so.409 new file mode 100644 index 0000000..a552883 Binary files /dev/null and b/server/dl_lib/libopencv_imgcodecs.so.409 differ diff --git a/server/dl_lib/libopencv_imgproc.so.409 b/server/dl_lib/libopencv_imgproc.so.409 new file mode 100644 index 0000000..8dfec00 Binary files /dev/null and b/server/dl_lib/libopencv_imgproc.so.409 differ diff --git a/server/dl_lib/libosdc.so b/server/dl_lib/libosdc.so new file mode 100644 index 0000000..64c015d Binary files /dev/null and b/server/dl_lib/libosdc.so differ diff --git a/server/dl_lib/libraw_dump.so b/server/dl_lib/libraw_dump.so new file mode 100644 index 0000000..8f06279 Binary files /dev/null and b/server/dl_lib/libraw_dump.so differ diff --git a/server/dl_lib/libsys.so b/server/dl_lib/libsys.so new file mode 100644 index 0000000..1532bdd Binary files /dev/null and b/server/dl_lib/libsys.so differ diff --git a/server/dl_lib/libtinyalsa.so b/server/dl_lib/libtinyalsa.so new file mode 100644 index 0000000..8b6dcc2 Binary files /dev/null and b/server/dl_lib/libtinyalsa.so differ diff --git a/server/dl_lib/libvdec.so b/server/dl_lib/libvdec.so new file mode 100644 index 0000000..7939c1e Binary files /dev/null and b/server/dl_lib/libvdec.so differ diff --git a/server/dl_lib/libvenc.so b/server/dl_lib/libvenc.so new file mode 100644 index 0000000..2daad0d Binary files /dev/null and b/server/dl_lib/libvenc.so differ diff --git a/server/dl_lib/libvpu.so b/server/dl_lib/libvpu.so new file mode 100644 index 0000000..a915eba Binary files /dev/null and b/server/dl_lib/libvpu.so differ diff --git a/server/go.mod b/server/go.mod new file mode 100644 index 0000000..eff0892 --- /dev/null +++ b/server/go.mod @@ -0,0 +1,79 @@ +module NanoKVM-Server + +go 1.24.0 + +require ( + github.com/creack/pty v1.1.24 + github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0 + github.com/gin-gonic/gin v1.10.0 + github.com/go-playground/validator/v10 v10.20.0 + github.com/golang-jwt/jwt/v5 v5.2.2 + github.com/gorilla/websocket v1.5.3 + github.com/mervick/aes-everywhere/go/aes256 v0.0.0-20240803013625-6759956693c0 + github.com/pion/dtls/v3 v3.0.3 + github.com/pion/rtp v1.8.18 + github.com/pion/webrtc/v4 v4.0.1 + github.com/rs/cors/wrapper/gin v0.0.0-20240830163046-1084d89a1692 + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/viper v1.19.0 + github.com/unrolled/secure v1.15.0 + golang.org/x/crypto v0.45.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pion/datachannel v1.5.9 // indirect + github.com/pion/ice/v4 v4.0.2 // indirect + github.com/pion/interceptor v0.1.39 // indirect + github.com/pion/logging v0.2.3 // indirect + github.com/pion/mdns/v2 v2.0.7 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/rtcp v1.2.15 // indirect + github.com/pion/sctp v1.8.33 // indirect + github.com/pion/sdp/v3 v3.0.9 // indirect + github.com/pion/srtp/v3 v3.0.4 // indirect + github.com/pion/stun/v3 v3.0.0 // indirect + github.com/pion/transport/v3 v3.0.7 // indirect + github.com/pion/turn/v4 v4.0.0 // indirect + github.com/rs/cors v1.11.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/wlynxg/anet v0.0.3 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect +) diff --git a/server/go.sum b/server/go.sum new file mode 100644 index 0000000..2ada1f1 --- /dev/null +++ b/server/go.sum @@ -0,0 +1,186 @@ +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0 h1:EUFmvQ8ffefnSAmaUZd9HZYZSw9w/bFjp3FiNaJ5WmE= +github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mervick/aes-everywhere/go/aes256 v0.0.0-20240803013625-6759956693c0 h1:IfWxTv9SQWw2Vw48Y2CZvVam1WeDBASWnlvNL8QwyQs= +github.com/mervick/aes-everywhere/go/aes256 v0.0.0-20240803013625-6759956693c0/go.mod h1:Eb5RMoo9kOQra/2uRiUTGP+LfNuM13Vqm7y7P34+KKo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pion/datachannel v1.5.9 h1:LpIWAOYPyDrXtU+BW7X0Yt/vGtYxtXQ8ql7dFfYUVZA= +github.com/pion/datachannel v1.5.9/go.mod h1:kDUuk4CU4Uxp82NH4LQZbISULkX/HtzKa4P7ldf9izE= +github.com/pion/dtls/v3 v3.0.3 h1:j5ajZbQwff7Z8k3pE3S+rQ4STvKvXUdKsi/07ka+OWM= +github.com/pion/dtls/v3 v3.0.3/go.mod h1:weOTUyIV4z0bQaVzKe8kpaP17+us3yAuiQsEAG1STMU= +github.com/pion/ice/v4 v4.0.2 h1:1JhBRX8iQLi0+TfcavTjPjI6GO41MFn4CeTBX+Y9h5s= +github.com/pion/ice/v4 v4.0.2/go.mod h1:DCdqyzgtsDNYN6/3U8044j3U7qsJ9KFJC92VnOWHvXg= +github.com/pion/interceptor v0.1.39 h1:Y6k0bN9Y3Lg/Wb21JBWp480tohtns8ybJ037AGr9UuA= +github.com/pion/interceptor v0.1.39/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic= +github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= +github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= +github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= +github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= +github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= +github.com/pion/rtp v1.8.18 h1:yEAb4+4a8nkPCecWzQB6V/uEU18X1lQCGAQCjP+pyvU= +github.com/pion/rtp v1.8.18/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= +github.com/pion/sctp v1.8.33 h1:dSE4wX6uTJBcNm8+YlMg7lw1wqyKHggsP5uKbdj+NZw= +github.com/pion/sctp v1.8.33/go.mod h1:beTnqSzewI53KWoG3nqB282oDMGrhNxBdb+JZnkCwRM= +github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY= +github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M= +github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M= +github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ= +github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= +github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= +github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= +github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= +github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= +github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= +github.com/pion/webrtc/v4 v4.0.1 h1:6Unwc6JzoTsjxetcAIoWH81RUM4K5dBc1BbJGcF9WVE= +github.com/pion/webrtc/v4 v4.0.1/go.mod h1:SfNn8CcFxR6OUVjLXVslAQ3a3994JhyE3Hw1jAuqEto= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= +github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors/wrapper/gin v0.0.0-20240830163046-1084d89a1692 h1:lwzJgPw5Y6pvC8mwbedX9HfdywUKcpNdcviftZsb1uY= +github.com/rs/cors/wrapper/gin v0.0.0-20240830163046-1084d89a1692/go.mod h1:742Ialb8SOs5yB2PqRDzFcyND3280PoaS5/wcKQUQKE= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/unrolled/secure v1.15.0 h1:q7x+pdp8jAHnbzxu6UheP8fRlG/rwYTb8TPuQ3rn9Og= +github.com/unrolled/secure v1.15.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= +github.com/wlynxg/anet v0.0.3 h1:PvR53psxFXstc12jelG6f1Lv4MWqE0tI76/hHGjh9rg= +github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/server/include/kvm_vision.h b/server/include/kvm_vision.h new file mode 100644 index 0000000..4f7af4f --- /dev/null +++ b/server/include/kvm_vision.h @@ -0,0 +1,74 @@ + + +#ifndef KVM_VISION_H_ +#define KVM_VISION_H_ + +#ifdef __cplusplus +extern "C" { +#endif +#include /* low-level i/o */ +#include +#include +#include +#include +#include +#include +#include + +#define IMG_BUFFER_FULL -3 +#define IMG_VENC_ERROR -2 +#define IMG_NOT_EXIST -1 +#define IMG_MJPEG_TYPE 0 +#define IMG_H264_TYPE_SPS 1 +#define IMG_H264_TYPE_PPS 2 +#define IMG_H264_TYPE_IF 3 +#define IMG_H264_TYPE_PF 4 + +#define NORMAL_RES 0 +#define NEW_RES 1 +#define UNSUPPORT_RES 2 +#define UNKNOWN_RES 3 +#define ERROR_RES 4 + +void kvmv_init(uint8_t _debug_info_en); +void set_venc_auto_recyc(uint8_t _enable); +/********************************************************************************** + * @name kvmv_read_img + * @author Sipeed BuGu + * @date 2024/10/25 + * @version R1.0 + * @brief Acquire the encoded image with auto init + * @param _width @input: Output image width + * @param _height @input: Output image height + * @param _type @input: Encode type + * @param _qlty @input: MJPEG: (50-100) | H264: (500-10000) + * @param _pp_kvm_data @output: Encode data + * @param _p_kvmv_data_size @output: Encode data size + * @return + -7: HDMI INPUT RES ERROR + -6: Unsupported resolution, please modify it in the host settings. + -5: Retrieving image, please wait + -4: Modifying image resolution, please wait + -3: img buffer full + -2: VENC Error + -1: No images were acquired + 0: Acquire MJPEG encoded images + 1: Acquire H264 encoded images(SPS)[Deprecated] + 2: Acquire H264 encoded images(PPS)[Deprecated] + 3: Acquire H264 encoded images(I) + 4: Acquire H264 encoded images(P) + 5: IMG not changed + **********************************************************************************/ +int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _qlty, uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size); +int free_kvmv_data(uint8_t ** _pp_kvm_data); +void free_all_kvmv_data(); +void set_h264_gop(uint8_t _gop); +void set_frame_detact(uint8_t _frame_detact); +void kvmv_deinit(); +uint8_t kvmv_hdmi_control(uint8_t _en); + +#ifdef __cplusplus +} +#endif + +#endif // KVM_VISION_H_ diff --git a/server/logger/formatter.go b/server/logger/formatter.go new file mode 100644 index 0000000..b1dca36 --- /dev/null +++ b/server/logger/formatter.go @@ -0,0 +1,42 @@ +package logger + +import ( + "bytes" + "fmt" + "path/filepath" + + "github.com/sirupsen/logrus" +) + +type formatter struct{} + +func (f *formatter) Format(entry *logrus.Entry) ([]byte, error) { + var ( + text string + buffer *bytes.Buffer + ) + + if entry.Buffer != nil { + buffer = entry.Buffer + } else { + buffer = &bytes.Buffer{} + } + + now := entry.Time.Format("2006-01-02 15:04:05.000") + + if entry.HasCaller() { + fileName := filepath.Base(entry.Caller.File) + text = fmt.Sprintf( + "[%s] [%s] [%s:%d] %s\n", + now, entry.Level, fileName, entry.Caller.Line, entry.Message, + ) + } else { + text = fmt.Sprintf( + "[%s] [%s] %s \n", + now, entry.Level, entry.Message, + ) + } + + buffer.WriteString(text) + return buffer.Bytes(), nil +} diff --git a/server/logger/logger.go b/server/logger/logger.go new file mode 100644 index 0000000..8edea27 --- /dev/null +++ b/server/logger/logger.go @@ -0,0 +1,51 @@ +package logger + +import ( + "os" + "path/filepath" + + "NanoKVM-Server/config" + + "github.com/sirupsen/logrus" +) + +func openLogFile(filename string) (*os.File, error) { + absPath, err := filepath.Abs(filename) + if err != nil { + return nil, err + } + + file, err := os.OpenFile(absPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + return nil, err + } + + return file, nil +} + +func Init() { + conf := config.GetInstance() + + level, err := logrus.ParseLevel(conf.Logger.Level) + if err != nil { + level = logrus.ErrorLevel + } + + logrus.SetLevel(level) + if conf.Logger.File == "" || conf.Logger.File == "stdout" { + logrus.SetOutput(os.Stdout) + } else { + fh, err := openLogFile(conf.Logger.File) + if err != nil { + logrus.Error("open log file failed:", err) + logrus.SetOutput(os.Stdout) + } else { + logrus.SetOutput(fh) + } + } + + logrus.SetReportCaller(true) + logrus.SetFormatter(&formatter{}) + + logrus.Info("logger set success") +} diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..4a7379b --- /dev/null +++ b/server/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "NanoKVM-Server/common" + "NanoKVM-Server/config" + "NanoKVM-Server/logger" + "NanoKVM-Server/middleware" + "NanoKVM-Server/router" + "NanoKVM-Server/service/vm/jiggler" + "NanoKVM-Server/utils" + + "github.com/gin-gonic/gin" + cors "github.com/rs/cors/wrapper/gin" +) + +func main() { + initialize() + defer dispose() + + run() +} + +func initialize() { + logger.Init() + + // init screen parameters + _ = common.GetScreen() + + // init HDMI + vision := common.GetKvmVision() + vision.SetHDMI(false) + time.Sleep(10 * time.Millisecond) + if !utils.IsHdmiDisabled() { + vision.SetHDMI(true) + } + + // run mouse jiggler + jiggler.GetJiggler().Run() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + go func() { + sig := <-sigChan + log.Printf("\nReceived signal: %v\n", sig) + + dispose() + os.Exit(0) + }() +} + +func run() { + conf := config.GetInstance() + + gin.SetMode(gin.ReleaseMode) + r := gin.New() + r.Use(gin.Recovery()) + if conf.Authentication == "disable" { + r.Use(cors.AllowAll()) + } + + router.Init(r) + + httpAddr := fmt.Sprintf(":%d", conf.Port.Http) + httpsAddr := fmt.Sprintf(":%d", conf.Port.Https) + + if conf.Proto == "https" { + go func() { + r.Use(middleware.Tls()) + err := r.RunTLS(httpsAddr, conf.Cert.Crt, conf.Cert.Key) + if err != nil { + panic("start https server failed") + } + }() + + runRedirect(httpAddr, httpsAddr) + } else { + if err := r.Run(httpAddr); err != nil { + panic("start http server failed") + } + } +} + +func runRedirect(httpPort string, httpsPort string) { + err := http.ListenAndServe(httpPort, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + host := req.Host + if strings.Contains(host, httpPort) { + host = strings.Split(host, httpPort)[0] + } + + targetURL := "https://" + host + req.URL.String() + if httpsPort != ":443" { + targetURL = "https://" + host + httpsPort + req.URL.String() + } + + http.Redirect(w, req, targetURL, http.StatusTemporaryRedirect) + })) + + if err != nil { + panic("start http server failed") + } +} + +func dispose() { + common.GetKvmVision().Close() +} diff --git a/server/middleware/jwt.go b/server/middleware/jwt.go new file mode 100644 index 0000000..8602881 --- /dev/null +++ b/server/middleware/jwt.go @@ -0,0 +1,75 @@ +package middleware + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/config" +) + +type Token struct { + Username string `json:"username"` + jwt.RegisteredClaims +} + +func CheckToken() gin.HandlerFunc { + return func(c *gin.Context) { + conf := config.GetInstance() + + if conf.Authentication == "disable" { + c.Next() + return + } + + cookie, err := c.Cookie("nano-kvm-token") + if err == nil { + _, err = ParseJWT(cookie) + if err == nil { + c.Next() + return + } + } + + c.JSON(http.StatusUnauthorized, "unauthorized") + c.Abort() + } +} + +func GenerateJWT(username string) (string, error) { + conf := config.GetInstance() + + expireDuration := time.Duration(conf.JWT.RefreshTokenDuration) * time.Second + + claims := Token{ + Username: username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(expireDuration)), + }, + } + + t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + return t.SignedString([]byte(conf.JWT.SecretKey)) +} + +func ParseJWT(jwtToken string) (*Token, error) { + conf := config.GetInstance() + + t, err := jwt.ParseWithClaims(jwtToken, &Token{}, func(token *jwt.Token) (interface{}, error) { + return []byte(conf.JWT.SecretKey), nil + }) + if err != nil { + log.Debugf("parse jwt error: %s", err) + return nil, err + } + + if claims, ok := t.Claims.(*Token); ok && t.Valid { + return claims, nil + } else { + return nil, err + } +} diff --git a/server/middleware/tls.go b/server/middleware/tls.go new file mode 100644 index 0000000..c5117fc --- /dev/null +++ b/server/middleware/tls.go @@ -0,0 +1,26 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/unrolled/secure" +) + +func Tls() gin.HandlerFunc { + secureMiddleware := secure.New(secure.Options{ + SSLRedirect: true, + }) + + secureFunc := func(c *gin.Context) { + err := secureMiddleware.Process(c.Writer, c.Request) + if err != nil { + c.Abort() + return + } + + if status := c.Writer.Status(); status > 300 && status < 399 { + c.Abort() + } + } + + return secureFunc +} diff --git a/server/proto/application.go b/server/proto/application.go new file mode 100644 index 0000000..c808202 --- /dev/null +++ b/server/proto/application.go @@ -0,0 +1,14 @@ +package proto + +type GetVersionRsp struct { + Current string `json:"current"` + Latest string `json:"latest"` +} + +type GetPreviewRsp struct { + Enabled bool `json:"enabled"` +} + +type SetPreviewReq struct { + Enable bool `validate:"omitempty"` +} diff --git a/server/proto/auth.go b/server/proto/auth.go new file mode 100644 index 0000000..6c4f28c --- /dev/null +++ b/server/proto/auth.go @@ -0,0 +1,28 @@ +package proto + +type LoginReq struct { + Username string `validate:"required"` + Password string `validate:"required"` +} + +type LoginRsp struct { + Token string `json:"token"` +} + +type GetAccountRsp struct { + Username string `json:"username"` +} + +type ChangePasswordReq struct { + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` +} + +type IsPasswordUpdatedRsp struct { + IsUpdated bool `json:"isUpdated"` +} + +type ConnectWifiReq struct { + Ssid string `validate:"required"` + Password string `valid:"required"` +} diff --git a/server/proto/download.go b/server/proto/download.go new file mode 100644 index 0000000..461fb9f --- /dev/null +++ b/server/proto/download.go @@ -0,0 +1,11 @@ +package proto + +type ImageEnabledRsp struct { + Enabled bool `json:"enabled"` +} + +type StatusImageRsp struct { + Status string `json:"status"` + File string `json:"file"` + Percentage string `json:"percentage"` +} diff --git a/server/proto/hid.go b/server/proto/hid.go new file mode 100644 index 0000000..b7bca75 --- /dev/null +++ b/server/proto/hid.go @@ -0,0 +1,9 @@ +package proto + +type GetHidModeRsp struct { + Mode string `json:"mode"` // normal or hid-only +} + +type SetHidModeReq struct { + Mode string `validate:"required"` // normal or hid-only +} diff --git a/server/proto/network.go b/server/proto/network.go new file mode 100644 index 0000000..7393c22 --- /dev/null +++ b/server/proto/network.go @@ -0,0 +1,44 @@ +package proto + +type WakeOnLANReq struct { + Mac string `form:"mac" validate:"required"` +} + +type GetMacRsp struct { + Macs []string `json:"macs"` +} + +type DeleteMacReq struct { + Mac string `form:"mac" validate:"required"` +} + +type SetMacNameReq struct { + Mac string `form:"mac" validate:"required"` + Name string `form:"name" validate:"required"` +} + +type TailscaleState string + +const ( + TailscaleNotInstall TailscaleState = "notInstall" + TailscaleNotRunning TailscaleState = "notRunning" + TailscaleNotLogin TailscaleState = "notLogin" + TailscaleStopped TailscaleState = "stopped" + TailscaleRunning TailscaleState = "running" +) + +type GetTailscaleStatusRsp struct { + State TailscaleState `json:"state"` + Name string `json:"name"` + IP string `json:"ip"` + Account string `json:"account"` +} + +type LoginTailscaleRsp struct { + Url string `json:"url"` +} + +type GetWifiRsp struct { + Supported bool `json:"supported"` + Connected bool `json:"connected"` +} diff --git a/server/proto/request.go b/server/proto/request.go new file mode 100644 index 0000000..f0f3b39 --- /dev/null +++ b/server/proto/request.go @@ -0,0 +1,49 @@ +package proto + +import ( + "os" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + log "github.com/sirupsen/logrus" +) + +var env = os.Getenv(gin.EnvGinMode) + +// ValidateRequest Validates request parameters. +func ValidateRequest(req interface{}) error { + validate := validator.New() + + if err := validate.Struct(req); err != nil { + log.Errorf("validate request failed, err: %s", err) + return err + } + + if env == "" || env == "debug" { + log.Debugf("request: %+v\n", req) + } + + return nil +} + +// ParseQueryRequest Validates GET requests. +func ParseQueryRequest(c *gin.Context, req interface{}) error { + var err error + if err = c.ShouldBindQuery(req); err != nil { + log.Errorf("parse request failed, err: %s", err) + return err + } + + return ValidateRequest(req) +} + +// ParseFormRequest Validates POST Requests. +func ParseFormRequest(c *gin.Context, req interface{}) error { + var err error + if err = c.ShouldBind(req); err != nil { + log.Errorf("parse request failed, err: %s", err) + return err + } + + return ValidateRequest(req) +} diff --git a/server/proto/response.go b/server/proto/response.go new file mode 100644 index 0000000..1b64ec8 --- /dev/null +++ b/server/proto/response.go @@ -0,0 +1,50 @@ +package proto + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Response struct { + Code int `json:"code"` // Status code. 0-success, others-failure + Msg string `json:"msg"` // Status details + Data interface{} `json:"data"` // Returned data +} + +func (r *Response) Ok() { + r.Code = 0 + r.Msg = "success" +} + +func (r *Response) OkWithData(data interface{}) { + r.Ok() + r.Data = data +} + +func (r *Response) Err(code int, msg string) { + r.Code = code + r.Msg = msg +} + +// OkRsp Successful response without data. +func (r *Response) OkRsp(c *gin.Context) { + r.Ok() + + c.JSON(http.StatusOK, r) +} + +// OkRspWithData Successful response with data. +func (r *Response) OkRspWithData(c *gin.Context, data interface{}) { + r.Ok() + r.Data = data + + c.JSON(http.StatusOK, r) +} + +// ErrRsp Failed response. +func (r *Response) ErrRsp(c *gin.Context, code int, msg string) { + r.Err(code, msg) + + c.JSON(http.StatusOK, r) +} diff --git a/server/proto/storage.go b/server/proto/storage.go new file mode 100644 index 0000000..8741da3 --- /dev/null +++ b/server/proto/storage.go @@ -0,0 +1,22 @@ +package proto + +type GetImagesRsp struct { + Files []string `json:"files"` +} + +type MountImageReq struct { + File string `json:"file" validate:"omitempty"` + Cdrom bool `json:"cdrom" validate:"omitempty"` +} + +type GetMountedImageRsp struct { + File string `json:"file"` +} + +type GetCdRomRsp struct { + Cdrom int64 `json:"cdrom"` +} + +type DeleteImageReq struct { + File string `json:"file" validate:"required"` +} diff --git a/server/proto/stream.go b/server/proto/stream.go new file mode 100644 index 0000000..f0bb202 --- /dev/null +++ b/server/proto/stream.go @@ -0,0 +1,9 @@ +package proto + +type UpdateFrameDetectReq struct { + Enabled bool `validate:"omitempty"` +} + +type StopFrameDetectReq struct { + Duration int `validate:"omitempty"` +} diff --git a/server/proto/vm.go b/server/proto/vm.go new file mode 100644 index 0000000..21d3844 --- /dev/null +++ b/server/proto/vm.go @@ -0,0 +1,138 @@ +package proto + +type IP struct { + Name string `json:"name"` + Addr string `json:"addr"` + Version string `json:"version"` + Type string `json:"type"` +} + +type GetInfoRsp struct { + IPs []IP `json:"ips"` + Mdns string `json:"mdns"` + Image string `json:"image"` + Application string `json:"application"` + DeviceKey string `json:"deviceKey"` +} + +type GetHardwareRsp struct { + Version string `json:"version"` +} + +type SetGpioReq struct { + Type string `validate:"required"` // reset / power + Duration uint `validate:"omitempty"` // press time (unit: milliseconds) +} + +type GetGpioRsp struct { + PWR bool `json:"pwr"` // power led + HDD bool `json:"hdd"` // hdd led +} + +type SetScreenReq struct { + Type string `validate:"required"` // resolution / fps / quality + Value int `validate:"number"` // value +} + +type GetScriptsRsp struct { + Files []string `json:"files"` +} + +type UploadScriptRsp struct { + File string `json:"file"` +} + +type RunScriptReq struct { + Name string `validate:"required"` + Type string `validate:"required"` // foreground | background +} + +type RunScriptRsp struct { + Log string `json:"log"` +} + +type DeleteScriptReq struct { + Name string `validate:"required"` +} + +type GetVirtualDeviceRsp struct { + Network bool `json:"network"` + Disk bool `json:"disk"` +} + +type UpdateVirtualDeviceReq struct { + Device string `validate:"required"` +} + +type UpdateVirtualDeviceRsp struct { + On bool `json:"on"` +} + +type SetMemoryLimitReq struct { + Enabled bool `validate:"omitempty"` + Limit int64 `validate:"omitempty"` +} + +type GetMemoryLimitRsp struct { + Enabled bool `json:"enabled"` + Limit int64 `json:"limit"` +} + +type SetOledReq struct { + Sleep int `validate:"omitempty"` +} + +type GetOLEDRsp struct { + Exist bool `json:"exist"` + Sleep int `json:"sleep"` +} + +type GetGetHdmiStateRsp struct { + Enabled bool `json:"enabled"` +} + +type GetSSHStateRsp struct { + Enabled bool `json:"enabled"` +} + +type GetSwapRsp struct { + Size int64 `json:"size"` // unit: MB +} + +type SetSwapReq struct { + Size int64 `validate:"omitempty"` // unit: MB +} + +type GetMouseJigglerRsp struct { + Enabled bool `json:"enabled"` + Mode string `json:"mode"` +} + +type SetMouseJigglerReq struct { + Enabled bool `validate:"omitempty"` + Mode string `validate:"omitempty"` +} + +type GetMdnsStateRsp struct { + Enabled bool `json:"enabled"` +} + +type SetHostnameReq struct { + Hostname string `validate:"required"` +} + +type GetHostnameRsp struct { + Hostname string `json:"hostname"` +} + +type SetWebTitleReq struct { + Title string `validate:"omitempty"` +} + +type GetWebTitleRsp struct { + Title string `json:"title"` +} + +type SetTlsReq struct { + Enabled bool `validate:"omitempty"` +} diff --git a/server/router/application.go b/server/router/application.go new file mode 100644 index 0000000..1c4d269 --- /dev/null +++ b/server/router/application.go @@ -0,0 +1,19 @@ +package router + +import ( + "NanoKVM-Server/middleware" + "NanoKVM-Server/service/application" + + "github.com/gin-gonic/gin" +) + +func applicationRouter(r *gin.Engine) { + service := application.NewService() + api := r.Group("/api").Use(middleware.CheckToken()) + + api.GET("/application/version", service.GetVersion) // get application version + api.POST("/application/update", service.Update) // update application + + api.GET("/application/preview", service.GetPreview) // get preview updates state + api.POST("/application/preview", service.SetPreview) // set preview updates state +} diff --git a/server/router/auth.go b/server/router/auth.go new file mode 100644 index 0000000..aa237fa --- /dev/null +++ b/server/router/auth.go @@ -0,0 +1,21 @@ +package router + +import ( + "github.com/gin-gonic/gin" + + "NanoKVM-Server/middleware" + "NanoKVM-Server/service/auth" +) + +func authRouter(r *gin.Engine) { + service := auth.NewService() + + r.POST("/api/auth/login", service.Login) // login + + api := r.Group("/api").Use(middleware.CheckToken()) + + api.GET("/auth/password", service.IsPasswordUpdated) // is password updated + api.GET("/auth/account", service.GetAccount) // get account + api.POST("/auth/password", service.ChangePassword) // change password + api.POST("/auth/logout", service.Logout) // logout +} diff --git a/server/router/download.go b/server/router/download.go new file mode 100644 index 0000000..16e9db2 --- /dev/null +++ b/server/router/download.go @@ -0,0 +1,17 @@ +package router + +import ( + "NanoKVM-Server/service/download" + "github.com/gin-gonic/gin" + + "NanoKVM-Server/middleware" +) + +func downloadRouter(r *gin.Engine) { + service := download.NewService() + api := r.Group("/api").Use(middleware.CheckToken()) + + api.POST("/download/image", service.DownloadImage) // download image + api.GET("/download/image/status", service.StatusImage) // download image + api.GET("/download/image/enabled", service.ImageEnabled) // download image +} diff --git a/server/router/extensions.go b/server/router/extensions.go new file mode 100644 index 0000000..696a94d --- /dev/null +++ b/server/router/extensions.go @@ -0,0 +1,25 @@ +package router + +import ( + "NanoKVM-Server/middleware" + "NanoKVM-Server/service/extensions/tailscale" + + "github.com/gin-gonic/gin" +) + +func extensionsRouter(r *gin.Engine) { + api := r.Group("/api/extensions").Use(middleware.CheckToken()) + + ts := tailscale.NewService() + + api.POST("/tailscale/install", ts.Install) // install tailscale + api.POST("/tailscale/uninstall", ts.Uninstall) // uninstall tailscale + api.GET("/tailscale/status", ts.GetStatus) // get tailscale status + api.POST("/tailscale/up", ts.Up) // run tailscale up + api.POST("/tailscale/down", ts.Down) // run tailscale down + api.POST("/tailscale/login", ts.Login) // tailscale login + api.POST("/tailscale/logout", ts.Logout) // tailscale logout + api.POST("/tailscale/start", ts.Start) // tailscale start + api.POST("/tailscale/stop", ts.Stop) // tailscale stop + api.POST("/tailscale/restart", ts.Restart) // tailscale restart +} diff --git a/server/router/hid.go b/server/router/hid.go new file mode 100644 index 0000000..edfd358 --- /dev/null +++ b/server/router/hid.go @@ -0,0 +1,19 @@ +package router + +import ( + "github.com/gin-gonic/gin" + + "NanoKVM-Server/middleware" + "NanoKVM-Server/service/hid" +) + +func hidRouter(r *gin.Engine) { + service := hid.NewService() + api := r.Group("/api").Use(middleware.CheckToken()) + + api.POST("/hid/paste", service.Paste) // paste + + api.GET("/hid/mode", service.GetHidMode) // get hid mode + api.POST("/hid/mode", service.SetHidMode) // set hid mode + api.POST("/hid/reset", service.ResetHid) // reset hid +} diff --git a/server/router/network.go b/server/router/network.go new file mode 100644 index 0000000..cbd96e1 --- /dev/null +++ b/server/router/network.go @@ -0,0 +1,22 @@ +package router + +import ( + "github.com/gin-gonic/gin" + + "NanoKVM-Server/middleware" + "NanoKVM-Server/service/network" +) + +func networkRouter(r *gin.Engine) { + service := network.NewService() + + r.POST("/api/network/wifi", service.ConnectWifi) // connect Wi-Fi + + api := r.Group("/api").Use(middleware.CheckToken()) + + api.POST("/network/wol", service.WakeOnLAN) // wake on lan + api.GET("/network/wol/mac", service.GetMac) // get mac list + api.DELETE("/network/wol/mac", service.DeleteMac) // delete mac + api.POST("/network/wol/mac/name", service.SetMacName) // set mac name + api.GET("/network/wifi", service.GetWifi) // get Wi-Fi information +} diff --git a/server/router/router.go b/server/router/router.go new file mode 100644 index 0000000..8b6148a --- /dev/null +++ b/server/router/router.go @@ -0,0 +1,42 @@ +package router + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/gin-gonic/contrib/static" + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +func Init(r *gin.Engine) { + web(r) + server(r) + log.Debugf("router init done") +} + +func web(r *gin.Engine) { + execPath, err := os.Executable() + if err != nil { + panic("invalid executable path") + } + + execDir := filepath.Dir(execPath) + webPath := fmt.Sprintf("%s/web", execDir) + + r.Use(static.Serve("/", static.LocalFile(webPath, true))) +} + +func server(r *gin.Engine) { + authRouter(r) + applicationRouter(r) + vmRouter(r) + streamRouter(r) + storageRouter(r) + networkRouter(r) + hidRouter(r) + wsRouter(r) + downloadRouter(r) + extensionsRouter(r) +} diff --git a/server/router/storage.go b/server/router/storage.go new file mode 100644 index 0000000..008f354 --- /dev/null +++ b/server/router/storage.go @@ -0,0 +1,19 @@ +package router + +import ( + "github.com/gin-gonic/gin" + + "NanoKVM-Server/middleware" + "NanoKVM-Server/service/storage" +) + +func storageRouter(r *gin.Engine) { + service := storage.NewService() + api := r.Group("/api").Use(middleware.CheckToken()) + + api.GET("/storage/image", service.GetImages) // get image list + api.GET("/storage/image/mounted", service.GetMountedImage) // get mounted image + api.POST("/storage/image/mount", service.MountImage) // mount image + api.GET("/storage/cdrom", service.GetCdRom) // get CD-ROM flag + api.POST("/storage/image/delete", service.DeleteImage) // delete image +} diff --git a/server/router/stream.go b/server/router/stream.go new file mode 100644 index 0000000..44fb5a8 --- /dev/null +++ b/server/router/stream.go @@ -0,0 +1,21 @@ +package router + +import ( + "NanoKVM-Server/middleware" + "NanoKVM-Server/service/stream/direct" + "NanoKVM-Server/service/stream/mjpeg" + "NanoKVM-Server/service/stream/webrtc" + + "github.com/gin-gonic/gin" +) + +func streamRouter(r *gin.Engine) { + api := r.Group("/api").Use(middleware.CheckToken()) + + api.GET("/stream/mjpeg", mjpeg.Connect) // mjpeg stream + api.POST("/stream/mjpeg/detect", mjpeg.UpdateFrameDetect) // update frame detect + api.POST("/stream/mjpeg/detect/stop", mjpeg.StopFrameDetect) // temporary stop frame detect + + api.GET("/stream/h264", webrtc.Connect) // h264 stream (webrtc) + api.GET("/stream/h264/direct", direct.Connect) // h264 stream (http) +} diff --git a/server/router/vm.go b/server/router/vm.go new file mode 100644 index 0000000..f5a78a4 --- /dev/null +++ b/server/router/vm.go @@ -0,0 +1,67 @@ +package router + +import ( + "github.com/gin-gonic/gin" + + "NanoKVM-Server/middleware" + "NanoKVM-Server/service/vm" +) + +func vmRouter(r *gin.Engine) { + service := vm.NewService() + + api := r.Group("/api").Use(middleware.CheckToken()) + + api.GET("/vm/info", service.GetInfo) // get device information + api.GET("/vm/hardware", service.GetHardware) // get hardware version + + api.POST("/vm/gpio", service.SetGpio) // update gpio + api.GET("/vm/gpio", service.GetGpio) // get gpio + api.POST("/vm/screen", service.SetScreen) // update screen + + api.GET("/vm/terminal", service.Terminal) // web terminal + + api.GET("/vm/script", service.GetScripts) // get script + api.POST("/vm/script/upload", service.UploadScript) // upload script + api.POST("/vm/script/run", service.RunScript) // run script + api.DELETE("/vm/script", service.DeleteScript) // delete script + + api.GET("/vm/device/virtual", service.GetVirtualDevice) // get virtual device + api.POST("/vm/device/virtual", service.UpdateVirtualDevice) // update virtual device + + api.GET("/vm/memory/limit", service.GetMemoryLimit) // get memory limit + api.POST("/vm/memory/limit", service.SetMemoryLimit) // set memory limit + + api.GET("/vm/oled", service.GetOLED) // get OLED configuration + api.POST("/vm/oled", service.SetOLED) // set OLED configuration + + // Only supported by PCIe version + api.GET("/vm/hdmi", service.GetHdmiState) // get HDMI state + api.POST("/vm/hdmi/reset", service.ResetHdmi) // reset hdmi + api.POST("/vm/hdmi/enable", service.EnableHdmi) // enable hdmi + api.POST("/vm/hdmi/disable", service.DisableHdmi) // disable hdmi + + api.GET("/vm/ssh", service.GetSSHState) // get SSH state + api.POST("/vm/ssh/enable", service.EnableSSH) // enable SSH + api.POST("/vm/ssh/disable", service.DisableSSH) // disable SSH + + api.GET("/vm/swap", service.GetSwap) // get swap file size + api.POST("/vm/swap", service.SetSwap) // set swap file size + + api.GET("/vm/mouse-jiggler", service.GetMouseJiggler) // get mouse jiggler + api.POST("/vm/mouse-jiggler/", service.SetMouseJiggler) // set mouse jiggler + + api.GET("/vm/hostname", service.GetHostname) // Get Hostname + api.POST("/vm/hostname", service.SetHostname) // Set Hostname + + api.GET("/vm/web-title", service.GetWebTitle) // Get web title + api.POST("/vm/web-title", service.SetWebTitle) // Set web title + + api.GET("/vm/mdns", service.GetMdnsState) // get mDNS state + api.POST("/vm/mdns/enable", service.EnableMdns) // enable mDNS + api.POST("/vm/mdns/disable", service.DisableMdns) // disable mDNS + + api.POST("/vm/tls", service.SetTls) // enable/disable TLS + + api.POST("/vm/system/reboot", service.Reboot) // reboot system +} diff --git a/server/router/ws.go b/server/router/ws.go new file mode 100644 index 0000000..4946724 --- /dev/null +++ b/server/router/ws.go @@ -0,0 +1,15 @@ +package router + +import ( + "github.com/gin-gonic/gin" + + "NanoKVM-Server/middleware" + "NanoKVM-Server/service/ws" +) + +func wsRouter(r *gin.Engine) { + service := ws.NewService() + api := r.Group("/api").Use(middleware.CheckToken()) + + api.GET("/ws", service.Connect) +} diff --git a/server/service/application/preview.go b/server/service/application/preview.go new file mode 100644 index 0000000..1fcad54 --- /dev/null +++ b/server/service/application/preview.go @@ -0,0 +1,60 @@ +package application + +import ( + "NanoKVM-Server/proto" + "os" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + PreviewUpdatesFlag = "/etc/kvm/preview_updates" +) + +func (s *Service) GetPreview(c *gin.Context) { + var rsp proto.Response + + isEnabled := isPreviewEnabled() + + rsp.OkRspWithData(c, &proto.GetPreviewRsp{ + Enabled: isEnabled, + }) +} + +func (s *Service) SetPreview(c *gin.Context) { + var req proto.SetPreviewReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + if req.Enable == isPreviewEnabled() { + rsp.OkRsp(c) + return + } + + if req.Enable { + if err := os.WriteFile(PreviewUpdatesFlag, []byte("1"), 0o644); err != nil { + log.Errorf("failed to write %s: %s", PreviewUpdatesFlag, err) + rsp.ErrRsp(c, -2, "enable failed") + return + } + } else { + if err := os.Remove(PreviewUpdatesFlag); err != nil { + log.Errorf("failed to remove %s: %s", PreviewUpdatesFlag, err) + rsp.ErrRsp(c, -3, "disable failed") + return + } + } + + rsp.OkRsp(c) + log.Debugf("set preview updates state: %t", req.Enable) +} + +func isPreviewEnabled() bool { + _, err := os.Stat(PreviewUpdatesFlag) + return err == nil +} diff --git a/server/service/application/service.go b/server/service/application/service.go new file mode 100644 index 0000000..6ed77c2 --- /dev/null +++ b/server/service/application/service.go @@ -0,0 +1,16 @@ +package application + +const ( + StableURL = "https://update.tindevil.com/batchukvm" + PreviewURL = "https://update.tindevil.com/batchukvm/preview" + + AppDir = "/kvmapp" + BackupDir = "/root/old" + CacheDir = "/root/.kvmcache" +) + +type Service struct{} + +func NewService() *Service { + return &Service{} +} diff --git a/server/service/application/update.go b/server/service/application/update.go new file mode 100644 index 0000000..9993397 --- /dev/null +++ b/server/service/application/update.go @@ -0,0 +1,174 @@ +package application + +import ( + "crypto/sha512" + "encoding/base64" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "sync" + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/proto" + "NanoKVM-Server/utils" +) + +const ( + maxTries = 3 +) + +var ( + updateMutex sync.Mutex + isUpdating bool +) + +func (s *Service) Update(c *gin.Context) { + var rsp proto.Response + + updateMutex.Lock() + if isUpdating { + updateMutex.Unlock() + rsp.ErrRsp(c, -1, "update already in progress") + return + } + isUpdating = true + updateMutex.Unlock() + + defer func() { + updateMutex.Lock() + isUpdating = false + updateMutex.Unlock() + }() + + if err := update(); err != nil { + rsp.ErrRsp(c, -1, fmt.Sprintf("update failed: %s", err)) + return + } + + rsp.OkRsp(c) + log.Debugf("update application success") + + // Sleep for a second before restarting the device + time.Sleep(1 * time.Second) + + _ = exec.Command("sh", "-c", "/etc/init.d/S95nanokvm restart").Run() +} + +func update() error { + _ = os.RemoveAll(CacheDir) + _ = os.MkdirAll(CacheDir, 0o755) + defer func() { + _ = os.RemoveAll(CacheDir) + }() + + // get latest information + latest, err := getLatest() + if err != nil { + return err + } + + // download + target := fmt.Sprintf("%s/%s", CacheDir, latest.Name) + + if err := download(latest.Url, target); err != nil { + log.Errorf("download app failed: %s", err) + return err + } + + // check sha512 + if err := checksum(target, latest.Sha512); err != nil { + log.Errorf("check sha512 failed: %s", err) + return err + } + + // decompress + dir, err := utils.UnTarGz(target, CacheDir) + log.Debugf("untar: %s", dir) + if err != nil { + log.Errorf("decompress app failed: %s", err) + return err + } + + // backup old version + if err := os.RemoveAll(BackupDir); err != nil { + log.Errorf("remove backup failed: %s", err) + return err + } + + if err := utils.MoveFilesRecursively(AppDir, BackupDir); err != nil { + log.Errorf("backup app failed: %s", err) + return err + } + + // update + if err := utils.MoveFilesRecursively(dir, AppDir); err != nil { + log.Errorf("failed to move update back in place: %s", err) + return err + } + + // modify permissions + if err := utils.ChmodRecursively(AppDir, 0o755); err != nil { + log.Errorf("chmod failed: %s", err) + return err + } + + return nil +} + +func download(url string, target string) (err error) { + for i := range maxTries { + log.Debugf("attempt #%d/%d", i+1, maxTries) + if i > 0 { + time.Sleep(time.Second * 3) // wait for 3 seconds before retrying the download attempt + } + + var req *http.Request + req, err = http.NewRequest("GET", url, nil) + if err != nil { + log.Errorf("new request err: %s", err) + continue + } + + log.Debugf("update will be saved to: %s", target) + err = utils.Download(req, target) + if err != nil { + log.Errorf("downloading latest application failed, try again...") + continue + } + return nil + } + return err +} + +func checksum(filePath string, expectedHash string) error { + file, err := os.Open(filePath) + if err != nil { + log.Errorf("failed to open file %s: %v", filePath, err) + return err + } + defer func() { + _ = file.Close() + }() + + hasher := sha512.New() + + _, err = io.Copy(hasher, file) + if err != nil { + log.Errorf("failed to copy file contents to hasher: %v", err) + return err + } + + hash := base64.StdEncoding.EncodeToString(hasher.Sum(nil)) + + if hash != expectedHash { + log.Errorf("invalid sha512 %s", hash) + return fmt.Errorf("invalid sha512 %s", hash) + } + + return nil +} diff --git a/server/service/application/version.go b/server/service/application/version.go new file mode 100644 index 0000000..569696e --- /dev/null +++ b/server/service/application/version.go @@ -0,0 +1,89 @@ +package application + +import ( + "NanoKVM-Server/proto" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +type Latest struct { + Version string `json:"version"` + Name string `json:"name"` + Sha512 string `json:"sha512"` + Size uint `json:"size"` + Url string `json:"url"` +} + +func (s *Service) GetVersion(c *gin.Context) { + var rsp proto.Response + + // current version + currentVersion := "1.0.0" + + versionFile := fmt.Sprintf("%s/version", AppDir) + if version, err := os.ReadFile(versionFile); err == nil { + currentVersion = strings.ReplaceAll(string(version), "\n", "") + } + + log.Debugf("current version: %s", currentVersion) + + // latest version + latest, err := getLatest() + if err != nil { + rsp.ErrRsp(c, -1, "get latest version failed") + return + } + + rsp.OkRspWithData(c, &proto.GetVersionRsp{ + Current: currentVersion, + Latest: latest.Version, + }) +} + +func getLatest() (*Latest, error) { + baseURL := StableURL + if isPreviewEnabled() { + baseURL = PreviewURL + } + + url := fmt.Sprintf("%s/latest.json?now=%d", baseURL, time.Now().Unix()) + + resp, err := http.Get(url) + if err != nil { + log.Debugf("failed to request version: %v", err) + return nil, err + } + defer func() { + _ = resp.Body.Close() + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Errorf("failed to read response: %v", err) + return nil, err + } + + if resp.StatusCode != http.StatusOK { + log.Errorf("server responded with status code: %d", resp.StatusCode) + return nil, fmt.Errorf("status code %d", resp.StatusCode) + } + + var latest Latest + if err := json.Unmarshal(body, &latest); err != nil { + log.Errorf("failed to unmarshal response: %s", err) + return nil, err + } + + latest.Url = fmt.Sprintf("%s/%s", baseURL, latest.Name) + + log.Debugf("get application latest version: %s", latest.Version) + return &latest, nil +} diff --git a/server/service/auth/account.go b/server/service/auth/account.go new file mode 100644 index 0000000..0305965 --- /dev/null +++ b/server/service/auth/account.go @@ -0,0 +1,113 @@ +package auth + +import ( + "NanoKVM-Server/utils" + "encoding/json" + "errors" + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/bcrypt" +) + +const AccountFile = "/etc/kvm/pwd" + +type Account struct { + Username string `json:"username"` + Password string `json:"password"` // should be named HashedPassword for clarity +} + +func GetAccount() (*Account, error) { + if _, err := os.Stat(AccountFile); err != nil { + if errors.Is(err, os.ErrNotExist) { + return getDefaultAccount(), nil + } + return nil, err + } + + content, err := os.ReadFile(AccountFile) + if err != nil { + return nil, err + } + + var account Account + if err = json.Unmarshal(content, &account); err != nil { + log.Errorf("unmarshal account failed: %s", err) + return nil, err + } + + return &account, nil +} + +func SetAccount(username string, hashedPassword string) error { + account, err := json.Marshal(&Account{ + Username: username, + Password: hashedPassword, + }) + if err != nil { + log.Errorf("failed to marshal account information to json: %s", err) + return err + } + + err = os.MkdirAll(filepath.Dir(AccountFile), 0o644) + if err != nil { + log.Errorf("create directory %s failed: %s", AccountFile, err) + return err + } + + err = os.WriteFile(AccountFile, account, 0o644) + if err != nil { + log.Errorf("write password failed: %s", err) + return err + } + + return nil +} + +func CompareAccount(username string, plainPassword string) bool { + account, err := GetAccount() + if err != nil { + return false + } + + if username != account.Username { + return false + } + + hashedPassword, err := utils.DecodeDecrypt(plainPassword) + if err != nil || hashedPassword == "" { + return false + } + + err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(hashedPassword)) + if err != nil { + // Compatible with old versions + accountHashedPassword, _ := utils.DecodeDecrypt(account.Password) + if accountHashedPassword == hashedPassword { + return true + } + + return false + } + + return true +} + +func DelAccount() error { + if err := os.Remove(AccountFile); err != nil { + log.Errorf("failed to delete password: %s", err) + return err + } + + return nil +} + +func getDefaultAccount() *Account { + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("admin"), bcrypt.DefaultCost) + + return &Account{ + Username: "admin", + Password: string(hashedPassword), + } +} diff --git a/server/service/auth/login.go b/server/service/auth/login.go new file mode 100644 index 0000000..c933ec1 --- /dev/null +++ b/server/service/auth/login.go @@ -0,0 +1,76 @@ +package auth + +import ( + "NanoKVM-Server/config" + "NanoKVM-Server/middleware" + "NanoKVM-Server/proto" + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +func (s *Service) Login(c *gin.Context) { + var req proto.LoginReq + var rsp proto.Response + + // authentication disabled + conf := config.GetInstance() + if conf.Authentication == "disable" { + rsp.OkRspWithData(c, &proto.LoginRsp{ + Token: "disabled", + }) + return + } + + if err := proto.ParseFormRequest(c, &req); err != nil { + time.Sleep(3 * time.Second) + rsp.ErrRsp(c, -1, "invalid parameters") + return + } + + if ok := CompareAccount(req.Username, req.Password); !ok { + time.Sleep(2 * time.Second) + rsp.ErrRsp(c, -2, "invalid username or password") + return + } + + token, err := middleware.GenerateJWT(req.Username) + if err != nil { + time.Sleep(1 * time.Second) + rsp.ErrRsp(c, -3, "generate token failed") + return + } + + rsp.OkRspWithData(c, &proto.LoginRsp{ + Token: token, + }) + + log.Debugf("login success, username: %s", req.Username) +} + +func (s *Service) Logout(c *gin.Context) { + conf := config.GetInstance() + + if conf.JWT.RevokeTokensOnLogout { + config.RegenerateSecretKey() + } + + var rsp proto.Response + rsp.OkRsp(c) +} + +func (s *Service) GetAccount(c *gin.Context) { + var rsp proto.Response + + account, err := GetAccount() + if err != nil { + rsp.ErrRsp(c, -1, "get account failed") + return + } + + rsp.OkRspWithData(c, &proto.GetAccountRsp{ + Username: account.Username, + }) + log.Debugf("get account successful") +} diff --git a/server/service/auth/password.go b/server/service/auth/password.go new file mode 100644 index 0000000..22c6016 --- /dev/null +++ b/server/service/auth/password.go @@ -0,0 +1,124 @@ +package auth + +import ( + "NanoKVM-Server/proto" + "NanoKVM-Server/utils" + "errors" + "io" + "os" + "os/exec" + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/bcrypt" +) + +func (s *Service) ChangePassword(c *gin.Context) { + var req proto.ChangePasswordReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid parameters") + return + } + + password, err := utils.DecodeDecrypt(req.Password) + if err != nil || password == "" { + rsp.ErrRsp(c, -2, "invalid password") + return + } + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + rsp.ErrRsp(c, -3, "failed to hash password") + return + } + + if err = SetAccount(req.Username, string(hashedPassword)); err != nil { + rsp.ErrRsp(c, -4, "failed to save password") + return + } + + // change root password + err = changeRootPassword(password) + if err != nil { + _ = DelAccount() + rsp.ErrRsp(c, -5, "failed to change password") + return + } + + rsp.OkRsp(c) + log.Debugf("change password success, username: %s", req.Username) +} + +func (s *Service) IsPasswordUpdated(c *gin.Context) { + var rsp proto.Response + + if _, err := os.Stat(AccountFile); err != nil { + rsp.OkRspWithData(c, &proto.IsPasswordUpdatedRsp{ + IsUpdated: false, + }) + return + } + + account, err := GetAccount() + if err != nil || account == nil { + rsp.ErrRsp(c, -1, "failed to get password") + return + } + + err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte("admin")) + + rsp.OkRspWithData(c, &proto.IsPasswordUpdatedRsp{ + // If the hash is not valid, still assume it's not updated + // The error we want to see is password and hash not matching + IsUpdated: errors.Is(err, bcrypt.ErrMismatchedHashAndPassword), + }) +} + +func changeRootPassword(password string) error { + err := passwd(password) + if err != nil { + log.Errorf("failed to change root password: %s", err) + return err + } + + log.Debugf("change root password successful.") + return nil +} + +func passwd(password string) error { + cmd := exec.Command("passwd", "root") + + stdin, err := cmd.StdinPipe() + if err != nil { + return err + } + defer func() { + _ = stdin.Close() + }() + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err = cmd.Start(); err != nil { + return err + } + + if _, err = io.WriteString(stdin, password+"\n"); err != nil { + return err + } + + time.Sleep(100 * time.Millisecond) + + if _, err = io.WriteString(stdin, password+"\n"); err != nil { + return err + } + + if err = cmd.Wait(); err != nil { + return err + } + + return nil +} diff --git a/server/service/auth/service.go b/server/service/auth/service.go new file mode 100644 index 0000000..86599af --- /dev/null +++ b/server/service/auth/service.go @@ -0,0 +1,7 @@ +package auth + +type Service struct{} + +func NewService() *Service { + return &Service{} +} diff --git a/server/service/download/service.go b/server/service/download/service.go new file mode 100644 index 0000000..3f63081 --- /dev/null +++ b/server/service/download/service.go @@ -0,0 +1,230 @@ +package download + +import ( + "NanoKVM-Server/proto" + "fmt" + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "time" +) + +type Service struct{} + +var sentinelPath = "/tmp/.download_in_progress" + +func NewService() *Service { + // Clear sentinel + // If we are starting from scratch, we need to remove the sentinel file as any downloads at this point are done or broken + _ = os.Remove(sentinelPath) + return &Service{} +} + +func (s *Service) ImageEnabled(c *gin.Context) { + var rsp proto.Response + + // Check if /data mount is RO/RW + testFile := "/data/.testfile" + file, err := os.Create(testFile) + defer file.Close() + defer os.Remove(testFile) + if err != nil { + if os.IsPermission(err) { + rsp.OkRspWithData(c, &proto.ImageEnabledRsp{ + Enabled: false, + }) + return + } + rsp.OkRspWithData(c, &proto.ImageEnabledRsp{ + Enabled: false, + }) + return + } + + rsp.OkRspWithData(c, &proto.ImageEnabledRsp{ + Enabled: true, + }) +} + +func (s *Service) StatusImage(c *gin.Context) { + var rsp proto.Response + + // Check if the sentinel file exists + log.Debug("StatusImage") + if _, err := os.Stat(sentinelPath); err == nil { + content, err := os.ReadFile(sentinelPath) + if err != nil { + log.Error("Failed to read sentinel file") + rsp.OkRspWithData(c, &proto.StatusImageRsp{ + Status: "in_progress", + File: "", + Percentage: "", + }) + return + } + splitted := strings.Split(string(content), ";") + if len(splitted) == 1 { + // No percentage, just the URL + rsp.OkRspWithData(c, &proto.StatusImageRsp{ + Status: "in_progress", + File: splitted[0], + Percentage: "", + }) + } else { + // Percentage is available + rsp.OkRspWithData(c, &proto.StatusImageRsp{ + Status: "in_progress", + File: splitted[0], + Percentage: splitted[1], + }) + } + + return + } + rsp.OkRspWithData(c, &proto.StatusImageRsp{ + Status: "idle", + File: "", + Percentage: "", + }) +} + +func (s *Service) DownloadImage(c *gin.Context) { + var req proto.MountImageReq + var rsp proto.Response + + log.Debug("DownloadImage") + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + if req.File == "" { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + // Parse the URI to see if its valid http/s + u, err := url.Parse(req.File) + if err != nil || u.Scheme == "" || u.Host == "" { + rsp.ErrRsp(c, -1, "invalid url") + return + } + + // Set a sentinel file to mark that there is a download in progress + // This is to prevent multiple downloads at the same time + if _, err := os.Stat(sentinelPath); err == nil { + log.Debug("Download in progress") + rsp.ErrRsp(c, -1, "download in progress") + return + } + // Create the sentinel file + err = os.WriteFile(sentinelPath, []byte(req.File), 0644) + if err != nil { + log.Error("Failed to create sentinel file") + rsp.ErrRsp(c, -1, "failed to create sentinel file") + return + } + + // Check if it actually exists and fail if it doesn't + resp, err := http.Head(req.File) + if resp.StatusCode != http.StatusOK || err != nil { + rsp.ErrRsp(c, resp.StatusCode, "failed when checking the url") + log.Error("Failed to check the URL") + defer os.Remove(sentinelPath) + return + } + defer resp.Body.Close() + + // Download the image in a goroutine to not block the request + go func() { + defer os.Remove(sentinelPath) + resp, err = http.Get(req.File) + if err != nil { + log.Error("Failed to download the file") + rsp.ErrRsp(c, -1, "failed to download the file") + return + } + defer resp.Body.Close() + // Create the destination file + destPath := filepath.Join("/data", filepath.Base(u.Path)) + out, err := os.Create(destPath) + if err != nil { + log.Error("Failed to create destination file") + rsp.ErrRsp(c, -1, "failed to create destination file") + return + } + defer out.Close() + + lw := &loggingWriter{writer: out, totalSize: resp.ContentLength} + lw.startTicker() + _, err = io.Copy(lw, resp.Body) + if err != nil { + log.Error("Failed to save the file") + rsp.ErrRsp(c, -1, "failed to save the file") + lw.stopTicker() + return + } + lw.stopTicker() + }() + rsp.OkRspWithData(c, &proto.StatusImageRsp{ + Status: "in_progress", + File: req.File, + Percentage: "", + }) +} + +type loggingWriter struct { + writer io.Writer + total int64 + totalSize int64 + ticker *time.Ticker + done chan bool +} + +func (lw *loggingWriter) startTicker() { + lw.ticker = time.NewTicker(2500 * time.Millisecond) + lw.done = make(chan bool) + go func() { + for { + select { + case <-lw.done: + return + case <-lw.ticker.C: + lw.updateSentinel() + } + } + }() +} + +func (lw *loggingWriter) stopTicker() { + lw.ticker.Stop() + lw.done <- true +} + +func (lw *loggingWriter) updateSentinel() { + percentage := float64(lw.total) / float64(lw.totalSize) * 100 + content, err := os.ReadFile(sentinelPath) + if err != nil { + log.Error("Failed to read sentinel file") + return + } + splitted := strings.Split(string(content), ";") + if len(splitted) == 0 { + return + } + err = os.WriteFile(sentinelPath, []byte(fmt.Sprintf("%s;%.2f%%", splitted[0], percentage)), 0644) + if err != nil { + log.Error("Failed to update sentinel file") + } +} + +func (lw *loggingWriter) Write(p []byte) (int, error) { + n, err := lw.writer.Write(p) + lw.total += int64(n) + return n, err +} diff --git a/server/service/extensions/tailscale/cli.go b/server/service/extensions/tailscale/cli.go new file mode 100644 index 0000000..300c2b1 --- /dev/null +++ b/server/service/extensions/tailscale/cli.go @@ -0,0 +1,147 @@ +package tailscale + +import ( + "NanoKVM-Server/utils" + "bufio" + "encoding/json" + "errors" + "fmt" + "os" + "os/exec" + "regexp" + "strings" +) + +const ( + ScriptPath = "/etc/init.d/S98tailscaled" + ScriptBackupPath = "/kvmapp/system/init.d/S98tailscaled" +) + +type Cli struct{} + +type TsStatus struct { + BackendState string `json:"BackendState"` + + Self struct { + HostName string `json:"HostName"` + TailscaleIPs []string `json:"TailscaleIPs"` + } `json:"Self"` + + CurrentTailnet struct { + Name string `json:"Name"` + } `json:"CurrentTailnet"` +} + +func NewCli() *Cli { + return &Cli{} +} + +func (c *Cli) Start() error { + for _, filePath := range []string{TailscalePath, TailscaledPath} { + if err := utils.EnsurePermission(filePath, 0o100); err != nil { + return err + } + } + + commands := []string{ + fmt.Sprintf("cp -f %s %s", ScriptBackupPath, ScriptPath), + fmt.Sprintf("%s start", ScriptPath), + } + + command := strings.Join(commands, " && ") + return exec.Command("sh", "-c", command).Run() +} + +func (c *Cli) Restart() error { + commands := []string{ + fmt.Sprintf("cp -f %s %s", ScriptBackupPath, ScriptPath), + fmt.Sprintf("%s restart", ScriptPath), + } + + command := strings.Join(commands, " && ") + return exec.Command("sh", "-c", command).Run() +} + +func (c *Cli) Stop() error { + command := fmt.Sprintf("%s stop", ScriptPath) + err := exec.Command("sh", "-c", command).Run() + if err != nil { + return err + } + + return os.Remove(ScriptPath) +} + +func (c *Cli) Up() error { + command := "tailscale up --accept-dns=false" + return exec.Command("sh", "-c", command).Run() +} + +func (c *Cli) Down() error { + command := "tailscale down" + return exec.Command("sh", "-c", command).Run() +} + +func (c *Cli) Status() (*TsStatus, error) { + command := "tailscale status --json" + cmd := exec.Command("sh", "-c", command) + + output, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + + // output is not in standard json format + if outputStr := string(output); !strings.HasPrefix(outputStr, "{") { + index := strings.Index(outputStr, "{") + if index == -1 { + return nil, errors.New("unknown output") + } + + output = []byte(outputStr[index:]) + } + + var status TsStatus + err = json.Unmarshal(output, &status) + if err != nil { + return nil, err + } + + return &status, nil +} + +func (c *Cli) Login() (string, error) { + command := "tailscale login --accept-dns=false --timeout=10m" + cmd := exec.Command("sh", "-c", command) + + stderr, err := cmd.StderrPipe() + if err != nil { + return "", err + } + defer func() { + _ = stderr.Close() + }() + + go func() { + _ = cmd.Run() + }() + + reader := bufio.NewReader(stderr) + for { + line, err := reader.ReadString('\n') + if err != nil { + return "", err + } + + if strings.Contains(line, "https") { + reg := regexp.MustCompile(`\s+`) + url := reg.ReplaceAllString(line, "") + return url, nil + } + } +} + +func (c *Cli) Logout() error { + command := "tailscale logout" + return exec.Command("sh", "-c", command).Run() +} diff --git a/server/service/extensions/tailscale/install.go b/server/service/extensions/tailscale/install.go new file mode 100644 index 0000000..6ccebff --- /dev/null +++ b/server/service/extensions/tailscale/install.go @@ -0,0 +1,118 @@ +package tailscale + +import ( + "NanoKVM-Server/utils" + "fmt" + "io" + "net/http" + "os" + + log "github.com/sirupsen/logrus" +) + +const ( + OriginalURL = "https://pkgs.tailscale.com/stable/tailscale_latest_riscv64.tgz" + Workspace = "/root/.tailscale" +) + +func isInstalled() bool { + _, err1 := os.Stat(TailscalePath) + _, err2 := os.Stat(TailscaledPath) + + return err1 == nil && err2 == nil +} + +func install() error { + _ = os.MkdirAll(Workspace, 0o755) + defer func() { + _ = os.RemoveAll(Workspace) + }() + + tarFile := fmt.Sprintf("%s/tailscale_riscv64.tgz", Workspace) + + // download + if err := download(tarFile); err != nil { + log.Errorf("failed to download tailscale: %s", err) + return err + } + + // decompress + dir, err := utils.UnTarGz(tarFile, Workspace) + if err != nil { + log.Errorf("failed to decompress tailscale: %s", err) + return err + } + + // move + tailscalePath := fmt.Sprintf("%s/tailscale", dir) + err = utils.MoveFile(tailscalePath, TailscalePath) + if err != nil { + log.Errorf("failed to move tailscale: %s", err) + return err + } + + tailscaledPath := fmt.Sprintf("%s/tailscaled", dir) + err = utils.MoveFile(tailscaledPath, TailscaledPath) + if err != nil { + log.Errorf("failed to move tailscaled: %s", err) + return err + } + + log.Debugf("install tailscale successfully") + return nil +} + +func download(target string) error { + url, err := getDownloadURL() + if err != nil { + log.Errorf("failed to get Tailscale download url: %s", err) + return err + } + + resp, err := http.Get(url) + if err != nil { + log.Errorf("failed to download Tailscale: %s", err) + return err + } + defer func() { + _ = resp.Body.Close() + }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + out, err := os.Create(target) + if err != nil { + log.Errorf("failed to create file: %s", err) + return err + } + defer func() { + _ = out.Close() + }() + + _, err = io.Copy(out, resp.Body) + if err != nil { + log.Errorf("failed to copy response body to file: %s", err) + return err + } + + log.Debugf("download Tailscale successfully") + return nil +} + +func getDownloadURL() (string, error) { + resp, err := (&http.Client{}).Get(OriginalURL) + if err != nil { + return "", err + } + defer func() { + _ = resp.Body.Close() + }() + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound { + return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + return resp.Request.URL.String(), nil +} diff --git a/server/service/extensions/tailscale/service.go b/server/service/extensions/tailscale/service.go new file mode 100644 index 0000000..8075bbd --- /dev/null +++ b/server/service/extensions/tailscale/service.go @@ -0,0 +1,237 @@ +package tailscale + +import ( + "NanoKVM-Server/proto" + "NanoKVM-Server/utils" + "net" + "os" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +type Service struct{} + +const ( + TailscalePath = "/usr/bin/tailscale" + TailscaledPath = "/usr/sbin/tailscaled" + + GoMemLimit int64 = 75 +) + +var StateMap = map[string]proto.TailscaleState{ + "NoState": proto.TailscaleNotRunning, + "Starting": proto.TailscaleNotRunning, + "NeedsLogin": proto.TailscaleNotLogin, + "Running": proto.TailscaleRunning, + "Stopped": proto.TailscaleStopped, +} + +func NewService() *Service { + return &Service{} +} + +func (s *Service) Install(c *gin.Context) { + var rsp proto.Response + + if !isInstalled() { + if err := install(); err != nil { + rsp.ErrRsp(c, -1, "install failed") + return + } + + _ = NewCli().Start() + } + + rsp.OkRsp(c) + log.Debugf("install tailscale successfully") +} + +func (s *Service) Uninstall(c *gin.Context) { + var rsp proto.Response + + _ = NewCli().Stop() + _ = utils.DelGoMemLimit() + + _ = os.Remove(TailscalePath) + _ = os.Remove(TailscaledPath) + + rsp.OkRsp(c) + log.Debugf("uninstall tailscale successfully") +} + +func (s *Service) Start(c *gin.Context) { + var rsp proto.Response + + err := NewCli().Start() + if err != nil { + rsp.ErrRsp(c, -1, "start failed") + log.Errorf("failed to run tailscale start: %s", err) + return + } + + if !utils.IsGoMemLimitExist() { + _ = utils.SetGoMemLimit(GoMemLimit) + } + + rsp.OkRsp(c) + log.Debugf("tailscale start successfully") +} + +func (s *Service) Restart(c *gin.Context) { + var rsp proto.Response + + err := NewCli().Restart() + if err != nil { + rsp.ErrRsp(c, -1, "restart failed") + log.Errorf("failed to run tailscale restart: %s", err) + return + } + + rsp.OkRsp(c) + log.Debugf("tailscale restart successfully") +} + +func (s *Service) Stop(c *gin.Context) { + var rsp proto.Response + + err := NewCli().Stop() + if err != nil { + rsp.ErrRsp(c, -1, "stop failed") + log.Errorf("failed to run tailscale stop: %s", err) + return + } + + _ = utils.DelGoMemLimit() + + rsp.OkRsp(c) + log.Debugf("tailscale stop successfully") +} + +func (s *Service) Up(c *gin.Context) { + var rsp proto.Response + + err := NewCli().Up() + if err != nil { + rsp.ErrRsp(c, -1, "tailscale up failed") + log.Errorf("failed to run tailscale up: %s", err) + return + } + + rsp.OkRsp(c) + log.Debugf("run tailscale up successfully") +} + +func (s *Service) Down(c *gin.Context) { + var rsp proto.Response + + err := NewCli().Down() + if err != nil { + rsp.ErrRsp(c, -1, "tailscale down failed") + log.Errorf("failed to run tailscale down: %s", err) + return + } + + rsp.OkRsp(c) + log.Debugf("run tailscale down successfully") +} + +func (s *Service) Login(c *gin.Context) { + var rsp proto.Response + + // check tailscale status + cli := NewCli() + status, err := cli.Status() + if err != nil { + _ = cli.Start() + status, err = cli.Status() + } + + if err != nil { + log.Errorf("failed to get tailscale status: %s", err) + rsp.ErrRsp(c, -1, "unknown status") + return + } + + if status.BackendState == "Running" { + rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{}) + return + } + + // get login url + url, err := cli.Login() + if err != nil { + log.Errorf("failed to run tailscale login: %s", err) + rsp.ErrRsp(c, -2, "login failed") + return + } + + if !utils.IsGoMemLimitExist() { + _ = utils.SetGoMemLimit(GoMemLimit) + } + + rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{ + Url: url, + }) + + log.Debugf("tailscale login url: %s", url) +} + +func (s *Service) Logout(c *gin.Context) { + var rsp proto.Response + + err := NewCli().Logout() + if err != nil { + rsp.ErrRsp(c, -1, "logout failed") + log.Errorf("failed to run tailscale logout: %s", err) + return + } + + rsp.OkRsp(c) + log.Debugf("tailscale logout successfully") +} + +func (s *Service) GetStatus(c *gin.Context) { + var rsp proto.Response + + if !isInstalled() { + rsp.OkRspWithData(c, &proto.GetTailscaleStatusRsp{ + State: proto.TailscaleNotInstall, + }) + return + } + + status, err := NewCli().Status() + if err != nil { + log.Debugf("failed to get tailscale status: %s", err) + rsp.OkRspWithData(c, &proto.GetTailscaleStatusRsp{ + State: proto.TailscaleNotRunning, + }) + return + } + + state, ok := StateMap[status.BackendState] + if !ok { + log.Errorf("unknown tailscale state: %s", status.BackendState) + rsp.ErrRsp(c, -1, "unknown state") + return + } + + ipv4 := "" + for _, tailscaleIp := range status.Self.TailscaleIPs { + ip := net.ParseIP(tailscaleIp) + if ip != nil && ip.To4() != nil { + ipv4 = ip.String() + } + } + + data := proto.GetTailscaleStatusRsp{ + State: state, + IP: ipv4, + Name: status.Self.HostName, + Account: status.CurrentTailnet.Name, + } + + rsp.OkRspWithData(c, &data) + log.Debugf("get tailscale status successfully") +} diff --git a/server/service/hid/hid.go b/server/service/hid/hid.go new file mode 100644 index 0000000..85e86b4 --- /dev/null +++ b/server/service/hid/hid.go @@ -0,0 +1,161 @@ +package hid + +import ( + "errors" + "os" + "sync" + "time" + + log "github.com/sirupsen/logrus" +) + +type Hid struct { + g0 *os.File + g1 *os.File + g2 *os.File + kbMutex sync.Mutex + mouseMutex sync.Mutex +} + +const ( + HID0 = "/dev/hidg0" + HID1 = "/dev/hidg1" + HID2 = "/dev/hidg2" +) + +var ( + hid *Hid + hidOnce sync.Once +) + +func GetHid() *Hid { + hidOnce.Do(func() { + hid = &Hid{} + }) + return hid +} + +func (h *Hid) Lock() { + h.kbMutex.Lock() + h.mouseMutex.Lock() +} + +func (h *Hid) Unlock() { + h.kbMutex.Unlock() + h.mouseMutex.Unlock() +} + +func (h *Hid) OpenNoLock() { + var err error + h.CloseNoLock() + + h.g0, err = os.OpenFile(HID0, os.O_WRONLY, 0o666) + if err != nil { + log.Errorf("open %s failed: %s", HID0, err) + } + + h.g1, err = os.OpenFile(HID1, os.O_WRONLY, 0o666) + if err != nil { + log.Errorf("open %s failed: %s", HID1, err) + } + + h.g2, err = os.OpenFile(HID2, os.O_WRONLY, 0o666) + if err != nil { + log.Errorf("open %s failed: %s", HID2, err) + } +} + +func (h *Hid) CloseNoLock() { + for _, file := range []*os.File{h.g0, h.g1, h.g2} { + if file != nil { + _ = file.Sync() + _ = file.Close() + } + } +} + +func (h *Hid) Open() { + h.kbMutex.Lock() + defer h.kbMutex.Unlock() + h.mouseMutex.Lock() + defer h.mouseMutex.Unlock() + + h.CloseNoLock() + + h.OpenNoLock() +} + +func (h *Hid) Close() { + h.kbMutex.Lock() + defer h.kbMutex.Unlock() + h.mouseMutex.Lock() + defer h.mouseMutex.Unlock() + + h.CloseNoLock() +} + +func (h *Hid) WriteHid0(data []byte) { + h.kbMutex.Lock() + _, err := h.g0.Write(data) + h.kbMutex.Unlock() + + if err != nil { + if errors.Is(err, os.ErrClosed) { + log.Errorf("hid already closed, reopen it...") + h.OpenNoLock() + } else { + log.Debugf("write to %s failed: %s", HID0, err) + } + return + } + + log.Debugf("write to %s: %v", HID0, data) +} + +func (h *Hid) WriteHid1(data []byte) { + deadline := time.Now().Add(8 * time.Millisecond) + + h.mouseMutex.Lock() + _ = h.g1.SetWriteDeadline(deadline) + _, err := h.g1.Write(data) + h.mouseMutex.Unlock() + + if err != nil { + switch { + case errors.Is(err, os.ErrClosed): + log.Errorf("hid already closed, reopen it...") + h.OpenNoLock() + case errors.Is(err, os.ErrDeadlineExceeded): + log.Debugf("write to %s timeout", HID1) + default: + log.Errorf("write to %s failed: %s", HID1, err) + } + return + } + + log.Debugf("write to %s: %v", HID1, data) +} + +func (h *Hid) WriteHid2(data []byte) { + deadline := time.Now().Add(8 * time.Millisecond) + + h.mouseMutex.Lock() + _ = h.g2.SetWriteDeadline(deadline) + _, err := h.g2.Write(data) + h.mouseMutex.Unlock() + + if err != nil { + switch { + case errors.Is(err, os.ErrClosed): + log.Errorf("hid already closed, reopen it...") + h.OpenNoLock() + case errors.Is(err, os.ErrDeadlineExceeded): + log.Debugf("write to %s timeout", HID2) + default: + log.Errorf("write to %s failed: %s", HID2, err) + } + return + } + + log.Debugf("write to %s: %v", HID2, data) +} diff --git a/server/service/hid/keyboard.go b/server/service/hid/keyboard.go new file mode 100644 index 0000000..72e1676 --- /dev/null +++ b/server/service/hid/keyboard.go @@ -0,0 +1,15 @@ +package hid + +func (h *Hid) Keyboard(queue <-chan []int) { + for event := range queue { + code := byte(event[0]) + + var modifier byte = 0x00 + if code > 0 { + modifier = byte(event[1]) | byte(event[2]) | byte(event[3]) | byte(event[4]) + } + + data := []byte{modifier, 0x00, code, 0x00, 0x00, 0x00, 0x00, 0x00} + h.WriteHid0(data) + } +} diff --git a/server/service/hid/mouse.go b/server/service/hid/mouse.go new file mode 100644 index 0000000..9acda14 --- /dev/null +++ b/server/service/hid/mouse.go @@ -0,0 +1,82 @@ +package hid + +import ( + "encoding/binary" + + log "github.com/sirupsen/logrus" +) + +const ( + MouseUp = iota + MouseDown + MouseMoveAbsolute + MouseMoveRelative + MouseScroll +) + +var mouseButtonMap = map[byte]bool{ + 0x01: true, + 0x02: true, + 0x04: true, +} + +func (h *Hid) Mouse(queue <-chan []int) { + for event := range queue { + + switch event[0] { + case MouseDown: + h.mouseDown(event) + case MouseUp: + h.mouseUp() + case MouseMoveAbsolute: + h.mouseMoveAbsolute(event) + case MouseMoveRelative: + h.mouseMoveRelative(event) + case MouseScroll: + h.mouseScroll(event) + default: + log.Debugf("invalid mouse event: %v", event) + } + } +} + +func (h *Hid) mouseDown(event []int) { + button := byte(event[1]) + + if _, ok := mouseButtonMap[button]; !ok { + log.Errorf("invalid mouse button: %v", event) + return + } + + data := []byte{button, 0, 0, 0} + h.WriteHid1(data) +} + +func (h *Hid) mouseUp() { + data := []byte{0, 0, 0, 0} + h.WriteHid1(data) +} + +func (h *Hid) mouseScroll(event []int) { + direction := 0x01 + if event[3] > 0 { + direction = -0x1 + } + + data := []byte{0, 0, 0, byte(direction)} + h.WriteHid1(data) +} +func (h *Hid) mouseMoveAbsolute(event []int) { + x := make([]byte, 2) + y := make([]byte, 2) + binary.LittleEndian.PutUint16(x, uint16(event[2])) + binary.LittleEndian.PutUint16(y, uint16(event[3])) + + data := []byte{0, x[0], x[1], y[0], y[1], 0} + h.WriteHid2(data) +} + +func (h *Hid) mouseMoveRelative(event []int) { + data := []byte{byte(event[1]), byte(event[2]), byte(event[3]), 0} + h.WriteHid1(data) +} diff --git a/server/service/hid/paste.go b/server/service/hid/paste.go new file mode 100644 index 0000000..1f8e701 --- /dev/null +++ b/server/service/hid/paste.go @@ -0,0 +1,197 @@ +package hid + +import ( + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/proto" +) + +type Char struct { + Modifiers int + Code int +} + +type PasteReq struct { + Content string `form:"content" validate:"required"` + Langue string `form:"langue"` +} + +func LangueSwitch(base map[rune]Char, lang string) map[rune]Char { + // wenn kein lang angegeben → Standardmap zurück + if lang == "" { + return base + } + + // immer Kopie erstellen + m := copyMap(base) + + switch lang { + case "de": + // Y tauschen + m['y'] = Char{0, 29} + m['Y'] = Char{2, 29} + + // Z tauschen + m['z'] = Char{0, 28} + m['Z'] = Char{2, 28} + + // deutsche Sonderzeichen hinzufügen oder remappen + m['\u00E4'] = Char{0, 52} // ä + m['\u00C4'] = Char{2, 52} // Ä + m['\u00F6'] = Char{0, 51} // ö + m['\u00D6'] = Char{2, 51} // Ö + m['\u00FC'] = Char{0, 47} // ü + m['\u00DC'] = Char{2, 47} // Ü + m['\u00DF'] = Char{0, 45} // ß + + //Tauschen + m['^'] = Char{0, 53} // muss doppelt sein + m['/'] = Char{2, 36} // Shift + 7 + m['('] = Char{2, 37} // Shift + 8 + m['&'] = Char{2, 35} // Shift + 6 + m[')'] = Char{2, 38} // Shift + 9 + m['`'] = Char{2, 46} // Grave Accent / Backtick + m['"'] = Char{2, 31} // Shift + 2 + m['?'] = Char{2, 45} // Shift + ß + m['{'] = Char{0x40, 36} // ALt Gr + 7 + m['['] = Char{0x40, 37} // ALt Gr + 8 + m[']'] = Char{0x40, 38} // ALt Gr + 6 + m['}'] = Char{0x40, 39} // ALt Gr + 0 + m['\\'] = Char{0x40, 45} // ALt Gr + ß + m['@'] = Char{0x40, 20} // ALt Gr + q + m['+'] = Char{0, 48} // Shift + + + m['*'] = Char{2, 48} // Shift + + + m['~'] = Char{0x40, 48} // Shift + + + m['#'] = Char{0, 49} // Shift + # + m['\''] = Char{2, 49} // Shift + # + m['<'] = Char{0, 100} // Shift + < + m['>'] = Char{2, 100} // Shift + < + m['|'] = Char{0x40, 100} // ALt Gr + < + m[';'] = Char{2, 54} // Shift + , + m[':'] = Char{2, 55} // Shift + . + m['-'] = Char{0, 56} // Shift + - + m['_'] = Char{2, 56} // Shift + - + + //neu + m['\u00B4'] = Char{0, 46} // ´ + m['\u00B0'] = Char{2, 53} // ° + m['\u00A7'] = Char{2, 32} // § + m['\u20AC'] = Char{0x40, 8} // € + m['\u00B2'] = Char{0x40, 31} // ² + m['\u00B3'] = Char{0x40, 32} // ³ + + } + return m +} + +func (s *Service) Paste(c *gin.Context) { + var req PasteReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + if len(req.Content) > 1024 { + rsp.ErrRsp(c, -2, "content too long") + return + } + + charMapLocal := LangueSwitch(charMap, req.Langue) + + keyUp := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + for _, char := range req.Content { + key, ok := charMapLocal[char] + if !ok { + log.Debugf("unknown key '%c' (rune: %d)", char, char) + continue + } + + keyDown := []byte{byte(key.Modifiers), 0x00, byte(key.Code), 0x00, 0x00, 0x00, 0x00, 0x00} + + hid.WriteHid0(keyDown) + hid.WriteHid0(keyUp) + time.Sleep(30 * time.Millisecond) + } + + rsp.OkRsp(c) + log.Debugf("hid paste success, total %d characters processed", len(req.Content)) +} + +func copyMap(src map[rune]Char) map[rune]Char { + dst := make(map[rune]Char, len(src)) + for k, v := range src { + dst[k] = v + } + return dst +} + +var charMap = map[rune]Char{ + // Lowercase letters + 'a': {0, 4}, 'b': {0, 5}, 'c': {0, 6}, 'd': {0, 7}, 'e': {0, 8}, + 'f': {0, 9}, 'g': {0, 10}, 'h': {0, 11}, 'i': {0, 12}, 'j': {0, 13}, + 'k': {0, 14}, 'l': {0, 15}, 'm': {0, 16}, 'n': {0, 17}, 'o': {0, 18}, + 'p': {0, 19}, 'q': {0, 20}, 'r': {0, 21}, 's': {0, 22}, 't': {0, 23}, + 'u': {0, 24}, 'v': {0, 25}, 'w': {0, 26}, 'x': {0, 27}, 'y': {0, 28}, + 'z': {0, 29}, + + // Uppercase letters (Modifier 2 typically means Left Shift) + 'A': {2, 4}, 'B': {2, 5}, 'C': {2, 6}, 'D': {2, 7}, 'E': {2, 8}, + 'F': {2, 9}, 'G': {2, 10}, 'H': {2, 11}, 'I': {2, 12}, 'J': {2, 13}, + 'K': {2, 14}, 'L': {2, 15}, 'M': {2, 16}, 'N': {2, 17}, 'O': {2, 18}, + 'P': {2, 19}, 'Q': {2, 20}, 'R': {2, 21}, 'S': {2, 22}, 'T': {2, 23}, + 'U': {2, 24}, 'V': {2, 25}, 'W': {2, 26}, 'X': {2, 27}, 'Y': {2, 28}, + 'Z': {2, 29}, + + // Numbers + '1': {0, 30}, '2': {0, 31}, '3': {0, 32}, '4': {0, 33}, '5': {0, 34}, + '6': {0, 35}, '7': {0, 36}, '8': {0, 37}, '9': {0, 38}, '0': {0, 39}, + + // Shifted numbers / Symbols + '!': {2, 30}, // Shift + 1 + '@': {2, 31}, // Shift + 2 + '#': {2, 32}, // Shift + 3 + '$': {2, 33}, // Shift + 4 + '%': {2, 34}, // Shift + 5 + '^': {2, 35}, // Shift + 6 + '&': {2, 36}, // Shift + 7 + '*': {2, 37}, // Shift + 8 + '(': {2, 38}, // Shift + 9 + ')': {2, 39}, // Shift + 0 + + // Other common characters + '\n': {0, 40}, // Enter (Return) + '\t': {0, 43}, // Tab + ' ': {0, 44}, // Space + '-': {0, 45}, // Hyphen / Minus + '=': {0, 46}, // Equals + '[': {0, 47}, // Left Square Bracket + ']': {0, 48}, // Right Square Bracket + '\\': {0, 49}, // Backslash + + ';': {0, 51}, // Semicolon + '\'': {0, 52}, // Apostrophe / Single Quote + '`': {0, 53}, // Grave Accent / Backtick + ',': {0, 54}, // Comma + '.': {0, 55}, // Period / Dot + '/': {0, 56}, // Slash + + // Shifted symbols + '_': {2, 45}, // Underscore (Shift + Hyphen) + '+': {2, 46}, // Plus (Shift + Equals) + '{': {2, 47}, // Left Curly Brace (Shift + Left Square Bracket) + '}': {2, 48}, // Right Curly Brace (Shift + Right Square Bracket) + '|': {2, 49}, // Pipe (Shift + Backslash) + + ':': {2, 51}, // Colon (Shift + Semicolon) + '"': {2, 52}, // Double Quote (Shift + Apostrophe) + '~': {2, 53}, // Tilde (Shift + Grave Accent) + '<': {2, 54}, // Less Than (Shift + Comma) + '>': {2, 55}, // Greater Than (Shift + Period) + '?': {2, 56}, // Question Mark (Shift + Slash) +} diff --git a/server/service/hid/service.go b/server/service/hid/service.go new file mode 100644 index 0000000..211e134 --- /dev/null +++ b/server/service/hid/service.go @@ -0,0 +1,11 @@ +package hid + +type Service struct { + hid *Hid +} + +func NewService() *Service { + return &Service{ + hid: GetHid(), + } +} diff --git a/server/service/hid/status.go b/server/service/hid/status.go new file mode 100644 index 0000000..08c06a1 --- /dev/null +++ b/server/service/hid/status.go @@ -0,0 +1,191 @@ +package hid + +import ( + "NanoKVM-Server/proto" + "errors" + "fmt" + "io" + "os" + "os/exec" + "strings" + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + ModeNormal = "normal" + ModeHidOnly = "hid-only" + ModeFlag = "/sys/kernel/config/usb_gadget/g0/bcdDevice" + + ModeNormalScript = "/kvmapp/system/init.d/S03usbdev" + ModeHidOnlyScript = "/kvmapp/system/init.d/S03usbhid" + + USBDevScript = "/etc/init.d/S03usbdev" +) + +var modeMap = map[string]string{ + "0x0510": ModeNormal, + "0x0623": ModeHidOnly, +} + +func (s *Service) GetHidMode(c *gin.Context) { + var rsp proto.Response + + mode, err := getHidMode() + if err != nil { + rsp.ErrRsp(c, -1, "get HID mode failed") + return + } + + rsp.OkRspWithData(c, &proto.GetHidModeRsp{ + Mode: mode, + }) + log.Debugf("get hid mode: %s", mode) +} + +func (s *Service) SetHidMode(c *gin.Context) { + var req proto.SetHidModeReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + if req.Mode != ModeNormal && req.Mode != ModeHidOnly { + rsp.ErrRsp(c, -2, "invalid arguments") + return + } + + if mode, _ := getHidMode(); req.Mode == mode { + rsp.OkRsp(c) + return + } + + h := GetHid() + h.Lock() + h.CloseNoLock() + defer func() { + h.OpenNoLock() + h.Unlock() + }() + + srcScript := ModeNormalScript + if req.Mode == ModeHidOnly { + srcScript = ModeHidOnlyScript + } + + if err := copyModeFile(srcScript); err != nil { + rsp.ErrRsp(c, -3, "operation failed") + return + } + + rsp.OkRsp(c) + + log.Println("reboot system...") + time.Sleep(500 * time.Millisecond) + _ = exec.Command("reboot").Run() +} + +func (s *Service) ResetHid(c *gin.Context) { + var rsp proto.Response + + h := GetHid() + h.Lock() + h.CloseNoLock() + defer func() { + h.OpenNoLock() + h.Unlock() + }() + + command := fmt.Sprintf("%s restart_phy", USBDevScript) + err := exec.Command("sh", "-c", command).Run() + if err != nil { + log.Errorf("failed to reset hid: %v", err) + rsp.ErrRsp(c, -1, "failed to reset hid") + return + } + + rsp.OkRsp(c) + log.Debugf("reset hid success") +} + +func copyModeFile(srcScript string) error { + // open the source file + srcFile, err := os.Open(srcScript) + if err != nil { + log.Errorf("failed to open %s: %s", srcScript, err) + return err + } + defer func() { + _ = srcFile.Close() + }() + + srcInfo, err := srcFile.Stat() + if err != nil { + log.Errorf("failed to get %s info: %s", srcScript, err) + return err + } + + // create and copy to temporary file + tmpFile, err := os.CreateTemp("/etc/init.d/", ".S03usbdev-") + if err != nil { + log.Errorf("failed to create temp %s: %s", USBDevScript, err) + return err + } + tmpPath := tmpFile.Name() + defer func() { + _ = os.Remove(tmpPath) + }() + log.Debugf("create temporary file: %s", tmpPath) + + if err := tmpFile.Chmod(srcInfo.Mode()); err != nil { + _ = tmpFile.Close() + log.Errorf("failed to set %s mode: %s", tmpPath, err) + return err + } + + if _, err := io.Copy(tmpFile, srcFile); err != nil { + _ = tmpFile.Close() + log.Errorf("failed to copy %s: %s", srcScript, err) + return err + } + + if err := tmpFile.Sync(); err != nil { + _ = tmpFile.Close() + log.Errorf("failed to sync %s: %s", tmpPath, err) + return err + } + + if err := tmpFile.Close(); err != nil { + log.Errorf("failed to close %s: %s", tmpPath, err) + return err + } + + // replace the target file with the temporary file + if err := os.Rename(tmpPath, USBDevScript); err != nil { + log.Errorf("failed to rename %s: %s", tmpPath, err) + return err + } + + log.Debugf("copy %s to %s successful", srcScript, USBDevScript) + return nil +} + +func getHidMode() (string, error) { + data, err := os.ReadFile(ModeFlag) + if err != nil { + log.Errorf("failed to read %s: %s", ModeFlag, err) + return "", err + } + + key := strings.TrimSpace(string(data)) + mode, ok := modeMap[key] + if !ok { + log.Errorf("invalid mode flag: %s", key) + return "", errors.New("invalid mode flag") + } + + return mode, nil +} diff --git a/server/service/network/service.go b/server/service/network/service.go new file mode 100644 index 0000000..6b02b6b --- /dev/null +++ b/server/service/network/service.go @@ -0,0 +1,7 @@ +package network + +type Service struct{} + +func NewService() *Service { + return &Service{} +} diff --git a/server/service/network/wifi.go b/server/service/network/wifi.go new file mode 100644 index 0000000..b2b2eb1 --- /dev/null +++ b/server/service/network/wifi.go @@ -0,0 +1,75 @@ +package network + +import ( + "NanoKVM-Server/proto" + "os" + "strings" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + WiFiExistFile = "/etc/kvm/wifi_exist" + WiFiSSID = "/etc/kvm/wifi.ssid" + WiFiPasswd = "/etc/kvm/wifi.pass" + WiFiConnect = "/kvmapp/kvm/wifi_try_connect" + WiFiStateFile = "/kvmapp/kvm/wifi_state" +) + +func (s *Service) GetWifi(c *gin.Context) { + var rsp proto.Response + + data := &proto.GetWifiRsp{} + + _, err := os.Stat(WiFiExistFile) + if err != nil { + rsp.OkRspWithData(c, data) + return + } + + data.Supported = true + + content, err := os.ReadFile(WiFiStateFile) + if err != nil { + rsp.OkRspWithData(c, data) + return + } + + state := strings.ReplaceAll(string(content), "\n", "") + data.Connected = state == "1" + + rsp.OkRspWithData(c, data) + log.Debugf("get wifi state: %s", state) +} + +func (s *Service) ConnectWifi(c *gin.Context) { + var req proto.ConnectWifiReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid parameters") + return + } + + if err := os.WriteFile(WiFiSSID, []byte(req.Ssid), 0o644); err != nil { + log.Errorf("failed to save wifi ssid: %s", err) + rsp.ErrRsp(c, -2, "failed to save wifi") + return + } + + if err := os.WriteFile(WiFiPasswd, []byte(req.Password), 0o644); err != nil { + log.Errorf("failed to save wifi password: %s", err) + rsp.ErrRsp(c, -3, "failed to save wifi") + return + } + + if err := os.WriteFile(WiFiConnect, nil, 0o644); err != nil { + log.Errorf("failed to connect wifi: %s", err) + rsp.ErrRsp(c, -4, "failed to connect wifi") + return + } + + rsp.OkRsp(c) + log.Debugf("set wifi successfully: %s", req.Ssid) +} diff --git a/server/service/network/wol.go b/server/service/network/wol.go new file mode 100644 index 0000000..9176328 --- /dev/null +++ b/server/service/network/wol.go @@ -0,0 +1,223 @@ +package network + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/proto" +) + +const ( + WolMacFile = "/etc/kvm/cache/wol" +) + +func (s *Service) WakeOnLAN(c *gin.Context) { + var req proto.WakeOnLANReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + mac, err := parseMAC(req.Mac) + if err != nil { + rsp.ErrRsp(c, -2, "invalid MAC address") + return + } + + command := fmt.Sprintf("ether-wake -b %s", mac) + cmd := exec.Command("sh", "-c", command) + + output, err := cmd.CombinedOutput() + if err != nil { + log.Errorf("failed to wake on lan: %s", err) + rsp.ErrRsp(c, -3, string(output)) + return + } + + go saveMac(mac) + + rsp.OkRsp(c) + log.Debugf("wake on lan: %s", mac) +} + +func (s *Service) GetMac(c *gin.Context) { + var rsp proto.Response + + content, err := os.ReadFile(WolMacFile) + if err != nil { + rsp.ErrRsp(c, -2, "open file error") + return + } + + data := &proto.GetMacRsp{ + Macs: strings.Split(string(content), "\n"), + } + + rsp.OkRspWithData(c, data) +} + +func (s *Service) SetMacName(c *gin.Context) { + var req proto.SetMacNameReq // Mac:string Name:string + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + content, err := os.ReadFile(WolMacFile) + if err != nil { + log.Errorf("failed to open %s: %s", WolMacFile, err) + rsp.ErrRsp(c, -2, "read failed") + return + } + + macs := strings.Split(string(content), "\n") + var newLines []string + macFound := false + + for _, line := range macs { + parts := strings.Split(line, " ") + if req.Mac != parts[0] { + newLines = append(newLines, line) + continue + } + newLines = append(newLines, parts[0]+" "+req.Name) + macFound = true + } + + if !macFound { + log.Errorf("failed to found mac %s: %s", req.Mac, err) + rsp.ErrRsp(c, -3, "write failed") + return + } + + data := strings.Join(newLines, "\n") + err = os.WriteFile(WolMacFile, []byte(data), 0o644) + if err != nil { + log.Errorf("failed to write %s: %s", WolMacFile, err) + rsp.ErrRsp(c, -3, "write failed") + return + } + + rsp.OkRsp(c) + log.Debugf("set wol mac name: %s %s", req.Mac, req.Name) +} + +func (s *Service) DeleteMac(c *gin.Context) { + var req proto.DeleteMacReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + content, err := os.ReadFile(WolMacFile) + if err != nil { + log.Errorf("failed to open %s: %s", WolMacFile, err) + rsp.ErrRsp(c, -2, "read failed") + return + } + + macs := strings.Split(string(content), "\n") + var newMacs []string + + for _, mac := range macs { + parts := strings.Split(mac, " ") + if req.Mac != parts[0] { + newMacs = append(newMacs, mac) + } + } + + data := strings.Join(newMacs, "\n") + err = os.WriteFile(WolMacFile, []byte(data), 0o644) + if err != nil { + log.Errorf("failed to write %s: %s", WolMacFile, err) + rsp.ErrRsp(c, -3, "write failed") + return + } + + rsp.OkRsp(c) + log.Debugf("delete wol mac: %s", req.Mac) +} + +func parseMAC(mac string) (string, error) { + mac = strings.ToUpper(strings.TrimSpace(mac)) + + mac = strings.ReplaceAll(mac, "-", "") + mac = strings.ReplaceAll(mac, ":", "") + mac = strings.ReplaceAll(mac, ".", "") + + matched, err := regexp.MatchString("^[0-9A-F]{12}$", mac) + if err != nil { + return "", err + } + if !matched { + return "", fmt.Errorf("invalid MAC address: %s", mac) + } + + var result strings.Builder + for i := 0; i < 12; i += 2 { + if i > 0 { + result.WriteString(":") + } + result.WriteString(mac[i : i+2]) + } + + return result.String(), nil +} + +func saveMac(mac string) { + if isMacExist(mac) { + return + } + + err := os.MkdirAll(filepath.Dir(WolMacFile), 0o644) + if err != nil { + log.Errorf("failed to create dir: %s", err) + return + } + + file, err := os.OpenFile(WolMacFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + log.Errorf("failed to open %s: %s", WolMacFile, err) + return + } + defer func() { + _ = file.Close() + }() + + content := fmt.Sprintf("%s\n", mac) + _, err = file.WriteString(content) + if err != nil { + log.Errorf("failed to write %s: %s", WolMacFile, err) + return + } +} + +func isMacExist(mac string) bool { + content, err := os.ReadFile(WolMacFile) + if err != nil { + return false + } + + macs := strings.Split(string(content), "\n") + for _, item := range macs { + parts := strings.Split(item, " ") + if mac == parts[0] { + return true + } + } + + return false +} diff --git a/server/service/storage/image.go b/server/service/storage/image.go new file mode 100644 index 0000000..4e0be46 --- /dev/null +++ b/server/service/storage/image.go @@ -0,0 +1,220 @@ +package storage + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/proto" + "NanoKVM-Server/service/hid" +) + +const ( + imageDirectory = "/data" + imageNone = "/dev/mmcblk0p3" + cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom" + mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file" + inquiryString = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/inquiry_string" + roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro" +) + +func (s *Service) GetImages(c *gin.Context) { + var rsp proto.Response + var images []string + + err := filepath.Walk(imageDirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if !info.IsDir() { + name := strings.ToLower(info.Name()) + if strings.HasSuffix(name, ".iso") || strings.HasSuffix(name, ".img") { + images = append(images, path) + } + } + + return nil + }) + if err != nil { + rsp.ErrRsp(c, -2, "get images failed") + return + } + + rsp.OkRspWithData(c, &proto.GetImagesRsp{ + Files: images, + }) + log.Debugf("get images success, total %d", len(images)) +} + +func (s *Service) MountImage(c *gin.Context) { + var req proto.MountImageReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + // cdrom and ro flag + // set to 0 when unmount image + // set to 1 when mount image and the CD-ROM is enabled + if req.File == "" || req.Cdrom { + flag := "0" + if req.File != "" && req.Cdrom { + flag = "1" + } + + // unmount + if err := os.WriteFile(mountDevice, []byte("\n"), 0o666); err != nil { + log.Errorf("unmount file failed: %s", err) + rsp.ErrRsp(c, -2, "unmount image failed") + return + } + + // ro flag + if err := os.WriteFile(roFlag, []byte(flag), 0o666); err != nil { + log.Errorf("set ro flag failed: %s", err) + rsp.ErrRsp(c, -2, "set ro flag failed") + return + } + + // cdrom flag + if err := os.WriteFile(cdromFlag, []byte(flag), 0o666); err != nil { + log.Errorf("set cdrom flag failed: %s", err) + rsp.ErrRsp(c, -2, "set cdrom flag failed") + return + } + } + + inquiryVen := "NanoKVM" + inquiryPrd := "USB Mass Storage" + inquiryVer := 0x0520 + if req.Cdrom { + inquiryPrd = "USB CD/DVD-ROM" + } + inquiryData := fmt.Sprintf("%-8s%-16s%04x", inquiryVen, inquiryPrd, inquiryVer) + + if err := os.WriteFile(inquiryString, []byte(inquiryData), 0o666); err != nil { + log.Errorf("set inquiry %s failed: %s", inquiryData, err) + rsp.ErrRsp(c, -2, "set inquiry failed") + return + } + + // mount + image := req.File + if image == "" { + image = imageNone + } + + if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil { + log.Errorf("mount file %s failed: %s", image, err) + rsp.ErrRsp(c, -2, "mount image failed") + return + } + + h := hid.GetHid() + h.Lock() + h.CloseNoLock() + defer func() { + h.OpenNoLock() + h.Unlock() + }() + + // reset usb + commands := []string{ + "echo > /sys/kernel/config/usb_gadget/g0/UDC", + "ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC", + } + + for _, command := range commands { + err := exec.Command("sh", "-c", command).Run() + if err != nil { + rsp.ErrRsp(c, -2, "execute command failed") + return + } + time.Sleep(100 * time.Millisecond) + } + + rsp.OkRsp(c) + log.Debugf("mount image %s success", req.File) +} + +func (s *Service) GetMountedImage(c *gin.Context) { + var rsp proto.Response + + content, err := os.ReadFile(mountDevice) + if err != nil { + rsp.ErrRsp(c, -2, "read failed") + return + } + + image := strings.ReplaceAll(string(content), "\n", "") + if image == imageNone { + image = "" + } + + data := &proto.GetMountedImageRsp{ + File: image, + } + + rsp.OkRspWithData(c, data) +} + +func (s *Service) GetCdRom(c *gin.Context) { + var rsp proto.Response + + content, err := os.ReadFile(cdromFlag) + if err != nil { + rsp.ErrRsp(c, -1, "read failed") + return + } + + flag := strings.ReplaceAll(string(content), "\n", "") + flatInt, err := strconv.ParseInt(flag, 10, 64) + if err != nil { + rsp.ErrRsp(c, -2, "parse failed") + return + } + + data := &proto.GetCdRomRsp{ + Cdrom: flatInt, + } + + rsp.OkRspWithData(c, data) +} + +func (s *Service) DeleteImage(c *gin.Context) { + var req proto.DeleteImageReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + filename := strings.ToLower(req.File) + validPrefix := strings.HasPrefix(filename, imageDirectory) + validSuffix := strings.HasSuffix(filename, ".iso") || strings.HasSuffix(filename, ".img") + + if !validPrefix || !validSuffix { + rsp.ErrRsp(c, -2, "invalid arguments") + return + } + + if err := os.Remove(req.File); err != nil { + rsp.ErrRsp(c, -3, "remove file failed") + log.Errorf("failed to remove file %s: %s", req.File, err) + return + } + + rsp.OkRsp(c) + log.Debugf("delete image %s success", req.File) +} diff --git a/server/service/storage/service.go b/server/service/storage/service.go new file mode 100644 index 0000000..37b8e84 --- /dev/null +++ b/server/service/storage/service.go @@ -0,0 +1,7 @@ +package storage + +type Service struct{} + +func NewService() *Service { + return &Service{} +} diff --git a/server/service/stream/direct/h264.go b/server/service/stream/direct/h264.go new file mode 100644 index 0000000..ea65d73 --- /dev/null +++ b/server/service/stream/direct/h264.go @@ -0,0 +1,44 @@ +package direct + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + log "github.com/sirupsen/logrus" +) + +var ( + streamer = newStreamer() + upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, + } +) + +func Connect(c *gin.Context) { + ws, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Errorf("failed to upgrade to websocket: %s", err) + return + } + defer func() { + _ = ws.Close() + log.Debugf("h264 websocket disconnected: %s", ws.RemoteAddr()) + }() + log.Debugf("h264 websocket connected: %s", ws.RemoteAddr()) + + _ = ws.SetReadDeadline(time.Time{}) + + streamer.addClient(ws) + defer streamer.removeClient(ws) + + for { + if _, _, err := ws.ReadMessage(); err != nil { + log.Debugf("failed to read message (client disconnected): %s", err) + return + } + } +} diff --git a/server/service/stream/direct/pool.go b/server/service/stream/direct/pool.go new file mode 100644 index 0000000..1f2fe06 --- /dev/null +++ b/server/service/stream/direct/pool.go @@ -0,0 +1,12 @@ +package direct + +import ( + "bytes" + "sync" +) + +var BufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} diff --git a/server/service/stream/direct/streamer.go b/server/service/stream/direct/streamer.go new file mode 100644 index 0000000..0f87653 --- /dev/null +++ b/server/service/stream/direct/streamer.go @@ -0,0 +1,123 @@ +package direct + +import ( + "NanoKVM-Server/common" + "NanoKVM-Server/service/stream" + "bytes" + "encoding/binary" + "sync" + "sync/atomic" + "time" + + "github.com/gorilla/websocket" + log "github.com/sirupsen/logrus" +) + +type Streamer struct { + mutex sync.RWMutex + clients map[*websocket.Conn]bool + running int32 +} + +func newStreamer() *Streamer { + return &Streamer{ + clients: make(map[*websocket.Conn]bool), + } +} + +func (s *Streamer) addClient(ws *websocket.Conn) { + s.mutex.Lock() + s.clients[ws] = true + s.mutex.Unlock() + + if atomic.CompareAndSwapInt32(&s.running, 0, 1) { + go s.run() + log.Debug("h264 stream started") + } +} + +func (s *Streamer) removeClient(ws *websocket.Conn) { + s.mutex.Lock() + delete(s.clients, ws) + s.mutex.Unlock() + + log.Debugf("h264 websocket disconnected, remaining clients: %d", len(s.clients)) +} + +func (s *Streamer) getClientCount() int { + s.mutex.RLock() + defer s.mutex.RUnlock() + + return len(s.clients) +} + +func (s *Streamer) run() { + defer atomic.StoreInt32(&s.running, 0) + + duration := time.Second / time.Duration(120) + ticker := time.NewTicker(duration) + defer ticker.Stop() + + screen := common.GetScreen() + vision := common.GetKvmVision() + startTime := time.Now() + + for range ticker.C { + if s.getClientCount() == 0 { + log.Debug("h264 stream stopped due to no clients") + return + } + + data, result := vision.ReadH264(screen.Width, screen.Height, screen.BitRate) + if result < 0 || len(data) == 0 { + continue + } + + isKeyFrame := byte(0) + if result == 3 { + isKeyFrame = byte(1) + } + + timestamp := time.Since(startTime).Microseconds() + + if err := s.send(isKeyFrame, timestamp, data); err != nil { + continue + } + + stream.GetFrameRateCounter().Update() + } +} + +func (s *Streamer) send(isKeyFrame byte, timestamp int64, data []byte) error { + buf := BufferPool.Get().(*bytes.Buffer) + defer BufferPool.Put(buf) + + buf.Reset() + + if err := buf.WriteByte(isKeyFrame); err != nil { + log.Errorf("failed to write keyframe flag: %s", err) + return err + } + + tsBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(tsBytes, uint64(timestamp)) + if _, err := buf.Write(tsBytes); err != nil { + log.Errorf("failed to write timestamp: %s", err) + return err + } + + if _, err := buf.Write(data); err != nil { + log.Errorf("failed to write h264 data: %s", err) + return err + } + + for client := range s.clients { + if err := client.WriteMessage(websocket.BinaryMessage, buf.Bytes()); err != nil { + log.Errorf("failed to write message to client %s: %s.", client.RemoteAddr(), err) + + s.removeClient(client) + } + } + + return nil +} diff --git a/server/service/stream/frame_rate.go b/server/service/stream/frame_rate.go new file mode 100644 index 0000000..f5de521 --- /dev/null +++ b/server/service/stream/frame_rate.go @@ -0,0 +1,63 @@ +package stream + +import ( + "fmt" + "os" + "sync" + "sync/atomic" + "time" + + log "github.com/sirupsen/logrus" +) + +var ( + counter *FrameRateCounter + counterOnce sync.Once +) + +type FrameRateCounter struct { + frameCount int32 + fps int32 + mutex sync.Mutex +} + +func GetFrameRateCounter() *FrameRateCounter { + counterOnce.Do(func() { + counter = &FrameRateCounter{} + + go func() { + ticker := time.NewTicker(3 * time.Second) + defer ticker.Stop() + + for range ticker.C { + counter.mutex.Lock() + + currentCount := atomic.LoadInt32(&counter.frameCount) + + counter.fps = currentCount / 3 + atomic.StoreInt32(&counter.frameCount, 0) + + counter.mutex.Unlock() + + data := fmt.Sprintf("%d", counter.fps) + err := os.WriteFile("/kvmapp/kvm/now_fps", []byte(data), 0o666) + if err != nil { + log.Errorf("failed to write fps: %s", err) + } + } + }() + }) + + return counter +} + +func (f *FrameRateCounter) Update() { + atomic.AddInt32(&f.frameCount, 1) +} + +func (f *FrameRateCounter) GetFPS() int32 { + f.mutex.Lock() + defer f.mutex.Unlock() + + return f.fps +} diff --git a/server/service/stream/h264/client.go b/server/service/stream/h264/client.go new file mode 100644 index 0000000..1d72a0d --- /dev/null +++ b/server/service/stream/h264/client.go @@ -0,0 +1,167 @@ +package h264 + +import ( + "encoding/json" + "sync" + + "github.com/gorilla/websocket" + "github.com/pion/webrtc/v4" + log "github.com/sirupsen/logrus" +) + +type Client struct { + ws *websocket.Conn + pc *webrtc.PeerConnection + mutex sync.Mutex +} + +type Message struct { + Event string `json:"event"` + Data string `json:"data"` +} + +// add video track +func (c *Client) addTrack() { + videoTrack, err := webrtc.NewTrackLocalStaticSample( + webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}, + "video", + "pion", + ) + if err != nil { + log.Errorf("failed to create video track: %s", err) + return + } + + _, err = c.pc.AddTrack(videoTrack) + if err != nil { + log.Errorf("failed to add video track: %s", err) + return + } + + trackMap[c.ws] = videoTrack +} + +// register callback events +func (c *Client) register() { + // new ICE candidate found + c.pc.OnICECandidate(func(candidate *webrtc.ICECandidate) { + if candidate == nil { + return + } + + candidateByte, err := json.Marshal(candidate.ToJSON()) + if err != nil { + log.Errorf("failed to marshal candidate: %s", err) + return + } + + _ = c.sendMessage("candidate", string(candidateByte)) + }) + + // ICE connection state has changed + c.pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) { + if state == webrtc.ICEConnectionStateConnected && !isSending { + // start sending h264 data + go send() + isSending = true + } + + log.Debugf("ice connection state has changed to %s", state.String()) + }) +} + +// read websocket message +func (c *Client) readMessage() { + message := &Message{} + + for { + _, raw, err := c.ws.ReadMessage() + if err != nil { + delete(trackMap, c.ws) + if isSending && len(trackMap) == 0 { + // stop sending when all websocket connections are closed + isSending = false + } + + log.Debugf("failed to read message: %s", err) + return + } + + if err := json.Unmarshal(raw, &message); err != nil { + log.Errorf("failed to unmarshal message: %s", err) + continue + } + + log.Debugf("receive message event: %s", message.Event) + + switch message.Event { + case "offer": + offer := webrtc.SessionDescription{} + if err := json.Unmarshal([]byte(message.Data), &offer); err != nil { + log.Errorf("failed to unmarshal offer message: %s", err) + return + } + + if err := c.pc.SetRemoteDescription(offer); err != nil { + log.Errorf("failed to set remote description: %s", err) + return + } + + answer, answerErr := c.pc.CreateAnswer(nil) + if answerErr != nil { + log.Errorf("failed to create answer: %s", answerErr) + return + } + + if err := c.pc.SetLocalDescription(answer); err != nil { + log.Errorf("failed to set local description: %s", err) + return + } + + answerByte, answerByteErr := json.Marshal(answer) + if answerByteErr != nil { + log.Errorf("failed to marshal answer: %s", answerByteErr) + return + } + + _ = c.sendMessage("answer", string(answerByte)) + + case "candidate": + candidate := webrtc.ICECandidateInit{} + if err := json.Unmarshal([]byte(message.Data), &candidate); err != nil { + log.Errorf("failed to unmarshal candidate message: %s", err) + return + } + + if err := c.pc.AddICECandidate(candidate); err != nil { + log.Errorf("failed to add ICE candidate: %s", err) + return + } + + case "heartbeat": + _ = c.sendMessage("heartbeat", "") + + default: + log.Debugf("unhandled message event: %s", message.Event) + } + } +} + +// send websocket message +func (c *Client) sendMessage(event string, data string) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + message := &Message{ + Event: event, + Data: data, + } + + if err := c.ws.WriteJSON(message); err != nil { + log.Errorf("failed to send message %s: %s", event, err) + return err + } + + log.Debugf("send message %s", message.Event) + return nil +} diff --git a/server/service/stream/h264/h264.go b/server/service/stream/h264/h264.go new file mode 100644 index 0000000..67358de --- /dev/null +++ b/server/service/stream/h264/h264.go @@ -0,0 +1,80 @@ +package h264 + +import ( + "NanoKVM-Server/config" + "net/http" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/pion/webrtc/v4" + log "github.com/sirupsen/logrus" +) + +var ( + upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, + } + trackMap = make(map[*websocket.Conn]*webrtc.TrackLocalStaticSample) + isSending = false +) + +func Connect(c *gin.Context) { + wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Errorf("failed to create websocket: %s", err) + return + } + + defer func() { + _ = wsConn.Close() + log.Debugf("h264 websocket disconnected") + }() + + var zeroTime time.Time + _ = wsConn.SetReadDeadline(zeroTime) + + conf := config.GetInstance() + + var iceServers []webrtc.ICEServer + + if conf.Stun != "" && conf.Stun != "disable" { + iceServers = append(iceServers, webrtc.ICEServer{ + URLs: []string{"stun:" + conf.Stun}, + }) + } + + if conf.Turn.TurnAddr != "" && conf.Turn.TurnUser != "" && conf.Turn.TurnCred != "" { + iceServers = append(iceServers, webrtc.ICEServer{ + URLs: []string{"turn:" + conf.Turn.TurnAddr}, + Username: conf.Turn.TurnUser, + Credential: conf.Turn.TurnCred, + }) + } + + peerConn, err := webrtc.NewPeerConnection(webrtc.Configuration{ + ICEServers: iceServers, + }) + if err != nil { + log.Errorf("failed to create PeerConnection: %s", err) + return + } + + defer func() { + _ = peerConn.Close() + log.Debugf("PeerConnection disconnected") + }() + + client := &Client{ + ws: wsConn, + pc: peerConn, + mutex: sync.Mutex{}, + } + + client.addTrack() + client.register() + client.readMessage() +} diff --git a/server/service/stream/h264/sender.go b/server/service/stream/h264/sender.go new file mode 100644 index 0000000..e59f57f --- /dev/null +++ b/server/service/stream/h264/sender.go @@ -0,0 +1,49 @@ +package h264 + +import ( + "NanoKVM-Server/common" + "time" + + "github.com/pion/webrtc/v4/pkg/media" + log "github.com/sirupsen/logrus" +) + +func send() { + screen := common.GetScreen() + common.CheckScreen() + + fps := screen.FPS + duration := time.Second / time.Duration(fps) + + ticker := time.NewTicker(duration) + defer ticker.Stop() + + vision := common.GetKvmVision() + for range ticker.C { + if !isSending && len(trackMap) == 0 { + return + } + + data, result := vision.ReadH264(screen.Width, screen.Height, screen.BitRate) + if result < 0 || len(data) == 0 { + continue + } + + sample := media.Sample{ + Data: data, + Duration: duration, + } + + for _, track := range trackMap { + if err := track.WriteSample(sample); err != nil { + log.Errorf("failed to send h264 data: %s", err) + } + } + + if screen.FPS != fps { + fps = screen.FPS + duration = time.Second / time.Duration(fps) + ticker.Reset(duration) + } + } +} diff --git a/server/service/stream/mjpeg/frame-detect.go b/server/service/stream/mjpeg/frame-detect.go new file mode 100644 index 0000000..ac2d8df --- /dev/null +++ b/server/service/stream/mjpeg/frame-detect.go @@ -0,0 +1,55 @@ +package mjpeg + +import ( + "NanoKVM-Server/common" + "NanoKVM-Server/proto" + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const FrameDetectInterval uint8 = 60 + +func UpdateFrameDetect(c *gin.Context) { + var req proto.UpdateFrameDetectReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid parameters") + return + } + + var frame uint8 = 0 + if req.Enabled { + frame = FrameDetectInterval + } + + common.GetKvmVision().SetFrameDetect(frame) + + rsp.OkRsp(c) + log.Debugf("update frame detect: %t", req.Enabled) +} + +func StopFrameDetect(c *gin.Context) { + var req proto.StopFrameDetectReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid parameters") + return + } + + duration := 10 * time.Second + if req.Duration > 0 { + duration = time.Duration(req.Duration) * time.Second + } + + vision := common.GetKvmVision() + + vision.SetFrameDetect(0) + time.Sleep(duration) + vision.SetFrameDetect(FrameDetectInterval) + + rsp.OkRsp(c) +} diff --git a/server/service/stream/mjpeg/mjpeg.go b/server/service/stream/mjpeg/mjpeg.go new file mode 100644 index 0000000..d09f818 --- /dev/null +++ b/server/service/stream/mjpeg/mjpeg.go @@ -0,0 +1,22 @@ +package mjpeg + +import ( + "time" + + "github.com/gin-gonic/gin" +) + +var streamer = NewStreamer() + +func Connect(c *gin.Context) { + c.Header("Content-Type", "multipart/x-mixed-replace; boundary=frame") + c.Header("Cache-Control", "no-cache") + c.Header("Connection", "keep-alive") + c.Header("Pragma", "no-cache") + c.Header("X-Server-Date", time.Now().Format(time.RFC1123)) + + streamer.AddClient(c) + defer streamer.RemoveClient(c) + + <-c.Request.Context().Done() +} diff --git a/server/service/stream/mjpeg/streamer.go b/server/service/stream/mjpeg/streamer.go new file mode 100644 index 0000000..5bddc20 --- /dev/null +++ b/server/service/stream/mjpeg/streamer.go @@ -0,0 +1,131 @@ +package mjpeg + +import ( + "NanoKVM-Server/common" + "NanoKVM-Server/service/stream" + "fmt" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +type Streamer struct { + mutex sync.RWMutex + clients map[*gin.Context]bool + running int32 +} + +func NewStreamer() *Streamer { + return &Streamer{ + clients: make(map[*gin.Context]bool), + } +} + +func (s *Streamer) AddClient(c *gin.Context) { + s.mutex.Lock() + s.clients[c] = true + s.mutex.Unlock() + + if atomic.CompareAndSwapInt32(&s.running, 0, 1) { + go s.run() + log.Debug("mjpeg stream started") + } +} + +func (s *Streamer) RemoveClient(c *gin.Context) { + s.mutex.Lock() + delete(s.clients, c) + s.mutex.Unlock() + + log.Debugf("mjpeg connection removed, remaining clients: %d", len(s.clients)) +} + +func (s *Streamer) getClients() []*gin.Context { + s.mutex.RLock() + defer s.mutex.RUnlock() + + clients := make([]*gin.Context, 0, len(s.clients)) + for c := range s.clients { + clients = append(clients, c) + } + + return clients +} + +func (s *Streamer) getClientCount() int { + s.mutex.RLock() + defer s.mutex.RUnlock() + + return len(s.clients) +} + +func (s *Streamer) run() { + defer atomic.StoreInt32(&s.running, 0) + + screen := common.GetScreen() + common.CheckScreen() + fps := screen.FPS + + vision := common.GetKvmVision() + + ticker := time.NewTicker(time.Second / time.Duration(fps)) + defer ticker.Stop() + + for range ticker.C { + if s.getClientCount() == 0 { + log.Debug("mjpeg stream stopped due to no clients") + return + } + + data, result := vision.ReadMjpeg(screen.Width, screen.Height, screen.Quality) + if result < 0 || result == 5 || len(data) == 0 { + continue + } + + clients := s.getClients() + for _, client := range clients { + if err := writeFrame(client, data); err != nil { + log.Errorf("failed to write mjpeg frame for client %s: %s", client.Request.RemoteAddr, err) + s.RemoveClient(client) + } + } + + if screen.FPS != fps && screen.FPS != 0 { + fps = screen.FPS + ticker.Reset(time.Second / time.Duration(fps)) + } + + stream.GetFrameRateCounter().Update() + } +} + +func writeFrame(c *gin.Context, data []byte) (err error) { + defer func() { + if r := recover(); r != nil { + err = c.Request.Context().Err() + if err == nil { + err = fmt.Errorf("panic recovered in writeFrame: %v", r) + } + } + }() + + header := "--frame\r\nContent-Type: image/jpeg\r\nContent-Length: " + strconv.Itoa(len(data)) + "\r\n\r\n" + if _, err = c.Writer.WriteString(header); err != nil { + return err + } + + if _, err = c.Writer.Write(data); err != nil { + return err + } + + if _, err = c.Writer.Write([]byte("\r\n")); err != nil { + return err + } + + c.Writer.Flush() + return nil +} diff --git a/server/service/stream/webrtc/client.go b/server/service/stream/webrtc/client.go new file mode 100644 index 0000000..1ace3cd --- /dev/null +++ b/server/service/stream/webrtc/client.go @@ -0,0 +1,112 @@ +package webrtc + +import ( + "encoding/json" + "errors" + + "github.com/gorilla/websocket" + "github.com/pion/rtp" + "github.com/pion/rtp/codecs" + "github.com/pion/webrtc/v4" + log "github.com/sirupsen/logrus" + + "sync" +) + +func NewClient(ws *websocket.Conn, videoConn *webrtc.PeerConnection) *Client { + return &Client{ + ws: ws, + video: videoConn, + mutex: sync.Mutex{}, + } +} + +func (c *Client) WriteMessage(event string, data string) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + message := &Message{ + Event: event, + Data: data, + } + + if err := c.ws.WriteJSON(message); err != nil { + log.Errorf("failed to send message %s: %v", event, err) + return err + } + + log.Debugf("sent message %s", event) + return nil +} + +func (c *Client) ReadMessage() (*Message, error) { + _, raw, err := c.ws.ReadMessage() + if err != nil { + log.Errorf("failed to read message: %v", err) + return nil, err + } + + var message Message + if err := json.Unmarshal(raw, &message); err != nil { + log.Errorf("failed to unmarshal message: %v", err) + return nil, nil + } + + return &message, nil +} + +func (c *Client) AddTrack() error { + // video track + videoTrack, err := webrtc.NewTrackLocalStaticRTP( + webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}, + "video", + "pion-video", + ) + if err != nil { + log.Errorf("failed to create video track: %s", err) + return err + } + + videoPacketizer := rtp.NewPacketizer( + 1200, + 100, + 0x1234ABCD, + &codecs.H264Payloader{}, + rtp.NewRandomSequencer(), + 90000, + ) + if videoPacketizer == nil { + err := errors.New("failed to create rtp packetizer") + log.Error(err) + return err + } + + videoSender, err := c.video.AddTrack(videoTrack) + if err != nil { + log.Errorf("failed to add video track: %s", err) + return err + } + go startRTCPReader(videoSender) + + track := &Track{ + videoPacketizer: videoPacketizer, + video: videoTrack, + } + track.updateExtension() + + c.mutex.Lock() + c.track = track + c.mutex.Unlock() + + return nil +} + +func startRTCPReader(sender *webrtc.RTPSender) { + rtcpBuf := make([]byte, 1500) + for { + if _, _, err := sender.Read(rtcpBuf); err != nil { + log.Debugf("RTCP reader error: %v", err) + return + } + } +} diff --git a/server/service/stream/webrtc/h264.go b/server/service/stream/webrtc/h264.go new file mode 100644 index 0000000..f2217c7 --- /dev/null +++ b/server/service/stream/webrtc/h264.go @@ -0,0 +1,158 @@ +package webrtc + +import ( + "NanoKVM-Server/config" + "net/http" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/pion/dtls/v3" + "github.com/pion/webrtc/v4" + log "github.com/sirupsen/logrus" +) + +var ( + upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, + } + globalManager *WebRTCManager + managerOnce sync.Once +) + +func getManager() *WebRTCManager { + managerOnce.Do(func() { + globalManager = NewWebRTCManager() + }) + return globalManager +} + +func Connect(c *gin.Context) { + // create WebSocket connection + wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Errorf("failed to create h264 websocket: %s", err) + return + } + defer func() { + _ = wsConn.Close() + log.Debugf("h264 websocket disconnected: %s", c.ClientIP()) + }() + log.Debugf("h264 websocket connected: %s", c.ClientIP()) + + var zeroTime time.Time + _ = wsConn.SetReadDeadline(zeroTime) + + // create video connection + iceServers := createICEServers() + + mediaEngine, err := createMediaEngine() + if err != nil { + log.Errorf("failed to create h264 media engine: %s", err) + return + } + + videoConn, err := createPeerConnection(iceServers, mediaEngine) + if err != nil { + log.Errorf("failed to create h264 video peer connection: %s", err) + return + } + defer func() { + _ = videoConn.Close() + log.Debugf("h264 video peer disconnected: %s", c.ClientIP()) + }() + + // create client + client := NewClient(wsConn, videoConn) + if err := client.AddTrack(); err != nil { + log.Errorf("failed to add track: %s", err) + return + } + + manager := getManager() + manager.AddClient(wsConn, client) + defer manager.RemoveClient(wsConn) + + // handle signaling + signalingHandler := NewSignalingHandler(client) + signalingHandler.RegisterCallbacks() + + // read and wait + for { + message, err := client.ReadMessage() + if err != nil { + return + } + if message != nil { + if err := signalingHandler.HandleMessage(message); err != nil { + log.Errorf("failed to handle signaling message: %s", err) + } + } + } +} + +func createICEServers() []webrtc.ICEServer { + var iceServers []webrtc.ICEServer + + conf := config.GetInstance() + + if conf.Stun != "" && conf.Stun != "disable" { + iceServers = append(iceServers, webrtc.ICEServer{ + URLs: []string{"stun:" + conf.Stun}, + }) + } + + if conf.Turn.TurnAddr != "" && conf.Turn.TurnUser != "" && conf.Turn.TurnCred != "" { + iceServers = append(iceServers, webrtc.ICEServer{ + URLs: []string{"turn:" + conf.Turn.TurnAddr}, + Username: conf.Turn.TurnUser, + Credential: conf.Turn.TurnCred, + }) + } + + return iceServers +} + +func createMediaEngine() (*webrtc.MediaEngine, error) { + mediaEngine := &webrtc.MediaEngine{} + + if err := mediaEngine.RegisterDefaultCodecs(); err != nil { + log.Errorf("failed to register default codecs: %s", err) + return nil, err + } + + if err := mediaEngine.RegisterHeaderExtension( + webrtc.RTPHeaderExtensionCapability{URI: "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"}, + webrtc.RTPCodecTypeVideo, + ); err != nil { + log.Errorf("failed to register header extension: %s", err) + return nil, err + } + + return mediaEngine, nil +} + +func createPeerConnection(iceServers []webrtc.ICEServer, mediaEngine *webrtc.MediaEngine) (*webrtc.PeerConnection, error) { + settingEngine := webrtc.SettingEngine{} + settingEngine.SetSRTPProtectionProfiles( + dtls.SRTP_AEAD_AES_128_GCM, + dtls.SRTP_AES128_CM_HMAC_SHA1_80, + ) + + apiOptions := []func(api *webrtc.API){ + webrtc.WithSettingEngine(settingEngine), + } + if mediaEngine != nil { + apiOptions = append(apiOptions, webrtc.WithMediaEngine(mediaEngine)) + } + + api := webrtc.NewAPI(apiOptions...) + + return api.NewPeerConnection(webrtc.Configuration{ + ICEServers: iceServers, + SDPSemantics: webrtc.SDPSemanticsUnifiedPlan, + }) +} diff --git a/server/service/stream/webrtc/manager.go b/server/service/stream/webrtc/manager.go new file mode 100644 index 0000000..59d03d0 --- /dev/null +++ b/server/service/stream/webrtc/manager.go @@ -0,0 +1,96 @@ +package webrtc + +import ( + "NanoKVM-Server/common" + "NanoKVM-Server/service/stream" + "sync" + "sync/atomic" + "time" + + "github.com/gorilla/websocket" + "github.com/pion/webrtc/v4/pkg/media" + log "github.com/sirupsen/logrus" +) + +func NewWebRTCManager() *WebRTCManager { + return &WebRTCManager{ + clients: make(map[*websocket.Conn]*Client), + videoSending: 0, + mutex: sync.RWMutex{}, + } +} + +func (m *WebRTCManager) AddClient(ws *websocket.Conn, client *Client) { + client.track.updateExtension() + + m.mutex.Lock() + m.clients[ws] = client + m.mutex.Unlock() + + log.Debugf("added client %s, total clients: %d", ws.RemoteAddr(), len(m.clients)) +} + +func (m *WebRTCManager) RemoveClient(ws *websocket.Conn) { + m.mutex.Lock() + delete(m.clients, ws) + m.mutex.Unlock() + + log.Debugf("removed client %s, total clients: %d", ws.RemoteAddr(), len(m.clients)) +} + +func (m *WebRTCManager) GetClientCount() int { + m.mutex.RLock() + defer m.mutex.RUnlock() + + return len(m.clients) +} + +func (m *WebRTCManager) StartVideoStream() { + if atomic.CompareAndSwapInt32(&m.videoSending, 0, 1) { + go m.sendVideoStream() + log.Debugf("start sending h264 stream") + } +} + +func (m *WebRTCManager) sendVideoStream() { + defer atomic.StoreInt32(&m.videoSending, 0) + + screen := common.GetScreen() + common.CheckScreen() + fps := screen.FPS + duration := time.Second / time.Duration(fps) + + vision := common.GetKvmVision() + + ticker := time.NewTicker(duration) + defer ticker.Stop() + + for range ticker.C { + if m.GetClientCount() == 0 { + log.Debugf("stop sending h264 stream") + return + } + + data, result := vision.ReadH264(screen.Width, screen.Height, screen.BitRate) + if result < 0 || len(data) == 0 { + continue + } + + sample := media.Sample{ + Data: data, + Duration: duration, + } + + for _, client := range m.clients { + client.track.writeVideo(sample) + } + + if screen.FPS != fps && screen.FPS != 0 { + fps = screen.FPS + duration = time.Second / time.Duration(fps) + ticker.Reset(duration) + } + + stream.GetFrameRateCounter().Update() + } +} diff --git a/server/service/stream/webrtc/signaling.go b/server/service/stream/webrtc/signaling.go new file mode 100644 index 0000000..7423989 --- /dev/null +++ b/server/service/stream/webrtc/signaling.go @@ -0,0 +1,149 @@ +package webrtc + +import ( + "encoding/json" + "errors" + + "github.com/pion/webrtc/v4" + log "github.com/sirupsen/logrus" +) + +func NewSignalingHandler(client *Client) *SignalingHandler { + return &SignalingHandler{ + client: client, + } +} + +// RegisterCallbacks Register callback functions +func (s *SignalingHandler) RegisterCallbacks() { + // video ICE candidate + s.client.video.OnICECandidate(func(candidate *webrtc.ICECandidate) { + if candidate == nil { + return + } + + candidateByte, err := json.Marshal(candidate.ToJSON()) + if err != nil { + log.Errorf("failed to marshal video candidate: %s", err) + return + } + + if err := s.client.WriteMessage("video-candidate", string(candidateByte)); err != nil { + log.Errorf("failed to send video candidate: %s", err) + } + }) + + manager := getManager() + + // video connection state change + s.client.video.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) { + if state == webrtc.ICEConnectionStateConnected { + manager.StartVideoStream() + } + + log.Debugf("video connection state changed to %s", state.String()) + }) +} + +// HandleMessage handle the received message +func (s *SignalingHandler) HandleMessage(message *Message) error { + switch message.Event { + case "video-offer": + return s.handleVideoOffer(message.Data) + case "video-candidate": + return s.handleVideoCandidate(message.Data) + case "heartbeat": + return s.handleHeartbeat() + default: + log.Debugf("Unhandled message event: %s", message.Event) + return nil + } +} + +func (s *SignalingHandler) handleVideoOffer(data string) error { + if s.client.video.SignalingState() != webrtc.SignalingStateStable { + err := errors.New("video signaling is not stable") + log.Error(err) + return err + } + + offer := webrtc.SessionDescription{} + if err := json.Unmarshal([]byte(data), &offer); err != nil { + log.Errorf("failed to unmarshal video offer: %s", err) + return err + } + + if err := s.client.video.SetRemoteDescription(offer); err != nil { + log.Errorf("failed to set remote description: %s", err) + return err + } + + answer, err := s.client.video.CreateAnswer(nil) + if err != nil { + log.Errorf("failed to create answer: %s", err) + return err + } + + if err := s.client.video.SetLocalDescription(answer); err != nil { + log.Errorf("failed to set local description: %s", err) + return err + } + + if err := s.updateHeaderExtensionID(); err != nil { + log.Errorf("could not update header extension ID: %v", err) + return err + } + + answerByte, err := json.Marshal(answer) + if err != nil { + log.Errorf("failed to marshal answer: %s", err) + return err + } + + return s.client.WriteMessage("video-answer", string(answerByte)) +} + +// set extension ID +func (s *SignalingHandler) updateHeaderExtensionID() error { + receivers := s.client.video.GetReceivers() + if len(receivers) == 0 { + return errors.New("no RTP receiver found for video") + } + + params := receivers[0].GetParameters() + if len(params.HeaderExtensions) == 0 { + return errors.New("no header extensions found in negotiated parameters") + } + + for _, ext := range params.HeaderExtensions { + if ext.URI == "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" { + s.client.track.playoutDelayExtensionID = uint8(ext.ID) + log.Debugf("found and set playout delay extension ID to: %d", ext.ID) + return nil + } + } + + log.Warnf("no track extension found in negotiated parameters, use default value 5") + return nil +} + +// handle video candidate +func (s *SignalingHandler) handleVideoCandidate(data string) error { + candidate := webrtc.ICECandidateInit{} + if err := json.Unmarshal([]byte(data), &candidate); err != nil { + log.Errorf("failed to unmarshal candidate: %s", err) + return err + } + + if err := s.client.video.AddICECandidate(candidate); err != nil { + log.Errorf("failed to add ICECandidate: %s", err) + return err + } + + return nil +} + +// handle heartbeat +func (s *SignalingHandler) handleHeartbeat() error { + return s.client.WriteMessage("heartbeat", "") +} diff --git a/server/service/stream/webrtc/track.go b/server/service/stream/webrtc/track.go new file mode 100644 index 0000000..3c3b279 --- /dev/null +++ b/server/service/stream/webrtc/track.go @@ -0,0 +1,53 @@ +package webrtc + +import ( + "github.com/pion/rtp" + "github.com/pion/webrtc/v4/pkg/media" + log "github.com/sirupsen/logrus" +) + +func (t *Track) updateExtension() { + if t.playoutDelayExtensionID == 0 { + t.playoutDelayExtensionID = 5 + } + + if t.playoutDelayExtensionData == nil || len(t.playoutDelayExtensionData) == 0 { + playoutDelay := &rtp.PlayoutDelayExtension{ + MinDelay: 0, + MaxDelay: 0, + } + playoutDelayExtensionData, err := playoutDelay.Marshal() + if err == nil { + t.playoutDelayExtensionData = playoutDelayExtensionData + } + } +} + +func (t *Track) writeVideoSample(sample media.Sample) error { + samples := uint32(sample.Duration.Seconds() * 90000) + packets := t.videoPacketizer.Packetize(sample.Data, samples) + + for _, p := range packets { + p.Header.Extension = true + p.Header.ExtensionProfile = 0xBEDE + + if err := p.Header.SetExtension(t.playoutDelayExtensionID, t.playoutDelayExtensionData); err != nil { + log.Errorf("Failed to set extension: %v", err) + return err + } + + if err := t.video.WriteRTP(p); err != nil { + log.Errorf("failed to write RTP: %v", err) + return err + } + } + + return nil +} + +func (t *Track) writeVideo(sample media.Sample) { + err := t.writeVideoSample(sample) + if err != nil { + log.Errorf("failed to write h264 video: %s", err) + } +} diff --git a/server/service/stream/webrtc/types.go b/server/service/stream/webrtc/types.go new file mode 100644 index 0000000..e109254 --- /dev/null +++ b/server/service/stream/webrtc/types.go @@ -0,0 +1,38 @@ +package webrtc + +import ( + "sync" + + "github.com/gorilla/websocket" + "github.com/pion/rtp" + "github.com/pion/webrtc/v4" +) + +type WebRTCManager struct { + clients map[*websocket.Conn]*Client + videoSending int32 + mutex sync.RWMutex +} + +type Client struct { + ws *websocket.Conn + video *webrtc.PeerConnection + track *Track + mutex sync.Mutex +} + +type SignalingHandler struct { + client *Client +} + +type Track struct { + playoutDelayExtensionID uint8 + playoutDelayExtensionData []byte + videoPacketizer rtp.Packetizer + video *webrtc.TrackLocalStaticRTP +} + +type Message struct { + Event string `json:"event"` + Data string `json:"data"` +} diff --git a/server/service/vm/gpio.go b/server/service/vm/gpio.go new file mode 100644 index 0000000..b7c3913 --- /dev/null +++ b/server/service/vm/gpio.go @@ -0,0 +1,116 @@ +package vm + +import ( + "fmt" + "os" + "strconv" + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/config" + "NanoKVM-Server/proto" +) + +func (s *Service) SetGpio(c *gin.Context) { + var req proto.SetGpioReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, fmt.Sprintf("invalid arguments: %s", err)) + return + } + + device := "" + conf := config.GetInstance().Hardware + + switch req.Type { + case "power": + device = conf.GPIOPower + case "reset": + device = conf.GPIOReset + default: + rsp.ErrRsp(c, -2, fmt.Sprintf("invalid power event: %s", req.Type)) + return + } + + var duration time.Duration + if req.Duration > 0 { + duration = time.Duration(req.Duration) * time.Millisecond + } else { + duration = 800 * time.Millisecond + } + + if err := writeGpio(device, duration); err != nil { + rsp.ErrRsp(c, -3, fmt.Sprintf("operation failed: %s", err)) + return + } + + log.Debugf("gpio %s set successfully", device) + rsp.OkRsp(c) +} + +func (s *Service) GetGpio(c *gin.Context) { + var rsp proto.Response + + conf := config.GetInstance().Hardware + + pwr, err := readGpio(conf.GPIOPowerLED) + if err != nil { + rsp.ErrRsp(c, -2, fmt.Sprintf("failed to read power led: %s", err)) + return + } + + hdd := false + if conf.Version == config.HWVersionAlpha { + hdd, err = readGpio(conf.GPIOHDDLed) + if err != nil { + rsp.ErrRsp(c, -2, fmt.Sprintf("failed to read hdd led: %s", err)) + return + } + } + + data := &proto.GetGpioRsp{ + PWR: pwr, + HDD: hdd, + } + rsp.OkRspWithData(c, data) +} + +func writeGpio(device string, duration time.Duration) error { + if err := os.WriteFile(device, []byte("1"), 0o666); err != nil { + log.Errorf("write gpio %s failed: %s", device, err) + return err + } + + time.Sleep(duration) + + if err := os.WriteFile(device, []byte("0"), 0o666); err != nil { + log.Errorf("write gpio %s failed: %s", device, err) + return err + } + + return nil +} + +func readGpio(device string) (bool, error) { + content, err := os.ReadFile(device) + if err != nil { + log.Errorf("read gpio %s failed: %s", device, err) + return false, err + } + + contentStr := string(content) + if len(contentStr) > 1 { + contentStr = contentStr[:len(contentStr)-1] + } + + value, err := strconv.Atoi(contentStr) + if err != nil { + log.Errorf("invalid gpio content: %s", content) + return false, nil + } + + return value == 0, nil +} diff --git a/server/service/vm/hdmi.go b/server/service/vm/hdmi.go new file mode 100644 index 0000000..c73e733 --- /dev/null +++ b/server/service/vm/hdmi.go @@ -0,0 +1,60 @@ +package vm + +import ( + "time" + + "NanoKVM-Server/common" + "NanoKVM-Server/proto" + "NanoKVM-Server/utils" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +func (s *Service) ResetHdmi(c *gin.Context) { + var rsp proto.Response + + vision := common.GetKvmVision() + + vision.SetHDMI(false) + time.Sleep(1 * time.Second) + vision.SetHDMI(true) + utils.PersistHDMIEnabled() + + rsp.OkRsp(c) + log.Debug("reset hdmi") +} + +func (s *Service) EnableHdmi(c *gin.Context) { + var rsp proto.Response + + vision := common.GetKvmVision() + + vision.SetHDMI(true) + utils.PersistHDMIEnabled() + + rsp.OkRsp(c) + log.Debug("enable hdmi") +} + +func (s *Service) DisableHdmi(c *gin.Context) { + var rsp proto.Response + + vision := common.GetKvmVision() + + vision.SetHDMI(false) + utils.PersistHDMIDisabled() + + rsp.OkRsp(c) + log.Debug("disable hdmi") +} + +func (s *Service) GetHdmiState(c *gin.Context) { + var rsp proto.Response + + rsp.OkRspWithData(c, &proto.GetGetHdmiStateRsp{ + Enabled: !utils.IsHdmiDisabled(), + }) + + log.Debug("get hdmi state") +} diff --git a/server/service/vm/hostname.go b/server/service/vm/hostname.go new file mode 100644 index 0000000..357f5c8 --- /dev/null +++ b/server/service/vm/hostname.go @@ -0,0 +1,84 @@ +package vm + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "NanoKVM-Server/proto" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + BootHostnameFile = "/boot/hostname" + EtcHostname = "/etc/hostname" + EtcHosts = "/etc/hosts" +) + +func (s *Service) SetHostname(c *gin.Context) { + var req proto.SetHostnameReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + dataRead, err := os.ReadFile(EtcHostname) + if err != nil { + rsp.ErrRsp(c, -1, "read Hostname failed") + return + } + + oldHostname := strings.Replace(string(dataRead), "\n", "", -1) + + if (oldHostname != req.Hostname) { + dataRead, err = os.ReadFile(EtcHosts) + if err != nil { + rsp.ErrRsp(c, -1, "read Hosts failed") + return + } + + data := []byte(strings.Replace(string(dataRead), oldHostname, req.Hostname, -1)) + + if err := os.WriteFile(EtcHosts, data, 0o644); err != nil { + rsp.ErrRsp(c, -2, "failed to write data") + return + } + } + + data := []byte(fmt.Sprintf("%s", req.Hostname)) + + if err := os.WriteFile(BootHostnameFile, data, 0o644); err != nil { + rsp.ErrRsp(c, -2, "failed to write data") + return + } + + if err := os.WriteFile(EtcHostname, data, 0o644); err != nil { + rsp.ErrRsp(c, -3, "failed to write data") + return + } + + rsp.OkRsp(c) + log.Debugf("set Hostname: %s", req.Hostname) + + _ = exec.Command("hostname", "-F", EtcHostname).Run() +} + +func (s *Service) GetHostname(c *gin.Context) { + var rsp proto.Response + + data, err := os.ReadFile(EtcHostname) + if err != nil { + rsp.ErrRsp(c, -1, "read Hostname failed") + return + } + + rsp.OkRspWithData(c, &proto.GetHostnameRsp{ + Hostname: strings.Replace(string(data), "\n", "", -1), + }) + log.Debugf("get Hostname successful") +} diff --git a/server/service/vm/info.go b/server/service/vm/info.go new file mode 100644 index 0000000..4fc3ec8 --- /dev/null +++ b/server/service/vm/info.go @@ -0,0 +1,115 @@ +package vm + +import ( + "NanoKVM-Server/config" + "fmt" + "os" + "strings" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/proto" +) + +var imageVersionMap = map[string]string{ + "2024-06-23-20-59-2d2bfb.img": "v1.0.0", + "2024-07-23-20-18-587710.img": "v1.1.0", + "2024-08-08-19-44-bef2ca.img": "v1.2.0", + "2024-11-13-09-59-9c961a.img": "v1.3.0", + "2025-02-17-19-08-3649fe.img": "v1.4.0", + "2025-04-17-14-21-98d17d.img": "v1.4.1", +} + +func (s *Service) GetInfo(c *gin.Context) { + var rsp proto.Response + + data := &proto.GetInfoRsp{ + IPs: getIPs(), + Mdns: getMdns(), + Image: getImageVersion(), + Application: getApplicationVersion(), + DeviceKey: getDeviceKey(), + } + + rsp.OkRspWithData(c, data) + log.Debug("get vm information success") +} + +func getIPs() (ips []proto.IP) { + interfaces, err := GetInterfaceInfos() + if err != nil { + return + } + + for _, iface := range interfaces { + if iface.IP.To4() != nil { + ips = append(ips, proto.IP{ + Name: iface.Name, + Addr: iface.IP.String(), + Version: "IPv4", + Type: iface.Type, + }) + } + } + + return +} + +func getMdns() string { + if pid := getAvahiDaemonPid(); pid == "" { + return "" + } + + content, err := os.ReadFile("/etc/hostname") + if err != nil { + return "" + } + + mdns := strings.ReplaceAll(string(content), "\n", "") + return fmt.Sprintf("%s.local", mdns) +} + +func getImageVersion() string { + content, err := os.ReadFile("/boot/ver") + if err != nil { + return "" + } + + image := strings.ReplaceAll(string(content), "\n", "") + + if version, ok := imageVersionMap[image]; ok { + return version + } + + return image +} + +func getApplicationVersion() string { + content, err := os.ReadFile("/kvmapp/version") + if err != nil { + return "1.0.0" + } + + return strings.ReplaceAll(string(content), "\n", "") +} + +func getDeviceKey() string { + content, err := os.ReadFile("/device_key") + if err != nil { + return "" + } + + return strings.ReplaceAll(string(content), "\n", "") +} + +func (s *Service) GetHardware(c *gin.Context) { + var rsp proto.Response + + conf := config.GetInstance() + version := conf.Hardware.Version.String() + + rsp.OkRspWithData(c, &proto.GetHardwareRsp{ + Version: version, + }) +} diff --git a/server/service/vm/ip.go b/server/service/vm/ip.go new file mode 100644 index 0000000..904b6e9 --- /dev/null +++ b/server/service/vm/ip.go @@ -0,0 +1,107 @@ +package vm + +import ( + "fmt" + "net" + "strings" + + log "github.com/sirupsen/logrus" +) + +const ( + Wired = "Wired" + Wireless = "Wireless" + Other = "Other" +) + +type InterfaceInfo struct { + Name string + Type string + IP net.IP +} + +func GetInterfaceInfos() ([]*InterfaceInfo, error) { + var interfaceInfos []*InterfaceInfo + + interfaces, err := net.Interfaces() + if err != nil { + log.Errorf("failed to get net interfaces: %s", err) + return nil, err + } + + for _, iface := range interfaces { + info := getInterfaceInfo(iface) + if info != nil { + interfaceInfos = append(interfaceInfos, info) + } + } + + if len(interfaceInfos) == 0 { + return nil, fmt.Errorf("no valid IP address") + } + + return interfaceInfos, nil +} + +func getInterfaceInfo(iface net.Interface) *InterfaceInfo { + if iface.Flags&net.FlagUp == 0 { + return nil + } + + interfaceType := getInterfaceType(iface) + if interfaceType == Other { + return nil + } + + interfaceIP := getInterfaceIP(iface) + if interfaceIP == nil { + return nil + } + + return &InterfaceInfo{ + Name: iface.Name, + Type: interfaceType, + IP: interfaceIP, + } +} + +func getInterfaceType(iface net.Interface) string { + if strings.HasPrefix(iface.Name, "eth") || strings.HasPrefix(iface.Name, "en") { + return Wired + } + + if strings.HasPrefix(iface.Name, "wlan") || strings.HasPrefix(iface.Name, "wl") { + return Wireless + } + + return Other +} + +func getInterfaceIP(iface net.Interface) net.IP { + addrs, err := iface.Addrs() + if err != nil { + log.Errorf("failed to get interface addresses: %s", err) + return nil + } + + for _, addr := range addrs { + var ip net.IP + + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + default: + continue + } + + if ip == nil { + continue + } + + return ip + } + + return nil +} diff --git a/server/service/vm/jiggler/jiggler.go b/server/service/vm/jiggler/jiggler.go new file mode 100644 index 0000000..4cac43f --- /dev/null +++ b/server/service/vm/jiggler/jiggler.go @@ -0,0 +1,134 @@ +package jiggler + +import ( + "NanoKVM-Server/service/hid" + "os" + "strings" + "sync" + "time" +) + +const ( + ConfigFile = "/etc/kvm/mouse-jiggler" + Interval = 15 * time.Second +) + +var ( + jiggler Jiggler + once sync.Once +) + +type Jiggler struct { + mutex sync.Mutex + enabled bool + running bool + mode string + lastUpdated time.Time +} + +func GetJiggler() *Jiggler { + once.Do(func() { + jiggler = Jiggler{ + mutex: sync.Mutex{}, + enabled: false, + running: false, + mode: "relative", + lastUpdated: time.Now(), + } + + content, err := os.ReadFile(ConfigFile) + if err != nil { + return + } + + mode := strings.ReplaceAll(string(content), "\n", "") + if mode != "" { + jiggler.mode = mode + } + + jiggler.enabled = true + }) + + return &jiggler +} + +func (j *Jiggler) Enable(mode string) error { + err := os.WriteFile(ConfigFile, []byte(mode), 0644) + if err != nil { + return err + } + + j.enabled = true + j.mode = mode + j.Run() + + return nil +} + +func (j *Jiggler) Disable() error { + if err := os.Remove(ConfigFile); err != nil { + return err + } + + j.enabled = false + j.mode = "relative" + + return nil +} + +func (j *Jiggler) Run() { + if !j.enabled || j.running { + return + } + + j.mutex.Lock() + j.running = true + j.mutex.Unlock() + + j.Update() + + go func() { + ticker := time.NewTicker(Interval) + defer ticker.Stop() + + for range ticker.C { + if !j.enabled { + j.running = false + return + } + + if time.Since(j.lastUpdated) > Interval { + move(j.mode) + j.Update() + } + } + }() +} + +func (j *Jiggler) Update() { + if j.running { + j.lastUpdated = time.Now() + } +} + +func (j *Jiggler) IsEnabled() bool { + return j.enabled +} + +func (j *Jiggler) GetMode() string { + return j.mode +} + +func move(mode string) { + h := hid.GetHid() + + if mode == "absolute" { + h.WriteHid2([]byte{0x00, 0x00, 0x3f, 0x00, 0x3f, 0x00}) + time.Sleep(100 * time.Millisecond) + h.WriteHid2([]byte{0x00, 0xff, 0x3f, 0xff, 0x3f, 0x00}) + } else { + h.WriteHid1([]byte{0x00, 0xa, 0xa, 0x00}) + time.Sleep(100 * time.Millisecond) + h.WriteHid1([]byte{0x00, 0xf6, 0xf6, 0x00}) + } +} diff --git a/server/service/vm/mdns.go b/server/service/vm/mdns.go new file mode 100644 index 0000000..dcde875 --- /dev/null +++ b/server/service/vm/mdns.go @@ -0,0 +1,92 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + AvahiDaemonPid = "/run/avahi-daemon/pid" + AvahiDaemonScript = "/etc/init.d/S50avahi-daemon" + AvahiDaemonBackupScript = "/kvmapp/system/init.d/S50avahi-daemon" +) + +func (s *Service) GetMdnsState(c *gin.Context) { + var rsp proto.Response + + pid := getAvahiDaemonPid() + + rsp.OkRspWithData(c, &proto.GetMdnsStateRsp{ + Enabled: pid != "", + }) +} + +func (s *Service) EnableMdns(c *gin.Context) { + var rsp proto.Response + + pid := getAvahiDaemonPid() + if pid != "" { + rsp.OkRsp(c) + return + } + + commands := []string{ + fmt.Sprintf("cp -f %s %s", AvahiDaemonBackupScript, AvahiDaemonScript), + fmt.Sprintf("%s start", AvahiDaemonScript), + } + + command := strings.Join(commands, " && ") + err := exec.Command("sh", "-c", command).Run() + if err != nil { + log.Errorf("failed to start avahi-daemon: %s", err) + rsp.ErrRsp(c, -1, "failed to enable mdns") + return + } + + rsp.OkRsp(c) + log.Debugf("avahi-daemon started") +} + +func (s *Service) DisableMdns(c *gin.Context) { + var rsp proto.Response + + pid := getAvahiDaemonPid() + if pid == "" { + rsp.OkRsp(c) + return + } + + command := fmt.Sprintf("kill -9 %s", pid) + err := exec.Command("sh", "-c", command).Run() + if err != nil { + log.Errorf("failed to stop avahi-daemon: %s", err) + rsp.ErrRsp(c, -1, "failed to disable mdns") + return + } + + _ = os.Remove(AvahiDaemonPid) + _ = os.Remove(AvahiDaemonScript) + + rsp.OkRsp(c) + log.Debugf("avahi-daemon stopped") +} + +func getAvahiDaemonPid() string { + if _, err := os.Stat(AvahiDaemonPid); err != nil { + return "" + } + + content, err := os.ReadFile(AvahiDaemonPid) + if err != nil { + log.Errorf("failed to read mdns pid: %s", err) + return "" + } + + return strings.ReplaceAll(string(content), "\n", "") +} diff --git a/server/service/vm/memory.go b/server/service/vm/memory.go new file mode 100644 index 0000000..9e7fa98 --- /dev/null +++ b/server/service/vm/memory.go @@ -0,0 +1,58 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "NanoKVM-Server/utils" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +func (s *Service) SetMemoryLimit(c *gin.Context) { + var req proto.SetMemoryLimitReq + var rsp proto.Response + + err := proto.ParseFormRequest(c, &req) + if err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + if req.Enabled { + err = utils.SetGoMemLimit(req.Limit) + } else { + err = utils.DelGoMemLimit() + } + + if err != nil { + rsp.ErrRsp(c, -2, "failed to set memory limit") + return + } + + rsp.OkRsp(c) + log.Debugf("set memory limit successful, enabled: %t, limit: %d", req.Enabled, req.Limit) +} + +func (s *Service) GetMemoryLimit(c *gin.Context) { + var rsp proto.Response + + exist := utils.IsGoMemLimitExist() + if !exist { + rsp.OkRspWithData(c, &proto.GetMemoryLimitRsp{ + Enabled: false, + Limit: 0, + }) + return + } + + limit, err := utils.GetGoMemLimit() + if err != nil { + rsp.ErrRsp(c, -1, "failed to get memory limit") + return + } + + rsp.OkRspWithData(c, &proto.GetMemoryLimitRsp{ + Enabled: true, + Limit: limit, + }) +} diff --git a/server/service/vm/mouse_jiggler.go b/server/service/vm/mouse_jiggler.go new file mode 100644 index 0000000..7495edf --- /dev/null +++ b/server/service/vm/mouse_jiggler.go @@ -0,0 +1,49 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "NanoKVM-Server/service/vm/jiggler" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +func (s *Service) GetMouseJiggler(c *gin.Context) { + var rsp proto.Response + + mouseJiggler := jiggler.GetJiggler() + + data := &proto.GetMouseJigglerRsp{ + Enabled: mouseJiggler.IsEnabled(), + Mode: mouseJiggler.GetMode(), + } + + rsp.OkRspWithData(c, data) +} + +func (s *Service) SetMouseJiggler(c *gin.Context) { + var req proto.SetMouseJigglerReq + var rsp proto.Response + + err := proto.ParseFormRequest(c, &req) + if err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + mouseJiggler := jiggler.GetJiggler() + + if req.Enabled { + err = mouseJiggler.Enable(req.Mode) + } else { + err = mouseJiggler.Disable() + } + + if err != nil { + rsp.ErrRsp(c, -2, "operation failed") + return + } + + rsp.OkRsp(c) + log.Debugf("set mouse jiggler: %t", req.Enabled) +} diff --git a/server/service/vm/oled.go b/server/service/vm/oled.go new file mode 100644 index 0000000..495e60f --- /dev/null +++ b/server/service/vm/oled.go @@ -0,0 +1,72 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "fmt" + "os" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + OLEDExistFile = "/etc/kvm/oled_exist" + OLEDSleepFile = "/etc/kvm/oled_sleep" +) + +func (s *Service) SetOLED(c *gin.Context) { + var req proto.SetOledReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + data := []byte(fmt.Sprintf("%d", req.Sleep)) + err := os.WriteFile(OLEDSleepFile, data, 0o644) + if err != nil { + rsp.ErrRsp(c, -2, "failed to write data") + return + } + + rsp.OkRsp(c) + log.Debugf("set OLED sleep: %d", req.Sleep) +} + +func (s *Service) GetOLED(c *gin.Context) { + var rsp proto.Response + + if _, err := os.Stat(OLEDExistFile); err != nil { + rsp.OkRspWithData(c, &proto.GetOLEDRsp{ + Exist: false, + Sleep: 0, + }) + return + } + + data, err := os.ReadFile(OLEDSleepFile) + if err != nil { + rsp.OkRspWithData(c, &proto.GetOLEDRsp{ + Exist: true, + Sleep: 0, + }) + return + } + + content := strings.TrimSpace(string(data)) + sleep, err := strconv.Atoi(content) + if err != nil { + log.Errorf("failed to parse OLED: %s", err) + rsp.ErrRsp(c, -1, "failed to parse OLED config") + return + } + + rsp.OkRspWithData(c, &proto.GetOLEDRsp{ + Exist: true, + Sleep: sleep, + }) + log.Debugf("get OLED config successful, sleep %d", sleep) +} diff --git a/server/service/vm/screen.go b/server/service/vm/screen.go new file mode 100644 index 0000000..a5b4c39 --- /dev/null +++ b/server/service/vm/screen.go @@ -0,0 +1,76 @@ +package vm + +import ( + "NanoKVM-Server/common" + "fmt" + "os" + "strconv" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/proto" +) + +var screenFileMap = map[string]string{ + "type": "/kvmapp/kvm/type", + "fps": "/kvmapp/kvm/fps", + "quality": "/kvmapp/kvm/qlty", + "resolution": "/kvmapp/kvm/res", +} + +func (s *Service) SetScreen(c *gin.Context) { + var req proto.SetScreenReq + var rsp proto.Response + + err := proto.ParseFormRequest(c, &req) + if err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + switch req.Type { + case "type": + data := "h264" + if req.Value == 0 { + data = "mjpeg" + } + err = writeScreen("type", data) + + case "gop": + gop := 30 + if req.Value >= 1 && req.Value <= 100 { + gop = req.Value + } + common.GetKvmVision().SetGop(uint8(gop)) + + default: + data := strconv.Itoa(req.Value) + err = writeScreen(req.Type, data) + } + + if err != nil { + rsp.ErrRsp(c, -2, "update screen failed") + return + } + + common.SetScreen(req.Type, req.Value) + + log.Debugf("update screen: %+v", req) + rsp.OkRsp(c) +} + +func writeScreen(key string, value string) error { + file, ok := screenFileMap[key] + if !ok { + return fmt.Errorf("invalid argument %s", key) + } + + err := os.WriteFile(file, []byte(value), 0o666) + if err != nil { + log.Errorf("write kvm %s failed: %s", file, err) + return err + } + + return nil +} diff --git a/server/service/vm/script.go b/server/service/vm/script.go new file mode 100644 index 0000000..9c93b96 --- /dev/null +++ b/server/service/vm/script.go @@ -0,0 +1,155 @@ +package vm + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/proto" + "NanoKVM-Server/utils" +) + +const ScriptDirectory = "/etc/kvm/scripts" + +func (s *Service) GetScripts(c *gin.Context) { + var rsp proto.Response + + var files []string + err := filepath.Walk(ScriptDirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if !info.IsDir() && isScript(info.Name()) { + files = append(files, info.Name()) + } + + return nil + }) + if err != nil { + rsp.ErrRsp(c, -1, "get scripts failed") + return + } + + rsp.OkRspWithData(c, &proto.GetScriptsRsp{ + Files: files, + }) + + log.Debugf("get scripts total %d", len(files)) +} + +func (s *Service) UploadScript(c *gin.Context) { + var rsp proto.Response + + _, header, err := c.Request.FormFile("file") + if err != nil { + rsp.ErrRsp(c, -1, "bad request") + return + } + + if !isScript(header.Filename) { + rsp.ErrRsp(c, -2, "invalid arguments") + return + } + + if _, err = os.Stat(ScriptDirectory); err != nil { + _ = os.MkdirAll(ScriptDirectory, 0o755) + } + + target := fmt.Sprintf("%s/%s", ScriptDirectory, header.Filename) + err = c.SaveUploadedFile(header, target) + if err != nil { + rsp.ErrRsp(c, -2, "save failed") + return + } + + _ = utils.EnsurePermission(target, 0o100) + + data := &proto.UploadScriptRsp{ + File: header.Filename, + } + rsp.OkRspWithData(c, data) + + log.Debugf("upload script %s success", header.Filename) +} + +func (s *Service) RunScript(c *gin.Context) { + var req proto.RunScriptReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + command := fmt.Sprintf("%s/%s", ScriptDirectory, req.Name) + + name := strings.ToLower(req.Name) + if strings.HasSuffix(name, ".py") { + command = fmt.Sprintf("python %s", command) + } + + var output []byte + var err error + cmd := exec.Command("sh", "-c", command) + + if req.Type == "foreground" { + output, err = cmd.CombinedOutput() + } else { + cmd.Stdout = nil + cmd.Stderr = nil + go func() { + err := cmd.Run() + if err != nil { + log.Errorf("run script %s in background failed: %s", req.Name, err) + } + }() + } + + if err != nil { + log.Errorf("run script %s failed: %s", req.Name, err.Error()) + rsp.ErrRsp(c, -2, "run script failed") + return + } + + rsp.OkRspWithData(c, &proto.RunScriptRsp{ + Log: string(output), + }) + + log.Debugf("run script %s success", req.Name) +} + +func (s *Service) DeleteScript(c *gin.Context) { + var req proto.DeleteScriptReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + file := fmt.Sprintf("%s/%s", ScriptDirectory, req.Name) + + if err := os.Remove(file); err != nil { + log.Errorf("delete script %s failed: %s", file, err) + rsp.ErrRsp(c, -3, "delete failed") + return + } + + rsp.OkRsp(c) + log.Debugf("delete script %s success", file) +} + +func isScript(name string) bool { + nameLower := strings.ToLower(name) + if strings.HasSuffix(nameLower, ".sh") || strings.HasSuffix(nameLower, ".py") { + return true + } + + return false +} diff --git a/server/service/vm/service.go b/server/service/vm/service.go new file mode 100644 index 0000000..59f225a --- /dev/null +++ b/server/service/vm/service.go @@ -0,0 +1,8 @@ +package vm + +type Service struct { +} + +func NewService() *Service { + return &Service{} +} diff --git a/server/service/vm/ssh.go b/server/service/vm/ssh.go new file mode 100644 index 0000000..fb854d4 --- /dev/null +++ b/server/service/vm/ssh.go @@ -0,0 +1,67 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "errors" + "fmt" + "os" + "os/exec" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + SSHScript = "/etc/init.d/S50sshd" + SSHStopFlag = "/etc/kvm/ssh_stop" +) + +func (s *Service) GetSSHState(c *gin.Context) { + var rsp proto.Response + + enabled := isSSHEnabled() + rsp.OkRspWithData(c, &proto.GetSSHStateRsp{ + Enabled: enabled, + }) +} + +func (s *Service) EnableSSH(c *gin.Context) { + var rsp proto.Response + + command := fmt.Sprintf("%s permanent_on", SSHScript) + err := exec.Command("sh", "-c", command).Run() + if err != nil { + log.Errorf("failed to run SSH script: %s", err) + rsp.ErrRsp(c, -1, "operation failed") + return + } + + rsp.OkRsp(c) + log.Debugf("SSH enabled") +} + +func (s *Service) DisableSSH(c *gin.Context) { + var rsp proto.Response + + command := fmt.Sprintf("%s permanent_off", SSHScript) + err := exec.Command("sh", "-c", command).Run() + if err != nil { + log.Errorf("failed to run SSH script: %s", err) + rsp.ErrRsp(c, -1, "operation failed") + return + } + + rsp.OkRsp(c) + log.Debugf("SSH disabled") +} + +func isSSHEnabled() bool { + _, err := os.Stat(SSHStopFlag) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return true + } + } + + return false +} diff --git a/server/service/vm/swap.go b/server/service/vm/swap.go new file mode 100644 index 0000000..cdbaaeb --- /dev/null +++ b/server/service/vm/swap.go @@ -0,0 +1,176 @@ +package vm + +import ( + "fmt" + "os" + "os/exec" + "strings" + "time" + + "NanoKVM-Server/proto" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + SwapFile = "/swapfile" + InittabPath = "/etc/inittab" + TempInittab = "/etc/.inittab.tmp" +) + +func (s *Service) GetSwap(c *gin.Context) { + var rsp proto.Response + + rsp.OkRspWithData(c, &proto.GetSwapRsp{ + Size: getSwapSize(), + }) +} + +func (s *Service) SetSwap(c *gin.Context) { + var rsp proto.Response + var req proto.SetSwapReq + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + size := getSwapSize() + if req.Size == size { + rsp.OkRsp(c) + return + } + + if req.Size == 0 { + if err := disableSwap(); err != nil { + rsp.ErrRsp(c, -2, "disable swap failed") + return + } + if err := disableInittab(); err != nil { + rsp.ErrRsp(c, -3, "disable inittab failed") + return + } + } else { + if err := enableSwap(req.Size); err != nil { + rsp.ErrRsp(c, -4, "enable swap failed") + return + } + if err := enableInittab(); err != nil { + rsp.ErrRsp(c, -5, "enable inittab failed") + return + } + } + + rsp.OkRsp(c) +} + +func getSwapSize() int64 { + fileInfo, err := os.Stat(SwapFile) + if err != nil { + return 0 + } + + return fileInfo.Size() / 1024 / 1024 +} + +func enableSwap(size int64) error { + if getSwapSize() > 0 { + if err := disableSwap(); err != nil { + return err + } + } + + commands := []string{ + fmt.Sprintf("fallocate -l %dM %s", size, SwapFile), + fmt.Sprintf("chmod 600 %s", SwapFile), + fmt.Sprintf("mkswap %s", SwapFile), + fmt.Sprintf("swapon %s", SwapFile), + } + + for _, command := range commands { + err := exec.Command("sh", "-c", command).Run() + if err != nil { + log.Errorf("failed to execute %s: %s", command, err) + return err + } + + time.Sleep(300 * time.Millisecond) + } + + log.Debugf("set swap file size: %d", size) + return nil +} + +func disableSwap() error { + command := "swapoff -a" + if err := exec.Command("sh", "-c", command).Run(); err != nil { + log.Errorf("failed to execute swapoff: %s", err) + return err + } + + if err := os.Remove(SwapFile); err != nil { + log.Errorf("failed to delete %s: %s", SwapFile, err) + return err + } + + return nil +} + +func enableInittab() error { + f, err := os.OpenFile(InittabPath, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + log.Errorf("read inittab failed: %s", err) + return err + } + defer func() { + _ = f.Close() + }() + + content := fmt.Sprintf("\nsi11::sysinit:/sbin/swapon %s", SwapFile) + _, err = f.WriteString(content) + if err != nil { + log.Errorf("write inittab failed: %s", err) + return err + } + + log.Debugf("write to %s: %s", InittabPath, content) + return nil +} + +func disableInittab() error { + defer func() { + _ = os.Remove(TempInittab) + }() + + input, err := os.ReadFile(InittabPath) + if err != nil { + log.Errorf("read fstab failed: %s", err) + return err + } + + lines := strings.Split(string(input), "\n") + output := make([]string, 0) + + for _, line := range lines { + if strings.HasSuffix(line, SwapFile) { + log.Debugf("%s delete line: %s", InittabPath, line) + } else { + output = append(output, line) + } + } + + content := strings.Join(output, "\n") + content = strings.TrimSuffix(content, "\n") + if err := os.WriteFile(TempInittab, []byte(content), 0644); err != nil { + log.Errorf("write temp fstab failed: %s", err) + return err + } + + if err := os.Rename(TempInittab, InittabPath); err != nil { + log.Errorf("replace fstab failed: %s", err) + return err + } + + return nil +} diff --git a/server/service/vm/system.go b/server/service/vm/system.go new file mode 100644 index 0000000..a5056a3 --- /dev/null +++ b/server/service/vm/system.go @@ -0,0 +1,25 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "os/exec" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +func (s *Service) Reboot(c *gin.Context) { + var rsp proto.Response + + log.Println("reboot system...") + + err := exec.Command("reboot").Run() + if err != nil { + rsp.ErrRsp(c, -1, "operation failed") + log.Errorf("failed to reboot: %s", err) + return + } + + rsp.OkRsp(c) + log.Debug("system rebooted") +} diff --git a/server/service/vm/terminal.go b/server/service/vm/terminal.go new file mode 100644 index 0000000..0b6f08e --- /dev/null +++ b/server/service/vm/terminal.go @@ -0,0 +1,110 @@ +package vm + +import ( + "encoding/json" + "net/http" + "os" + "os/exec" + "time" + + "github.com/creack/pty" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + log "github.com/sirupsen/logrus" +) + +const ( + messageWait = 10 * time.Second + maxMessageSize = 1024 +) + +type WinSize struct { + Rows uint16 `json:"rows"` + Cols uint16 `json:"cols"` +} + +var upgrader = websocket.Upgrader{ + ReadBufferSize: maxMessageSize, + WriteBufferSize: maxMessageSize, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func (s *Service) Terminal(c *gin.Context) { + ws, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Errorf("failed to init websocket: %s", err) + return + } + defer func() { + _ = ws.Close() + }() + + cmd := exec.Command("/bin/sh") + ptmx, err := pty.Start(cmd) + if err != nil { + log.Errorf("failed to start pty: %s", err) + return + } + defer func() { + _ = ptmx.Close() + _ = cmd.Process.Kill() + }() + + go wsWrite(ws, ptmx) + wsRead(ws, ptmx) +} + +// pty to ws +func wsWrite(ws *websocket.Conn, ptmx *os.File) { + data := make([]byte, maxMessageSize) + + for { + n, err := ptmx.Read(data) + if err != nil { + return + } + + if n > 0 { + _ = ws.SetWriteDeadline(time.Now().Add(messageWait)) + + err = ws.WriteMessage(websocket.BinaryMessage, data[:n]) + if err != nil { + log.Errorf("write ws message failed: %s", err) + return + } + } + } +} + +// ws to pty +func wsRead(ws *websocket.Conn, ptmx *os.File) { + var zeroTime time.Time + _ = ws.SetReadDeadline(zeroTime) + + for { + msgType, p, err := ws.ReadMessage() + if err != nil { + return + } + + // resize message + if msgType == websocket.BinaryMessage { + var winSize WinSize + if err := json.Unmarshal(p, &winSize); err == nil { + _ = pty.Setsize(ptmx, &pty.Winsize{ + Rows: winSize.Rows, + Cols: winSize.Cols, + }) + } + continue + } + + _, err = ptmx.Write(p) + if err != nil { + log.Errorf("failed to write to pty: %s", err) + return + } + } +} diff --git a/server/service/vm/tls.go b/server/service/vm/tls.go new file mode 100644 index 0000000..4ca216a --- /dev/null +++ b/server/service/vm/tls.go @@ -0,0 +1,76 @@ +package vm + +import ( + "fmt" + "os/exec" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/config" + "NanoKVM-Server/proto" + "NanoKVM-Server/utils" +) + +func (s *Service) SetTls(c *gin.Context) { + var req proto.SetTlsReq + var rsp proto.Response + + err := proto.ParseFormRequest(c, &req) + if err != nil { + rsp.ErrRsp(c, -1, fmt.Sprintf("invalid arguments: %s", err)) + return + } + + if req.Enabled { + err = enableTls() + } else { + err = disableTls() + } + + if err != nil { + log.Errorf("failed to set TLS: %s", err) + rsp.ErrRsp(c, -2, "operation failed") + return + } + + rsp.OkRsp(c) + + _ = exec.Command("sh", "-c", "/etc/init.d/S95nanokvm restart").Run() +} + +func enableTls() error { + if err := utils.GenerateCert(); err != nil { + return err + } + + conf, err := config.Read() + if err != nil { + return err + } + + conf.Proto = "https" + conf.Cert.Crt = "/etc/kvm/server.crt" + conf.Cert.Key = "/etc/kvm/server.key" + + if err := config.Write(conf); err != nil { + return err + } + + return nil +} + +func disableTls() error { + conf, err := config.Read() + if err != nil { + return err + } + + conf.Proto = "http" + + if err := config.Write(conf); err != nil { + return err + } + + return nil +} diff --git a/server/service/vm/virtual-device.go b/server/service/vm/virtual-device.go new file mode 100644 index 0000000..2eb40e9 --- /dev/null +++ b/server/service/vm/virtual-device.go @@ -0,0 +1,134 @@ +package vm + +import ( + "errors" + "os" + "os/exec" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/proto" + "NanoKVM-Server/service/hid" +) + +const ( + virtualNetwork = "/boot/usb.rndis0" + virtualDisk = "/boot/usb.disk0" +) + +var ( + mountNetworkCommands = []string{ + "touch /boot/usb.rndis0", + "/etc/init.d/S03usbdev stop", + "/etc/init.d/S03usbdev start", + } + + unmountNetworkCommands = []string{ + "/etc/init.d/S03usbdev stop", + "rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/rndis.usb0", + "rm /boot/usb.rndis0", + "/etc/init.d/S03usbdev start", + } + + mountDiskCommands = []string{ + "touch /boot/usb.disk0", + "/etc/init.d/S03usbdev stop", + "/etc/init.d/S03usbdev start", + } + + unmountDiskCommands = []string{ + "/etc/init.d/S03usbdev stop", + "rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0", + "rm /boot/usb.disk0", + "/etc/init.d/S03usbdev start", + } +) + +func (s *Service) GetVirtualDevice(c *gin.Context) { + var rsp proto.Response + + network, _ := isDeviceExist(virtualNetwork) + disk, _ := isDeviceExist(virtualDisk) + + rsp.OkRspWithData(c, &proto.GetVirtualDeviceRsp{ + Network: network, + Disk: disk, + }) + log.Debugf("get virtual device success") +} + +func (s *Service) UpdateVirtualDevice(c *gin.Context) { + var req proto.UpdateVirtualDeviceReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid argument") + return + } + + var device string + var commands []string + + switch req.Device { + case "network": + device = virtualNetwork + + exist, _ := isDeviceExist(device) + if !exist { + commands = mountNetworkCommands + } else { + commands = unmountNetworkCommands + } + case "disk": + device = virtualDisk + + exist, _ := isDeviceExist(device) + if !exist { + commands = mountDiskCommands + } else { + commands = unmountDiskCommands + } + default: + rsp.ErrRsp(c, -2, "invalid arguments") + return + } + + h := hid.GetHid() + h.Lock() + h.CloseNoLock() + defer func() { + h.OpenNoLock() + h.Unlock() + }() + + for _, command := range commands { + err := exec.Command("sh", "-c", command).Run() + if err != nil { + rsp.ErrRsp(c, -3, "operation failed") + return + } + } + + on, _ := isDeviceExist(device) + rsp.OkRspWithData(c, &proto.UpdateVirtualDeviceRsp{ + On: on, + }) + + log.Debugf("update virtual device %s success", req.Device) +} + +func isDeviceExist(device string) (bool, error) { + _, err := os.Stat(device) + + if err == nil { + return true, nil + } + + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + + log.Errorf("check file %s err: %s", device, err) + return false, err +} diff --git a/server/service/vm/web_title.go b/server/service/vm/web_title.go new file mode 100644 index 0000000..0d75fc3 --- /dev/null +++ b/server/service/vm/web_title.go @@ -0,0 +1,58 @@ +package vm + +import ( + "os" + "strings" + + "NanoKVM-Server/proto" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + WebTitleFile = "/etc/kvm/web-title" +) + +func (s *Service) SetWebTitle(c *gin.Context) { + var req proto.SetWebTitleReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + if req.Title == "" || req.Title == "BatchuKVM" { + err := os.Remove(WebTitleFile) + if err != nil { + rsp.ErrRsp(c, -2, "reset failed") + return + } + } else { + err := os.WriteFile(WebTitleFile, []byte(req.Title), 0o644) + if err != nil { + rsp.ErrRsp(c, -3, "write failed") + return + } + } + + rsp.OkRsp(c) + log.Debugf("set web title: %s", req.Title) +} + +func (s *Service) GetWebTitle(c *gin.Context) { + var rsp proto.Response + + data, err := os.ReadFile(WebTitleFile) + if err != nil { + rsp.ErrRsp(c, -1, "read web title failed") + return + } + + rsp.OkRspWithData(c, &proto.GetWebTitleRsp{ + Title: strings.Replace(string(data), "\n", "", -1), + }) + + log.Debugf("get web title successful") +} diff --git a/server/service/ws/message.go b/server/service/ws/message.go new file mode 100644 index 0000000..00da5a3 --- /dev/null +++ b/server/service/ws/message.go @@ -0,0 +1,6 @@ +package ws + +type Stream struct { + Type string `json:"type"` + State int `json:"state"` +} diff --git a/server/service/ws/service.go b/server/service/ws/service.go new file mode 100644 index 0000000..ec75d61 --- /dev/null +++ b/server/service/ws/service.go @@ -0,0 +1,7 @@ +package ws + +type Service struct{} + +func NewService() *Service { + return &Service{} +} diff --git a/server/service/ws/ws.go b/server/service/ws/ws.go new file mode 100644 index 0000000..4318e0e --- /dev/null +++ b/server/service/ws/ws.go @@ -0,0 +1,117 @@ +package ws + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + log "github.com/sirupsen/logrus" + + "NanoKVM-Server/service/hid" + "NanoKVM-Server/service/vm/jiggler" +) + +const ( + KeyboardEvent int = 1 + MouseEvent int = 2 +) + +type WsClient struct { + conn *websocket.Conn + hid *hid.Hid + keyboard chan []int + mouse chan []int +} + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func (s *Service) Connect(c *gin.Context) { + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Errorf("create websocket failed: %s", err) + return + } + log.Debug("websocket connected") + + client := &WsClient{ + hid: hid.GetHid(), + conn: conn, + keyboard: make(chan []int, 200), + mouse: make(chan []int, 200), + } + + go client.Start() +} + +func (c *WsClient) Start() { + defer c.Clean() + + c.hid.Open() + + go c.hid.Keyboard(c.keyboard) + go c.hid.Mouse(c.mouse) + + _ = c.Read() +} + +func (c *WsClient) Read() error { + var zeroTime time.Time + _ = c.conn.SetReadDeadline(zeroTime) + + for { + _, message, err := c.conn.ReadMessage() + if err != nil { + return err + } + + log.Debugf("received message: %s", message) + + var event []int + err = json.Unmarshal(message, &event) + if err != nil { + log.Debugf("received invalid message: %s", message) + continue + } + + if event[0] == KeyboardEvent { + c.keyboard <- event[1:] + } else if event[0] == MouseEvent { + c.mouse <- event[1:] + } + + // update latest HID operation time + jiggler.GetJiggler().Update() + } +} + +func (c *WsClient) Write(message []byte) error { + _ = c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) + return c.conn.WriteMessage(websocket.TextMessage, message) +} + +func (c *WsClient) Clean() { + _ = c.conn.Close() + + go clearQueue(c.keyboard) + close(c.keyboard) + + go clearQueue(c.mouse) + close(c.mouse) + + c.hid.Close() + + log.Debug("websocket disconnected") +} + +func clearQueue(queue chan []int) { + for range queue { + } +} diff --git a/server/utils/cert.go b/server/utils/cert.go new file mode 100644 index 0000000..499ffc0 --- /dev/null +++ b/server/utils/cert.go @@ -0,0 +1,100 @@ +package utils + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "os" + "time" + + log "github.com/sirupsen/logrus" +) + +func GenerateCert() error { + var ( + host = "localhost" + ipAddress = []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")} + validFor = time.Hour * 24 * 365 * 10 + certFile = "/etc/kvm/server.crt" + keyFile = "/etc/kvm/server.key" + ) + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Errorf("failed to generate RSA private key: %v", err) + return err + } + publicKey := &privateKey.PublicKey + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Errorf("failed to generate serial number: %v", err) + return err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: host, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(validFor), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: false, + DNSNames: []string{host}, + IPAddresses: ipAddress, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey, privateKey) + if err != nil { + log.Errorf("failed to create certificate: %v", err) + return err + } + + // generate certificate + certOut, err := os.Create(certFile) + if err != nil { + log.Errorf("failed to create %s: %v", certFile, err) + return err + } + + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + log.Errorf("failed to encode %s: %v", certFile, err) + return err + } + + _ = certOut.Sync() + _ = certOut.Close() + log.Debugf("%s generated", certFile) + + // generate private key + keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) // 权限 0600 + if err != nil { + log.Errorf("failed to create %s: %v", keyFile, err) + return err + } + + privateBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + log.Errorf("failed to marshal private key: %v", err) + return err + } + + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privateBytes}); err != nil { + log.Errorf("failed to encode %s: %v", keyFile, err) + return err + } + + _ = keyOut.Sync() + _ = keyOut.Close() + log.Debugf("%s generated", keyFile) + + return nil +} diff --git a/server/utils/chmod.go b/server/utils/chmod.go new file mode 100644 index 0000000..8b9b11d --- /dev/null +++ b/server/utils/chmod.go @@ -0,0 +1,23 @@ +package utils + +import ( + "os" + "path/filepath" +) + +func ChmodRecursively(path string, mode uint32) error { + return filepath.Walk(path, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if !info.IsDir() { + err = os.Chmod(path, os.FileMode(mode)) + if err != nil { + return err + } + } + + return nil + }) +} diff --git a/server/utils/encrypt.go b/server/utils/encrypt.go new file mode 100644 index 0000000..721d286 --- /dev/null +++ b/server/utils/encrypt.go @@ -0,0 +1,30 @@ +package utils + +import ( + "net/url" + + "github.com/mervick/aes-everywhere/go/aes256" + log "github.com/sirupsen/logrus" +) + +// SecretKey is only used to prevent the data from being transmitted in plaintext. +const SecretKey = "NanoKVM-KOREA-TestKey-2512092155" + +func Decrypt(ciphertext string) (string, error) { + if ciphertext == "" { + return "", nil + } + + decrypt := aes256.Decrypt(ciphertext, SecretKey) + return decrypt, nil +} + +func DecodeDecrypt(data string) (string, error) { + ciphertext, err := url.QueryUnescape(data) + if err != nil { + log.Errorf("decode ciphertext failed: %s", err) + return "", err + } + + return Decrypt(ciphertext) +} diff --git a/server/utils/hdmi.go b/server/utils/hdmi.go new file mode 100644 index 0000000..493f97b --- /dev/null +++ b/server/utils/hdmi.go @@ -0,0 +1,38 @@ +package utils + +import ( + "os" + + log "github.com/sirupsen/logrus" +) + +const ( + HDMIDisableFile = "/etc/kvm/hdmi_disable" +) + +func PersistHDMIDisabled() { + f, err := os.OpenFile(HDMIDisableFile, os.O_CREATE|os.O_RDONLY, 0644) + if err != nil { + log.Error("failed to create hdmi disable file:", err) + return + } + f.Close() +} + +func PersistHDMIEnabled() { + if err := os.Remove(HDMIDisableFile); err != nil { + log.Error("failed to remove hdmi disable file:", err) + return + } +} + +func IsHdmiDisabled() bool { + if _, err := os.Stat(HDMIDisableFile); err != nil { + if os.IsNotExist(err) { + return false // HDMI is enabled + } + log.Error("failed to check hdmi disable file:", err) + return false // Assume HDMI is enabled on error + } + return true // HDMI is disabled +} diff --git a/server/utils/http.go b/server/utils/http.go new file mode 100644 index 0000000..8f77fc7 --- /dev/null +++ b/server/utils/http.go @@ -0,0 +1,56 @@ +package utils + +import ( + "errors" + "io" + "net/http" + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" +) + +func Download(req *http.Request, target string) error { + log.Debugf("downloading %s to %s", req.URL.String(), target) + err := os.MkdirAll(filepath.Dir(target), 0o755) + if err != nil { + log.Errorf("create dir %s err: %s", filepath.Dir(target), err) + return err + } + out, err := os.OpenFile(target, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o755) + if err != nil { + log.Errorf("cannot create file '%s', error: %s", target, err) + return err + } + defer func() { + _ = out.Close() + }() + + resp, err := (&http.Client{}).Do(req) + if err != nil { + log.Errorf("request error: %s", err) + return err + } + defer func() { + _ = resp.Body.Close() + }() + + if resp.StatusCode != http.StatusOK { + log.Errorf("request failed, status code: %d", resp.StatusCode) + return errors.New("update website is inaccessible right now") + } + + contentType := resp.Header.Get("Content-Type") + if contentType != "application/octet-stream" && contentType != "application/zip" && contentType != "application/gzip" { + log.Debugf("unexpected content-type, it should be either octet-stream or (g)zip, but got: %s", contentType) + return errors.New("unsupported content type") + } + + _, err = io.Copy(out, resp.Body) + if err != nil { + log.Errorf("download file to %s err: %s", target, err) + return err + } + + return nil +} diff --git a/server/utils/memory.go b/server/utils/memory.go new file mode 100644 index 0000000..56c8047 --- /dev/null +++ b/server/utils/memory.go @@ -0,0 +1,77 @@ +package utils + +import ( + "fmt" + "os" + "runtime/debug" + "strconv" + "strings" + + log "github.com/sirupsen/logrus" +) + +const GoMemLimitFile = "/etc/kvm/GOMEMLIMIT" + +func InitGoMemLimit() { + if !IsGoMemLimitExist() { + return + } + + limit, err := GetGoMemLimit() + if err != nil { + return + } + + debug.SetMemoryLimit(limit * 1024 * 1024) + log.Debugf("set GOMEMLIMIT to %d MB", limit) +} + +func SetGoMemLimit(limit int64) error { + memoryLimit := max(limit, 50) + debug.SetMemoryLimit(memoryLimit * 1024 * 1024) + + log.Debugf("set GOMEMLIMIT to %d MB", limit) + + data := []byte(fmt.Sprintf("%d", limit)) + err := os.WriteFile(GoMemLimitFile, data, 0o644) + if err != nil { + log.Errorf("failed to write GOMEMLIMIT: %s", err) + return err + } + + return nil +} + +func GetGoMemLimit() (int64, error) { + data, err := os.ReadFile(GoMemLimitFile) + if err != nil { + log.Errorf("failed to read GOMEMLIMIT: %s", err) + return 0, err + } + + content := strings.TrimSpace(string(data)) + limit, err := strconv.ParseInt(content, 10, 64) + if err != nil { + log.Errorf("failed to parse GOMEMLIMIT: %s", err) + return 0, err + } + + return limit, nil +} + +func DelGoMemLimit() error { + debug.SetMemoryLimit(1024 * 1024 * 1024) + + err := os.Remove(GoMemLimitFile) + if err != nil { + log.Errorf("failed to delete GOMEMLIMIT: %s", err) + return err + } + + return nil +} + +func IsGoMemLimitExist() bool { + _, err := os.Stat(GoMemLimitFile) + return err == nil +} diff --git a/server/utils/move_file.go b/server/utils/move_file.go new file mode 100644 index 0000000..a887ba5 --- /dev/null +++ b/server/utils/move_file.go @@ -0,0 +1,78 @@ +package utils + +import ( + "io" + "os" + "path/filepath" + "strings" +) + +func MoveFile(src, dst string) error { + if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + return err + } + err := os.Rename(src, dst) + if err != nil { + if strings.Contains(err.Error(), "invalid cross-device link") { + return MoveFileCrossFS(src, dst) + } + return err + } + return nil +} + +func MoveFileCrossFS(src, dst string) error { + tmp := dst + ".tmp" + srcFile, err := os.Open(src) + if err != nil { + return err + } + + tmpFile, err := os.Create(tmp) + if err != nil { + _ = srcFile.Close() + return err + } + _, err = io.Copy(tmpFile, srcFile) + if err != nil { + _ = srcFile.Close() + _ = tmpFile.Close() + return err + } + _ = srcFile.Close() + _ = tmpFile.Close() + fi, err := os.Stat(src) + if err != nil { + return err + } + err = os.Chmod(tmp, fi.Mode()) + if err != nil { + return err + } + _ = os.Remove(src) + err = os.Rename(tmp, dst) + if err != nil { + return err + } + return nil +} + +func MoveFilesRecursively(src, dst string) error { + return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + fileName := strings.Replace(path, src, "", 1) + dstName := dst + fileName + fileInfo, err := os.Stat(path) + if err != nil { + return err + } + + if fileInfo.IsDir() { + return os.MkdirAll(dstName, fileInfo.Mode()) + } + return MoveFile(path, dstName) + }) +} diff --git a/server/utils/permission.go b/server/utils/permission.go new file mode 100644 index 0000000..f237504 --- /dev/null +++ b/server/utils/permission.go @@ -0,0 +1,48 @@ +package utils + +import "os" + +func HasPermission(filePath string, perm os.FileMode) (bool, error) { + fileInfo, err := os.Stat(filePath) + if err != nil { + return false, err + } + + mode := fileInfo.Mode().Perm() + if mode&perm == perm { + return true, nil + } + + return false, nil +} + +func AddPermission(filePath string, perm os.FileMode) error { + fileInfo, err := os.Stat(filePath) + if err != nil { + return err + } + + mode := fileInfo.Mode() | perm + err = os.Chmod(filePath, mode) + if err != nil { + return err + } + + return nil +} + +func EnsurePermission(filePath string, perm os.FileMode) error { + hasPerm, err := HasPermission(filePath, perm) + if err != nil { + return err + } + + if !hasPerm { + err = AddPermission(filePath, perm) + if err != nil { + return err + } + } + + return nil +} diff --git a/server/utils/untar.go b/server/utils/untar.go new file mode 100644 index 0000000..26342c6 --- /dev/null +++ b/server/utils/untar.go @@ -0,0 +1,82 @@ +package utils + +import ( + "archive/tar" + "compress/gzip" + "io" + "os" + "path/filepath" + "strings" +) + +func UnTarGz(srcFile string, destDir string) (string, error) { + if err := os.MkdirAll(destDir, 0755); err != nil { + return "", err + } + + fr, err := os.Open(srcFile) + if err != nil { + return "", err + } + defer func() { + _ = fr.Close() + }() + + gr, err := gzip.NewReader(fr) + if err != nil { + return "", err + } + defer func() { + _ = gr.Close() + }() + + tr := tar.NewReader(gr) + + targetFile := "" + for { + header, err := tr.Next() + + if err == io.EOF { + break + } + + if err != nil { + return "", err + } + + if targetFile == "" { + parts := strings.Split(header.Name, "/") + if len(parts) > 0 { + targetFile = filepath.Join(destDir, parts[0]) + } + } + + filename := filepath.Join(destDir, header.Name) + + switch header.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(filename, os.FileMode(header.Mode)); err != nil { + return "", err + } + + case tar.TypeReg: + file, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + if err != nil { + return "", err + } + + if _, err := io.Copy(file, tr); err != nil { + _ = file.Close() + return "", err + } + _ = file.Close() + + case tar.TypeSymlink: + if err := os.Symlink(header.Linkname, filename); err != nil { + return "", err + } + } + } + + return targetFile, nil +} diff --git a/server/utils/unzip.go b/server/utils/unzip.go new file mode 100644 index 0000000..bcc959e --- /dev/null +++ b/server/utils/unzip.go @@ -0,0 +1,61 @@ +package utils + +import ( + "archive/zip" + "io" + "os" + "path/filepath" +) + +func Unzip(filename string, dest string) error { + r, err := zip.OpenReader(filename) + if err != nil { + return err + } + defer func() { + _ = r.Close() + }() + + for _, f := range r.File { + dstPath := filepath.Join(dest, filepath.Clean("/"+f.Name)) + if f.FileInfo().IsDir() { + err = os.MkdirAll(dstPath, 0o755) + if err != nil { + return err + } + } else { + err = unzipFile(dstPath, f) + if err != nil { + return err + } + } + } + return nil +} + +func unzipFile(dstPath string, f *zip.File) error { + err := os.MkdirAll(filepath.Dir(dstPath), 0o755) + if err != nil { + return err + } + out, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer func() { + _ = out.Close() + }() + + archivedFile, err := f.Open() + if err != nil { + return err + } + + if _, err = io.Copy(out, archivedFile); err != nil { + return err + } + if err = os.Chmod(dstPath, f.Mode()); err != nil { + return err + } + return nil +} diff --git a/support/README.md b/support/README.md new file mode 100644 index 0000000..2ec9912 --- /dev/null +++ b/support/README.md @@ -0,0 +1,5 @@ +# NanoKVM Support Instructions + +`/support` contains auxiliary functions for NanoKVM, such as image subsystem, system status monitoring, system updates, screen key drivers, and a few system functions. + +Currently, NanoKVM is divided into two versions based on the main control chip: SG2002 (which includes NanoKVM-Lite/Full/PCIe) and H618 (including NanoKVM-Pro). Different chips have significantly different projects and compilation environments. To distinguish between them, they are stored separately in `/support/sg2002` and `/support/h618`. diff --git a/support/README_ZH.md b/support/README_ZH.md new file mode 100644 index 0000000..93faaee --- /dev/null +++ b/support/README_ZH.md @@ -0,0 +1,5 @@ +# NanoKVM support 说明 + +`/support`包含NanoKVM辅助性的功能,如图像子系统、系统状态监控、系统更新、屏幕按键驱动和少部分的系统功能 + +当前 NanoKVM 根据主控芯片的不同分为两个版本:SG2002(包含NanoKVM-Lite/Full/PCIe)和H618(包括NanoKVM-Pro),不同的芯片有差异较大的工程和编译环境,为做区分,将它们分别存放在`/support/sg2002`和`/support/h618` diff --git a/support/sg2002/README.md b/support/sg2002/README.md new file mode 100644 index 0000000..2950be9 --- /dev/null +++ b/support/sg2002/README.md @@ -0,0 +1,26 @@ +# NanoKVM Support Instructions + +## Environment Preparation +1. NanoKVM-Lite/Full/PCIe is based on the SG2002 as the main control chip. The projects in the support section are compiled under the [MaixCDK](https://github.com/sipeed/MaixCDK) framework. Before compiling, please ensure that the `MaixCDK` environment is correctly configured. For configuration instructions, click [here](https://github.com/sipeed/MaixCDK/blob/main/docs/doc_zh/README.md). + +## kvm_system Compilation Instructions + +> The `kvm_system` is responsible for monitoring the NanoKVM system status, system updates, screen key drivers, and a few system functions, compiled with MaixCDK. + +1. Before compiling, please ensure that the above-mentioned `MaixCDK` environment is correctly configured. +2. Modify the paths of `MAIXCDK_PATH` and `NanoKVM_PATH` in `./build`. +3. Execute `./build kvm_system` to compile `kvm_system`. +4. Use `scp ./kvm_system/dist/kvm_system_release/kvm_system root@192.168.x.x:/kvmapp/kvm_system` to copy it to NanoKVM for testing. +5. Use `./build add_to_kvmapp` to place the executable file into the `/kvmapp` installation package. +6. Use `./build kvm_system clean` to clean the compilation of `kvm_system`. + +## kvm_vision Compilation Instructions + +> `kvm_vision` refers to the image acquisition and encoding subsystem of NanoKVM, compiled with MaixCDK to produce dynamic libraries for Go calls. Use `kvm_vision_test` to compile and test the dynamic library. + +1. Before compiling, please ensure that the above-mentioned `MaixCDK` environment is correctly configured. +2. Modify the paths of `MAIXCDK_PATH` and `NanoKVM_PATH` in `./build`. +3. Execute `./build kvm_vision` to compile `kvm_vision_test`. +4. You can test the dynamic libraries in `kvm_vision_test/dist/kvm_vision_test_release/dl_lib/`. +5. Use `./build add_to_kvmapp` to place the dynamic libraries into the `/kvmapp` installation package. +6. Use `./build kvm_vision clean` to clean the compilation of `kvm_vision`. \ No newline at end of file diff --git a/support/sg2002/README_ZH.md b/support/sg2002/README_ZH.md new file mode 100644 index 0000000..bf5c75d --- /dev/null +++ b/support/sg2002/README_ZH.md @@ -0,0 +1,26 @@ +# NanoKVM support 说明 + +## 环境准备 +1. NanoKVM-Lite/Full/PCIe 以SG2002为主控芯片,support 部分的工程在[MaixCDK](https://github.com/sipeed/MaixCDK) 框架下编译。编译前,请保证`MaixCDK`环境已正确配置,配置教程点击[这里](https://github.com/sipeed/MaixCDK/blob/main/docs/doc_zh/README.md) + +## kvm_system 编译说明 + +> `kvm_system` 负责 NanoKVM 系统状态监控、系统更新、屏幕按键驱动和少部分的系统功能,借助 MaixCDK 编译 + +1. 编译前,请保证上述 1.`MaixCDK`环境已正确配置 +2. 修改 `./build` 中 `MAIXCDK_PATH` 和 `NanoKVM_PATH`的路径 +3. 执行 `./build kvm_system` 编译 kvm_system +4. 使用 `scp ./kvm_system/dist/kvm_system_release/kvm_system root@192.168.x.x:/kvmapp/kvm_system` 拷贝入 NanoKVM 测试 +5. 使用 `./build add_to_kvmapp` 可以将可执行文件放入 `/kvmapp` 安装包内 +6. 使用 `./build kvm_system clean` 可以清除 kvm_system 的编译 + +## kvm_vision 编译说明 + +> `kvm_vision` 是 NanoKVM 图像获取编码子系统的统称,使用MaixCDK编译出动态库供Go调用,使用 `kvm_vision_test` 编译和测试动态库 + +1. 编译前,请保证上述 1.`MaixCDK`环境已正确配置 +2. 修改 `./build` 中 `MAIXCDK_PATH` 和 `NanoKVM_PATH`的路径 +3. 执行 `./build kvm_vision` 编译 kvm_vision_test +4. 可使用 `kvm_vision_test/dist/kvm_vision_test_release/dl_lib/` 中的动态库测试 +5. 使用 `./build add_to_kvmapp` 可以将动态库放入 `/kvmapp` 安装包内 +6. 使用 `./build kvm_vision clean` 可以清除 kvm_system 的编译 diff --git a/support/sg2002/additional/kvm/CMakeLists.txt b/support/sg2002/additional/kvm/CMakeLists.txt new file mode 100644 index 0000000..a97185c --- /dev/null +++ b/support/sg2002/additional/kvm/CMakeLists.txt @@ -0,0 +1,6 @@ +list(APPEND ADD_REQUIREMENTS vision basic peripheral) +list(APPEND ADD_INCLUDE "include") + +append_srcs_dir(ADD_SRCS "src") + +register_component(DYNAMIC) \ No newline at end of file diff --git a/support/sg2002/additional/kvm/Kconfig b/support/sg2002/additional/kvm/Kconfig new file mode 100644 index 0000000..e69de29 diff --git a/support/sg2002/additional/kvm/include/kvm_vision.h b/support/sg2002/additional/kvm/include/kvm_vision.h new file mode 100644 index 0000000..687789e --- /dev/null +++ b/support/sg2002/additional/kvm/include/kvm_vision.h @@ -0,0 +1,82 @@ + + +#ifndef KVM_VISION_H_ +#define KVM_VISION_H_ + +#include "kvm_mmf.hpp" +#include "maix_camera.hpp" +#include "maix_basic.hpp" + +#include "maix_i2c.hpp" + +#ifdef __cplusplus +extern "C" { +#endif +#include /* low-level i/o */ +#include +#include +#include +#include +#include +#include +#include + +// #include "kvm_mmf.hpp" + +#define IMG_BUFFER_FULL -3 +#define IMG_VENC_ERROR -2 +#define IMG_NOT_EXIST -1 +#define IMG_MJPEG_TYPE 0 +#define IMG_H264_TYPE_SPS 1 +#define IMG_H264_TYPE_PPS 2 +#define IMG_H264_TYPE_IF 3 +#define IMG_H264_TYPE_PF 4 + +#define NORMAL_RES 0 +#define NEW_RES 1 +#define UNSUPPORT_RES 2 +#define UNKNOWN_RES 3 +#define ERROR_RES 4 + +void kvmv_init(uint8_t _debug_info_en = 0); +void set_venc_auto_recyc(uint8_t _enable); +/********************************************************************************** + * @name kvmv_read_img + * @author Sipeed BuGu + * @date 2024/10/25 + * @version R1.0 + * @brief Acquire the encoded image with auto init + * @param _width @input: Output image width + * @param _height @input: Output image height + * @param _type @input: Encode type + * @param _qlty @input: MJPEG: (50-100) | H264: (500-10000) + * @param _pp_kvm_data @output: Encode data + * @param _p_kvmv_data_size @output: Encode data size + * @return + -7: HDMI INPUT RES ERROR + -6: Unsupported resolution, please modify it in the host settings. + -5: Retrieving image, please wait + -4: Modifying image resolution, please wait + -3: img buffer full + -2: VENC Errorl + -1: No images were acquired + 0: Acquire MJPEG encoded images + 1: Acquire H264 encoded images(SPS)[Deprecated] + 2: Acquire H264 encoded images(PPS)[Deprecated] + 3: Acquire H264 encoded images(I) + 4: Acquire H264 encoded images(P) + 5: IMG not changed + **********************************************************************************/ +int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _qlty, uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size); +int free_kvmv_data(uint8_t ** _pp_kvm_data); +void free_all_kvmv_data(); +void set_h264_gop(uint8_t _gop); +void set_frame_detact(uint8_t _frame_detact); +void kvmv_deinit(); +uint8_t kvmv_hdmi_control(uint8_t _en); + +#ifdef __cplusplus +} +#endif + +#endif // KVM_VISION_H_ \ No newline at end of file diff --git a/support/sg2002/additional/kvm/src/kvm_vision.cpp b/support/sg2002/additional/kvm/src/kvm_vision.cpp new file mode 100644 index 0000000..bac9487 --- /dev/null +++ b/support/sg2002/additional/kvm/src/kvm_vision.cpp @@ -0,0 +1,1857 @@ +/** + * 待解决的问题: + * // 分辨率跟随输出 + * // 自动切换分辨率 + * // HDMI分辨率问题 + * // H264输入空图,VENC容易炸问题 + * // MJPEG/H264切换时卡退 + * // 再加一条手动清空全部内存 + * // deinit时free全部内存 + * // free 错内存时会炸的问题 + */ +#include "kvm_vision.h" + +#define default_venc_chn 1 + +#define VENC_MJPEG 0 +#define VENC_H264 1 + +#define KVMV_MAX_TRY_NUM 2 +#define vi_min_width 32 +#define vi_min_height 3 +#define vi_max_width 1920 +#define vi_max_height 1080 + +#define Farame_sample_size 76800 // 320*240 + +#define default_vi_width 1920 +#define default_vi_height 1080 +#define default_vpss_width 1920 +#define default_vpss_height 1080 +#define default_venc_type VENC_MJPEG +#define default_mjpeg_qlty 60 +#define default_h264_qlty 1000 +#define default_h264_gop 30 + +#define kvmv_data_buffer_size 4 +#define Try_rounds_HDMI_err_res 5 + +#define vi_width_path "/kvmapp/kvm/width" +#define vi_height_path "/kvmapp/kvm/height" +#define hdmi_mode_path "/etc/kvm/hdmi_mode" +#define hdmi_state_path "/proc/lt_int" +#define watchdog_mode_path "/etc/kvm/watchdog" +#define watchdog_temp_path "/tmp/watchdog" +#define watchdog_file "/tmp/nanokvm_wd" + +#define LT6911_ADDR 0x2B +#define LT6911_READ 0xFF +#define LT6911_WRITE 0x00 + +pthread_mutex_t vi_mutex; + +static char NanoKVM_edit[] = { + 0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x41,0x0C,0x33,0xC2,0x66,0xBA,0x00,0x00, + 0x2B,0x1F,0x01,0x04,0xA5,0x50,0x22,0x78,0x3B,0xCC,0xE5,0xAB,0x51,0x48,0xA6,0x26, + 0x0C,0x50,0x54,0xBF,0xEF,0x00,0xD1,0xC0,0xB3,0x00,0x95,0x00,0x81,0x80,0x81,0x40, + 0x81,0xC0,0x01,0x01,0x01,0x01,0xD8,0x59,0x00,0x60,0xA3,0x38,0x28,0x40,0xA0,0x10, + 0x3A,0x10,0x20,0x4F,0x31,0x00,0x00,0x1A,0x00,0x00,0x00,0xFF,0x00,0x55,0x4B,0x30, + 0x32,0x31,0x34,0x33,0x30,0x34,0x37,0x37,0x31,0x38,0x00,0x00,0x00,0xFC,0x00,0x50, + 0x48,0x4C,0x20,0x33,0x34,0x32,0x45,0x32,0x0A,0x20,0x20,0x20,0x00,0x00,0x00,0xFD, + 0x00,0x30,0x4B,0x63,0x63,0x1E,0x01,0x0A,0x20,0x20,0x20,0x20,0x20,0x20,0x01,0x8E +}; + +using namespace maix; +using namespace maix::sys; +using namespace maix::peripheral; +i2c::I2C LT6911_i2c(4, i2c::Mode::MASTER); + +typedef struct { + uint16_t vi_width = default_vi_width; + uint16_t vi_height = default_vi_height; + uint16_t vpss_width = default_vpss_width; + uint16_t vpss_height = default_vpss_height; + uint8_t venc_type; + uint16_t qlty; + uint8_t cam_state = 0; + uint8_t stream_stop; + uint8_t frame_detact = 0; + uint8_t display; + uint8_t reinit_flag = 1; + uint8_t reopen_cam_flag = 0; + uint8_t hdmi_cable_state = 0; + uint8_t try_exit_thread = 0; + uint8_t thread_is_running = 0; + uint8_t Auto_res = 0; + uint8_t hdmi_version = 0; + uint8_t hw_version = 0; + uint8_t hdmi_stop_flag = 0; + uint8_t hdmi_reading_flag = 0; + uint8_t hdmi_mode = 0; + uint8_t hdmi_res_type = 0; + uint8_t hdmi_res_err = 0; + uint8_t hdmi_try_rounds = 0; + uint8_t vi_detect_state = 0; + uint8_t venc_auto_recyc = 0; +} kvmv_cfg_t; + +typedef struct { + uint8_t* p_img_data = NULL; + uint8_t img_data_type = 0; + uint32_t img_data_size = 0; +} kvmv_data_t; + +typedef struct { + uint8_t mmf_venc_chn; + uint8_t enc_h264_running; + uint8_t enc_h264_init; + mmf_venc_cfg_t kvm_venc_cfg; +} kvm_venc_t; + +camera::Camera *cam = new camera::Camera(default_vpss_width, default_vpss_height, image::FMT_YVU420SP); +// camera::Camera *cam = new camera::Camera(320, 240, image::FMT_RGB888); + +kvmv_cfg_t kvmv_cfg; + +kvmv_data_t kvmv_data_buffer[kvmv_data_buffer_size]; + +uint8_t kvmv_data_buffer_index = 0; + +uint8_t debug_en = 0; +void debug(const char *format, ...) +{ + if(debug_en){ + printf(format); + } +} + +uint8_t to_roll(int8_t _input) +{ + if(_input < 0) return _input + kvmv_data_buffer_size; + if(_input >= kvmv_data_buffer_size) return _input - kvmv_data_buffer_size; + return _input; +} + +int maxmin_data(int _max, int _min, int _data) +{ + if(_data > _max) return _max; + if(_data < _min) return _min; + return _data; +} + +kvmv_data_t* get_save_buffer() +{ + kvmv_data_buffer_index = to_roll(kvmv_data_buffer_index + 1); + // debug("[kvmv]kvmv_data_buffer_index = %d\n", kvmv_data_buffer_index); + // debug("[kvmv]kvmv_data_buffer.p_img_data = %d\n", kvmv_data_buffer[kvmv_data_buffer_index].p_img_data); + if(kvmv_data_buffer[kvmv_data_buffer_index].p_img_data == NULL){ + return &kvmv_data_buffer[kvmv_data_buffer_index]; + } + return NULL; +} + +// ====HDMI RES================================================== + +uint16_t hdmi_res_list[][2] = { + {1920, 1080}, + {1600, 900}, + {1440, 1080}, + {1440, 900}, + {1280, 1024}, + {1280, 960}, + {1280, 800}, + {1280, 720}, + {1152, 864}, + {1024, 768}, + {800, 600}, +}; + +uint16_t hdmi_unsupported_res_list[][2] = { + {1680, 1050}, + {1440, 1050}, + {1400, 1050}, + {1368, 768}, + {1366, 768}, + {720, 576}, +}; + +/* return 0 : normal res; +/* return 1 : new res; + * return 2 : unsupport res; + * return 3 : unknow res; + */ +uint8_t check_res(uint16_t _width, uint16_t _height) +{ + uint8_t i; + uint8_t ret; + + for(i = 0; i < sizeof(hdmi_res_list)/4; i++){ + if(_width == hdmi_res_list[i][0] && _height == hdmi_res_list[i][1]) return NORMAL_RES; + } + for(i = 0; i < sizeof(hdmi_unsupported_res_list)/4; i++){ + if(_width == hdmi_unsupported_res_list[i][0] && _height == hdmi_unsupported_res_list[i][1]) return UNSUPPORT_RES; + } + return UNKNOWN_RES; +} + +void write_res_to_file(uint16_t _width, uint16_t _height) +{ + char Cmd[100]={0}; + sprintf(Cmd, "echo %d > %s", _width, vi_width_path); + system(Cmd); + sprintf(Cmd, "echo %d > %s", _height, vi_height_path); + system(Cmd); + system("sync"); +} + +/* return 0 : VI not init; + * return 1 : HDMI and CSI status are normal; + * return 2 : HDMI abnormal; + * return 3 : CSI abnormal: width too small; + * return 4 : CSI abnormal: width too large; + * return 5 : CSI abnormal: height too small; + * return 6 : CSI abnormal: height too large; + * return 7 : CSI abnormal: Unknown reason; + */ +uint8_t get_vi_state() +{ + char VI_State[10]={0}; + char cmd[100] = "cat /proc/cvitek/vi_dbg | grep -A 17 VIDevFPS | awk '{print $3}'"; + uint8_t FPS[2]; + uint8_t VIWHGTLSCnt[4]; + FILE* fp = popen( cmd, "r" ); + uint8_t ret = 0; + + if (fgets(VI_State, sizeof(VI_State), fp) != NULL){ + FPS[0] = atoi(VI_State); + // debug("VIDevFPS = %d\n", FPS[0]); + } else { + pclose(fp); + return ret; // VI not init; + } + if (fgets(VI_State, sizeof(VI_State), fp) != NULL){ + FPS[1] = atoi(VI_State); + // debug("VIFPS = %d\n", FPS[1]); + } + if (FPS[0] == 0){ + ret = 2; // HDMI not OK; + } else if (FPS[1] == 0){ + ret = 3; // HDMI OK ; CSI not; + } else { + ret = 1; // HDMI CSI OK; + } + if(ret == 3){ + // Ignore other information + uint8_t count = 0; + for(count = 0; count < 13; count ++){ + fgets(VI_State, sizeof(VI_State), fp); + } + // Check if the resolution might be set incorrectly + for(count = 0; count < 4; count ++){ + if (fgets(VI_State, sizeof(VI_State), fp) != NULL){ + // debug("VI_State = %s", VI_State); + VIWHGTLSCnt[count] = atoi(VI_State); + // printf("count = %d, val = %d\n", count, atoi(VI_State)); + } + } + + if(VIWHGTLSCnt[0] != 0) ret = 3; // The vi width setting value is too small + else if(VIWHGTLSCnt[1] != 0) ret = 4; // The vi width setting value is too large + else if(VIWHGTLSCnt[2] != 0) ret = 5; // The vi height setting value is too small + else if(VIWHGTLSCnt[3] != 0) ret = 6; // The vi height setting value is too large + else ret = 7; // printf("[kvmv] Unexpected situation\n"); + } + pclose(fp); + return ret; +} + +int set_hdmi_mode(uint8_t _hdmi_mode) +{ + if(_hdmi_mode >= 0 && _hdmi_mode <= 2){ + char Cmd[100]={0}; + sprintf(Cmd, "echo %d > %s", _hdmi_mode, hdmi_mode_path); + system(Cmd); + return 1; + } else { + debug("[kvmv] Incorrect HDMI mode.\n"); + return 0; + } +} + +int get_hdmi_mode(void) +{ + if(access(hdmi_mode_path, F_OK) == 0){ + // exist + FILE *fp; + int file_size; + uint8_t tmp8; + uint8_t RW_Data[2]; + + fp = fopen(hdmi_mode_path, "r"); + fread(RW_Data, sizeof(char), 1, fp); + fclose(fp); + RW_Data[2] = 0; + tmp8 = atoi((char*)RW_Data); + if(tmp8 > 2) { + tmp8 = 0; + char Cmd[100]={0}; + sprintf(Cmd, "echo 0 > %s", hdmi_mode_path); + system(Cmd); + } + if(tmp8 != kvmv_cfg.hdmi_mode){ + kvmv_cfg.hdmi_mode = tmp8; + debug("[kvmv] hdmi mode = %d\n", kvmv_cfg.hdmi_mode); + return 1; + } else { + return 0; + } + } + kvmv_cfg.hdmi_mode = 0; + return 0; +} + +uint8_t watchdog_sf_is_open(void) +{ + if(access(watchdog_mode_path, F_OK) == 0) return 1; + else if(access(watchdog_temp_path, F_OK) == 0) return 1; + else return 0; +} + +int vision_update_watchdog() +{ + FILE *file; + + file = fopen(watchdog_file, "w"); + if (file == NULL) { + debug("[kvmv] watchdog open error\n"); + return -1; + } + // fprintf(file, "%s", 'v'); + fclose(file); + return 1; +} + +int get_manual_resolution(void) +{ + uint8_t RW_Data[35]; + FILE *fp; + int file_size; + uint16_t tmp_width, tmp_height; + int res = 0; + + // get res + if(access("/kvmapp/kvm/width", F_OK) == 0){ + fp = fopen("/kvmapp/kvm/width", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + RW_Data[file_size] = 0; + tmp_width = atoi((char*)RW_Data); + } else { + tmp_width = 1920; + } + if(access("/kvmapp/kvm/height", F_OK) == 0){ + fp = fopen("/kvmapp/kvm/height", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + RW_Data[file_size] = 0; + tmp_height = atoi((char*)RW_Data); + } else { + tmp_height = 1080; + } + + // res min limit + if(tmp_width < vi_min_width){ + tmp_width = vi_min_width; + char Cmd[100]={0}; + sprintf(Cmd, "echo %d > %s", vi_min_width, vi_width_path); + system(Cmd); + } + if(tmp_height < vi_min_height){ + tmp_height = vi_min_height; + char Cmd[100]={0}; + sprintf(Cmd, "echo %d > %s", vi_min_height, vi_height_path); + system(Cmd); + } + // res max limit + if(tmp_width > vi_max_width){ + tmp_width = vi_max_width; + char Cmd[100]={0}; + sprintf(Cmd, "echo %d > %s", vi_max_width, vi_width_path); + system(Cmd); + } + if(tmp_height > vi_max_height){ + tmp_height = vi_max_height; + char Cmd[100]={0}; + sprintf(Cmd, "echo %d > %s", vi_max_height, vi_height_path); + system(Cmd); + } + + // res change ? + if(kvmv_cfg.vi_width != tmp_width){ + kvmv_cfg.vi_width = tmp_width; + printf("[kvmk] get new width = %d\n", kvmv_cfg.vi_width); + res = 1; + } + if(kvmv_cfg.vi_height != tmp_height){ + kvmv_cfg.vi_height = tmp_height; + printf("[kvmk] get new height = %d\n", kvmv_cfg.vi_height); + res = 1; + } + return res; +} + +uint8_t auto_try_res() +{ + char Cmd[100]={0}; + uint8_t err_code; + uint8_t auto_trying_times = 0; + + for (auto_trying_times = 0; auto_trying_times < sizeof(hdmi_res_list)/4; auto_trying_times++){ + err_code = get_vi_state(); + switch(err_code){ + case 0: + // shouldn't be possible to run here + cam->restart(default_vpss_width, default_vpss_height, image::FMT_YVU420SP); + printf("[kvmv] VI not init\n"); + break; + case 1: + printf("[kvmv] VI subsystem is normal\n"); + return 1; + break; + case 2: + // HDMI not detected or resolution not supported; interval checks will continue + printf("[kvmv] Cannot obtain HDMI input\n"); + auto_trying_times--; + break; + case 3: // width too small + case 4: // width too large + case 5: // height too small + case 6: // height too large + // CSI abnormal due to resolution error + // The test list is short; sequential testing can be performed + printf("[kvmv] Trying %d * %d res ..\n", hdmi_res_list[auto_trying_times][0], hdmi_res_list[auto_trying_times][1]); + sprintf(Cmd, "echo %d > %s", hdmi_res_list[auto_trying_times][0], vi_width_path); + system(Cmd); + sprintf(Cmd, "echo %d > %s", hdmi_res_list[auto_trying_times][1], vi_height_path); + system(Cmd); + + kvmv_cfg.vi_width = hdmi_res_list[auto_trying_times][0]; + kvmv_cfg.vi_height = hdmi_res_list[auto_trying_times][1]; + printf("[kvmv] restart cam...\n"); + cam->restart(default_vpss_width, default_vpss_height, image::FMT_YVU420SP); + time::sleep_ms(50); + break; + case 7: // Unknown reason + printf("[kvmv] CSI abnormal: Unknown reason\n"); + break; + } + } + if (get_vi_state() == 1) return 1; + if (get_vi_state() == 2) return 2; + else return 0; +} + +/* return : + * 0 : error + * 1 : out of mem + * 2 : normal +*/ +uint8_t chack_ion() +{ + // cat /sys/kernel/debug/ion/cvi_carveout_heap_dump/summary | grep "usage rate:" | awk -F '[:%]' '{print $2}' + uint8_t RW_Data[10]; + uint8_t ATOI_Data[3] = {0}; + uint8_t ion_usage_rate; + char Cmd[150]={0}; + // sprintf( Cmd, "cat /sys/kernel/debug/ion/cvi_carveout_heap_dump/summary | grep \"usage rate:\" | awk -F '[:%]' '{print $2}'"); + sprintf( Cmd, "cat /sys/kernel/debug/ion/cvi_carveout_heap_dump/summary | grep \"usage rate:\" | awk '{print $2}'"); + FILE* fp = popen( Cmd, "r" ); + if ( NULL == fp ) + { + pclose(fp); + return 0; + } + fgets((char*)RW_Data, 8, fp); + pclose(fp); + RW_Data[8] = 0; + if (RW_Data[6] == '&') return 1; + else { + ATOI_Data[0] = RW_Data[5]; + ATOI_Data[1] = RW_Data[6]; + } + ion_usage_rate = atoi((char*)ATOI_Data); + + if(ion_usage_rate >= 95) return 1; + else return 2; +} + +void lt6911_enable() +{ + uint8_t buf[2]; + buf[0] = 0xff; + buf[1] = 0x80; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0xee; + buf[1] = 0x01; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + if(kvmv_cfg.hdmi_version != 0){ + // disable watchdog + buf[0] = 0x10; + buf[1] = 0x00; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + } +} + +void lt6911_disable() +{ + uint8_t buf[2]; + buf[0] = 0xff; + buf[1] = 0x80; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0xee; + buf[1] = 0x00; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); +} + +void lt6911_get_hdmi_errer() +{ + uint8_t buf[6]; + + buf[0] = 0xff; + buf[1] = 0xC0; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0x20; + buf[1] = 0x01; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + time::sleep_ms(100); + + buf[0] = 0x24; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + + maix::Bytes *dat = LT6911_i2c.readfrom(LT6911_ADDR, 6); + + buf[0] = 0x20; + buf[1] = 0x07; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + for(int i = 0; i < 6; i++){ + buf[i] = (uint8_t)dat->data[i]; + } + delete dat; + + debug("hdmi_errer_code = %x, %x, %x, %x, %x, %x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); +} + +uint8_t lt6911_get_hdmi_res() +{ + uint8_t buf[2]; + uint8_t revbuf[4]; + uint16_t Vactive; + uint16_t Hactive; + + + if(kvmv_cfg.hdmi_version == 0){ + // LT6911C + buf[0] = 0xff; + buf[1] = 0xd2; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0x83; + buf[1] = 0x11; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + time::sleep_ms(5); + + // Vactive + buf[0] = 0x96; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 2); + + // Hactive + buf[0] = 0x8b; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat1 = LT6911_i2c.readfrom(LT6911_ADDR, 2); + + revbuf[0] = (uint8_t)dat0->data[0]; + revbuf[1] = (uint8_t)dat0->data[1]; + revbuf[2] = (uint8_t)dat1->data[0]; + revbuf[3] = (uint8_t)dat1->data[1]; + + Vactive = (revbuf[0] << 8)|revbuf[1]; + Hactive = (revbuf[2] << 8)|revbuf[3]; + Hactive *= 2; + + debug("[hdmi]HDMI res modification event\n"); + debug("[hdmi]new res: %d * %d\n", Hactive, Vactive); + + delete dat0; + delete dat1; + + if (Vactive != 0 && Hactive != 0){ + return 1; + } else { + // system("echo 0 > %s", vi_height_path); + // system("echo 0 > %s", vi_width_path); + return 0; + } + } else if (kvmv_cfg.hdmi_version == 1){ + // LT6911UXC + buf[0] = 0xff; + buf[1] = 0x86; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0xff; + buf[1] = 0x86; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + // HDMI signal disappear/stable + buf[0] = 0xA3; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 1); + + revbuf[0] = (uint8_t)dat0->data[0]; + delete dat0; + + debug("[hdmi]HDMI-UXC res modification event\n"); + + if(revbuf[0] == 0x55) return 1; + else if(revbuf[0] == 0x88) return 0; + else return 0; + } else { + return 0; + } +} + +void lt6911_get_hdmi_clk() +{ + uint8_t buf[2]; + uint8_t revbuf[3]; + uint32_t clk; + + buf[0] = 0xff; + buf[1] = 0xa0; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0x34; + buf[1] = 0x0b; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + time::sleep_ms(50); + + // clk + buf[0] = 0xff; + buf[1] = 0xb8; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0xb1; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 3); + + revbuf[0] = (uint8_t)dat0->data[0]; + revbuf[1] = (uint8_t)dat0->data[1]; + revbuf[2] = (uint8_t)dat0->data[2]; + revbuf[0] &= 0x07; + + clk = revbuf[0]; + clk <<= 8; + clk |= revbuf[1]; + clk <<= 8; + clk |= revbuf[2]; + + debug("[hdmi]HDMI CLK = %d\n", clk); + + delete dat0; +} + +uint8_t lt6911_get_csi_res(uint16_t *p_width, uint16_t *p_height) +{ + uint8_t buf[2]; + uint8_t revbuf[4]; + uint8_t res_type; + static uint16_t old_Vactive; + static uint16_t old_Hactive; + uint16_t Vactive; + uint16_t Hactive; + + if(kvmv_cfg.hdmi_version == 0){ + // LT6911C + buf[0] = 0xff; + buf[1] = 0xc2; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + // Vactive + buf[0] = 0x06; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 2); + + // Hactive + buf[0] = 0x38; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat1 = LT6911_i2c.readfrom(LT6911_ADDR, 2); + + revbuf[0] = (uint8_t)dat0->data[0]; + revbuf[1] = (uint8_t)dat0->data[1]; + revbuf[2] = (uint8_t)dat1->data[0]; + revbuf[3] = (uint8_t)dat1->data[1]; + + delete dat0; + delete dat1; + + Vactive = (revbuf[0] << 8)|revbuf[1]; + Hactive = (revbuf[2] << 8)|revbuf[3]; + } else if(kvmv_cfg.hdmi_version == 1) { + // LT6911UXC + debug("[hdmi]UXC get csi res\n"); + buf[0] = 0xff; + buf[1] = 0x85; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + // Vactive + buf[0] = 0xF0; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 2); + + // Hactive + buf[0] = 0xEA; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat1 = LT6911_i2c.readfrom(LT6911_ADDR, 2); + + revbuf[0] = (uint8_t)dat0->data[0]; + revbuf[1] = (uint8_t)dat0->data[1]; + revbuf[2] = (uint8_t)dat1->data[0]; + revbuf[3] = (uint8_t)dat1->data[1]; + + delete dat0; + delete dat1; + + Vactive = (revbuf[0] << 8)|revbuf[1]; + Hactive = (revbuf[2] << 8)|revbuf[3]; + } else { + return UNKNOWN_RES; + } + + res_type = check_res(Hactive, Vactive); + if(res_type == NORMAL_RES) + { + if(old_Hactive != Hactive || old_Vactive != Vactive){ + old_Hactive = Hactive; + old_Vactive = Vactive; + // p_kvm_cfg->width = Hactive; + // p_kvm_cfg->height = Vactive; + *p_width = Hactive; + *p_height = Vactive; + res_type = NEW_RES; + } + } + + switch (res_type) + { + case NORMAL_RES: + printf("[hdmi] get res : %d * %d\n", Hactive, Vactive); + break; + case NEW_RES: + printf("[hdmi] get new res : %d * %d\n", Hactive, Vactive); + write_res_to_file(Hactive, Vactive); + break; + case UNSUPPORT_RES: + printf("[hdmi] get unsupport res : %d * %d\n", Hactive, Vactive); + break; + case UNKNOWN_RES: + printf("[hdmi] get unknown res : %d * %d\n", Hactive, Vactive); + break; + } + + return res_type; +} + +void lt6911_write_reg(uint8_t reg, uint8_t val) +{ + uint8_t buf[2]; + buf[0] = reg; + buf[1] = val; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); +} + +void lt6911_read_reg(uint8_t reg) +{ + uint8_t buf[16]; + buf[0] = reg; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + + maix::Bytes *dat = LT6911_i2c.readfrom(LT6911_ADDR, 16); + + for(int i = 0; i < 16; i++){ + buf[i] = (uint8_t)dat->data[i]; + } + + delete dat; + + debug("[hdmi]%3x %3x %3x %3x %3x %3x %3x %3x |%3x %3x %3x %3x %3x %3x %3x %3x \n", \ + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], \ + buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]); +} + +uint8_t lt6911_read_one_reg(uint8_t reg) +{ + uint8_t ret; + ret = reg; + LT6911_i2c.writeto(LT6911_ADDR, &ret, 1); + + maix::Bytes *dat = LT6911_i2c.readfrom(LT6911_ADDR, 1); + + ret = (uint8_t)dat->data[0]; + + delete dat; + return ret; +} + +void lt6911_write_edid(void) +{ + uint8_t i, j; + uint8_t buf[2]; + lt6911_enable(); + + // to 90 + lt6911_write_reg(0xff, 0x90); + buf[0] = lt6911_read_one_reg(0x02); + buf[0] &= 0xDF; + lt6911_write_reg(0x02, buf[0]); + buf[0] |= 0x20; + lt6911_write_reg(0x02, buf[0]); + + // to 80 + lt6911_write_reg(0xff, 0x80); + // wren enable + lt6911_write_reg(0x5A, 0x86); + lt6911_write_reg(0x5A, 0x82); + + for(i = 0; i < 16; i++){ + // 写wren命令(为一个pulse,此时不需要考虑wrrd_mode,spi_paddr[1:0]的值) + lt6911_write_reg(0x5A, 0x86); + lt6911_write_reg(0x5A, 0x82); + + // 配置spi_len[3:0]= 15,可配置,spi内部加1,即配置一次写入16个字节 + lt6911_write_reg(0x5E, 0xEF); + lt6911_write_reg(0x5A, 0xA2); + lt6911_write_reg(0x5A, 0x82); + + lt6911_write_reg(0x58, 0x01); + + if(i < 8) { + for(j = 0; j < 16; j++){ + lt6911_write_reg(0x59, NanoKVM_edit[i*16+j]); + } + } else { + for(j = 0; j < 16; j++){ + lt6911_write_reg(0x59, 0x00); + } + } + + // 把fifo数据写到flash(当wrrd_mode = 1,spi_paddr= 2’b10,addr[23:0](地址在写入过程中需要保持不变)准备好的情况下,给一个spi_sta的pulse,就开始写入flash) + lt6911_write_reg(0x5B, 0x00); + lt6911_write_reg(0x5C, 0x21); + lt6911_write_reg(0x5D, i*16); + lt6911_write_reg(0x5E, 0xE0); + lt6911_write_reg(0x5A, 0x92); + lt6911_write_reg(0x5A, 0x82); + + } + + lt6911_write_reg(0x5A, 0x8A); + lt6911_write_reg(0x5A, 0x82); + + lt6911_disable(); + debug("[hdmi]lt6911_write_edid OK\n"); +} + +void lt6911_read_edid(void) +{ + uint8_t i, j; + lt6911_enable(); + + // to 80 + lt6911_write_reg(0xff, 0x80); + lt6911_write_reg(0xEE, 0x01); + + // configure parameter + lt6911_write_reg(0x5A, 0x80); + lt6911_write_reg(0x5E, 0xC0); + lt6911_write_reg(0x58, 0x00); + lt6911_write_reg(0x59, 0x51); + lt6911_write_reg(0x5A, 0x90); + time::sleep_ms(1); + lt6911_write_reg(0x5A, 0x80); + + for(i = 0; i < 16; i++){ + // to 90 + lt6911_write_reg(0xff, 0x90); + // fifo rst_n + lt6911_write_reg(0x02, 0xdf); + time::sleep_ms(1); + lt6911_write_reg(0x02, 0xff); + + // wren + // to 80 + lt6911_write_reg(0xff, 0x80); + // fifo rst_n + lt6911_write_reg(0x5A, 0x84); + time::sleep_ms(1); + lt6911_write_reg(0x5A, 0x80); + + // flash to fifo + lt6911_write_reg(0x5E, 0x6F); + lt6911_write_reg(0x5A, 0xA0); + time::sleep_ms(1); + lt6911_write_reg(0x5A, 0x80); + lt6911_write_reg(0x5B, 0x00); + lt6911_write_reg(0x5C, 0x21); + lt6911_write_reg(0x5D, 16*i); + lt6911_write_reg(0x5A, 0x90); + time::sleep_ms(5); + lt6911_write_reg(0x5A, 0x80); + lt6911_write_reg(0x58, 0x01); + + // for(j = 0; j < 16; j++){ + debug("[hdmi] EDID: "); + lt6911_read_reg(0x5F); + // } + time::sleep_ms(10); + } + + // wrdi + lt6911_write_reg(0x5A, 0x88); + time::sleep_ms(1); + lt6911_write_reg(0x5A, 0x80); + + lt6911_disable(); + debug("[hdmi]lt6911_read_edid OK\n"); +} + +void lt6911_read_fw(void) +{ + uint32_t i, j; + uint8_t buf[3]; + lt6911_enable(); + + // to 80 + lt6911_write_reg(0xff, 0x80); + lt6911_write_reg(0xEE, 0x01); + + // configure parameter + lt6911_write_reg(0x5A, 0x80); + lt6911_write_reg(0x5E, 0xC0); + lt6911_write_reg(0x58, 0x00); + lt6911_write_reg(0x59, 0x51); + lt6911_write_reg(0x5A, 0x90); + time::sleep_ms(1); + lt6911_write_reg(0x5A, 0x80); + + for(i = 0x1; i < 50000; i++){ + buf[0] = ((i*16) & 0xFF0000) >> 16; + buf[1] = ((i*16) & 0xFF00) >> 8; + buf[2] = ((i*16) & 0xFF); + // to 90 + lt6911_write_reg(0xff, 0x90); + // fifo rst_n + lt6911_write_reg(0x02, 0xdf); + time::sleep_ms(1); + lt6911_write_reg(0x02, 0xff); + + // wren + // to 80 + lt6911_write_reg(0xff, 0x80); + // fifo rst_n + lt6911_write_reg(0x5A, 0x84); + time::sleep_ms(1); + lt6911_write_reg(0x5A, 0x80); + + // flash to fifo + lt6911_write_reg(0x5E, 0x6F); + lt6911_write_reg(0x5A, 0xA0); + time::sleep_ms(1); + lt6911_write_reg(0x5A, 0x80); + lt6911_write_reg(0x5B, buf[0]); + lt6911_write_reg(0x5C, buf[1]); + lt6911_write_reg(0x5D, buf[2]); + lt6911_write_reg(0x5A, 0x90); + time::sleep_ms(5); + lt6911_write_reg(0x5A, 0x80); + lt6911_write_reg(0x58, 0x01); + + // for(j = 0; j < 16; j++){ + debug("[hdmi]REG %6x: ", i*16); + lt6911_read_reg(0x5F); + // } + time::sleep_ms(10); + } + + // wrdi + lt6911_write_reg(0x5A, 0x88); + time::sleep_ms(1); + lt6911_write_reg(0x5A, 0x80); + + lt6911_disable(); + debug("[hdmi]lt6911_read_edid OK\n"); +} + +// ============================================================== + +void* watchdog_sf_feed(void * arg) +{ + while(true) + { + if(kvmv_cfg.try_exit_thread == 1) + break; + time::sleep_ms(500); + if (watchdog_sf_is_open()){ + if (chack_ion() == 1){ + debug("[kvmv] Ion memory is full reboot now!\n"); + system("reboot"); + } + // debug("[kvmv] watchdog_sf_feed now!\n"); + vision_update_watchdog(); + } + } +} + +void get_hdmi_version() +{ + FILE *fp; + uint8_t RW_Data[2]; + system("/kvmapp/system/init.d/S15kvmhwd get_hdmi_version"); + if(access("/etc/kvm/hdmi_version", F_OK) == 0){ + fp = fopen("/etc/kvm/hdmi_version", "r"); + fread(RW_Data, sizeof(char), 2, fp); + fclose(fp); + if(RW_Data[0] == 'u'){ + // 6911uxc + if(RW_Data[1] == 'e'){ + kvmv_cfg.hdmi_version = 2; + debug("[hdmi]HDMI-UE exist!\n"); + set_hdmi_mode(1); + } else if(RW_Data[1] == 'x') { + kvmv_cfg.hdmi_version = 1; + debug("[hdmi]HDMI-UX exist!\n"); + } else { + kvmv_cfg.hdmi_version = 1; + debug("[hdmi]Incomplete version number, set to 'ux'\n"); + } + } else { + // 6911c + kvmv_cfg.hdmi_version = 0; + debug("[hdmi]HDMI-C exist!\n"); + } + RW_Data[0] = 0; + } else { + kvmv_cfg.hdmi_version = 0; + } +} + +void* vi_subsystem_detection(void * arg) +{ + uint64_t __attribute__((unused)) int_time; + + FILE *fp; + uint8_t RW_Data[2]; + uint8_t file_size; + uint8_t tmp8; + uint8_t rising_times = 0; + uint8_t falling_times = 0; + uint8_t cam_need_restart = 0; + uint8_t auto_change_mode = 0; + kvmv_cfg.thread_is_running = 1; + if(access("/proc/lt_int", F_OK) != 0){ + time::sleep_ms(10); + debug("[hdmi]/proc/lt_int not ok\n"); + } + + get_hdmi_version(); + + // while(!app::need_exit()) + uint8_t while_count_detect_res = 0; + while(true) + { + if(kvmv_cfg.try_exit_thread == 1) + break; + + uint8_t get_new_hdmi_mode = get_hdmi_mode(); + uint8_t try_res; + uint8_t err_code; + + switch (kvmv_cfg.hdmi_mode){ + case 0: + // Switching to Mode 0 requires restarting HDMI (effective only for PCIe version) + // Handling of automatic detection situations + if(get_new_hdmi_mode == 1){ + kvmv_cfg.vi_detect_state = 0; + // reset hdmi_state + char Cmd[100]={0}; + sprintf(Cmd, "echo 0 > %s", hdmi_state_path); + system(Cmd); + // reset hdmi + kvmv_hdmi_control(0); + time::sleep_ms(10); + kvmv_hdmi_control(1); + } + // Initialize VI with I2C information after HDMI insertion + fp = fopen(hdmi_state_path, "r+"); + if(fp != NULL){ + // fseek(fp, 0, SEEK_END); + // file_size = ftell(fp); + // fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), 2, fp); + tmp8 = atoi((char*)RW_Data); + // debug("[hdmi]UXC tmp8 = %d\n", tmp8); + if(tmp8 != 0){ + // reset hdmi_state + fputs("0", fp); + // count edge ints + rising_times = tmp8%10; // RISING times + falling_times = tmp8/10; // FALLING times + + if(kvmv_cfg.hdmi_stop_flag != 1){ + kvmv_cfg.hdmi_reading_flag = 1; + if(kvmv_cfg.hdmi_version == 0){ + // LT6911C + if(rising_times != 0){ + lt6911_enable(); + if(lt6911_get_hdmi_res()){ + // hdmi get res + debug("[hdmi] C HDMI cable insertion!\n"); + kvmv_cfg.hdmi_cable_state = 1; + kvmv_cfg.hdmi_res_type = lt6911_get_csi_res(&kvmv_cfg.vi_width, &kvmv_cfg.vi_height); + if (kvmv_cfg.hdmi_res_type == NEW_RES) kvmv_cfg.reopen_cam_flag = 1; + else if (kvmv_cfg.hdmi_res_type == UNKNOWN_RES){ + /* Move HDMI resolution modification directly + to mode1 to solve deadlock problem */ + auto_change_mode = 1; + set_hdmi_mode(1); + } + } else { + // HDMI res = 0*0/x*0 + debug("[hdmi] C HDMI cable unplugged!\n"); + kvmv_cfg.hdmi_cable_state = 0; + } + lt6911_disable(); + } + } else if (kvmv_cfg.hdmi_version == 1){ + // LT6911UXC + debug("[hdmi] UXC int\n"); + if(falling_times != 0){ + debug("[hdmi] UXC int && \n"); + lt6911_enable(); + if(lt6911_get_hdmi_res()){ + // hdmi get res + debug("[hdmi] UXC HDMI cable insertion!\n"); + kvmv_cfg.hdmi_cable_state = 1; + kvmv_cfg.hdmi_res_type = lt6911_get_csi_res(&kvmv_cfg.vi_width, &kvmv_cfg.vi_height); + if (kvmv_cfg.hdmi_res_type == NEW_RES) kvmv_cfg.reopen_cam_flag = 1; + else if (kvmv_cfg.hdmi_res_type == UNKNOWN_RES){ + /* Move HDMI resolution modification directly + to mode1 to solve deadlock problem */ + auto_change_mode = 1; + set_hdmi_mode(1); + } + } else { + // HDMI res = 0*0/x*0 + debug("[hdmi] UXC HDMI cable unplugged!\n"); + kvmv_cfg.hdmi_cable_state = 0; + } + lt6911_disable(); + } + } else { + debug("[hdmi] Chip not supported for reading \n"); + } + kvmv_cfg.hdmi_reading_flag = 0; + } + } + fclose(fp); + } + break; + /* Mode 1 & 2 will disable API access to the image during detection, + and will output -4: Modifying image resolution, please wait.*/ + case 1: // Automatically trying common resolutions + /* kvmv_cfg.vi_detect_state : + * 0: HDMI standard mode, detection program does not interfere with the camera + * 1: Preparing / Testing in progress + * 2: Test completed: Suitable resolution found, + */ + + if(get_new_hdmi_mode == 1){ + kvmv_cfg.vi_detect_state = 1; + } + if(kvmv_cfg.vi_detect_state == 1){ + try_res = auto_try_res(); + if (try_res == 1) { + kvmv_cfg.hdmi_res_err = NORMAL_RES; + kvmv_cfg.hdmi_try_rounds = 0; + kvmv_cfg.vi_detect_state = 2; + } else if (try_res == 0) { + /* There may be a situation where the correct resolution cannot be recognized + after one round of detection. By default, Try_rounds_HDMI_err_res rounds will be detected, + and if it cannot be detected, jump to the next mode */ + kvmv_cfg.hdmi_res_err = ERROR_RES; + kvmv_cfg.hdmi_try_rounds++; + if(kvmv_cfg.hdmi_try_rounds >= Try_rounds_HDMI_err_res){ + kvmv_cfg.hdmi_try_rounds = 0; + kvmv_cfg.vi_detect_state = 1; + // printf("[kvmv] Suitable resolution not found, switching to manual input mode automatically\n"); + if(auto_change_mode == 1){ + auto_change_mode = 0; + set_hdmi_mode(0); + } else { + set_hdmi_mode(2); + } + } + } else if (try_res == 2) { + kvmv_cfg.hdmi_try_rounds = 0; + // Cannot obtain HDMI input / No signal on HDMI + } + } else if (kvmv_cfg.vi_detect_state == 2){ + // Low-frequency detection of HDMI status, no log output + printf("[kvmv] kvmv_cfg.vi_detect_state == 2\n"); + err_code = get_vi_state(); + if (err_code != 1) { + kvmv_cfg.vi_detect_state = 1; + } + time::sleep_ms(1000); + } else { + kvmv_cfg.vi_detect_state = 1; + } + break; + case 2: + // Manually initialize VI. + while_count_detect_res = (while_count_detect_res + 1)%100; + if(while_count_detect_res == 1){ + if (kvmv_cfg.vi_detect_state == 1){ + // detect_res + if (get_manual_resolution()) { + debug("[kvmv] restart cam...\n"); + cam->restart(default_vpss_width, default_vpss_height, image::FMT_YVU420SP); + } + + // dbg info + err_code = get_vi_state(); + switch(err_code){ + case 0: + debug("[kvmv] VI not init\n"); + break; + case 1: + debug("[kvmv] HDMI and CSI status are normal\n"); + kvmv_cfg.vi_detect_state = 2; + break; + case 2: + debug("[kvmv] HDMI abnormal\n"); + break; + case 3: + debug("[kvmv] CSI abnormal: width too small\n"); + break; + case 4: + debug("[kvmv] CSI abnormal: width too large\n"); + break; + case 5: + debug("[kvmv] CSI abnormal: height too small\n"); + break; + case 6: + debug("[kvmv] CSI abnormal: height too large\n"); + break; + case 7: + debug("[kvmv] CSI abnormal: Unknown reason\n"); + break; + } + } else if (kvmv_cfg.vi_detect_state == 2){ + // detection of HDMI status, no log output + err_code = get_vi_state(); + if (err_code != 1) kvmv_cfg.vi_detect_state = 1; + } else { + kvmv_cfg.vi_detect_state = 1; + } + } + break; + + default: + debug("Non-existent hdmi state = %d\n", kvmv_cfg.hdmi_mode); + break; + } + + time::sleep_ms(10); + } + kvmv_cfg.thread_is_running = 0; +} + +int sync_vi_res() +{ + int res = 0; + uint8_t RW_Data[35]; + FILE *fp; + int file_size; + uint16_t tmp16; + + // vi_width: + if (access(vi_width_path, F_OK) != 0){ + kvmv_cfg.vi_width = default_vi_width; + kvmv_cfg.vi_height = default_vi_height; + res = -1; + return res; + } else { + fp = fopen(vi_width_path, "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + RW_Data[file_size] = 0; + tmp16 = atoi((char*)RW_Data); + if(tmp16 != kvmv_cfg.vi_width){ + kvmv_cfg.vi_width = tmp16; + debug("[hdmi] Get new HDMI width = %d\r\n", kvmv_cfg.vi_width); + res = 1; + } + } + // vi_height: + if (access(vi_height_path, F_OK) != 0){ + kvmv_cfg.vi_height = default_vi_height; + res = -1; + return res; + } else { + fp = fopen(vi_height_path, "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + RW_Data[file_size] = 0; + tmp16 = atoi((char*)RW_Data); + if(tmp16 != kvmv_cfg.vi_height){ + kvmv_cfg.vi_height = tmp16; + debug("[hdmi] Get new HDMI height = %d\r\n", kvmv_cfg.vi_height); + res = 1; + } + } + return res; +} + +uint8_t frame_changed(image::Image *raw) +{ + static int raw_size = 0; + static uint8_t Farame_sample[Farame_sample_size] = {0}; + uint8_t ret = 0; + uint8_t sample_byte; + if(raw->data_size() != raw_size){ + raw_size = raw->data_size(); + ret = 1; + } + int Detection_Pixel_Interval = raw_size/(Farame_sample_size*1.5); + for(int i = 0; i < Farame_sample_size; i ++){ + // printf("[kvmv] i = %d\n", i); + if(i >= raw_size){ + ret = 0; + } + sample_byte = *(uint8_t*)(raw->data()+(i*Detection_Pixel_Interval)); + if(sample_byte != Farame_sample[i]){ + Farame_sample[i] = sample_byte; + ret = 1; + } + } + return ret; +} + +void jpg_dump(kvmv_data_t* dump_to, image::Image *raw) +{ + dump_to->p_img_data = (uint8_t *)malloc(raw->data_size()); + dump_to->img_data_size = raw->data_size(); + dump_to->img_data_type = VENC_MJPEG; + memcpy(dump_to->p_img_data, (uint8_t *)raw->data(), raw->data_size()); +} + +uint8_t kvmvenc_gop = default_h264_gop; +kvm_venc_t kvm_venc; +mmf_venc_cfg_t cfg; +void init_venc_h264(uint16_t _width, uint16_t _height, uint16_t _qlty) +{ + cfg.type = 2; //1, h265, 2, h264 + cfg.w = _width; + cfg.h = _height; + cfg.fmt = mmf_invert_format_to_mmf(image::Format::FMT_YVU420SP); + cfg.jpg_quality = 0; // unused + cfg.gop = kvmvenc_gop; + cfg.intput_fps = 60; + cfg.output_fps = 60; + cfg.bitrate = _qlty; // 码率 + + kvm_venc.mmf_venc_chn = default_venc_chn; + kvm_venc.enc_h264_init = 0; + kvm_venc.kvm_venc_cfg = cfg; + + // if(mmf_vdec_is_used(kvm_venc.mmf_venc_chn)){ + mmf_del_venc_channel(kvm_venc.mmf_venc_chn); + // } + if (0 != mmf_add_venc_channel(kvm_venc.mmf_venc_chn, &kvm_venc.kvm_venc_cfg)) { + err::check_raise(err::ERR_RUNTIME, "mmf venc init failed!"); + } + kvm_venc.enc_h264_init = 1; + + // init_kvm_h264_stream(&kvm_h264_stream, mmf_stream_buf); + // init_h264_stream_struct(&kvm_h264_stream); +} + +int h264_stream_dump(kvmv_data_t* dump_to, mmf_stream_t* dump_from) +{ + static int8_t I_Frame_index = -1; + // debug("[kvmv]dump_from->count = %d\n", dump_from->count); + if (dump_from->count == 3) { + + dump_to->p_img_data = (uint8_t *)malloc(dump_from->data_size[0]+dump_from->data_size[1]+dump_from->data_size[2]); + dump_to->img_data_size = dump_from->data_size[0]+dump_from->data_size[1]+dump_from->data_size[2]; + dump_to->img_data_type = IMG_H264_TYPE_IF; + memcpy(dump_to->p_img_data, dump_from->data[0], dump_from->data_size[0]); + memcpy(dump_to->p_img_data+dump_from->data_size[0], dump_from->data[1], dump_from->data_size[1]); + memcpy(dump_to->p_img_data+dump_from->data_size[0]+dump_from->data_size[1], dump_from->data[2], dump_from->data_size[2]); + + debug("[kvmv]SPS size = %d\n", dump_from->data_size[0]); + debug("[kvmv]PPS size = %d\n", dump_from->data_size[1]); + debug("[kvmv]I-Frame size = %d\n", dump_from->data_size[2]); + + return IMG_H264_TYPE_IF; + + } else if (dump_from->count == 1) { + // debug("[kvmv]dump P-Frame\r\n"); + I_Frame_index = -1; + dump_to->p_img_data = (uint8_t *)malloc(dump_from->data_size[0]); + dump_to->img_data_size = dump_from->data_size[0]; + dump_to->img_data_type = IMG_H264_TYPE_PF; + memcpy(dump_to->p_img_data, dump_from->data[0], dump_from->data_size[0]); + return IMG_H264_TYPE_PF; + } else { + debug("[kvmv]venc error!\r\n"); + return IMG_VENC_ERROR; + } +} + +void set_h264_gop(uint8_t _gop) +{ + kvm_venc.enc_h264_init = 0; // call + kvmvenc_gop = maxmin_data(100, 1, (int)_gop); + debug("[kvmv] set_h264_gop = %d\n", kvmvenc_gop); +} + +void set_frame_detact(uint8_t _frame_detact) +{ + uint8_t frame_detact = maxmin_data(100, 0, (int)_frame_detact); + kvmv_cfg.frame_detact = frame_detact; + debug("[kvmv] set_frame_detact = %d\n", kvmv_cfg.frame_detact); + kvmv_cfg.stream_stop = 0; +} + +int8_t raw_to_h264(image::Image *raw, kvmv_data_t* ret_stream, uint16_t _qlty) +{ + uint64_t __attribute__((unused)) start_time; + uint64_t __attribute__((unused)) frame_time; + int8_t ret = 0; + static uint8_t P_Frame_Count = 0; + // start_time = time::time_ms(); + // log::info("getimg: %d \r\n", (int)(time::time_ms())); + mmf_stream_t _stream = {0}; + if(kvm_venc.enc_h264_init != 1 || raw->width() != kvm_venc.kvm_venc_cfg.w || raw->height() != kvm_venc.kvm_venc_cfg.h || _qlty != kvm_venc.kvm_venc_cfg.bitrate){ + debug("[kvmv]init_venc_h264 enc_h264_init = %d; raw->width() = %d | %d raw->height() = %d | %d \n", + kvm_venc.enc_h264_init, + raw->width(), kvm_venc.kvm_venc_cfg.w, + raw->height(), kvm_venc.kvm_venc_cfg.h); + + init_venc_h264(raw->width(), raw->height(), _qlty); + debug("[kvmv]init_venc_h264 finish enc_h264_init = %d; raw->width() = %d | %d raw->height() = %d | %d \n", + kvm_venc.enc_h264_init, + raw->width(), kvm_venc.kvm_venc_cfg.w, + raw->height(), kvm_venc.kvm_venc_cfg.h); + // if(kvm_venc.enc_h264_init == 1){ + // init_venc_h264(raw->width(), raw->height(), _qlty); + // } else { + // init_venc_h264(default_vpss_width, default_vpss_height, default_h264_qlty); + // } + } + // log::info("init(): %d \r\n", (int)(time::time_ms() - start_time)); + if (mmf_venc_push(kvm_venc.mmf_venc_chn, (uint8_t *)raw->data(), raw->width(), raw->height(), mmf_invert_format_to_mmf(raw->format()))) { + mmf_del_venc_channel(kvm_venc.mmf_venc_chn); + kvm_venc.enc_h264_init = 0; + // rtmp->unlock(); + debug("[kvmv]mmf venc push failed!\n"); + // err::check_raise(err::ERR_RUNTIME, "mmf venc push failed!\r\n"); + return -1; + } + // log::info("push(): %d \r\n", (int)(time::time_ms() - start_time)); + if (mmf_venc_pop(kvm_venc.mmf_venc_chn, &_stream)) { + // log::error("mmf_venc_pop failed\n"); + mmf_venc_free(kvm_venc.mmf_venc_chn); + mmf_del_venc_channel(kvm_venc.mmf_venc_chn); + kvm_venc.enc_h264_init = 0; + debug("[kvmv]mmf venc push failed!\n"); + // rtmp->unlock(); + return -1; + } + // log::info("pop(): %d \r\n", (int)(time::time_ms() - start_time)); + ret = h264_stream_dump(ret_stream, &_stream); + mmf_venc_free(kvm_venc.mmf_venc_chn); + // log::info("dump(): %d \r\n", (int)(time::time_ms() - start_time)); + + // debug("[kvmv]_stream.data[0][4] = %d;\n", _stream.data[0][4]); + debug("[kvmv]Frame size = %d;\n", ret_stream->img_data_size); + + if(ret == IMG_H264_TYPE_IF){ + debug("[kvmv]================ GOP = %d ================\n", kvm_venc.kvm_venc_cfg.gop); + debug("[kvmv]SPS; PPS; I-Frame, I-Frame size = %d\n", ret_stream->img_data_size); + P_Frame_Count = 0; + + } else if(ret == IMG_H264_TYPE_PF){ + debug("[kvmv]P-Frame size = %d, P-count = %d\n", ret_stream->img_data_size, P_Frame_Count); + P_Frame_Count++; + } + debug("[kvmv]dump ret = %d\n", ret); + return ret; +} + +void kvmv_init(uint8_t _debug_info_en) +{ + pthread_t thread; + pthread_mutex_init(&vi_mutex, NULL); + if(_debug_info_en == 0) debug_en = 0; + else debug_en = 1; + + // debug("[kvmv]kvmv_init - 1\r\n"); + + cam->hmirror(1); + cam->vflip(1); + cam->restart(default_vpss_width, default_vpss_height, image::FMT_YVU420SP); + for(int i = 0; i < kvmv_data_buffer_size; i++){ + kvmv_data_buffer[i].p_img_data = NULL; + } + + kvmv_cfg.try_exit_thread = 0; + // debug("[kvmv]kvmv_init - 2\r\n"); + + if(kvmv_cfg.thread_is_running == 1){ + debug("[kvmv]thread is running!\r\n"); + } else { + if (0 != pthread_create(&thread, NULL, vi_subsystem_detection, NULL)) { + debug("[kvmv]create vi_subsystem_detection thread failed!\r\n"); + // return -1; + } + + if (0 != pthread_create(&thread, NULL, watchdog_sf_feed, NULL)) { + debug("[kvmv]create watchdog_sf_feed thread failed!\r\n"); + // return -1; + } + } + // debug("[kvmv]kvmv_init - 3\r\n"); +} + +uint8_t check_kvmv(uint8_t _try_num) +{ + if(kvmv_cfg.hdmi_cable_state == 0){ + debug("[kvmv]HDMI Cable not exist!\n"); + return 0; + } + if(_try_num >= KVMV_MAX_TRY_NUM){ + debug("[kvmv]try_num >= KVMV_MAX_TRY_NUM!\n"); + return 0; + } + // if(sync_vi_res() != 0){ + if(kvmv_cfg.reopen_cam_flag == 1){ + // vi size changed + kvmv_cfg.reopen_cam_flag = 0; + // cam->open(kvmv_cfg.vpss_width, kvmv_cfg.vpss_height, image::FMT_YVU420SP, 3); + cam->restart(default_vpss_width, default_vpss_height, image::FMT_YVU420SP); + debug("[kvmv]vi size changed, try again\n"); + return 1; + } + debug("[kvmv]just try again\n"); + return 1; +} + +void set_venc_auto_recyc(uint8_t _enable) +{ + if(_enable) kvmv_cfg.venc_auto_recyc = 1; + else kvmv_cfg.venc_auto_recyc = 0; +} + +/********************************************************************************** + * @name kvmv_read_img + * @author Sipeed BuGu + * @date 2024/10/25 + * @version R1.0 + * @brief Acquire the encoded image with auto init + * @param _width @input: Output image width + * @param _height @input: Output image height + * @param _type @input: Encode type + * @param _qlty @input: MJPEG: (50-100) | H264: (500-10000) + * @param _pp_kvm_data @output: Encode data + * @param _p_kvmv_data_size @output: Encode data size + * @return + -7: HDMI INPUT RES ERROR + -6: Unsupported resolution, please modify it in the host settings. + -5: Retrieving image, please wait + -4: Modifying image resolution, please wait + -3: img buffer full + -2: VENC Errorl + -1: No images were acquired + 0: Acquire MJPEG encoded images + 1: Acquire H264 encoded images(SPS)[Deprecated] + 2: Acquire H264 encoded images(PPS)[Deprecated] + 3: Acquire H264 encoded images(I) + 4: Acquire H264 encoded images(P) + 5: IMG not changed + **********************************************************************************/ +int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _qlty, uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size) +{ + static uint8_t frame_undetact_count = 0; + // uint64_t __attribute__((unused)) start_time = time::time_ms(); + debug("[kvmv]kvmv_read_img type = %d...\n", _type); + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 1; + // pthread_mutex_lock(&vi_mutex); // Add lock + int mutex_res = pthread_mutex_timedlock(&vi_mutex, &ts); + if(mutex_res != 0){ + return -5; + } + if (kvmv_cfg.hdmi_res_err == ERROR_RES){ + pthread_mutex_unlock(&vi_mutex); + return -7; + } + if (kvmv_cfg.hdmi_res_type == UNSUPPORT_RES){ + pthread_mutex_unlock(&vi_mutex); + return -6; + } + if (kvmv_cfg.vi_detect_state == 1){ + pthread_mutex_unlock(&vi_mutex); + return -4; + } + uint8_t try_num = 0; + do { + if(kvmv_cfg.vpss_width != _width || kvmv_cfg.vpss_height != _height){ + if(_width == 0 || _height == 0){ + // Follow the HDMI output + kvmv_cfg.vpss_width = kvmv_cfg.vi_width; + kvmv_cfg.vpss_height = kvmv_cfg.vi_height; + + if(kvmv_cfg.Auto_res == 0){ + kvmv_cfg.Auto_res = 1; + cam->set_resolution(kvmv_cfg.vpss_width, kvmv_cfg.vpss_height); + kvmv_cfg.reinit_flag = 1; + } + + } else { + kvmv_cfg.Auto_res = 0; + // Set the output + kvmv_cfg.vpss_width = _width; + kvmv_cfg.vpss_height = _height; + + cam->set_resolution(kvmv_cfg.vpss_width, kvmv_cfg.vpss_height); + kvmv_cfg.reinit_flag = 1; + } + } + + // + if (kvmv_cfg.reinit_flag == 1) { + cam->hmirror(1); + cam->vflip(1); + kvmv_cfg.reinit_flag = 0; + } + // debug("[kvmv]befor read img: %d \r\n", (int)(time::time_ms() - start_time)); + image::Image *img = cam->read(); + // debug("[kvmv]read img: %d \r\n", (int)(time::time_ms() - start_time)); + + if(img != NULL){ + // frame detect + if(_type == VENC_MJPEG && kvmv_cfg.frame_detact != 0){ + if(kvmv_cfg.stream_stop == 0){ + frame_undetact_count++; + } else { + frame_undetact_count = kvmv_cfg.frame_detact; + } + + if(frame_undetact_count == kvmv_cfg.frame_detact){ + frame_undetact_count = 0; + + if(frame_changed(img) == 0){ + debug("[kvmv]frame not changed...\n"); + kvmv_cfg.stream_stop = 1; + delete img; + pthread_mutex_unlock(&vi_mutex); + return 5; + } else { + kvmv_cfg.stream_stop = 0; + } + } + } + if(kvmv_cfg.cam_state == 0) { + kvmv_cfg.cam_state = 1; + kvmv_cfg.hdmi_cable_state = 1; + system("echo 1 > /kvmapp/kvm/state"); + } + } else { + if(kvmv_cfg.cam_state == 1) { + kvmv_cfg.cam_state = 0; + system("echo 0 > /kvmapp/kvm/state"); + } + delete img; + debug("[kvmv]can`t get img...\n"); + continue; + // pthread_mutex_unlock(&vi_mutex); + // return IMG_NOT_EXIST; + } + // debug("[kvmv]cheak img null?: %d \r\n", (int)(time::time_ms() - start_time)); + + // img exist + // Encode + if(kvmv_cfg.venc_type == VENC_MJPEG && kvmv_cfg.venc_type != _type){ + if(kvmv_cfg.venc_auto_recyc == 1){ + mmf_enc_jpg_deinit(0); + } + kvm_venc.enc_h264_init = 1; + } + if(kvmv_cfg.venc_type == VENC_H264 && kvmv_cfg.venc_type != _type){ + if(kvmv_cfg.venc_auto_recyc == 1){ + mmf_del_venc_channel(kvm_venc.mmf_venc_chn); + } + kvm_venc.enc_h264_init = 0; + } + + kvmv_cfg.venc_type = _type; + + if(kvmv_cfg.venc_type == VENC_MJPEG){ + image::Image *jpg = img->to_jpeg(maxmin_data(99, 51, (int)_qlty)); + kvmv_data_t* p_kvmv_data = get_save_buffer(); + if(p_kvmv_data == NULL){ + // buffer full + delete jpg; + delete img; + debug("[kvmv]jpg buffer full\n"); + *_pp_kvm_data = NULL; + pthread_mutex_unlock(&vi_mutex); + return IMG_BUFFER_FULL; + } + jpg_dump(p_kvmv_data, jpg); + delete jpg; + delete img; + *_pp_kvm_data = p_kvmv_data->p_img_data; + *_p_kvmv_data_size = p_kvmv_data->img_data_size; + pthread_mutex_unlock(&vi_mutex); + return IMG_MJPEG_TYPE; + } else if (kvmv_cfg.venc_type == VENC_H264){ + int ret; + kvmv_data_t* p_kvmv_data = get_save_buffer(); + if(p_kvmv_data == NULL){ + // buffer full + delete img; + *_pp_kvm_data = NULL; + pthread_mutex_unlock(&vi_mutex); + return IMG_BUFFER_FULL; + } + // debug("[kvmv]get_save_buffer: %d \r\n", (int)(time::time_ms() - start_time)); + ret = raw_to_h264(img, p_kvmv_data, maxmin_data(10000, 500, (int)_qlty)); + // debug("[kvmv]venc raw_to_h264: %d \r\n", (int)(time::time_ms() - start_time)); + delete img; + *_pp_kvm_data = p_kvmv_data->p_img_data; + *_p_kvmv_data_size = p_kvmv_data->img_data_size; + pthread_mutex_unlock(&vi_mutex); + return ret; + } + } while (check_kvmv(try_num++)); + // debug("[kvmv]return: %d \r\n", (int)(time::time_ms() - start_time)); + *_pp_kvm_data = NULL; + pthread_mutex_unlock(&vi_mutex); + return IMG_NOT_EXIST; +} + +int free_kvmv_data(uint8_t ** _pp_kvm_data) +{ + // debug("[kvmv]free_kvmv_data - 1\r\n"); + for(int i = 0; i < kvmv_data_buffer_size; i++){ + if(*_pp_kvm_data == kvmv_data_buffer[i].p_img_data){ + // debug("[kvmv]free buffer : %d\n", *_pp_kvm_data); + if (*_pp_kvm_data != NULL){ + // debug("[kvmv]free_kvmv_data - 2\r\n"); + free(*_pp_kvm_data); + // debug("[kvmv]free_kvmv_data - 3\r\n"); + kvmv_data_buffer[i].p_img_data = NULL; + uint8_t _type = kvmv_data_buffer[i].img_data_type; + return _type; + } else { + return IMG_NOT_EXIST; + } + } + } + return IMG_NOT_EXIST; +} + +void free_all_kvmv_data() +{ + for(int i = 0; i <= kvmv_data_buffer_size; i++){ + if(kvmv_data_buffer[i].p_img_data != NULL){ + free(kvmv_data_buffer[i].p_img_data); + kvmv_data_buffer[i].p_img_data = NULL; + } + } +} + +void kvmv_deinit() +{ + pthread_mutex_destroy(&vi_mutex); + kvmv_cfg.try_exit_thread = 1; + cam->close(); + mmf_deinit(); + free_all_kvmv_data(); +} + +uint8_t kvmv_hdmi_control(uint8_t _en) +{ + if(kvmv_cfg.hw_version == 0){ + + FILE *fp; + uint8_t RW_Data[2]; + if(access("/etc/kvm/hw", F_OK) == 0){ + fp = fopen("/etc/kvm/hw", "r"); + fread(RW_Data, sizeof(char), 1, fp); + fclose(fp); + switch(RW_Data[0]){ + case 'a': + case 'b': + case 'p': + kvmv_cfg.hw_version = RW_Data[0]; + break; + default : + kvmv_cfg.hw_version = 'a'; + } + } + } + if(kvmv_cfg.hw_version != 'p'){ + debug("[kvmv]Hardware not support!\n"); + return -1; + } + if(access("/sys/class/gpio/gpio451/value", F_OK) != 0){ + system("echo 451 > /sys/class/gpio/export"); + system("echo out > /sys/class/gpio/gpio451/direction"); + } + if(_en == 0){ + kvmv_cfg.hdmi_stop_flag = 1; + while(kvmv_cfg.hdmi_reading_flag == 1) time::sleep_ms(10); + system("echo 0 > /sys/class/gpio/gpio451/value"); + return 0; + } else { + kvmv_cfg.hdmi_stop_flag = 0; + system("echo 1 > /sys/class/gpio/gpio451/value"); + return 0; + } + return -1; +} diff --git a/support/sg2002/additional/kvm_mmf/CMakeLists.txt b/support/sg2002/additional/kvm_mmf/CMakeLists.txt new file mode 100644 index 0000000..f98387c --- /dev/null +++ b/support/sg2002/additional/kvm_mmf/CMakeLists.txt @@ -0,0 +1,66 @@ + +list(APPEND ADD_REQUIREMENTS basic ini) +list(APPEND ADD_INCLUDE "include") + +# sophgo-middleware +set(middleware_src_path "${CMAKE_CURRENT_SOURCE_DIR}/../3rd_party/sophgo-middleware/sophgo-middleware") + + +set(source_dir "src") +append_srcs_dir(ADD_SRCS "${source_dir}") + +# middleware +set(middleware_include_dir . + ${middleware_src_path}/v2/component/panel/${CONFIG_SOPHGO_MIDDLEWARE_CHIP} + ${middleware_src_path}/v2/include + ${middleware_src_path}/v2/include/isp/${CONFIG_SOPHGO_MIDDLEWARE_CHIP} + ${middleware_src_path}/v2/sample/common + ${middleware_src_path}/v2/uapi + ${middleware_src_path}/v2/3rdparty/inih + ${middleware_src_path}/v2/modules/ive/include/ +) +list(APPEND ADD_INCLUDE ${middleware_include_dir}) +set_property(SOURCE ${middleware_include_dir} PROPERTY GENERATED 1) + +append_srcs_dir(middleware_src_dir ${middleware_src_path}/v2/sample/common + ${middleware_src_path}/v2/component/isp/sensor/sg200x/lontium_lt6911 + ) +list(APPEND ADD_SRCS ${middleware_src_dir} + "${source_dir}/kvm_mmf.cpp") +set_property(SOURCE ${middleware_src_dir} PROPERTY GENERATED 1) + +set(middleware_static_lib_file "") + list(APPEND ADD_STATIC_LIB ${middleware_static_lib_file}) +set_property(SOURCE ${middleware_static_lib_file} PROPERTY GENERATED 1) + +set(mmf_lib_dir ${middleware_src_path}/v2/lib) + +if(CONFIG_SOPHGO_MIDDLEWARE_C_LIBRARY STREQUAL "musl") + set(middleware_dynamic_lib_file ${mmf_lib_dir}/libcvi_bin.so + ${mmf_lib_dir}/libae.so + ${mmf_lib_dir}/libaf.so + ${mmf_lib_dir}/libawb.so + ${mmf_lib_dir}/libcvi_bin_isp.so + ${mmf_lib_dir}/libisp_algo.so + ${mmf_lib_dir}/libisp.so + ${mmf_lib_dir}/libsys.so + ${mmf_lib_dir}/libvdec.so + ${mmf_lib_dir}/libvenc.so + ${mmf_lib_dir}/libvpu.so + ${mmf_lib_dir}/3rd/libini.so) +elseif(CONFIG_SOPHGO_MIDDLEWARE_C_LIBRARY STREQUAL "glibc") + set(middleware_dynamic_lib_file ${mmf_lib_dir}/libcvi_vb.so + ${mmf_lib_dir}/libcvi_sys.so + ${mmf_lib_dir}/libcvi_debug.so + ) +else() + message(FATAL_ERROR "No sophgo middleware c library support, please check menuoconfig to select c library") +endif() +list(APPEND ADD_DYNAMIC_LIB ${middleware_dynamic_lib_file}) +set_property(SOURCE ${middleware_dynamic_lib_file} PROPERTY GENERATED 1) + +list(APPEND ADD_DEFINITIONS_PRIVATE -DSENSOR_LONTIUM_LT6911 + -DSENSOR2_TYPE=LONTIUM_LT6911_2M_60FPS_8BIT) + + +register_component(DYNAMIC) diff --git a/support/sg2002/additional/kvm_mmf/Kconfig b/support/sg2002/additional/kvm_mmf/Kconfig new file mode 100644 index 0000000..b4b5a07 --- /dev/null +++ b/support/sg2002/additional/kvm_mmf/Kconfig @@ -0,0 +1,32 @@ +# sophgo middleware version major minor patch +menu "sophgo middleware version" +config SOPHGO_MIDDLEWARE_CHIP + string "sophgo chip" + default "cv181x" + help + sophgo chip. cv180x or cv181x + +config SOPHGO_MIDDLEWARE_C_LIBRARY + string "sophgo c standard library" + default "musl" + help + sophgo c standard library. glibc or musl + +config SOPHGO_MIDDLEWARE_VERSION_MAJOR + int "sophgo middleware package major version" + default 0 + help + sophgo middleware package major version, 0 means auto select according to board + +config SOPHGO_MIDDLEWARE_VERSION_MINOR + int "sophgo middleware package minor version" + default 0 + help + sophgo middleware package minor version + +config SOPHGO_MIDDLEWARE_VERSION_PATCH + int "sophgo middleware package patch version" + default 4 + help + sophgo middleware package patch version +endmenu diff --git a/support/sg2002/additional/kvm_mmf/include/kvm_mmf.hpp b/support/sg2002/additional/kvm_mmf/include/kvm_mmf.hpp new file mode 100644 index 0000000..b98dd12 --- /dev/null +++ b/support/sg2002/additional/kvm_mmf/include/kvm_mmf.hpp @@ -0,0 +1,68 @@ +#ifndef __KVM_MMF_HPP__ +#define __KVM_MMF_HPP__ + +#include "stdint.h" + +typedef struct { + uint8_t *data[8]; + int data_size[8]; + int count; +} mmf_stream_t; + +typedef struct { + uint8_t type; // 0, jpg; 1, h265; 2, h264 + int w; + int h; + int fmt; + uint8_t jpg_quality; // jpeg + int gop; // h264 + int intput_fps; // h264 + int output_fps; // h264 + int bitrate; // h264 +} mmf_venc_cfg_t; + +// init sys +int mmf_init(void); +int mmf_deinit(void); +int mmf_try_deinit(bool force); +bool mmf_is_init(void); + +// manage vi channels(vi->vpssgroup->vpss->frame) +int mmf_get_vi_unused_channel(void); +int mmf_vi_init(void); +int mmf_vi_deinit(void); +int mmf_add_vi_channel_with_enc(int ch, int width, int height, int format); +int mmf_add_vi_channel(int ch, int width, int height, int format); +int mmf_del_vi_channel(int ch); +int mmf_del_vi_channel_all(void); +int mmf_reset_vi_channel(int ch, int width, int height, int format); +bool mmf_vi_chn_is_open(int ch); +int mmf_vi_aligned_width(int ch); +void mmf_set_vi_hmirror(int ch, bool en); +void mmf_set_vi_vflip(int ch, bool en); +void mmf_get_vi_hmirror(int ch, bool *en); +void mmf_get_vi_vflip(int ch, bool *en); + +// get vi frame +int mmf_vi_frame_pop(int ch, void **data, int *len, int *width, int *height, int *format); +void mmf_vi_frame_free(int ch); + +// invert format +int mmf_invert_format_to_maix(int mmf_format); +int mmf_invert_format_to_mmf(int maix_format); + +// venc +int mmf_enc_jpg_init(int ch, int w, int h, int format, int quality); +int mmf_enc_jpg_deinit(int ch); +int mmf_enc_jpg_push(int ch, uint8_t *data, int w, int h, int format); +int mmf_enc_jpg_push_with_quality(int ch, uint8_t *data, int w, int h, int format, int quality); +int mmf_enc_jpg_pop(int ch, uint8_t **data, int *size); +int mmf_enc_jpg_free(int ch); +int mmf_add_venc_channel(int ch, mmf_venc_cfg_t *cfg); +int mmf_del_venc_channel(int ch); +int mmf_del_venc_channel_all(); +int mmf_venc_push(int ch, uint8_t *data, int w, int h, int format); +int mmf_venc_pop(int ch, mmf_stream_t *stream); +int mmf_venc_free(int ch); + +#endif // __KVM_MMF_HPP__ diff --git a/support/sg2002/additional/kvm_mmf/src/kvm_mmf.cpp b/support/sg2002/additional/kvm_mmf/src/kvm_mmf.cpp new file mode 100644 index 0000000..89318bd --- /dev/null +++ b/support/sg2002/additional/kvm_mmf/src/kvm_mmf.cpp @@ -0,0 +1,2352 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "math.h" +#include + +#include /* low-level i/o */ +#include "cvi_buffer.h" +#include "cvi_ae_comm.h" +#include "cvi_awb_comm.h" +#include "cvi_comm_isp.h" +#include "cvi_comm_sns.h" +#include "cvi_ae.h" +#include "cvi_awb.h" +#include "cvi_isp.h" +#include "cvi_sns_ctrl.h" +#include "cvi_ive.h" +#include "cvi_sys.h" +#include "sample_comm.h" +#include "kvm_mmf.hpp" +#include "global_config.h" + +#define MMF_VI_MAX_CHN 2 // manually limit the max channel number of vi +#define MMF_RGN_MAX_NUM 16 +#define MMF_VENC_MAX_CHN 4 + +#define MMF_VB_VI_ID 0 + +#if VPSS_MAX_PHY_CHN_NUM < MMF_VI_MAX_CHN +#error "VPSS_MAX_PHY_CHN_NUM < MMF_VI_MAX_CHN" +#endif + +typedef struct { + uint8_t ch; + SIZE_S input; + SIZE_S output; + int fps; + uint8_t depth; + uint8_t fit; // fit = 0, width to new width, height to new height, may be stretch + // fit = 1, keep aspect ratio, fill blank area with black color + // fit = 2, keep aspect ratio, crop image to fit new size + int input_fmt; + int output_fmt; +} vpss_info_t; + +typedef struct { + uint8_t ch; + uint8_t type; // 0, jpg; 1, h265; 2, h264 + uint8_t is_inited; + uint8_t is_used; + uint8_t is_running; + uint8_t use_vpss; + VIDEO_FRAME_INFO_S *capture_frame; + VENC_STREAM_S capture_stream; + mmf_venc_cfg_t cfg; + vpss_info_t vpss; + uint32_t pool_id; +} venc_info_t; + +typedef enum { + MMF_MOD_VO, + MMF_MOD_VI, + MMF_MOD_VPSS, + MMF_MOD_VENC, + MMF_MOD_VDEC, + MMF_MOD_REGION, +} mmf_mod_type_t; + +typedef struct { + char name[15]; + uint8_t is_used; + mmf_mod_type_t mod; + uint32_t pool_id; + uint32_t size; + uint32_t max_num; +} mmf_vb_pool_t; + +typedef struct { + int mmf_used_cnt; + bool vi_is_inited; + bool vi_chn_is_inited[MMF_VI_MAX_CHN]; + int vi_chn_pool_id[MMF_VI_MAX_CHN]; + SIZE_S vi_size; + VIDEO_FRAME_INFO_S vi_frame[MMF_VI_MAX_CHN]; + VB_CONFIG_S vb_conf; + + int ive_is_init; + IVE_HANDLE ive_handle; + IVE_IMAGE_S ive_rgb2yuv_rgb_img; + IVE_IMAGE_S ive_rgb2yuv_yuv_img; + int ive_rgb2yuv_w; + int ive_rgb2yuv_h; + + bool rgn_is_init[MMF_RGN_MAX_NUM]; + bool rgn_is_bind[MMF_RGN_MAX_NUM]; + RGN_TYPE_E rgn_type[MMF_RGN_MAX_NUM]; + int rgn_id[MMF_RGN_MAX_NUM]; + MOD_ID_E rgn_mod_id[MMF_RGN_MAX_NUM]; + CVI_S32 rgn_dev_id[MMF_RGN_MAX_NUM]; + CVI_S32 rgn_chn_id[MMF_RGN_MAX_NUM]; + uint8_t* rgn_canvas_data[MMF_RGN_MAX_NUM]; + int rgn_canvas_w[MMF_RGN_MAX_NUM]; + int rgn_canvas_h[MMF_RGN_MAX_NUM]; + int rgn_canvas_format[MMF_RGN_MAX_NUM]; + + int enc_jpg_is_init; + VENC_STREAM_S enc_jpeg_frame; + int enc_jpg_frame_w; + int enc_jpg_frame_h; + int enc_jpg_frame_fmt; + int enc_jpg_running; + int enc_jpg_quality; + VIDEO_FRAME_INFO_S *enc_jpg_frame; + int enc_jpg_input_pool_id; + int enc_jpg_output_pool_id; + + int vb_of_vi_is_config : 1; + int vb_of_private_is_config : 1; + int vb_size_of_vi; + int vb_count_of_vi; + int vb_size_of_private; + int vb_count_of_private; + + + SAMPLE_SNS_TYPE_E sensor_type; + + venc_info_t venc[MMF_VENC_MAX_CHN]; + uint8_t h265_or_h264_is_used; + + mmf_vb_pool_t vb_pool[VB_MAX_COMM_POOLS]; +} priv_t; + +typedef struct { + int enc_jpg_enable : 1; + bool vi_hmirror[MMF_VI_MAX_CHN]; + bool vi_vflip[MMF_VI_MAX_CHN]; +} g_priv_t; + +static priv_t priv; +static g_priv_t g_priv; + +#define MODULE_NAME "soph_vi" + +static int _is_module_in_use(const char *module_name) { + FILE *fp; + char buffer[256]; + + fp = fopen("/proc/modules", "r"); + if (fp == NULL) { + perror("fopen"); + return -1; + } + + while (fgets(buffer, sizeof(buffer), fp) != NULL) { + char mod_name[256]; + int usage_count; + + sscanf(buffer, "%255s %*s %d", mod_name, &usage_count); + + if (strcmp(mod_name, module_name) == 0) { + fclose(fp); + return usage_count > 0; + } + } + + fclose(fp); + return 0; +} + +static int reinit_soph_vb(void) +{ + printf("mmf insmod..\r\n"); + system("rmmod soph_ive soph_vc_driver soph_rgn soph_dwa soph_vpss soph_vi soph_snsr_i2c soph_mipi_rx soph_fast_image soph_rtos_cmdqu soph_base"); + // system("insmod /mnt/system/ko/soph_sys.ko"); + system("insmod /mnt/system/ko/soph_base.ko"); + system("insmod /mnt/system/ko/soph_rtos_cmdqu.ko"); + system("insmod /mnt/system/ko/soph_fast_image.ko"); + system("insmod /mnt/system/ko/soph_mipi_rx.ko"); + system("insmod /mnt/system/ko/soph_snsr_i2c.ko"); + system("insmod /mnt/system/ko/soph_vi.ko"); + system("insmod /mnt/system/ko/soph_vpss.ko"); + system("insmod /mnt/system/ko/soph_dwa.ko"); + system("insmod /mnt/system/ko/soph_rgn.ko"); + system("insmod /mnt/system/ko/soph_vc_driver.ko"); + system("insmod /mnt/system/ko/soph_ive.ko"); + + return 0; +} + +static int _get_vb_pool_cnt(void) +{ + FILE *file; + char line[1024]; + int poolIdCount = 0; + + file = fopen("/proc/cvitek/vb", "r"); + if (file == NULL) { + perror("can not open /proc/cvitek/vb"); + return 0; + } + + while (fgets(line, sizeof(line), file)) { + if (strstr(line, "PoolId(") != NULL) { + poolIdCount++; + } + } + + fclose(file); + return poolIdCount; +} + +static int _create_vb_pool(char *name, mmf_mod_type_t mod, uint32_t size, uint32_t max_num) +{ + uint32_t pool_id = -1; + VB_POOL_CONFIG_S stVbPoolCfg; + stVbPoolCfg.u32BlkCnt = max_num; + stVbPoolCfg.u32BlkSize = size; + stVbPoolCfg.enRemapMode = VB_REMAP_MODE_CACHED; + + if (max_num == 0 || size == 0) { + return -1; + } + + pool_id = CVI_VB_CreatePool(&stVbPoolCfg); + if (pool_id == VB_INVALID_POOLID || pool_id >= VB_MAX_COMM_POOLS) { + return -2; + } + + mmf_vb_pool_t *info = (mmf_vb_pool_t *)&priv.vb_pool[pool_id]; + info->pool_id = pool_id; + strncpy(info->name, name, sizeof(info->name)); + info->mod = mod; + info->size = size; + info->max_num = max_num; + info->is_used = 1; + + return pool_id; +} + +static int _destroy_vb_pool(uint32_t pool_id) +{ + CVI_S32 s32Ret; + mmf_vb_pool_t *info = (mmf_vb_pool_t *)&priv.vb_pool[pool_id]; + if (info->is_used) { + s32Ret = CVI_VB_DestroyPool(pool_id); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VB_DestroyPool : %d fail!\n", pool_id); + return -1; + } + memset(info, 0, sizeof(mmf_vb_pool_t)); + } + + return 0; +} + +__attribute__((unused)) static void _list_vb_pool(void) +{ + printf("====== VB POOL =======\r\n"); + for (int pool_id = 0; pool_id < VB_MAX_COMM_POOLS; pool_id ++) { + mmf_vb_pool_t *info = (mmf_vb_pool_t *)&priv.vb_pool[pool_id]; + if (info->is_used) { + printf("[%d] name:%s size:%d num:%d\r\n", pool_id, info->name, info->size, info->max_num); + } + } + printf("\r\n"); +} + +static SAMPLE_VI_CONFIG_S g_stViConfig; +static SAMPLE_INI_CFG_S g_stIniCfg; +static CVI_S32 _mmf_vpss_deinit_new(VPSS_GRP VpssGrp); + +static int _free_leak_memory_of_ion(void) +{ + #define MAX_LINE_LENGTH 256 + FILE *fp; + char line[MAX_LINE_LENGTH]; + char alloc_buf_size_str[20], phy_addr_str[20], buffer_name[20]; + int alloc_buf_size; + uint64_t phy_addr; + + fp = fopen("/sys/kernel/debug/ion/cvi_carveout_heap_dump/summary", "r"); + if (fp == NULL) { + fprintf(stderr, "Error opening file\n"); + return 1; + } + + while (fgets(line, MAX_LINE_LENGTH, fp) != NULL) { + if (sscanf(line, "%*d %s %s %*d %s", alloc_buf_size_str, phy_addr_str, buffer_name) == 3) { + printf("[ION] %s %s %s\r\n", alloc_buf_size_str, phy_addr_str, buffer_name); + // FIXME: release jpeg_ion + if (strcmp(buffer_name, "VI_DMA_BUF") + && strcmp(buffer_name, "ISP_SHARED_BUFFER_0")) + continue; + struct sys_ion_data_new ion_data = { + .cached = 1, + .dmabuf_fd = (uint32_t)-1, + }; + + alloc_buf_size = atoi(alloc_buf_size_str); + phy_addr = (unsigned int)strtol(phy_addr_str, NULL, 16); + + ion_data.size = alloc_buf_size; + ion_data.addr_p = phy_addr; + memset(ion_data.name, 0, sizeof(ion_data.name)); + strcpy((char *)ion_data.name, buffer_name); + + printf("alloc_buf_size(%s): %d, phy_addr(%s): %#lx, buffer_name: %s\n", + alloc_buf_size_str, alloc_buf_size, phy_addr_str, phy_addr, buffer_name); + + printf("ion_data.size:%d, ion_data.addr_p:%#x, ion_data.name:%s\r\n", ion_data.size, (int)ion_data.addr_p, ion_data.name); + + int res = ionFree(&ion_data); + if (res) { + printf("ionFree failed! res:%#x\r\n", res); + mmf_deinit(); + return -1; + } + + } + } + + fclose(fp); + + return 0; +} + +static int _free_leak_memory_of_vb(void) { + #define MAX_LINE_LENGTH 256 + FILE *fp; + char line[MAX_LINE_LENGTH]; + int pool_id = 0; + uint64_t phy_addr = 0; + int blk_cnt = 0; + int blk_size = 0; + int free_cnt = 0; + + fp = fopen("/proc/cvitek/vb", "r"); + if (fp == NULL) { + fprintf(stderr, "Error opening file\n"); + return 1; + } + + while (fgets(line, MAX_LINE_LENGTH, fp) != NULL) { + + if (strstr(line, "PoolId :")) { + sscanf(line, "%*s : %d", &pool_id); + } else if (strstr(line, "PhysAddr :")) { + sscanf(line, "%*s : %lx", &phy_addr); + } else if (strstr(line, "BlkSz :")) { + sscanf(line, "%*s : %d", &blk_size); + } else if (strstr(line, "BlkCnt : ")) { + sscanf(line, "%*s : %d", &blk_cnt); + } else if (strstr(line, "Free :")) { + sscanf(line, "%*s : %d", &free_cnt); + + CVI_SYS_Exit(); + CVI_VB_Exit(); + + if (free_cnt != blk_cnt) { + printf("relese PoolId: %d, PhysAddr: 0x%lx, BlkSize: %d, BlkCnt: %d Free: %d\n", + pool_id, phy_addr, blk_size, blk_cnt, free_cnt); + for (int i = 0; i < blk_cnt; i ++) { + uint64_t try_release_phy_addr = phy_addr + i * blk_size; + printf("try release poolid:%d phy:%#lx\r\n", pool_id, try_release_phy_addr); + VB_BLK blk = CVI_VB_PhysAddr2Handle(try_release_phy_addr); + if (0 != CVI_VB_ReleaseBlock(blk)) { + printf("release poolid:%d phy:%#lx failed!\r\n", pool_id, try_release_phy_addr); + } + } + } + } + } + + fclose(fp); + + return 0; +} + +static inline CVI_VOID VENC_GetPicBufferConfig2(CVI_U32 u32Width, CVI_U32 u32Height, + PIXEL_FORMAT_E enPixelFormat, DATA_BITWIDTH_E enBitWidth, COMPRESS_MODE_E enCmpMode, + VB_CAL_CONFIG_S *pstVbCfg) +{ + CVI_U32 u32AlignWidth = ALIGN(u32Width, VENC_ALIGN_W); + CVI_U32 u32AlignHeight = u32Height; + CVI_U32 u32Align = VENC_ALIGN_W; + + COMMON_GetPicBufferConfig(u32AlignWidth, u32AlignHeight, enPixelFormat, + enBitWidth, enCmpMode, u32Align, pstVbCfg); +} + +static VIDEO_FRAME_INFO_S *_mmf_alloc_frame(int id, SIZE_S stSize, PIXEL_FORMAT_E enPixelFormat) +{ + VIDEO_FRAME_INFO_S *pstVideoFrame; + VIDEO_FRAME_S *pstVFrame; + VB_BLK blk; + VB_CAL_CONFIG_S stVbCfg; + + pstVideoFrame = (VIDEO_FRAME_INFO_S *)calloc(sizeof(*pstVideoFrame), 1); + if (pstVideoFrame == NULL) { + SAMPLE_PRT("Failed to allocate VIDEO_FRAME_INFO_S\n"); + return NULL; + } + + memset(&stVbCfg, 0, sizeof(stVbCfg)); + VENC_GetPicBufferConfig2(stSize.u32Width, + stSize.u32Height, + enPixelFormat, + DATA_BITWIDTH_8, + COMPRESS_MODE_NONE, + &stVbCfg); + + pstVFrame = &pstVideoFrame->stVFrame; + + pstVFrame->enCompressMode = COMPRESS_MODE_NONE; + pstVFrame->enPixelFormat = enPixelFormat; + pstVFrame->enVideoFormat = VIDEO_FORMAT_LINEAR; + pstVFrame->enColorGamut = COLOR_GAMUT_BT709; + pstVFrame->u32Width = stSize.u32Width; + pstVFrame->u32Height = stSize.u32Height; + pstVFrame->u32TimeRef = 0; + pstVFrame->u64PTS = 0; + pstVFrame->enDynamicRange = DYNAMIC_RANGE_SDR8; + + blk = CVI_VB_GetBlock(id, stVbCfg.u32VBSize); + if (blk == VB_INVALID_HANDLE) { + SAMPLE_PRT("Can't acquire vb block. id: %d size:%d\n", id, stVbCfg.u32VBSize); + free(pstVideoFrame); + return NULL; + } + + pstVideoFrame->u32PoolId = CVI_VB_Handle2PoolId(blk); + pstVFrame->u64PhyAddr[0] = CVI_VB_Handle2PhysAddr(blk); + pstVFrame->u32Stride[0] = stVbCfg.u32MainStride; + pstVFrame->u32Length[0] = stVbCfg.u32MainYSize; + pstVFrame->pu8VirAddr[0] = (CVI_U8 *)CVI_SYS_MmapCache(pstVFrame->u64PhyAddr[0], stVbCfg.u32VBSize); + + if (stVbCfg.plane_num > 1) { + pstVFrame->u64PhyAddr[1] = ALIGN(pstVFrame->u64PhyAddr[0] + stVbCfg.u32MainYSize, stVbCfg.u16AddrAlign); + pstVFrame->u32Stride[1] = stVbCfg.u32CStride; + pstVFrame->u32Length[1] = stVbCfg.u32MainCSize; + pstVFrame->pu8VirAddr[1] = (CVI_U8 *)pstVFrame->pu8VirAddr[0] + pstVFrame->u32Length[0]; + } + + if (stVbCfg.plane_num > 2) { + pstVFrame->u64PhyAddr[2] = ALIGN(pstVFrame->u64PhyAddr[1] + stVbCfg.u32MainCSize, stVbCfg.u16AddrAlign); + pstVFrame->u32Stride[2] = stVbCfg.u32CStride; + pstVFrame->u32Length[2] = stVbCfg.u32MainCSize; + pstVFrame->pu8VirAddr[2] = (CVI_U8 *)pstVFrame->pu8VirAddr[1] + pstVFrame->u32Length[1]; + } + + // CVI_VENC_TRACE("phy addr(%#llx, %#llx, %#llx), Size %x\n", (long long)pstVFrame->u64PhyAddr[0] + // , (long long)pstVFrame->u64PhyAddr[1], (long long)pstVFrame->u64PhyAddr[2], stVbCfg.u32VBSize); + // CVI_VENC_TRACE("vir addr(%p, %p, %p), Size %x\n", pstVFrame->pu8VirAddr[0] + // , pstVFrame->pu8VirAddr[1], pstVFrame->pu8VirAddr[2], stVbCfg.u32MainSize); + + return pstVideoFrame; +} + +static CVI_S32 _mmf_free_frame(VIDEO_FRAME_INFO_S *pstVideoFrame) +{ + VIDEO_FRAME_S *pstVFrame = &pstVideoFrame->stVFrame; + VB_BLK blk; + + if (pstVFrame->pu8VirAddr[0]) + CVI_SYS_Munmap((CVI_VOID *)pstVFrame->pu8VirAddr[0], pstVFrame->u32Length[0]); + if (pstVFrame->pu8VirAddr[1]) + CVI_SYS_Munmap((CVI_VOID *)pstVFrame->pu8VirAddr[1], pstVFrame->u32Length[1]); + if (pstVFrame->pu8VirAddr[2]) + CVI_SYS_Munmap((CVI_VOID *)pstVFrame->pu8VirAddr[2], pstVFrame->u32Length[2]); + + blk = CVI_VB_PhysAddr2Handle(pstVFrame->u64PhyAddr[0]); + if (blk != VB_INVALID_HANDLE) { + CVI_VB_ReleaseBlock(blk); + } + + free(pstVideoFrame); + + return CVI_SUCCESS; +} + +static int cvi_ive_init(void) +{ + CVI_S32 s32Ret; + if (priv.ive_is_init) + return 0; + + priv.ive_rgb2yuv_w = 640; + priv.ive_rgb2yuv_h = 480; + priv.ive_handle = CVI_IVE_CreateHandle(); + if (priv.ive_handle == NULL) { + printf("CVI_IVE_CreateHandle failed!\n"); + return -1; + } + + s32Ret = CVI_IVE_CreateImage_Cached(priv.ive_handle, &priv.ive_rgb2yuv_rgb_img, IVE_IMAGE_TYPE_U8C3_PACKAGE, priv.ive_rgb2yuv_w, priv.ive_rgb2yuv_h); + if (s32Ret != CVI_SUCCESS) { + printf("Create src image failed!\n"); + CVI_IVE_DestroyHandle(priv.ive_handle); + return -1; + } + + s32Ret = CVI_IVE_CreateImage_Cached(priv.ive_handle, &priv.ive_rgb2yuv_yuv_img, IVE_IMAGE_TYPE_YUV420SP, priv.ive_rgb2yuv_w, priv.ive_rgb2yuv_h); + if (s32Ret != CVI_SUCCESS) { + printf("Create src image failed!\n"); + CVI_IVE_DestroyHandle(priv.ive_handle); + return -1; + } + + priv.ive_is_init = 1; + return 0; +} + +static int cvi_ive_deinit(void) +{ + if (priv.ive_is_init == 0) + return 0; + + CVI_SYS_FreeI(priv.ive_handle, &priv.ive_rgb2yuv_rgb_img); + CVI_SYS_FreeI(priv.ive_handle, &priv.ive_rgb2yuv_yuv_img); + CVI_IVE_DestroyHandle(priv.ive_handle); + + priv.ive_is_init = 0; + return 0; +} + +static int cvi_rgb2nv21(uint8_t *src, int input_w, int input_h) +{ + CVI_S32 s32Ret; + + int width = ALIGN(input_w, DEFAULT_ALIGN); + int height = input_h; + + if (width != priv.ive_rgb2yuv_w || height != priv.ive_rgb2yuv_h) { + CVI_SYS_FreeI(priv.ive_handle, &priv.ive_rgb2yuv_rgb_img); + CVI_SYS_FreeI(priv.ive_handle, &priv.ive_rgb2yuv_yuv_img); + priv.ive_rgb2yuv_w = width; + priv.ive_rgb2yuv_h = height; + printf("reinit rgb2nv21 buffer, buff w:%d h:%d\n", priv.ive_rgb2yuv_w, priv.ive_rgb2yuv_h); + s32Ret = CVI_IVE_CreateImage_Cached(priv.ive_handle, &priv.ive_rgb2yuv_rgb_img, IVE_IMAGE_TYPE_U8C3_PACKAGE, priv.ive_rgb2yuv_w, priv.ive_rgb2yuv_h); + if (s32Ret != CVI_SUCCESS) { + printf("Create src image failed!\n"); + return -1; + } + + s32Ret = CVI_IVE_CreateImage_Cached(priv.ive_handle, &priv.ive_rgb2yuv_yuv_img, IVE_IMAGE_TYPE_YUV420SP, priv.ive_rgb2yuv_w, priv.ive_rgb2yuv_h); + if (s32Ret != CVI_SUCCESS) { + printf("Create src image failed!\n"); + return -1; + } + } + + if (width != input_w) { + for (int h = 0; h < height; h++) { + memcpy((uint8_t *)priv.ive_rgb2yuv_rgb_img.u64VirAddr[0] + width * h * 3, (uint8_t *)src + input_w * h * 3, input_w * 3); + } + } else { + memcpy((uint8_t *)priv.ive_rgb2yuv_rgb_img.u64VirAddr[0], (uint8_t *)src, width * height * 3); + } + + IVE_CSC_CTRL_S stCtrl; + stCtrl.enMode = IVE_CSC_MODE_VIDEO_BT601_RGB2YUV; + s32Ret = CVI_IVE_CSC(priv.ive_handle, &priv.ive_rgb2yuv_rgb_img, &priv.ive_rgb2yuv_yuv_img, &stCtrl, 1); + if (s32Ret != CVI_SUCCESS) { + printf("Run HW IVE CSC YUV2RGB failed!\n"); + return -1; + } + return 0; +} + +static int _try_release_sys(void) +{ + CVI_S32 s32Ret = CVI_FAILURE; + SAMPLE_INI_CFG_S stIniCfg; + SAMPLE_VI_CONFIG_S stViConfig; + if (SAMPLE_COMM_VI_ParseIni(&stIniCfg)) { + SAMPLE_PRT("Parse complete\n"); + return s32Ret; + } + + priv.sensor_type = stIniCfg.enSnsType[0]; + + s32Ret = CVI_VI_SetDevNum(stIniCfg.devNum); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("VI_SetDevNum failed with %#x\n", s32Ret); + return s32Ret; + } + + s32Ret = SAMPLE_COMM_VI_IniToViCfg(&stIniCfg, &stViConfig); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("SAMPLE_COMM_VI_IniToViCfg failed with %#x\n", s32Ret); + return s32Ret; + } + + s32Ret = SAMPLE_COMM_VI_DestroyIsp(&stViConfig); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("SAMPLE_COMM_VI_DestroyIsp failed with %#x\n", s32Ret); + return s32Ret; + } + + s32Ret = SAMPLE_COMM_VI_DestroyVi(&stViConfig); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("SAMPLE_COMM_VI_DestroyVi failed with %#x\n", s32Ret); + return s32Ret; + } + + SAMPLE_COMM_SYS_Exit(); + return s32Ret; +} + +int _try_release_vi(void) +{ + CVI_S32 s32Ret = CVI_FAILURE; + s32Ret = mmf_del_vi_channel_all(); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("mmf_del_vi_channel_all failed with %#x\n", s32Ret); + return s32Ret; + } + return s32Ret; +} + +int _try_release_venc_all(void) +{ + for (int ch = 0; ch < VENC_MAX_CHN_NUM; ch ++) { + CVI_VENC_StopRecvFrame(ch); + CVI_VENC_ResetChn(ch); + CVI_VENC_DestroyChn(ch); + } + return 0; +} + +int _try_release_vpss_all(void) +{ + for (int ch = 0; ch < 4; ch ++) { + _mmf_vpss_deinit_new(ch); + } + return 0; +} + +static void _mmf_sys_exit(void) +{ + if (g_stViConfig.s32WorkingViNum != 0) { + SAMPLE_COMM_VI_DestroyIsp(&g_stViConfig); + SAMPLE_COMM_VI_DestroyVi(&g_stViConfig); + } + SAMPLE_COMM_SYS_Exit(); +} + +static CVI_S32 _mmf_sys_init(SIZE_S stSize) +{ + VB_CONFIG_S stVbConf; + CVI_U32 u32BlkSize, u32BlkRotSize; + CVI_S32 s32Ret = CVI_SUCCESS; + COMPRESS_MODE_E enCompressMode = COMPRESS_MODE_NONE; + + memset(&stVbConf, 0, sizeof(VB_CONFIG_S)); + memcpy(&stVbConf, &priv.vb_conf, sizeof(VB_CONFIG_S)); + + // vi + u32BlkSize = COMMON_GetPicBufferSize(stSize.u32Width, stSize.u32Height, PIXEL_FORMAT_UYVY, + DATA_BITWIDTH_8, enCompressMode, DEFAULT_ALIGN); + u32BlkRotSize = COMMON_GetPicBufferSize(stSize.u32Height, stSize.u32Width, PIXEL_FORMAT_UYVY, + DATA_BITWIDTH_8, enCompressMode, DEFAULT_ALIGN); + u32BlkSize = MAX(u32BlkSize, u32BlkRotSize); + stVbConf.astCommPool[MMF_VB_VI_ID].u32BlkSize = u32BlkSize; + stVbConf.astCommPool[MMF_VB_VI_ID].u32BlkCnt = 3; + stVbConf.astCommPool[MMF_VB_VI_ID].enRemapMode = VB_REMAP_MODE_CACHED; + stVbConf.u32MaxPoolCnt = 1; + + s32Ret = SAMPLE_COMM_SYS_Init(&stVbConf); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("system init failed with %#x\n", s32Ret); + goto error; + } + + return s32Ret; +error: + _mmf_sys_exit(); + return s32Ret; +} + +static CVI_S32 _mmf_vpss_deinit(VPSS_GRP VpssGrp, VPSS_CHN VpssChn) +{ + CVI_BOOL abChnEnable[VPSS_MAX_PHY_CHN_NUM] = {0}; + CVI_S32 s32Ret = CVI_SUCCESS; + + /*start vpss*/ + abChnEnable[VpssChn] = CVI_TRUE; + s32Ret = SAMPLE_COMM_VPSS_Stop(VpssGrp, abChnEnable); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("init vpss group failed. s32Ret: 0x%x !\n", s32Ret); + } + + return s32Ret; +} + +static CVI_S32 _mmf_vpss_deinit_new(VPSS_GRP VpssGrp) +{ + CVI_S32 s32Ret = CVI_SUCCESS; + + s32Ret = CVI_VPSS_StopGrp(VpssGrp); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("Vpss Stop Grp %d failed! Please check param\n", VpssGrp); + return CVI_FAILURE; + } + + s32Ret = CVI_VPSS_DestroyGrp(VpssGrp); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("Vpss Destroy Grp %d failed! Please check\n", VpssGrp); + return CVI_FAILURE; + } + + return s32Ret; +} + +// fit = 0, width to new width, height to new height, may be stretch +// fit = 1, keep aspect ratio, fill blank area with black color +// fit = other, keep aspect ratio, crop image to fit new size +static CVI_S32 _mmf_vpss_init(VPSS_GRP VpssGrp, VPSS_CHN VpssChn, SIZE_S stSizeIn, SIZE_S stSizeOut, PIXEL_FORMAT_E formatIn, PIXEL_FORMAT_E formatOut, +int fps, int depth, bool mirror, bool flip, int fit) +{ + VPSS_GRP_ATTR_S stVpssGrpAttr; + VPSS_CROP_INFO_S stGrpCropInfo; + CVI_BOOL abChnEnable[VPSS_MAX_PHY_CHN_NUM] = {0}; + VPSS_CHN_ATTR_S astVpssChnAttr[VPSS_MAX_PHY_CHN_NUM]; + CVI_S32 s32Ret = CVI_SUCCESS; + + memset(&stVpssGrpAttr, 0, sizeof(VPSS_GRP_ATTR_S)); + stVpssGrpAttr.stFrameRate.s32SrcFrameRate = -1; + stVpssGrpAttr.stFrameRate.s32DstFrameRate = -1; + stVpssGrpAttr.enPixelFormat = formatIn; + stVpssGrpAttr.u32MaxW = stSizeIn.u32Width; + stVpssGrpAttr.u32MaxH = stSizeIn.u32Height; + stVpssGrpAttr.u8VpssDev = 0; + + CVI_FLOAT corp_scale_w = (CVI_FLOAT)stSizeIn.u32Width / stSizeOut.u32Width; + CVI_FLOAT corp_scale_h = (CVI_FLOAT)stSizeIn.u32Height / stSizeOut.u32Height; + CVI_U32 crop_w = -1, crop_h = -1; + if (fit == 0) { + memset(astVpssChnAttr, 0, sizeof(VPSS_CHN_ATTR_S) * VPSS_MAX_PHY_CHN_NUM); + astVpssChnAttr[VpssChn].u32Width = stSizeOut.u32Width; + astVpssChnAttr[VpssChn].u32Height = stSizeOut.u32Height; + astVpssChnAttr[VpssChn].enVideoFormat = VIDEO_FORMAT_LINEAR; + astVpssChnAttr[VpssChn].enPixelFormat = formatOut; + astVpssChnAttr[VpssChn].stFrameRate.s32SrcFrameRate = fps; + astVpssChnAttr[VpssChn].stFrameRate.s32DstFrameRate = fps; + astVpssChnAttr[VpssChn].u32Depth = depth; + astVpssChnAttr[VpssChn].bMirror = mirror; + astVpssChnAttr[VpssChn].bFlip = flip; + astVpssChnAttr[VpssChn].stAspectRatio.enMode = ASPECT_RATIO_MANUAL; + astVpssChnAttr[VpssChn].stAspectRatio.stVideoRect.s32X = 0; + astVpssChnAttr[VpssChn].stAspectRatio.stVideoRect.s32Y = 0; + astVpssChnAttr[VpssChn].stAspectRatio.stVideoRect.u32Width = stSizeOut.u32Width; + astVpssChnAttr[VpssChn].stAspectRatio.stVideoRect.u32Height = stSizeOut.u32Height; + astVpssChnAttr[VpssChn].stAspectRatio.bEnableBgColor = CVI_TRUE; + astVpssChnAttr[VpssChn].stAspectRatio.u32BgColor = COLOR_RGB_BLACK; + astVpssChnAttr[VpssChn].stNormalize.bEnable = CVI_FALSE; + + stGrpCropInfo.bEnable = false; + } else if (fit == 1) { + memset(astVpssChnAttr, 0, sizeof(VPSS_CHN_ATTR_S) * VPSS_MAX_PHY_CHN_NUM); + astVpssChnAttr[VpssChn].u32Width = stSizeOut.u32Width; + astVpssChnAttr[VpssChn].u32Height = stSizeOut.u32Height; + astVpssChnAttr[VpssChn].enVideoFormat = VIDEO_FORMAT_LINEAR; + astVpssChnAttr[VpssChn].enPixelFormat = formatOut; + astVpssChnAttr[VpssChn].stFrameRate.s32SrcFrameRate = fps; + astVpssChnAttr[VpssChn].stFrameRate.s32DstFrameRate = fps; + astVpssChnAttr[VpssChn].u32Depth = depth; + astVpssChnAttr[VpssChn].bMirror = mirror; + astVpssChnAttr[VpssChn].bFlip = flip; + astVpssChnAttr[VpssChn].stAspectRatio.enMode = ASPECT_RATIO_AUTO; + astVpssChnAttr[VpssChn].stAspectRatio.bEnableBgColor = CVI_TRUE; + astVpssChnAttr[VpssChn].stAspectRatio.u32BgColor = COLOR_RGB_BLACK; + astVpssChnAttr[VpssChn].stNormalize.bEnable = CVI_FALSE; + + stGrpCropInfo.bEnable = false; + } else { + memset(astVpssChnAttr, 0, sizeof(VPSS_CHN_ATTR_S) * VPSS_MAX_PHY_CHN_NUM); + astVpssChnAttr[VpssChn].u32Width = stSizeOut.u32Width; + astVpssChnAttr[VpssChn].u32Height = stSizeOut.u32Height; + astVpssChnAttr[VpssChn].enVideoFormat = VIDEO_FORMAT_LINEAR; + astVpssChnAttr[VpssChn].enPixelFormat = formatOut; + astVpssChnAttr[VpssChn].stFrameRate.s32SrcFrameRate = fps; + astVpssChnAttr[VpssChn].stFrameRate.s32DstFrameRate = fps; + astVpssChnAttr[VpssChn].u32Depth = depth; + astVpssChnAttr[VpssChn].bMirror = mirror; + astVpssChnAttr[VpssChn].bFlip = flip; + astVpssChnAttr[VpssChn].stAspectRatio.enMode = ASPECT_RATIO_AUTO; + astVpssChnAttr[VpssChn].stAspectRatio.bEnableBgColor = CVI_TRUE; + astVpssChnAttr[VpssChn].stAspectRatio.u32BgColor = COLOR_RGB_BLACK; + astVpssChnAttr[VpssChn].stNormalize.bEnable = CVI_FALSE; + + crop_w = corp_scale_w < corp_scale_h ? stSizeOut.u32Width * corp_scale_w: stSizeOut.u32Width * corp_scale_h; + crop_h = corp_scale_w < corp_scale_h ? stSizeOut.u32Height * corp_scale_w: stSizeOut.u32Height * corp_scale_h; + if (corp_scale_h < 0 || corp_scale_w < 0) { + SAMPLE_PRT("crop scale error. corp_scale_w: %f, corp_scale_h: %f\n", corp_scale_w, corp_scale_h); + goto error; + } + + stGrpCropInfo.bEnable = true; + stGrpCropInfo.stCropRect.s32X = (stSizeIn.u32Width - crop_w) / 2; + stGrpCropInfo.stCropRect.s32Y = (stSizeIn.u32Height - crop_h) / 2; + stGrpCropInfo.stCropRect.u32Width = crop_w; + stGrpCropInfo.stCropRect.u32Height = crop_h; + } + + /*start vpss*/ + abChnEnable[0] = CVI_TRUE; + s32Ret = SAMPLE_COMM_VPSS_Init(VpssGrp, abChnEnable, &stVpssGrpAttr, astVpssChnAttr); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("init vpss group failed. s32Ret: 0x%x ! retry!!!\n", s32Ret); + s32Ret = SAMPLE_COMM_VPSS_Stop(VpssGrp, abChnEnable); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("stop vpss group failed. s32Ret: 0x%x !\n", s32Ret); + } + s32Ret = SAMPLE_COMM_VPSS_Init(VpssGrp, abChnEnable, &stVpssGrpAttr, astVpssChnAttr); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("retry to init vpss group failed. s32Ret: 0x%x !\n", s32Ret); + return s32Ret; + } else { + SAMPLE_PRT("retry to init vpss group ok!\n"); + } + } + + if (crop_w != 0 && crop_h != 0) { + s32Ret = CVI_VPSS_SetChnCrop(VpssGrp, VpssChn, &stGrpCropInfo); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("set vpss group crop failed. s32Ret: 0x%x !\n", s32Ret); + goto error; + } + } + + s32Ret = SAMPLE_COMM_VPSS_Start(VpssGrp, abChnEnable, &stVpssGrpAttr, astVpssChnAttr); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("start vpss group failed. s32Ret: 0x%x !\n", s32Ret); + goto error; + } + + return s32Ret; +error: + _mmf_vpss_deinit(VpssGrp, VpssChn); + return s32Ret; +} + +static CVI_S32 _mmf_init(void) +{ + MMF_VERSION_S stVersion; + SAMPLE_INI_CFG_S stIniCfg; + SAMPLE_VI_CONFIG_S stViConfig; + + PIC_SIZE_E enPicSize; + SIZE_S stSize; + CVI_S32 s32Ret = CVI_SUCCESS; + LOG_LEVEL_CONF_S log_conf; + + int old_pool_cnt = _get_vb_pool_cnt(); + if (old_pool_cnt > 0) { + if (_is_module_in_use("soph_vi") == 0) { + reinit_soph_vb(); + } else { + printf("You may have repeatedly initialized maix multi-media!"); + } + } + + CVI_SYS_GetVersion(&stVersion); + SAMPLE_PRT("maix multi-media version:%s\n", stVersion.version); + + log_conf.enModId = CVI_ID_LOG; + log_conf.s32Level = CVI_DBG_DEBUG; + CVI_LOG_SetLevelConf(&log_conf); + + // Get config from ini if found. + if (SAMPLE_COMM_VI_ParseIni(&stIniCfg)) { + SAMPLE_PRT("Parse complete\n"); + } + + //Set sensor number + CVI_VI_SetDevNum(stIniCfg.devNum); + + /************************************************ + * step1: Config VI + ************************************************/ + s32Ret = SAMPLE_COMM_VI_IniToViCfg(&stIniCfg, &stViConfig); + if (s32Ret != CVI_SUCCESS) + return s32Ret; + + memcpy(&g_stViConfig, &stViConfig, sizeof(SAMPLE_VI_CONFIG_S)); + memcpy(&g_stIniCfg, &stIniCfg, sizeof(SAMPLE_INI_CFG_S)); + + /************************************************ + * step2: Get input size + ************************************************/ + s32Ret = SAMPLE_COMM_VI_GetSizeBySensor(stIniCfg.enSnsType[0], &enPicSize); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("SAMPLE_COMM_VI_GetSizeBySensor failed with %#x\n", s32Ret); + return s32Ret; + } + + s32Ret = SAMPLE_COMM_SYS_GetPicSize(enPicSize, &stSize); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("SAMPLE_COMM_SYS_GetPicSize failed with %#x\n", s32Ret); + return s32Ret; + } + + /************************************************ + * step3: Init modules + ************************************************/ + if (0 != _free_leak_memory_of_ion()) { + SAMPLE_PRT("free leak ion memory error\n"); + } + + if (0 != _free_leak_memory_of_vb()) { + SAMPLE_PRT("free leak vb memory error\n"); + } + + s32Ret = _mmf_sys_init(stSize); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("sys init failed. s32Ret: 0x%x !\n", s32Ret); + goto _need_exit_sys_and_deinit_vi; + } + + s32Ret = SAMPLE_PLAT_VI_INIT(&stViConfig); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("vi init failed. s32Ret: 0x%x !\n", s32Ret); + SAMPLE_PRT("Please try to check if the camera is working.\n"); + goto _need_exit_sys_and_deinit_vi; + } + + priv.vi_size.u32Width = stSize.u32Width; + priv.vi_size.u32Height = stSize.u32Height; + + return s32Ret; + +_need_exit_sys_and_deinit_vi: + _mmf_sys_exit(); + + return s32Ret; +} + +static void _mmf_deinit(void) +{ + UNUSED(cvi_ive_deinit); + mmf_del_vi_channel_all(); + mmf_del_venc_channel_all(); + mmf_enc_jpg_deinit(0); + _try_release_venc_all(); + _try_release_vpss_all(); + mmf_vi_deinit(); + // mmf_del_region_channel_all(); // need not release + _mmf_sys_exit(); +} + +static int _vi_get_unused_ch() { + for (int i = 0; i < MMF_VI_MAX_CHN; i++) { + if (priv.vi_chn_is_inited[i] == false) { + return i; + } + } + return -1; +} + +int mmf_init(void) +{ + if (priv.mmf_used_cnt) { + priv.mmf_used_cnt ++; + // printf("maix multi-media already inited(cnt:%d)\n", priv.mmf_used_cnt); + return 0; + } + + if (_try_release_sys() != CVI_SUCCESS) { + printf("try release sys failed\n"); + return -1; + } else { + printf("try release sys ok\n"); + } + + if (_mmf_init() != CVI_SUCCESS) { + printf("maix multi-media init failed\n"); + return -1; + } else { + printf("maix multi-media init ok\n"); + } + + UNUSED(cvi_ive_init); + priv.mmf_used_cnt = 1; + + if (_try_release_vi() != CVI_SUCCESS) { + printf("try release vio failed\n"); + return -1; + } else { + printf("try release vio ok\n"); + } + + if (_try_release_venc_all() != CVI_SUCCESS) { + printf("try release venc failed\n"); + return -1; + } else { + printf("try release venc ok\n"); + } + + return 0; +} + +bool mmf_is_init(void) +{ + return priv.mmf_used_cnt > 0 ? true : false; +} + +int mmf_try_deinit(bool force) { + if (!priv.mmf_used_cnt) { + return 0; + } + + if (force) { + priv.mmf_used_cnt = 0; + printf("maix multi-media driver destroyed.\n"); + _mmf_deinit(); + } else { + priv.mmf_used_cnt --; + if (priv.mmf_used_cnt) { + return 0; + } else { + printf("maix multi-media driver destroyed.\n"); + _mmf_deinit(); + } + } + return 0; +} + +int mmf_deinit(void) { + return mmf_try_deinit(false); +} + +int mmf_get_vi_unused_channel(void) { + return _vi_get_unused_ch(); +} + +static CVI_S32 _mmf_vpss_chn_init(VPSS_GRP VpssGrp, VPSS_CHN VpssChn, int width, int height, PIXEL_FORMAT_E format, int fps, int depth, bool mirror, bool flip, int fit) +{ +#if 1 + VPSS_GRP_ATTR_S stGrpAttr; + VPSS_CROP_INFO_S stChnCropInfo; + VPSS_CHN_ATTR_S chn_attr = {0}; + CVI_S32 s32Ret = CVI_SUCCESS; + + s32Ret = CVI_VPSS_GetGrpAttr(VpssGrp, &stGrpAttr); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_GetGrpAttr failed. s32Ret: 0x%x !\n", s32Ret); + return s32Ret; + } + CVI_FLOAT corp_scale_w = (CVI_FLOAT)stGrpAttr.u32MaxW / width; + CVI_FLOAT corp_scale_h = (CVI_FLOAT)stGrpAttr.u32MaxH / height; + CVI_U32 crop_w = -1, crop_h = -1; + if (fit == 0) { + chn_attr.u32Width = width; + chn_attr.u32Height = height; + chn_attr.enVideoFormat = VIDEO_FORMAT_LINEAR; + chn_attr.enPixelFormat = format; + chn_attr.stFrameRate.s32SrcFrameRate = fps; + chn_attr.stFrameRate.s32DstFrameRate = fps; + chn_attr.u32Depth = depth; + chn_attr.bMirror = mirror; + chn_attr.bFlip = flip; + chn_attr.stAspectRatio.enMode = ASPECT_RATIO_MANUAL; + chn_attr.stAspectRatio.stVideoRect.s32X = 0; + chn_attr.stAspectRatio.stVideoRect.s32Y = 0; + chn_attr.stAspectRatio.stVideoRect.u32Width = width; + chn_attr.stAspectRatio.stVideoRect.u32Height = height; + chn_attr.stAspectRatio.bEnableBgColor = CVI_TRUE; + chn_attr.stAspectRatio.u32BgColor = COLOR_RGB_BLACK; + chn_attr.stNormalize.bEnable = CVI_FALSE; + + stChnCropInfo.bEnable = false; + } else if (fit == 1) { + chn_attr.u32Width = width; + chn_attr.u32Height = height; + chn_attr.enVideoFormat = VIDEO_FORMAT_LINEAR; + chn_attr.enPixelFormat = format; + chn_attr.stFrameRate.s32SrcFrameRate = fps; + chn_attr.stFrameRate.s32DstFrameRate = fps; + chn_attr.u32Depth = depth; + chn_attr.bMirror = mirror; + chn_attr.bFlip = flip; + chn_attr.stAspectRatio.enMode = ASPECT_RATIO_AUTO; + chn_attr.stAspectRatio.bEnableBgColor = CVI_TRUE; + chn_attr.stAspectRatio.u32BgColor = COLOR_RGB_BLACK; + chn_attr.stNormalize.bEnable = CVI_FALSE; + + stChnCropInfo.bEnable = false; + } else { + chn_attr.u32Width = width; + chn_attr.u32Height = height; + chn_attr.enVideoFormat = VIDEO_FORMAT_LINEAR; + chn_attr.enPixelFormat = format; + chn_attr.stFrameRate.s32SrcFrameRate = fps; + chn_attr.stFrameRate.s32DstFrameRate = fps; + chn_attr.u32Depth = depth; + chn_attr.bMirror = mirror; + chn_attr.bFlip = flip; + chn_attr.stAspectRatio.enMode = ASPECT_RATIO_AUTO; + chn_attr.stAspectRatio.bEnableBgColor = CVI_TRUE; + chn_attr.stAspectRatio.u32BgColor = COLOR_RGB_BLACK; + chn_attr.stNormalize.bEnable = CVI_FALSE; + + crop_w = corp_scale_w < corp_scale_h ? width * corp_scale_w: width * corp_scale_h; + crop_h = corp_scale_w < corp_scale_h ? height * corp_scale_w: height * corp_scale_h; + if (corp_scale_h < 0 || corp_scale_w < 0) { + SAMPLE_PRT("crop scale error. corp_scale_w: %f, corp_scale_h: %f\n", corp_scale_w, corp_scale_h); + return -1; + } + + stChnCropInfo.bEnable = true; + stChnCropInfo.stCropRect.s32X = (stGrpAttr.u32MaxW - crop_w) / 2; + stChnCropInfo.stCropRect.s32Y = (stGrpAttr.u32MaxH - crop_h) / 2; + stChnCropInfo.stCropRect.u32Width = crop_w; + stChnCropInfo.stCropRect.u32Height = crop_h; + } + + if (crop_w != 0 && crop_h != 0) { + s32Ret = CVI_VPSS_SetChnCrop(VpssGrp, VpssChn, &stChnCropInfo); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("set vpss group crop failed. s32Ret: 0x%x !\n", s32Ret); + return -1; + } + } + + s32Ret = CVI_VPSS_SetChnAttr(VpssGrp, VpssChn, &chn_attr); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_SetChnAttr failed with %#x\n", s32Ret); + return CVI_FAILURE; + } + + s32Ret = CVI_VPSS_EnableChn(VpssGrp, VpssChn); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_EnableChn failed with %#x\n", s32Ret); + return CVI_FAILURE; + } + + return s32Ret; +#else + CVI_S32 s32Ret; + VPSS_CHN_ATTR_S chn_attr = {0}; + chn_attr.u32Width = width; + chn_attr.u32Height = height; + chn_attr.enVideoFormat = VIDEO_FORMAT_LINEAR; + chn_attr.enPixelFormat = format; + chn_attr.stFrameRate.s32SrcFrameRate = fps; + chn_attr.stFrameRate.s32DstFrameRate = fps; + chn_attr.u32Depth = depth; + chn_attr.bMirror = mirror; + chn_attr.bFlip = flip; + chn_attr.stAspectRatio.enMode = ASPECT_RATIO_MANUAL; + chn_attr.stAspectRatio.stVideoRect.s32X = 0; + chn_attr.stAspectRatio.stVideoRect.s32Y = 0; + chn_attr.stAspectRatio.stVideoRect.u32Width = width; + chn_attr.stAspectRatio.stVideoRect.u32Height = height; + chn_attr.stAspectRatio.bEnableBgColor = CVI_TRUE; + chn_attr.stAspectRatio.u32BgColor = COLOR_RGB_BLACK; + chn_attr.stNormalize.bEnable = CVI_FALSE; + + s32Ret = CVI_VPSS_SetChnAttr(VpssGrp, VpssChn, &chn_attr); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_SetChnAttr failed with %#x\n", s32Ret); + return CVI_FAILURE; + } + + s32Ret = CVI_VPSS_EnableChn(VpssGrp, VpssChn); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_EnableChn failed with %#x\n", s32Ret); + return CVI_FAILURE; + } + + return CVI_SUCCESS; +#endif +} + +static CVI_S32 _mmf_vpss_chn_deinit(VPSS_GRP VpssGrp, VPSS_CHN VpssChn) +{ + return CVI_VPSS_DisableChn(VpssGrp, VpssChn); +} + +static CVI_S32 _mmf_vpss_init_new(VPSS_GRP VpssGrp, CVI_U32 width, CVI_U32 height, PIXEL_FORMAT_E format) +{ + VPSS_GRP_ATTR_S stVpssGrpAttr; + CVI_S32 s32Ret = CVI_SUCCESS; + + memset(&stVpssGrpAttr, 0, sizeof(VPSS_GRP_ATTR_S)); + stVpssGrpAttr.stFrameRate.s32SrcFrameRate = -1; + stVpssGrpAttr.stFrameRate.s32DstFrameRate = -1; + stVpssGrpAttr.enPixelFormat = format; + stVpssGrpAttr.u32MaxW = width; + stVpssGrpAttr.u32MaxH = height; + stVpssGrpAttr.u8VpssDev = 0; + + s32Ret = CVI_VPSS_CreateGrp(VpssGrp, &stVpssGrpAttr); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_CreateGrp(grp:%d) retry(%#x)!\n", VpssGrp, s32Ret); + CVI_VPSS_DestroyGrp(VpssGrp); + + s32Ret = CVI_VPSS_CreateGrp(VpssGrp, &stVpssGrpAttr); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_CreateGrp(grp:%d) failed with %#x!\n", VpssGrp, s32Ret); + return CVI_FAILURE; + } + } + + s32Ret = CVI_VPSS_ResetGrp(VpssGrp); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_ResetGrp(grp:%d) failed with %#x!%d\n", VpssGrp, s32Ret, CVI_ERR_VPSS_ILLEGAL_PARAM); + return CVI_FAILURE; + } + + s32Ret = CVI_VPSS_StartGrp(VpssGrp); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_StartGrp failed with %#x\n", s32Ret); + return CVI_FAILURE; + } + return s32Ret; +} + +int mmf_vi_init(void) +{ + if (priv.vi_is_inited) { + return 0; + } + + CVI_S32 s32Ret = CVI_SUCCESS; + s32Ret = _mmf_vpss_init_new(0, priv.vi_size.u32Width, priv.vi_size.u32Height, PIXEL_FORMAT_UYVY); // PIXEL_FORMAT_UYVY PIXEL_FORMAT_NV21 + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("_mmf_vpss_init_new failed. s32Ret: 0x%x !\n", s32Ret); + } + + priv.vi_is_inited = true; + + return s32Ret; +} + +int mmf_vi_deinit(void) +{ + if (!priv.vi_is_inited) { + return 0; + } + + CVI_S32 s32Ret = CVI_SUCCESS; + s32Ret = _mmf_vpss_deinit_new(0); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("_mmf_vpss_deinit_new failed with %#x!\n", s32Ret); + return CVI_FAILURE; + } + + priv.vi_is_inited = false; + + return s32Ret; +} + +static int _mmf_add_vi_channel(int ch, int width, int height, int format) { + uint32_t pool_size_out = 0; + int pool_id = -1; + + if (!priv.mmf_used_cnt) { + printf("%s: maix multi-media or vi not inited\n", __func__); + return -1; + } + + if (!priv.vi_is_inited) { + if (0 != mmf_vi_init()) { + printf("mmf_vi_init failed!\r\n"); + return -1; + } + } + + if (width <= 0 || height <= 0) { + printf("invalid width or height\n"); + return -1; + } + + if (format != PIXEL_FORMAT_NV21 + && format != PIXEL_FORMAT_RGB_888) { + printf("invalid format\n"); + return -1; + } + + // if ((format == PIXEL_FORMAT_RGB_888 && width * height * 3 > 640 * 640 * 3) + // || (format == PIXEL_FORMAT_RGB_888 && width * height * 3 / 2 > 2560 * 1440 * 3 / 2)) { + // printf("camera size is too large, for NV21, maximum resolution 2560x1440, for RGB888, maximum resolution 640x640!\n"); + // return -1; + // } + + if (mmf_vi_chn_is_open(ch)) { + printf("vi ch %d already open\n", ch); + return -1; + } + + CVI_S32 s32Ret = CVI_SUCCESS; + int fps = 30; + int depth = 2; + int width_out = ALIGN(width, DEFAULT_ALIGN); + int height_out = height; + PIXEL_FORMAT_E format_out = (PIXEL_FORMAT_E)format; + bool mirror = !g_priv.vi_hmirror[ch]; + bool flip = !g_priv.vi_vflip[ch]; + s32Ret = _mmf_vpss_chn_deinit(0, ch); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("_mmf_vpss_chn_deinit failed with %#x!\n", s32Ret); + return CVI_FAILURE; + } + + s32Ret = _mmf_vpss_chn_init(0, ch, width_out, height_out, format_out, fps, depth, mirror, flip, 2); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("_mmf_vpss_chn_init failed with %#x!\n", s32Ret); + return CVI_FAILURE; + } + + s32Ret = SAMPLE_COMM_VI_Bind_VPSS(0, ch, 0); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("vi bind vpss failed. s32Ret: 0x%x !\n", s32Ret); + goto _need_deinit_vpss_chn; + } + + char name[20]; + snprintf(name, 20, "vi_vpss%.1d", ch); + pool_size_out = COMMON_GetPicBufferSize(width_out, height_out, format_out, DATA_BITWIDTH_8, COMPRESS_MODE_NONE, DEFAULT_ALIGN); + pool_id = _create_vb_pool(name, MMF_MOD_VI, pool_size_out, 2); + if (pool_id < 0) { + printf("[%s][%d]_create_vb_pool failed, id %d\n", __func__, __LINE__, pool_id); + goto _need_deinit_vpss_chn; + } + + s32Ret = CVI_VPSS_AttachVbPool(0, ch, pool_id); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_AttachVbPool failed. s32Ret: 0x%x !\n", s32Ret); + goto _need_deinit_vpss_chn; + } + + // VIDEO_FRAME_INFO_S frame; + // if ((s32Ret = CVI_VPSS_GetChnFrame(0, ch, &frame, 3000)) != CVI_SUCCESS) { + // SAMPLE_PRT("vi get frame failed: 0x%x !\n", s32Ret); + // if ((s32Ret = SAMPLE_COMM_VI_UnBind_VPSS(0, ch, 0)) != CVI_SUCCESS) { + // SAMPLE_PRT("vi unbind vpss failed. s32Ret: 0x%x !\n", s32Ret); + // } + // goto _need_deinit_vpss_chn; + // } + // CVI_VPSS_ReleaseChnFrame(0, ch, &frame); + + priv.vi_chn_pool_id[ch] = pool_id; + priv.vi_chn_is_inited[ch] = true; + + return 0; +_need_deinit_vpss_chn: + _mmf_vpss_chn_deinit(0, ch); + return -1; +} + +int mmf_add_vi_channel(int ch, int width, int height, int format) { + printf("mmf_add_vi_channel..\r\n"); + return _mmf_add_vi_channel(ch, width, height, format); +} + +int mmf_del_vi_channel(int ch) { + if (ch < 0 || ch >= MMF_VI_MAX_CHN) { + printf("[%d] invalid ch %d\n", __LINE__, ch); + return -1; + } + + if (priv.vi_chn_is_inited[ch] == false) { + return 0; + } + + CVI_S32 s32Ret = CVI_SUCCESS; + s32Ret = SAMPLE_COMM_VI_UnBind_VPSS(0, ch, 0); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("vi unbind vpss failed. s32Ret: 0x%x !\n", s32Ret); + // return -1; // continue to deinit vpss + } + + if (0 != _mmf_vpss_chn_deinit(0, ch)) { + SAMPLE_PRT("_mmf_vpss_chn_deinit failed. s32Ret: 0x%x !\n", s32Ret); + } + + _destroy_vb_pool(priv.vi_chn_pool_id[ch]); + + priv.vi_chn_pool_id[ch] = -1; + priv.vi_chn_is_inited[ch] = false; + return s32Ret; +} + +int mmf_del_vi_channel_all() { + for (int i = 0; i < MMF_VI_MAX_CHN; i++) { + if (priv.vi_chn_is_inited[i] == true) { + mmf_del_vi_channel(i); + } + } + return 0; +} + +bool mmf_vi_chn_is_open(int ch) { + if (ch < 0 || ch >= MMF_VI_MAX_CHN) { + return false; + } + + return priv.vi_chn_is_inited[ch]; +} + +int mmf_reset_vi_channel(int ch, int width, int height, int format) +{ + mmf_del_vi_channel(ch); + return mmf_add_vi_channel(ch, width, height, format); +} + +int mmf_vi_aligned_width(int ch) { + UNUSED(ch); + return DEFAULT_ALIGN; +} + +int mmf_vi_frame_pop(int ch, void **data, int *len, int *width, int *height, int *format) { + if (!priv.vi_chn_is_inited[ch]) { + // printf("vi ch %d not open\n", ch); + return -1; + } + if (ch < 0 || ch >= MMF_VI_MAX_CHN) { + printf("[%d] invalid ch %d\n", __LINE__, ch); + return -1; + } + if (data == NULL || len == NULL || width == NULL || height == NULL || format == NULL) { + printf("invalid param\n"); + return -1; + } + + int ret = -1; + VIDEO_FRAME_INFO_S *frame = &priv.vi_frame[ch]; + if (CVI_VPSS_GetChnFrame(0, ch, frame, 1000) == 0) { + int image_size = frame->stVFrame.u32Length[0] + + frame->stVFrame.u32Length[1] + + frame->stVFrame.u32Length[2]; + CVI_VOID *vir_addr; + vir_addr = CVI_SYS_MmapCache(frame->stVFrame.u64PhyAddr[0], image_size); + CVI_SYS_IonInvalidateCache(frame->stVFrame.u64PhyAddr[0], vir_addr, image_size); + + frame->stVFrame.pu8VirAddr[0] = (CVI_U8 *)vir_addr; // save virtual address for munmap + // printf("width: %d, height: %d, total_buf_length: %d, phy:%#lx vir:%p\n", + // frame->stVFrame.u32Width, + // frame->stVFrame.u32Height, image_size, + // frame->stVFrame.u64PhyAddr[0], vir_addr); + + *data = vir_addr; + *len = image_size; + *width = frame->stVFrame.u32Width; + *height = frame->stVFrame.u32Height; + *format = frame->stVFrame.enPixelFormat; + return 0; + } + return ret; +} + +void mmf_vi_frame_free(int ch) { + VIDEO_FRAME_INFO_S *frame = &priv.vi_frame[ch]; + int image_size = frame->stVFrame.u32Length[0] + + frame->stVFrame.u32Length[1] + + frame->stVFrame.u32Length[2]; + CVI_SYS_Munmap(frame->stVFrame.pu8VirAddr[0], image_size); + if (CVI_VPSS_ReleaseChnFrame(0, ch, frame) != 0) { + SAMPLE_PRT("CVI_VI_ReleaseChnFrame NG\n"); + } +} + +int mmf_region_frame_push(int ch, void *data, int len) +{ + CVI_S32 s32Ret; + RGN_CANVAS_INFO_S stCanvasInfo; + + if (ch < 0 || ch >= MMF_RGN_MAX_NUM) { + SAMPLE_PRT("Handle ch is illegal %d!\n", ch); + return CVI_FAILURE; + } + + if (!priv.rgn_is_init[ch]) { + return 0; + } + + if (!priv.rgn_is_bind[ch]) { + return 0; + } + + s32Ret = CVI_RGN_GetCanvasInfo(ch, &stCanvasInfo); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_RGN_GetCanvasInfo failed with %#x!\n", s32Ret); + return CVI_FAILURE; + } + + if (stCanvasInfo.enPixelFormat == PIXEL_FORMAT_ARGB_8888) { + if (!data || (CVI_U32)len != stCanvasInfo.stSize.u32Width * stCanvasInfo.stSize.u32Height * 4) { + printf("Param is error!\r\n"); + return CVI_FAILURE; + } + memcpy(stCanvasInfo.pu8VirtAddr, data, len); + } else { + printf("Not support format!\r\n"); + return CVI_FAILURE; + } + + s32Ret = CVI_RGN_UpdateCanvas(ch); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_RGN_UpdateCanvas failed with %#x!\n", s32Ret); + return CVI_FAILURE; + } + return s32Ret; +} + +int mmf_enc_jpg_init(int ch, int w, int h, int format, int quality) +{ + if (priv.enc_jpg_is_init) + return 0; + + if (quality <= 50) { + printf("quality range is (50, 100]\n"); + return -1; + } + + if (mmf_init()) { + return -1; + } + + // if ((format == PIXEL_FORMAT_RGB_888 && w * h * 3 > 640 * 640 * 3) + // || (format == PIXEL_FORMAT_RGB_888 && w * h * 3 / 2 > 2560 * 1440 * 3 / 2)) { + // printf("image size is too large, for NV21, maximum resolution 2560x1440, for RGB888, maximum resolution 640x640!\n"); + // return -1; + // } + + CVI_S32 s32Ret = CVI_SUCCESS; + + VENC_CHN_ATTR_S stVencChnAttr; + memset(&stVencChnAttr, 0, sizeof(VENC_CHN_ATTR_S)); + stVencChnAttr.stVencAttr.enType = PT_JPEG; + stVencChnAttr.stVencAttr.u32MaxPicWidth = w; + stVencChnAttr.stVencAttr.u32MaxPicHeight = h; + stVencChnAttr.stVencAttr.u32PicWidth = w; + stVencChnAttr.stVencAttr.u32PicHeight = h; + stVencChnAttr.stVencAttr.bEsBufQueueEn = CVI_FALSE; + stVencChnAttr.stVencAttr.bIsoSendFrmEn = CVI_FALSE; + stVencChnAttr.stVencAttr.bByFrame = 1; + stVencChnAttr.stRcAttr.enRcMode = VENC_RC_MODE_MJPEGFIXQP; + + s32Ret = CVI_VENC_CreateChn(ch, &stVencChnAttr); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_CreateChn [%d] failed with %#x\n", ch, s32Ret); + return s32Ret; + } + + s32Ret = CVI_VENC_ResetChn(ch); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_CreateChn [%d] failed with %#x\n", ch, s32Ret); + return s32Ret; + } + + s32Ret = CVI_VENC_DestroyChn(ch); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_destroyChn [%d] failed with %#x\n", ch, s32Ret); + } + + s32Ret = CVI_VENC_CreateChn(ch, &stVencChnAttr); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_CreateChn [%d] failed with %#x\n", ch, s32Ret); + return s32Ret; + } + + VENC_JPEG_PARAM_S stJpegParam; + memset(&stJpegParam, 0, sizeof(VENC_JPEG_PARAM_S)); + s32Ret = CVI_VENC_GetJpegParam(ch, &stJpegParam); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_GetJpegParam failed with %#x\n", s32Ret); + CVI_VENC_DestroyChn(ch); + return s32Ret; + } + stJpegParam.u32Qfactor = quality; + s32Ret = CVI_VENC_SetJpegParam(ch, &stJpegParam); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_SetJpegParam failed with %#x\n", s32Ret); + CVI_VENC_DestroyChn(ch); + return s32Ret; + } + + switch (format) { + case PIXEL_FORMAT_RGB_888: + { + s32Ret = _mmf_vpss_init(2, ch, (SIZE_S){(CVI_U32)w, (CVI_U32)h}, (SIZE_S){(CVI_U32)w, (CVI_U32)h}, PIXEL_FORMAT_RGB_888, PIXEL_FORMAT_YUV_PLANAR_420, 30, 0, CVI_FALSE, CVI_FALSE, 0); + if (s32Ret != CVI_SUCCESS) { + printf("VPSS init failed with %#x\n", s32Ret); + CVI_VENC_StopRecvFrame(ch); + CVI_VENC_DestroyChn(ch); + return s32Ret; + } + + s32Ret = SAMPLE_COMM_VPSS_Bind_VENC(2, ch, ch); + if (s32Ret != CVI_SUCCESS) { + printf("VPSS bind VENC failed with %#x\n", s32Ret); + _mmf_vpss_deinit(2, ch); + CVI_VENC_StopRecvFrame(ch); + CVI_VENC_DestroyChn(ch); + return s32Ret; + } + + uint32_t input_size = 0, output_size = 0; + input_size = COMMON_GetPicBufferSize(w, h, (PIXEL_FORMAT_E)format, DATA_BITWIDTH_8, COMPRESS_MODE_NONE, DEFAULT_ALIGN); + output_size = COMMON_GetPicBufferSize(w, h, (PIXEL_FORMAT_E)PIXEL_FORMAT_YUV_PLANAR_420, DATA_BITWIDTH_8, COMPRESS_MODE_NONE, DEFAULT_ALIGN); + int pool_id = _create_vb_pool((char *)"enc_jpeg_in", MMF_MOD_VENC, input_size, 1); + if (pool_id < 0) { + printf("[%s][%d]_create_vb_pool failed, id %d\n", __func__, __LINE__, pool_id); + return -1; + } + priv.enc_jpg_input_pool_id = pool_id; + + pool_id = _create_vb_pool((char *)"enc_jpeg_out", MMF_MOD_VENC, output_size, 1); + if (pool_id < 0) { + printf("[%s][%d]_create_vb_pool failed, id %d\n", __func__, __LINE__, pool_id); + return -1; + } + priv.enc_jpg_output_pool_id = pool_id; + + s32Ret = CVI_VPSS_AttachVbPool(2, ch, priv.enc_jpg_output_pool_id); + if (s32Ret != CVI_SUCCESS) { + SAMPLE_PRT("CVI_VPSS_AttachVbPool failed. s32Ret: 0x%x !\n", s32Ret); + _destroy_vb_pool(priv.enc_jpg_output_pool_id); + _destroy_vb_pool(priv.enc_jpg_input_pool_id); + return CVI_FAILURE; + } + } + break; + case PIXEL_FORMAT_NV21: + { + uint32_t size = COMMON_GetPicBufferSize(w, h, (PIXEL_FORMAT_E)format, DATA_BITWIDTH_8, COMPRESS_MODE_NONE, DEFAULT_ALIGN); + int pool_id = _create_vb_pool((char *)"enc_jpeg_in", MMF_MOD_VENC, size, 1); + if (pool_id < 0) { + printf("[%s][%d]_create_vb_pool failed, id %d\n", __func__, __LINE__, pool_id); + return -1; + } + priv.enc_jpg_input_pool_id = pool_id; + } + break; + default: + printf("unknown format!\n"); + CVI_VENC_StopRecvFrame(ch); + CVI_VENC_DestroyChn(ch); + return -1; + } + + VENC_RECV_PIC_PARAM_S stRecvParam; + stRecvParam.s32RecvPicNum = -1; + s32Ret = CVI_VENC_StartRecvFrame(ch, &stRecvParam); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_StartRecvPic failed with %#x\n", s32Ret); + return CVI_FAILURE; + } + + if (priv.enc_jpg_frame) { + _mmf_free_frame(priv.enc_jpg_frame); + priv.enc_jpg_frame = NULL; + } + + priv.enc_jpg_frame_w = w; + priv.enc_jpg_frame_h = h; + priv.enc_jpg_frame_fmt = format; + priv.enc_jpg_quality = quality; + priv.enc_jpg_is_init = 1; + priv.enc_jpg_running = 0; + + return s32Ret; +} + +int mmf_enc_jpg_deinit(int ch) +{ + if (!priv.enc_jpg_is_init) + return 0; + + CVI_S32 s32Ret = CVI_SUCCESS; + + if (!mmf_enc_jpg_pop(ch, NULL, NULL)) { + mmf_enc_jpg_free(ch); + } + + switch (priv.enc_jpg_frame_fmt) { + case PIXEL_FORMAT_RGB_888: + s32Ret = SAMPLE_COMM_VPSS_UnBind_VENC(2, ch, ch); + if (s32Ret != CVI_SUCCESS) { + printf("VPSS unbind VENC failed with %d\n", s32Ret); + } + + s32Ret = _mmf_vpss_deinit(2, ch); + if (s32Ret != CVI_SUCCESS) { + printf("VPSS deinit failed with %d\n", s32Ret); + } + break; + case PIXEL_FORMAT_NV21: + break; + default: + break; + } + + s32Ret = CVI_VENC_StopRecvFrame(ch); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_StopRecvPic failed with %d\n", s32Ret); + } + + s32Ret = CVI_VENC_ResetChn(ch); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_ResetChn vechn[%d] failed with %#x!\n", + ch, s32Ret); + } + + s32Ret = CVI_VENC_DestroyChn(ch); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_DestroyChn [%d] failed with %d\n", ch, s32Ret); + } + + if (priv.enc_jpg_frame) { + _mmf_free_frame(priv.enc_jpg_frame); + priv.enc_jpg_frame = NULL; + } + + switch (priv.enc_jpg_frame_fmt) { + case PIXEL_FORMAT_RGB_888: + _destroy_vb_pool(priv.enc_jpg_output_pool_id); + priv.enc_jpg_output_pool_id = -1; + _destroy_vb_pool(priv.enc_jpg_input_pool_id); + priv.enc_jpg_output_pool_id = -1; + break; + case PIXEL_FORMAT_NV21: + _destroy_vb_pool(priv.enc_jpg_input_pool_id); + priv.enc_jpg_output_pool_id = -1; + break; + default: + break; + } + + if (mmf_deinit()) { + return -1; + } + + priv.enc_jpg_frame_w = 0; + priv.enc_jpg_frame_h = 0; + priv.enc_jpg_frame_fmt = 0; + priv.enc_jpg_quality = -1; + priv.enc_jpg_is_init = 0; + priv.enc_jpg_running = 0; + + return s32Ret; +} + +int mmf_enc_jpg_push_with_quality(int ch, uint8_t *data, int w, int h, int format, int quality) +{ + UNUSED(ch); + CVI_S32 s32Ret = CVI_SUCCESS; + if (priv.enc_jpg_running) { + return s32Ret; + } + + int real_format = format; + if (format == PIXEL_FORMAT_UINT8_C1) { + format = PIXEL_FORMAT_NV21; + } + + SIZE_S stSize = {(CVI_U32)w, (CVI_U32)h}; + if (priv.enc_jpg_frame == NULL || priv.enc_jpg_frame_w != w || priv.enc_jpg_frame_h != h || priv.enc_jpg_frame_fmt != format + || priv.enc_jpg_quality != quality) { + mmf_enc_jpg_deinit(ch); + mmf_enc_jpg_init(ch, w, h, format, quality); + priv.enc_jpg_frame_w = w; + priv.enc_jpg_frame_h = h; + priv.enc_jpg_frame_fmt = format; + priv.enc_jpg_quality = quality; + if (priv.enc_jpg_frame) { + _mmf_free_frame(priv.enc_jpg_frame); + priv.enc_jpg_frame = NULL; + } + priv.enc_jpg_frame = (VIDEO_FRAME_INFO_S *)_mmf_alloc_frame(priv.enc_jpg_input_pool_id, stSize, (PIXEL_FORMAT_E)format); + if (!priv.enc_jpg_frame) { + printf("Alloc frame failed!\r\n"); + return -1; + } + } + + switch (real_format) { + case PIXEL_FORMAT_UINT8_C1: + if (priv.enc_jpg_frame->stVFrame.u32Stride[0] != (CVI_U32)w) { + for (int height = 0; height < h; height ++) { + memcpy((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * height, + ((uint8_t *)data) + w * height, w); + } + + for (int height = priv.enc_jpg_frame->stVFrame.u32Height; height < h / 2; height ++) { + memset((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * height, + 128, w); + } + } else { + memcpy(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], ((uint8_t *)data), w * h); + memset(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + w * h, 128, w * h / 2); + } + CVI_SYS_IonFlushCache(priv.enc_jpg_frame->stVFrame.u64PhyAddr[0], + priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], + priv.enc_jpg_frame->stVFrame.u32Stride[0] * h * 3 / 2); + s32Ret = CVI_VENC_SendFrame(ch, priv.enc_jpg_frame, 1000); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_SendFrame failed with %d\n", s32Ret); + return s32Ret; + } + break; + case PIXEL_FORMAT_RGB_888: + { + if (priv.enc_jpg_frame->stVFrame.u32Stride[0] != (CVI_U32)w * 3) { + for (CVI_U32 h = 0; h < priv.enc_jpg_frame->stVFrame.u32Height; h++) { + memcpy((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * h, ((uint8_t *)data) + w * h * 3, w * 3); + } + } else { + memcpy(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], data, w * h * 3); + } + + s32Ret = CVI_VPSS_SendFrame(2, priv.enc_jpg_frame, 1000); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VPSS_SendFrame failed with %#x\n", s32Ret); + return s32Ret; + } + } + break; + case PIXEL_FORMAT_NV21: + if (priv.enc_jpg_frame->stVFrame.u32Stride[0] != (CVI_U32)w) { + for (CVI_U32 h = 0; h < priv.enc_jpg_frame->stVFrame.u32Height * 3 / 2; h ++) { + memcpy((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * h, + ((uint8_t *)data) + w * h, w); + } + } else { + memcpy(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], ((uint8_t *)data), w * h * 3 / 2); + } + + s32Ret = CVI_VENC_SendFrame(ch, priv.enc_jpg_frame, 1000); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_SendFrame failed with %d\n", s32Ret); + return s32Ret; + } + break; + default: return -1; + } + + priv.enc_jpg_running = 1; + + return s32Ret; +} + +int mmf_enc_jpg_push(int ch, uint8_t *data, int w, int h, int format) +{ + UNUSED(ch); + CVI_S32 s32Ret = CVI_SUCCESS; + if (priv.enc_jpg_running) { + return s32Ret; + } + + SIZE_S stSize = {(CVI_U32)w, (CVI_U32)h}; + if (priv.enc_jpg_frame == NULL || priv.enc_jpg_frame_w != w || priv.enc_jpg_frame_h != h || priv.enc_jpg_frame_fmt != format) { + mmf_enc_jpg_deinit(ch); + mmf_enc_jpg_init(ch, w, h, format, 80); + priv.enc_jpg_frame_w = w; + priv.enc_jpg_frame_h = h; + priv.enc_jpg_frame_fmt = format; + if (priv.enc_jpg_frame) { + _mmf_free_frame(priv.enc_jpg_frame); + priv.enc_jpg_frame = NULL; + } + priv.enc_jpg_frame = (VIDEO_FRAME_INFO_S *)_mmf_alloc_frame(priv.enc_jpg_input_pool_id, stSize, (PIXEL_FORMAT_E)format); + if (!priv.enc_jpg_frame) { + printf("Alloc frame failed!\r\n"); + return -1; + } + } + + switch (format) { + case PIXEL_FORMAT_RGB_888: + { + if (priv.enc_jpg_frame->stVFrame.u32Stride[0] != (CVI_U32)w * 3) { + for (CVI_U32 h = 0; h < priv.enc_jpg_frame->stVFrame.u32Height; h++) { + memcpy((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * h, ((uint8_t *)data) + w * h * 3, w * 3); + } + } else { + memcpy(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], data, w * h * 3); + } + + s32Ret = CVI_VPSS_SendFrame(2, priv.enc_jpg_frame, 1000); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VPSS_SendFrame failed with %#x\n", s32Ret); + return s32Ret; + } + } + break; + case PIXEL_FORMAT_NV21: + if (priv.enc_jpg_frame->stVFrame.u32Stride[0] != (CVI_U32)w) { + for (CVI_U32 h = 0; h < priv.enc_jpg_frame->stVFrame.u32Height * 3 / 2; h ++) { + memcpy((uint8_t *)priv.enc_jpg_frame->stVFrame.pu8VirAddr[0] + priv.enc_jpg_frame->stVFrame.u32Stride[0] * h, + ((uint8_t *)data) + w * h, w); + } + } else { + memcpy(priv.enc_jpg_frame->stVFrame.pu8VirAddr[0], ((uint8_t *)data), w * h * 3 / 2); + } + + s32Ret = CVI_VENC_SendFrame(ch, priv.enc_jpg_frame, 1000); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_SendFrame failed with %d\n", s32Ret); + return s32Ret; + } + break; + default: return -1; + } + + priv.enc_jpg_running = 1; + + return s32Ret; +} + +int mmf_enc_jpg_pop(int ch, uint8_t **data, int *size) +{ + CVI_S32 s32Ret = CVI_SUCCESS; + if (!priv.enc_jpg_running) { + return s32Ret; + } + + priv.enc_jpeg_frame.pstPack = (VENC_PACK_S *)malloc(sizeof(VENC_PACK_S) * 1); + if (!priv.enc_jpeg_frame.pstPack) { + printf("Malloc failed!\r\n"); + return -1; + } + + VENC_CHN_STATUS_S stStatus; + s32Ret = CVI_VENC_QueryStatus(ch, &stStatus); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_QueryStatus failed with %#x\n", s32Ret); + return s32Ret; + } + + if (stStatus.u32CurPacks > 0) { + s32Ret = CVI_VENC_GetStream(ch, &priv.enc_jpeg_frame, 1000); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_GetStream failed with %#x\n", s32Ret); + return s32Ret; + } + } else { + printf("CVI_VENC_QueryStatus find not pack\r\n"); + return -1; + } + + if (data) + *data = priv.enc_jpeg_frame.pstPack[0].pu8Addr; + if (size) + *size = priv.enc_jpeg_frame.pstPack[0].u32Len; + + return s32Ret; +} + +int mmf_enc_jpg_free(int ch) +{ + CVI_S32 s32Ret = CVI_SUCCESS; + if (!priv.enc_jpg_running) { + return s32Ret; + } + + s32Ret = CVI_VENC_ReleaseStream(ch, &priv.enc_jpeg_frame); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_ReleaseStream failed with %#x\n", s32Ret); + return s32Ret; + } + + if (priv.enc_jpeg_frame.pstPack) { + free(priv.enc_jpeg_frame.pstPack); + priv.enc_jpeg_frame.pstPack = NULL; + } + + priv.enc_jpg_running = 0; + return s32Ret; +} + +int mmf_invert_format_to_mmf(int maix_format) { + switch (maix_format) { + case 0: + return PIXEL_FORMAT_RGB_888; + case 1: + return PIXEL_FORMAT_BGR_888; + case 3: + return PIXEL_FORMAT_ARGB_8888; + case 8: + return PIXEL_FORMAT_NV21; + case 12: + return PIXEL_FORMAT_UINT8_C1; + default: + return -1; + } +} + +void mmf_set_vi_hmirror(int ch, bool en) +{ + if (ch > MMF_VI_MAX_CHN) { + printf("invalid ch, must be [0, %d)\r\n", ch); + return; + } + + g_priv.vi_hmirror[ch] = en; +} + +void mmf_get_vi_hmirror(int ch, bool *en) +{ + if (ch > MMF_VI_MAX_CHN) { + printf("invalid ch, must be [0, %d)\r\n", ch); + return; + } + + *en = (bool)g_priv.vi_hmirror[ch]; +} + +void mmf_set_vi_vflip(int ch, bool en) +{ + if (ch > MMF_VI_MAX_CHN) { + printf("invalid ch, must be [0, %d)\r\n", ch); + return; + } + + g_priv.vi_vflip[ch] = en; +} + +void mmf_get_vi_vflip(int ch, bool *en) +{ + if (ch > MMF_VI_MAX_CHN) { + printf("invalid ch, must be [0, %d)\r\n", ch); + return; + } + + *en = (bool)g_priv.vi_vflip[ch]; +} + +int mmf_add_venc_channel(int ch, mmf_venc_cfg_t *cfg) { + CVI_S32 s32Ret = CVI_SUCCESS; + if (ch >= MMF_VENC_MAX_CHN || priv.venc[ch].is_used) { + printf("Invalid venc ch:%d\r\n", ch); + return -1; + } + + switch (cfg->type) { + case 2: + { + VENC_CHN_ATTR_S stVencChnAttr; + memset(&stVencChnAttr, 0, sizeof(VENC_CHN_ATTR_S)); + stVencChnAttr.stVencAttr.enType = PT_H264; + stVencChnAttr.stVencAttr.u32MaxPicWidth = cfg->w; + stVencChnAttr.stVencAttr.u32MaxPicHeight = cfg->h; + stVencChnAttr.stVencAttr.u32BufSize = 1024 * 1024; // 1024Kb + stVencChnAttr.stVencAttr.bByFrame = CVI_TRUE; + stVencChnAttr.stVencAttr.u32PicWidth = cfg->w; + stVencChnAttr.stVencAttr.u32PicHeight = cfg->h; + stVencChnAttr.stVencAttr.bEsBufQueueEn = CVI_TRUE; + stVencChnAttr.stVencAttr.bIsoSendFrmEn = CVI_TRUE; + stVencChnAttr.stGopAttr.enGopMode = VENC_GOPMODE_NORMALP; + stVencChnAttr.stGopAttr.stNormalP.s32IPQpDelta = 2; + stVencChnAttr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR; + stVencChnAttr.stRcAttr.stH264Cbr.u32Gop = cfg->gop; + stVencChnAttr.stRcAttr.stH264Cbr.u32StatTime = 2; + stVencChnAttr.stRcAttr.stH264Cbr.u32SrcFrameRate = cfg->intput_fps; + stVencChnAttr.stRcAttr.stH264Cbr.fr32DstFrameRate = cfg->output_fps; + stVencChnAttr.stRcAttr.stH264Cbr.u32BitRate = cfg->bitrate; + stVencChnAttr.stRcAttr.stH264Cbr.bVariFpsEn = 0; + s32Ret = CVI_VENC_CreateChn(ch, &stVencChnAttr); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_CreateChn [%d] failed with %d\n", ch, s32Ret); + return s32Ret; + } + + VENC_RECV_PIC_PARAM_S stRecvParam; + stRecvParam.s32RecvPicNum = -1; + s32Ret = CVI_VENC_StartRecvFrame(ch, &stRecvParam); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_StartRecvPic failed with %d\n", s32Ret); + return CVI_FAILURE; + } + + VENC_RC_PARAM_S stRcParam; + s32Ret = CVI_VENC_GetRcParam(ch, &stRcParam); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_GetRcParam failed with %d\n", s32Ret); + return s32Ret; + } + stRcParam.s32FirstFrameStartQp = 35; + stRcParam.s32InitialDelay = 1000; + stRcParam.stParamH264Cbr.u32MinIprop = 1; + stRcParam.stParamH264Cbr.u32MaxIprop = 10; + stRcParam.stParamH264Cbr.u32MaxQp = 51; + stRcParam.stParamH264Cbr.u32MinQp = 20; + stRcParam.stParamH264Cbr.u32MaxIQp = 51; + stRcParam.stParamH264Cbr.u32MinIQp = 20; + s32Ret = CVI_VENC_SetRcParam(ch, &stRcParam); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_SetRcParam failed with %d\n", s32Ret); + return s32Ret; + } + + VENC_FRAMELOST_S stFL; + s32Ret = CVI_VENC_GetFrameLostStrategy(ch, &stFL); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_GetFrameLostStrategy failed with %d\n", s32Ret); + return s32Ret; + } + stFL.enFrmLostMode = FRMLOST_PSKIP; + s32Ret = CVI_VENC_SetFrameLostStrategy(ch, &stFL); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_SetFrameLostStrategy failed with %d\n", s32Ret); + return s32Ret; + } + + break; + } + default: printf("Only support h264 encode! type:%d\r\n", cfg->type); + return -1; + } + + char name[20]; + snprintf(name, 20, "venc%.1d", ch); + uint32_t size = VDEC_GetPicBufferSize((PAYLOAD_TYPE_E)cfg->type, cfg->w, cfg->h, (PIXEL_FORMAT_E)cfg->fmt, DATA_BITWIDTH_8, COMPRESS_MODE_NONE); + int pool_id = _create_vb_pool(name, MMF_MOD_VENC, size, 1); + if (pool_id < 0) { + printf("[%s][%d]_create_vb_pool failed, id %d\n", __func__, __LINE__, pool_id); + return CVI_FAILURE; + } + + venc_info_t *info = (venc_info_t *)&priv.venc[ch]; + info->ch = ch; + info->type = cfg->type; + info->pool_id = pool_id; + memcpy(&info->cfg, cfg, sizeof(mmf_venc_cfg_t)); + info->capture_frame = (VIDEO_FRAME_INFO_S *)_mmf_alloc_frame(info->pool_id, (SIZE_S){(CVI_U32)cfg->w, (CVI_U32)cfg->h}, (PIXEL_FORMAT_E)cfg->fmt); + if (!info->capture_frame) { + printf("Alloc frame failed!\r\n"); + CVI_VENC_DestroyChn(ch); + _destroy_vb_pool(pool_id); + return -1; + } + info->is_used = 1; + info->is_inited = 1; + priv.h265_or_h264_is_used = 1; + + return 0; +} + +int mmf_del_venc_channel(int ch) { + if (!priv.venc[ch].is_inited) { + return 0; + } + + mmf_stream_t stream; + if (!mmf_venc_pop(ch, &stream)) { + mmf_venc_free(ch); + } + + CVI_S32 s32Ret = CVI_SUCCESS; + s32Ret = CVI_VENC_StopRecvFrame(ch); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_StopRecvPic failed with %d\n", s32Ret); + } + + s32Ret = CVI_VENC_ResetChn(ch); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_ResetChn vechn[%d] failed with %#x!\n", ch, s32Ret); + } + + s32Ret = CVI_VENC_DestroyChn(ch); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_DestroyChn [%d] failed with %d\n", ch, s32Ret); + } + + if (priv.venc[ch].capture_frame) { + _mmf_free_frame(priv.venc[ch].capture_frame); + priv.venc[ch].capture_frame = NULL; + } + + _destroy_vb_pool(priv.venc[ch].pool_id); + + if (priv.venc[ch].type == 2 || priv.venc[ch].type == 1) { + priv.h265_or_h264_is_used = 0; + } + priv.venc[ch].is_inited = 0; + priv.venc[ch].is_used = 0; + + return 0; +} + +int mmf_del_venc_channel_all() { + for (int i = 0; i < MMF_VENC_MAX_CHN; i ++) { + mmf_del_venc_channel(i); + } + return 0; +} + +int mmf_venc_push(int ch, uint8_t *data, int w, int h, int format) { + int res = 0; + CVI_S32 s32Ret = CVI_SUCCESS; + if (ch >= MMF_VENC_MAX_CHN || data == NULL + || (format != PIXEL_FORMAT_NV21 && format != PIXEL_FORMAT_RGB_888)) { + printf("Invalid param. ch:%d data:%p format:%d\r\n", ch, data, format); + return -1; + } + + venc_info_t *info = (venc_info_t *)&priv.venc[ch]; + VIDEO_FRAME_INFO_S *frame_info = (VIDEO_FRAME_INFO_S *)info->capture_frame; + if (frame_info == NULL) { + printf("frame info is null!\r\n"); + return -1; + } + + switch (format) { + case PIXEL_FORMAT_NV21: + { + if (frame_info->stVFrame.u32Stride[0] != (CVI_U32)w) { + for (int h0 = 0; h0 < h * 3 / 2; h0 ++) { + memcpy((uint8_t *)frame_info->stVFrame.pu8VirAddr[0] + frame_info->stVFrame.u32Stride[0] * h, + ((uint8_t *)data) + w * h0, w); + } + } else { + memcpy((uint8_t *)frame_info->stVFrame.pu8VirAddr[0], ((uint8_t *)data), w * h * 3 / 2); + } + } + break; + default: + printf("Not support format:%d\r\n", format); + return -1; + } + + s32Ret = CVI_VENC_SendFrame(ch, frame_info, 1000); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_SendFrame failed with %#x\n", s32Ret); + return s32Ret; + } + + info->is_running = 1; + + return res; +} + +int mmf_venc_pop(int ch, mmf_stream_t *stream) { + CVI_S32 s32Ret = CVI_SUCCESS; + if (ch >= MMF_VENC_MAX_CHN || !priv.venc[ch].is_inited) { + printf("Invalid venc ch:%d\r\n", ch); + return -1; + } + + venc_info_t *info = (venc_info_t *)&priv.venc[ch]; + VENC_STREAM_S *venc_stream = (VENC_STREAM_S *)&priv.venc[ch].capture_stream; + if (!info->is_running) { + return s32Ret; + } + + int fd = CVI_VENC_GetFd(ch); + if (fd < 0) { + printf("CVI_VENC_GetFd failed with %d\n", fd); + return -1; + } + + fd_set readFds; + struct timeval timeoutVal; + FD_ZERO(&readFds); + FD_SET(fd, &readFds); + timeoutVal.tv_sec = 0; + timeoutVal.tv_usec = 80*1000; + s32Ret = select(fd + 1, &readFds, NULL, NULL, &timeoutVal); + if (s32Ret < 0) { + if (errno == EINTR) { + printf("VencChn(%d) select failed!\n", ch); + return -1; + } + } else if (s32Ret == 0) { + printf("VencChn(%d) select timeout!\n", ch); + return -1; + } + + venc_stream->pstPack = (VENC_PACK_S *)malloc(sizeof(VENC_PACK_S) * 8); + if (!venc_stream->pstPack) { + printf("Malloc failed!\r\n"); + return -1; + } + + + VENC_CHN_STATUS_S stStatus; + s32Ret = CVI_VENC_QueryStatus(ch, &stStatus); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_QueryStatus failed with %#x\n", s32Ret); + return s32Ret; + } + + if (stStatus.u32CurPacks > 0) { + s32Ret = CVI_VENC_GetStream(ch, venc_stream, 1000); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_GetStream failed with %#x\n", s32Ret); + free(venc_stream->pstPack); + return s32Ret; + } + } else { + printf("CVI_VENC_QueryStatus find not pack\r\n"); + free(venc_stream->pstPack); + return -1; + } + + if (stream) { + stream->count = venc_stream->u32PackCount; + if (stream->count > 8) { + printf("pack count is too large! cnt:%d\r\n", stream->count); + free(venc_stream->pstPack); + return -1; + } + for (int i = 0; i < stream->count; i++) { + stream->data[i] = venc_stream->pstPack[i].pu8Addr + venc_stream->pstPack[i].u32Offset; + stream->data_size[i] = venc_stream->pstPack[i].u32Len - venc_stream->pstPack[i].u32Offset; + } + } + + return 0; +} + +int mmf_venc_free(int ch) { + CVI_S32 s32Ret = CVI_SUCCESS; + if (ch >= MMF_VENC_MAX_CHN || !priv.venc[ch].is_inited) { + printf("Invalid venc ch:%d\r\n", ch); + return -1; + } + + venc_info_t *info = (venc_info_t *)&priv.venc[ch]; + VENC_STREAM_S *venc_stream = (VENC_STREAM_S *)&priv.venc[ch].capture_stream; + if (!info->is_running) { + return s32Ret; + } + + s32Ret = CVI_VENC_ReleaseStream(ch, venc_stream); + if (s32Ret != CVI_SUCCESS) { + printf("CVI_VENC_ReleaseStream failed with %#x\n", s32Ret); + return s32Ret; + } + + if (venc_stream->pstPack) { + free(venc_stream->pstPack); + venc_stream->pstPack = NULL; + } + + info->is_running = 0; + return s32Ret; +} + + + + \ No newline at end of file diff --git a/support/sg2002/additional/peripheral/port/maixcam/maix_i2c.cpp b/support/sg2002/additional/peripheral/port/maixcam/maix_i2c.cpp new file mode 100644 index 0000000..8737746 --- /dev/null +++ b/support/sg2002/additional/peripheral/port/maixcam/maix_i2c.cpp @@ -0,0 +1,322 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + + +#include "maix_i2c.hpp" +#include "maix_basic.hpp" +#include "cstdio" +#include +#include +#include +#include +#include + +#define DEV_PATH "/dev/i2c-%d" + +namespace maix::peripheral::i2c +{ + + std::vector list_devices() + { + std::vector data; + // list all files in /dev, find i2c-* + std::vector *files = fs::listdir("/dev"); + for (auto &file : *files) + { + if (file.find("i2c-") != std::string::npos) + { + try + { + data.push_back(std::stoi(file.substr(4))); + } + catch (const std::exception &e) + { + log::error("i2c bus id %s format error", file.c_str()); + } + } + } + delete files; + return data; + } + + I2C::I2C(int id, i2c::Mode mode, int freq, i2c::AddrSize addr_size) + { + char buf[32]; + + snprintf(buf, sizeof(buf), DEV_PATH, id); + if(access(buf, F_OK) == 0){ + if (mode == i2c::SLAVE) + { + throw err::Exception(err::Err::ERR_NOT_IMPL, "i2c::SLAVE mode not implemented"); + } + if (addr_size != i2c::SEVEN_BIT) + { + throw err::Exception(err::Err::ERR_NOT_IMPL, "addr_size " + std::to_string(addr_size) + " not support"); + } + + int fd = ::open(buf, O_RDWR); + if (fd < 0) + { + throw err::Exception(err::Err::ERR_IO, "open " + std::string(buf) + " failed"); + } + _fd = fd; + _freq = freq; + _mode = mode; + _addr_size = addr_size; + } else { + printf("not exit i2c\r\n"); + } + } + + I2C::~I2C() + { + if (_fd > 0) + { + ::close(_fd); + } + } + + std::vector I2C::scan(int addr) + { + std::vector data; + int addr_start = 0x08; + int addr_end = 0x77; + if(addr > 0) + { + addr_start = addr; + addr_end = addr; + } + + if (_mode != i2c::Mode::MASTER) + { + log::error("Only for master mode"); + return data; + } + + switch (_addr_size) + { + case i2c::AddrSize::SEVEN_BIT: + for (int address = addr_start; address <= addr_end; ++address) + { + if (::ioctl(_fd, I2C_SLAVE, address) < 0) + { + continue; + } + + unsigned char buffer[1]; + if (::read(_fd, buffer, sizeof(buffer)) >= 0) + { + data.push_back(address); + } + } + break; + default: + log::error("bit %d not support", _addr_size); + return data; + } + + return data; + } + + int I2C::writeto(int addr, const uint8_t *data, int len) + { + if (_mode != i2c::Mode::MASTER) + { + log::error("Only for master mode"); + return (int)-err::Err::ERR_NOT_PERMIT; + } + + if (0 != ioctl(_fd, I2C_SLAVE, addr)) + { + // log::error("set slave address failed"); + return (int)-err::Err::ERR_IO; + } + + if (len != ::write(_fd, data, len)) + { + log::error("write failed"); + return (int)-err::Err::ERR_IO; + } + + return len; + } + + int I2C::writeto(int addr, const std::vector data) + { + return writeto(addr, data.data(), (int)data.size()); + } + + int I2C::writeto(int addr, const Bytes &data) + { + return writeto(addr, data.data, (int)data.size()); + } + + Bytes* I2C::readfrom(int addr, int len) + { + Bytes *data = new Bytes(nullptr, len); + + if (_mode != i2c::Mode::MASTER) + { + log::error("Only for master mode"); + return nullptr; + } + + if (0 != ioctl(_fd, I2C_SLAVE, addr)) + { + // log::error("set slave address failed"); + return nullptr; + } + + if (len != ::read(_fd, data->data, len)) + { + log::error("read failed"); + delete data; + return nullptr; + } + return data; + } + + int I2C::writeto_mem(int addr, int mem_addr, const uint8_t *data, int len, int mem_addr_size, bool mem_addr_le) + { + if (_mode != i2c::Mode::MASTER) + { + log::error("Only for master mode"); + return (int)-err::Err::ERR_NOT_PERMIT; + } + if(mem_addr_size % 8 != 0) + { + log::error("mem_addr_size must be multiple of 8"); + return (int)-err::Err::ERR_IO; + } + + if (0 != ioctl(_fd, I2C_SLAVE, addr)) + { + // log::error("set slave address failed"); + return (int)-err::Err::ERR_IO; + } + + std::vector data_final; + + if(mem_addr_size == 8) + { + data_final.push_back((unsigned char)mem_addr); + } + else + { + if(mem_addr_le) + { + for(int i = 0; i < mem_addr_size / 8; i++) + { + data_final.push_back((unsigned char)(mem_addr & 0xff)); + mem_addr >>= 8; + } + } + else + { + for(int i = 0; i < mem_addr_size / 8; i++) + { + data_final.push_back((unsigned char)(mem_addr >> (8 * (mem_addr_size / 8 - i - 1)))); + } + } + } + for(int i = 0; i < len; i++) + { + data_final.push_back(data[i]); + } + int write_len = ::write(_fd, data_final.data(), data_final.size()); + if (data_final.size() != (size_t)write_len) + { + log::error("write failed, write_len: %d", write_len); + return (int)-err::Err::ERR_IO; + } + + return len; + } + + int I2C::writeto_mem(int addr, int mem_addr, const std::vector data, int mem_addr_size, bool mem_addr_le) + { + return writeto_mem(addr, mem_addr, data.data(), (int)data.size(), mem_addr_size, mem_addr_le); + } + + int I2C::writeto_mem(int addr, int mem_addr, const Bytes &data, int mem_addr_size, bool mem_addr_le) + { + return writeto_mem(addr, mem_addr, data.data, (int)data.size(), mem_addr_size, mem_addr_le); + } + + Bytes* I2C::readfrom_mem(int addr, int mem_addr, int len, int mem_addr_size, bool mem_addr_le) + { + // write mem_addr and restart to read + if (_mode != i2c::Mode::MASTER) + { + log::error("Only for master mode"); + return nullptr; + } + if(mem_addr_size % 8 != 0) + { + log::error("mem_addr_size must be multiple of 8"); + return nullptr; + } + + // write mem_addr first and restart to read + if (0 != ioctl(_fd, I2C_SLAVE, addr)) + { + // log::error("set slave address failed"); + return nullptr; + } + + std::vector data_final; + if(mem_addr_size == 8) + { + data_final.push_back((unsigned char)mem_addr); + } + else + { + if(mem_addr_le) + { + for(int i = 0; i < mem_addr_size / 8; i++) + { + data_final.push_back((unsigned char)(mem_addr & 0xff)); + mem_addr >>= 8; + } + } + else + { + + for(int i = 0; i < mem_addr_size / 8; i++) + { + data_final.push_back((unsigned char)(mem_addr >> (8 * (mem_addr_size / 8 - i - 1)))); + } + } + } + // write read with I2C_RDWR + struct i2c_msg msgs[2]; + msgs[0].addr = addr; + msgs[0].flags = 0; + msgs[0].len = data_final.size(); + msgs[0].buf = data_final.data(); + + Bytes *data = new Bytes(nullptr, len); + msgs[1].addr = addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = data->data; + + struct i2c_rdwr_ioctl_data msgset; + msgset.msgs = msgs; + msgset.nmsgs = 2; + + int read_len = ioctl(_fd, I2C_RDWR, &msgset); + if (read_len != 2) + { + log::error("read failed"); + delete data; + return nullptr; + } + + return data; + } +} diff --git a/support/sg2002/additional/sophgo-middleware/v2/sample/common/sample_common_sensor.c b/support/sg2002/additional/sophgo-middleware/v2/sample/common/sample_common_sensor.c new file mode 100644 index 0000000..0044ad2 --- /dev/null +++ b/support/sg2002/additional/sophgo-middleware/v2/sample/common/sample_common_sensor.c @@ -0,0 +1,2228 @@ +/* + * Copyright (C) Cvitek Co., Ltd. 2019-2020. All rights reserved. + * + * File Name: sample/common/sample_common_sensor.c + * Description: + * Common sample code for sensor configure. + */ + +#include +#include +#include +#include +#include "cvi_mipi.h" +#include "cvi_sns_ctrl.h" +#include +#include +#include "cvi_comm_isp.h" +#include "sample_comm.h" +#include "ini.h" + +#define INI_FILE_PATH "/mnt/data/sensor_cfg.ini" +#define INI_DEF_PATH "/mnt/system/usr/bin/sensor_cfg.ini" +#define SNSCFGPATH_SIZE 100 + +static CVI_CHAR g_snsCfgPath[SNSCFGPATH_SIZE]; + +ISP_PUB_ATTR_S ISP_PUB_ATTR_SAMPLE = { { 0, 0, 1920, 1080 }, { 1920, 1080 }, 30, BAYER_RGGB, WDR_MODE_NONE, 0}; + +static SAMPLE_INI_CFG_S stDefIniCfg = { + .enSource = VI_PIPE_FRAME_SOURCE_DEV, + .devNum = 1, + .enSnsType[0] = SONY_IMX327_MIPI_2M_30FPS_12BIT, + .enWDRMode[0] = WDR_MODE_NONE, + .s32BusId[0] = 3, + .s32SnsI2cAddr[0] = -1, + .s32SnsI2cAddr[1] = -1, + .MipiDev[0] = 0xFF, + .MipiDev[1] = 0xFF, + .MipiDev[2] = 0xFF, + .u8UseMultiSns = 0, +}; + +// default is MIPI-CSI Bayer format sensor +VI_DEV_ATTR_S DEV_ATTR_SENSOR_BASE = { + VI_MODE_MIPI, + VI_WORK_MODE_1Multiplex, + VI_SCAN_PROGRESSIVE, + {-1, -1, -1, -1}, + VI_DATA_SEQ_YUYV, + + { + /*port_vsync port_vsync_neg port_hsync port_hsync_neg */ + VI_VSYNC_PULSE, VI_VSYNC_NEG_LOW, VI_HSYNC_VALID_SINGNAL, VI_HSYNC_NEG_HIGH, + VI_VSYNC_VALID_SIGNAL, VI_VSYNC_VALID_NEG_HIGH, + + /*hsync_hfb hsync_act hsync_hhb*/ + {0, 1920, 0, + /*vsync0_vhb vsync0_act vsync0_hhb*/ + 0, 1080, 0, + /*vsync1_vhb vsync1_act vsync1_hhb*/ + 0, 0, 0} + }, + VI_DATA_TYPE_RGB, + {1920, 1080}, + { + WDR_MODE_NONE, + 1080, + 0 + }, + .enBayerFormat = BAYER_FORMAT_BG, +}; + +/****************************************************************************** + * If add new sensor , Modify the method as follows if need + *1-----*snsr_type_name + *2-----SAMPLE_COMM_SNS_GetSize + *3-----SAMPLE_COMM_SNS_GetDevAttr + *4-----SAMPLE_COMM_SNS_GetYuvBypassSts + *5-----SAMPLE_COMM_SNS_GetIspAttrBySns + *6-----SAMPLE_COMM_SNS_GetSnsObj + ******************************************************************************/ +/****************************************************************************** + * Structure : Configure sensor mode + ******************************************************************************/ +static const char *snsr_type_name[SAMPLE_SNS_TYPE_BUTT] = { + /* ------ LINEAR BEGIN ------*/ + "BRIGATES_BG0808_MIPI_2M_30FPS_10BIT", + "BYD_BF2253L_MIPI_1200P_30FPS_10BIT", + "CVSENS_CV4001_MIPI_4M_1440P_25FPS_12BIT", + "GCORE_GC02M1_MIPI_2M_30FPS_10BIT", + "GCORE_GC0312_MIPI_480P_20FPS_8BIT", + "GCORE_GC0329_MIPI_480P_10FPS_8BIT", + "GCORE_GC1054_MIPI_1M_30FPS_10BIT", + "GCORE_GC1084_MIPI_1M_30FPS_10BIT", + "GCORE_GC1084_SLAVE_MIPI_1M_30FPS_10BIT", + "GCORE_GC2053_MIPI_2M_30FPS_10BIT", + "GCORE_GC2053_SLAVE_MIPI_2M_30FPS_10BIT", + "GCORE_GC2053_1L_MIPI_2M_30FPS_10BIT", + "GCORE_GC2083_MIPI_2M_30FPS_10BIT", + "GCORE_GC2093_MIPI_2M_30FPS_10BIT", + "GCORE_GC2093_MIPI_2M_60FPS_10BIT", + "GCORE_GC2093_SLAVE_MIPI_2M_30FPS_10BIT", + "GCORE_GC2145_MIPI_2M_12FPS_8BIT", + "GCORE_GC4023_MIPI_4M_30FPS_10BIT", + "GCORE_GC4653_MIPI_4M_30FPS_10BIT", + "GCORE_GC4653_SLAVE_MIPI_4M_30FPS_10BIT", + "IMGDS_MIS2008_MIPI_2M_1080P_30FPS_12BIT", + "NEXTCHIP_N5_1M_2CH_25FPS_8BIT", + "NEXTCHIP_N5_2M_25FPS_8BIT", + "NEXTCHIP_N6_2M_4CH_25FPS_8BIT", + "OV_OS02D10_MIPI_2M_30FPS_10BIT", + "OV_OS02D10_SLAVE_MIPI_2M_30FPS_10BIT", + "OV_OS02K10_SLAVE_MIPI_2M_30FPS_12BIT", + "OV_OS04A10_MIPI_4M_1440P_30FPS_12BIT", + "OV_OS04C10_MIPI_4M_30FPS_12BIT", + "OV_OS04C10_MIPI_4M_1440P_30FPS_12BIT", + "OV_OS04C10_SLAVE_MIPI_4M_30FPS_12BIT", + "OV_OS08A20_MIPI_4M_30FPS_10BIT", + "OV_OS08A20_SLAVE_MIPI_4M_30FPS_10BIT", + "OV_OS08A20_MIPI_5M_30FPS_10BIT", + "OV_OS08A20_SLAVE_MIPI_5M_30FPS_10BIT", + "OV_OS08A20_MIPI_8M_30FPS_10BIT", + "OV_OS08A20_SLAVE_MIPI_8M_30FPS_10BIT", + "OV_OV4689_MIPI_4M_30FPS_10BIT", + "OV_OV5647_MIPI_2M_30FPS_10BIT", + "OV_OV6211_MIPI_400P_120FPS_10BIT", + "OV_OV7251_MIPI_480P_120FPS_10BIT", + "PICO384_THERMAL_384X288", + "PICO640_THERMAL_479P", + "PIXELPLUS_PR2020_1M_25FPS_8BIT", + "PIXELPLUS_PR2020_1M_30FPS_8BIT", + "PIXELPLUS_PR2020_2M_25FPS_8BIT", + "PIXELPLUS_PR2020_2M_30FPS_8BIT", + "PIXELPLUS_PR2100_2M_25FPS_8BIT", + "PIXELPLUS_PR2100_2M_2CH_25FPS_8BIT", + "PIXELPLUS_PR2100_2M_4CH_25FPS_8BIT", + "SMS_SC035GS_MIPI_480P_120FPS_12BIT", + "SMS_SC035GS_1L_MIPI_480P_120FPS_10BIT", + "SMS_SC035HGS_MIPI_480P_120FPS_12BIT", + "SMS_SC035HGS_1L_MIPI_480P_120FPS_10BIT", + "SMS_SC1336_1L_MIPI_1M_30FPS_10BIT", + "SMS_SC1336_1L_MIPI_1M_60FPS_10BIT", + "SMS_SC1346_1L_MIPI_1M_30FPS_10BIT", + "SMS_SC1346_1L_MIPI_1M_60FPS_10BIT", + "SMS_SC1346_1L_SLAVE_MIPI_1M_30FPS_10BIT", + "SMS_SC1346_1L_SLAVE_MIPI_1M_60FPS_10BIT", + "SMS_SC200AI_MIPI_2M_30FPS_10BIT", + "SMS_SC301IOT_MIPI_3M_30FPS_10BIT", + "SMS_SC401AI_MIPI_3M_30FPS_10BIT", + "SMS_SC401AI_MIPI_4M_30FPS_10BIT", + "SMS_SC500AI_MIPI_4M_30FPS_10BIT", + "SMS_SC500AI_MIPI_5M_30FPS_10BIT", + "SMS_SC501AI_2L_MIPI_5M_30FPS_10BIT", + "SMS_SC531AI_2L_MIPI_5M_30FPS_10BIT", + "SMS_SC850SL_MIPI_8M_30FPS_12BIT", + "SMS_SC3332_MIPI_3M_30FPS_10BIT", + "SMS_SC3335_MIPI_3M_30FPS_10BIT", + "SMS_SC3335_SLAVE_MIPI_3M_30FPS_10BIT", + "SMS_SC3336_MIPI_3M_30FPS_10BIT", + "SMS_SC3336P_MIPI_3M_30FPS_10BIT", + "SMS_SC2331_1L_MIPI_2M_30FPS_10BIT", + "SMS_SC2331_1L_SLAVE_MIPI_2M_30FPS_10BIT", + "SMS_SC2331_1L_SLAVE1_MIPI_2M_30FPS_10BIT", + "SMS_SC2335_MIPI_2M_30FPS_10BIT", + "SMS_SC2336_MIPI_2M_30FPS_10BIT", + "SMS_SC2336_SLAVE_MIPI_2M_30FPS_10BIT", + "SMS_SC2336_SLAVE1_MIPI_2M_30FPS_10BIT", + "SMS_SC2336_1L_MIPI_2M_30FPS_10BIT", + "SMS_SC2336P_MIPI_2M_30FPS_10BIT", + "SMS_SC2336P_1L_MIPI_2M_30FPS_10BIT", + "SMS_SC223A_1L_MIPI_2M_30FPS_10BIT", + "SMS_SC4210_MIPI_4M_30FPS_12BIT", + "SMS_SC4336_MIPI_4M_30FPS_10BIT", + "SMS_SC4336P_MIPI_4M_30FPS_10BIT", + "SMS_SC5336_2L_MIPI_5M_30FPS_10BIT", + "SMS_SC8238_MIPI_8M_30FPS_10BIT", + "SOI_F23_MIPI_2M_30FPS_10BIT", + "SOI_F35_MIPI_2M_30FPS_10BIT", + "SOI_F35_SLAVE_MIPI_2M_30FPS_10BIT", + "SOI_F37P_MIPI_2M_30FPS_10BIT", + "SOI_H65_MIPI_1M_30FPS_10BIT", + "SOI_K06_MIPI_4M_25FPS_10BIT", + "SOI_Q03_MIPI_3M_30FPS_10BIT", + "SOI_Q03P_MIPI_3M_30FPS_10BIT", + "SONY_IMX290_MIPI_1M_30FPS_12BIT", + "SONY_IMX290_MIPI_2M_60FPS_12BIT", + "SONY_IMX307_MIPI_2M_30FPS_12BIT", + "SONY_IMX307_SLAVE_MIPI_2M_30FPS_12BIT", + "SONY_IMX307_2L_MIPI_2M_30FPS_12BIT", + "SONY_IMX307_SUBLVDS_2M_30FPS_12BIT", + "SONY_IMX307_MIPI_2M_60FPS_12BIT", + "SONY_IMX307_SUBLVDS_2M_60FPS_12BIT", +#ifdef FPGA_PORTING + "SONY_IMX327_MIPI_1M_30FPS_10BIT", +#endif + "SONY_IMX327_MIPI_2M_30FPS_12BIT", + "SONY_IMX327_SLAVE_MIPI_2M_30FPS_12BIT", + "SONY_IMX327_2L_MIPI_2M_30FPS_12BIT", + "SONY_IMX327_SUBLVDS_2M_30FPS_12BIT", + "SONY_IMX327_MIPI_2M_60FPS_12BIT", + "SONY_IMX334_MIPI_8M_30FPS_12BIT", + "SONY_IMX335_MIPI_4M_30FPS_12BIT", + "SONY_IMX335_MIPI_4M_1600P_30FPS_12BIT", + "SONY_IMX335_2L_MIPI_4M_30FPS_10BIT", + "SONY_IMX335_MIPI_5M_30FPS_12BIT", + "SONY_IMX335_MIPI_2M_60FPS_10BIT", + "SONY_IMX335_MIPI_4M_60FPS_10BIT", + "SONY_IMX335_MIPI_5M_60FPS_10BIT", + "SONY_IMX347_MIPI_4M_60FPS_12BIT", + "SONY_IMX385_MIPI_2M_30FPS_12BIT", + "TECHPOINT_TP2850_MIPI_2M_30FPS_8BIT", + "TECHPOINT_TP2850_MIPI_4M_30FPS_8BIT", + "TECHPOINT_TP2825_MIPI_2M_30FPS_8BIT", + "TECHPOINT_TP2863_MIPI_1M_25FPS_8BIT", + "TECHPOINT_TP2863_MIPI_2M_25FPS_8BIT", + "TECHPOINT_TP2863_MIPI_1M_30FPS_8BIT", + "TECHPOINT_TP2863_MIPI_2M_30FPS_8BIT", + "VIVO_MCS369_2M_30FPS_12BIT", + "VIVO_MCS369Q_4M_30FPS_12BIT", + "VIVO_MM308M2_2M_25FPS_8BIT", + "LONTIUM_LT6911_2M_60FPS_8BIT", + /* ------ LINEAR END ------*/ + + /* ------ WDR 2TO1 BEGIN ------*/ + "BRIGATES_BG0808_MIPI_2M_30FPS_10BIT_WDR2TO1", + "CVSENS_CV4001_MIPI_4M_1440P_15FPS_WDR2TO1", + "GCORE_GC2093_MIPI_2M_30FPS_10BIT_WDR2TO1", + "GCORE_GC2093_SLAVE_MIPI_2M_30FPS_10BIT_WDR2TO1", + "OV_OS04A10_MIPI_4M_1440P_30FPS_10BIT_WDR2TO1", + "OV_OS04C10_MIPI_4M_30FPS_10BIT_WDR2TO1", + "OV_OS04C10_MIPI_4M_1440P_30FPS_10BIT_WDR2TO1", + "OV_OS04C10_SLAVE_MIPI_4M_30FPS_10BIT_WDR2TO1", + "OV_OS08A20_MIPI_4M_30FPS_10BIT_WDR2TO1", + "OV_OS08A20_SLAVE_MIPI_4M_30FPS_10BIT_WDR2TO1", + "OV_OS08A20_MIPI_5M_30FPS_10BIT_WDR2TO1", + "OV_OS08A20_SLAVE_MIPI_5M_30FPS_10BIT_WDR2TO1", + "OV_OS08A20_MIPI_8M_30FPS_10BIT_WDR2TO1", + "OV_OS08A20_SLAVE_MIPI_8M_30FPS_10BIT_WDR2TO1", + "SMS_SC1336_1L_MIPI_1M_30FPS_10BIT_WDR2TO1", + "SMS_SC1336_1L_MIPI_1M_60FPS_10BIT_WDR2TO1", + "SMS_SC1346_1L_MIPI_1M_30FPS_10BIT_WDR2TO1", + "SMS_SC1346_1L_MIPI_1M_60FPS_10BIT_WDR2TO1", + "SMS_SC200AI_MIPI_2M_30FPS_10BIT_WDR2TO1", + "SMS_SC500AI_MIPI_4M_30FPS_10BIT_WDR2TO1", + "SMS_SC500AI_MIPI_5M_30FPS_10BIT_WDR2TO1", + "SMS_SC850SL_MIPI_8M_30FPS_10BIT_WDR2TO1", + "SMS_SC4210_MIPI_4M_30FPS_10BIT_WDR2TO1", + "SMS_SC8238_MIPI_8M_15FPS_10BIT_WDR2TO1", + "SOI_F35_MIPI_2M_30FPS_10BIT_WDR2TO1", + "SOI_F35_SLAVE_MIPI_2M_30FPS_10BIT_WDR2TO1", + "SONY_IMX307_MIPI_2M_30FPS_12BIT_WDR2TO1", + "SONY_IMX307_2L_MIPI_2M_30FPS_12BIT_WDR2TO1", + "SONY_IMX307_SLAVE_MIPI_2M_30FPS_12BIT_WDR2TO1", + "SONY_IMX307_SUBLVDS_2M_30FPS_12BIT_WDR2TO1", +#ifdef FPGA_PORTING + "SONY_IMX327_MIPI_1M_30FPS_10BIT_WDR2TO1", +#endif + "SONY_IMX327_MIPI_2M_30FPS_12BIT_WDR2TO1", + "SONY_IMX327_2L_MIPI_2M_30FPS_12BIT_WDR2TO1", + "SONY_IMX327_SLAVE_MIPI_2M_30FPS_12BIT_WDR2TO1", + "SONY_IMX327_SUBLVDS_2M_30FPS_12BIT_WDR2TO1", + "SONY_IMX334_MIPI_8M_30FPS_12BIT_WDR2TO1", + "SONY_IMX335_MIPI_2M_30FPS_10BIT_WDR2TO1", + "SONY_IMX335_MIPI_4M_30FPS_10BIT_WDR2TO1", + "SONY_IMX335_MIPI_4M_1600P_30FPS_10BIT_WDR2TO1", + "SONY_IMX335_MIPI_5M_30FPS_10BIT_WDR2TO1", + /* ------ WDR 2TO1 END ------*/ +}; + +CVI_CHAR *SAMPLE_COMM_SNS_GetSnsrTypeName(void) +{ + return (CVI_CHAR *)snsr_type_name; +} +/****************************************************************************** + * funciton : Get enSize by diffrent sensor + * PIC_CIF: 352 * 288 + * PIC_D1_PAL: 720 * 576 + * PIC_D1_NTSC: 720 * 480 + * PIC_720P: 1280 * 720 + * PIC_1600x1200: + * PIC_1080P: 1920 * 1080 + * PIC_1088: 1920 * 1088 + * PIC_1440P: 2560 * 1440 + * PIC_2304x1296: + * PIC_2048x1536: + * PIC_2592x1520: + * PIC_2560x1600: + * PIC_2592x1944: + * PIC_2592x1536: + * PIC_2688x1520: + * PIC_2716x1524: + * PIC_2880x1620: + * PIC_3844x1124: + * PIC_3840x2160: + * PIC_3000x3000: + * PIC_4000x3000: + * PIC_4096x2160: + * PIC_3840x8640: + * PIC_7688x1124: + * PIC_640x480: + * PIC_479P: 632 * 479 + * PIC_400x400: + * PIC_288P: 384 * 288 + ******************************************************************************/ +CVI_S32 SAMPLE_COMM_SNS_GetSize(SAMPLE_SNS_TYPE_E enMode, PIC_SIZE_E *penSize) +{ + CVI_S32 s32Ret = CVI_SUCCESS; + + if (!penSize) + return CVI_FAILURE; + + switch (enMode) { + case GCORE_GC1054_MIPI_1M_30FPS_10BIT: + case GCORE_GC1084_MIPI_1M_30FPS_10BIT: + case GCORE_GC1084_SLAVE_MIPI_1M_30FPS_10BIT: + case NEXTCHIP_N5_1M_2CH_25FPS_8BIT: + case PIXELPLUS_PR2020_1M_25FPS_8BIT: + case PIXELPLUS_PR2020_1M_30FPS_8BIT: + case SMS_SC1336_1L_MIPI_1M_30FPS_10BIT: + case SMS_SC1336_1L_MIPI_1M_30FPS_10BIT_WDR2TO1: + case SMS_SC1336_1L_MIPI_1M_60FPS_10BIT: + case SMS_SC1336_1L_MIPI_1M_60FPS_10BIT_WDR2TO1: + case SMS_SC1346_1L_MIPI_1M_30FPS_10BIT: + case SMS_SC1346_1L_MIPI_1M_30FPS_10BIT_WDR2TO1: + case SMS_SC1346_1L_MIPI_1M_60FPS_10BIT: + case SMS_SC1346_1L_MIPI_1M_60FPS_10BIT_WDR2TO1: + case SMS_SC1346_1L_SLAVE_MIPI_1M_30FPS_10BIT: + case SMS_SC1346_1L_SLAVE_MIPI_1M_60FPS_10BIT: + case SOI_H65_MIPI_1M_30FPS_10BIT: + case SONY_IMX290_MIPI_1M_30FPS_12BIT: + case TECHPOINT_TP2863_MIPI_1M_25FPS_8BIT: + case TECHPOINT_TP2863_MIPI_1M_30FPS_8BIT: +#ifdef FPGA_PORTING + case SONY_IMX327_MIPI_1M_30FPS_10BIT: + case SONY_IMX327_MIPI_1M_30FPS_10BIT_WDR2TO1: +#endif + *penSize = PIC_720P; + break; + case GCORE_GC02M1_MIPI_2M_30FPS_10BIT: + case GCORE_GC2145_MIPI_2M_12FPS_8BIT: + case BYD_BF2253L_MIPI_1200P_30FPS_10BIT: + *penSize = PIC_1600x1200; + break; + case BRIGATES_BG0808_MIPI_2M_30FPS_10BIT: + case BRIGATES_BG0808_MIPI_2M_30FPS_10BIT_WDR2TO1: + case GCORE_GC2053_MIPI_2M_30FPS_10BIT: + case GCORE_GC2053_SLAVE_MIPI_2M_30FPS_10BIT: + case GCORE_GC2053_1L_MIPI_2M_30FPS_10BIT: + case GCORE_GC2083_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_MIPI_2M_60FPS_10BIT: + case GCORE_GC2093_MIPI_2M_30FPS_10BIT_WDR2TO1: + case GCORE_GC2093_SLAVE_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_SLAVE_MIPI_2M_30FPS_10BIT_WDR2TO1: + case NEXTCHIP_N5_2M_25FPS_8BIT: + case NEXTCHIP_N6_2M_4CH_25FPS_8BIT: + case OV_OS02D10_MIPI_2M_30FPS_10BIT: + case OV_OS02D10_SLAVE_MIPI_2M_30FPS_10BIT: + case OV_OS02K10_SLAVE_MIPI_2M_30FPS_12BIT: + case OV_OV5647_MIPI_2M_30FPS_10BIT: + case PIXELPLUS_PR2020_2M_25FPS_8BIT: + case PIXELPLUS_PR2020_2M_30FPS_8BIT: + case PIXELPLUS_PR2100_2M_25FPS_8BIT: + case SMS_SC2331_1L_MIPI_2M_30FPS_10BIT: + case SMS_SC2331_1L_SLAVE_MIPI_2M_30FPS_10BIT: + case SMS_SC2331_1L_SLAVE1_MIPI_2M_30FPS_10BIT: + case SMS_SC2335_MIPI_2M_30FPS_10BIT: + case SMS_SC2336_MIPI_2M_30FPS_10BIT: + case SMS_SC2336_SLAVE_MIPI_2M_30FPS_10BIT: + case SMS_SC2336_SLAVE1_MIPI_2M_30FPS_10BIT: + case SMS_SC2336_1L_MIPI_2M_30FPS_10BIT: + case SMS_SC2336P_MIPI_2M_30FPS_10BIT: + case SMS_SC2336P_1L_MIPI_2M_30FPS_10BIT: + case SMS_SC223A_1L_MIPI_2M_30FPS_10BIT: + case SMS_SC200AI_MIPI_2M_30FPS_10BIT: + case SMS_SC200AI_MIPI_2M_30FPS_10BIT_WDR2TO1: + case SOI_F23_MIPI_2M_30FPS_10BIT: + case SOI_F35_MIPI_2M_30FPS_10BIT: + case SOI_F35_MIPI_2M_30FPS_10BIT_WDR2TO1: + case SOI_F35_SLAVE_MIPI_2M_30FPS_10BIT: + case SOI_F35_SLAVE_MIPI_2M_30FPS_10BIT_WDR2TO1: + case SOI_F37P_MIPI_2M_30FPS_10BIT: + case SONY_IMX290_MIPI_2M_60FPS_12BIT: + case SONY_IMX307_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_2L_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_2L_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_SLAVE_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_SLAVE_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_SUBLVDS_2M_30FPS_12BIT: + case SONY_IMX307_SUBLVDS_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_MIPI_2M_60FPS_12BIT: + case SONY_IMX307_SUBLVDS_2M_60FPS_12BIT: + case SONY_IMX327_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_2L_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_2L_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_SLAVE_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_SLAVE_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_SUBLVDS_2M_30FPS_12BIT: + case SONY_IMX327_SUBLVDS_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_MIPI_2M_60FPS_12BIT: + case SONY_IMX335_MIPI_2M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_2M_60FPS_10BIT: + case SONY_IMX385_MIPI_2M_30FPS_12BIT: + case SONY_IMX385_MIPI_2M_30FPS_12BIT_WDR2TO1: + case TECHPOINT_TP2850_MIPI_2M_30FPS_8BIT: + case TECHPOINT_TP2825_MIPI_2M_30FPS_8BIT: + case TECHPOINT_TP2863_MIPI_2M_25FPS_8BIT: + case TECHPOINT_TP2863_MIPI_2M_30FPS_8BIT: + case VIVO_MCS369_2M_30FPS_12BIT: + case VIVO_MM308M2_2M_25FPS_8BIT: + case IMGDS_MIS2008_MIPI_2M_1080P_30FPS_12BIT: + *penSize = PIC_1080P; + break; + case LONTIUM_LT6911_2M_60FPS_8BIT: + *penSize = PIC_CUSTOMIZE; + break; + case GCORE_GC4023_MIPI_4M_30FPS_10BIT: + case GCORE_GC4653_MIPI_4M_30FPS_10BIT: + case GCORE_GC4653_SLAVE_MIPI_4M_30FPS_10BIT: + case OV_OS04A10_MIPI_4M_1440P_30FPS_12BIT: + case OV_OS04A10_MIPI_4M_1440P_30FPS_10BIT_WDR2TO1: + case OV_OS04C10_MIPI_4M_1440P_30FPS_12BIT: + case OV_OS04C10_MIPI_4M_1440P_30FPS_10BIT_WDR2TO1: + case OV_OS08A20_MIPI_4M_30FPS_10BIT: + case OV_OS08A20_MIPI_4M_30FPS_10BIT_WDR2TO1: + case OV_OS08A20_SLAVE_MIPI_4M_30FPS_10BIT: + case OV_OS08A20_SLAVE_MIPI_4M_30FPS_10BIT_WDR2TO1: + case SMS_SC401AI_MIPI_4M_30FPS_10BIT: + case SMS_SC500AI_MIPI_4M_30FPS_10BIT: + case SMS_SC500AI_MIPI_4M_30FPS_10BIT_WDR2TO1: + case SMS_SC4210_MIPI_4M_30FPS_12BIT: + case SMS_SC4210_MIPI_4M_30FPS_10BIT_WDR2TO1: + case SMS_SC4336_MIPI_4M_30FPS_10BIT: + case SMS_SC4336P_MIPI_4M_30FPS_10BIT: + case SOI_K06_MIPI_4M_25FPS_10BIT: + case SONY_IMX335_MIPI_4M_30FPS_12BIT: + case SONY_IMX335_MIPI_4M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_2L_MIPI_4M_30FPS_10BIT: + case SONY_IMX335_MIPI_4M_60FPS_10BIT: + case TECHPOINT_TP2850_MIPI_4M_30FPS_8BIT: + case VIVO_MCS369Q_4M_30FPS_12BIT: + case CVSENS_CV4001_MIPI_4M_1440P_25FPS_12BIT: + case CVSENS_CV4001_MIPI_4M_1440P_15FPS_WDR2TO1: + *penSize = PIC_1440P; + break; + case SMS_SC401AI_MIPI_3M_30FPS_10BIT: + case SMS_SC3332_MIPI_3M_30FPS_10BIT: + case SMS_SC3335_MIPI_3M_30FPS_10BIT: + case SMS_SC3335_SLAVE_MIPI_3M_30FPS_10BIT: + case SMS_SC3336_MIPI_3M_30FPS_10BIT: + case SMS_SC3336P_MIPI_3M_30FPS_10BIT: + case SOI_Q03_MIPI_3M_30FPS_10BIT: + case SOI_Q03P_MIPI_3M_30FPS_10BIT: + *penSize = PIC_2304x1296; + break; + case SMS_SC301IOT_MIPI_3M_30FPS_10BIT: + *penSize = PIC_2048x1536; + break; + case SONY_IMX335_MIPI_4M_1600P_30FPS_12BIT: + case SONY_IMX335_MIPI_4M_1600P_30FPS_10BIT_WDR2TO1: + *penSize = PIC_2560x1600; + break; + case OV_OS08A20_MIPI_5M_30FPS_10BIT: + case OV_OS08A20_MIPI_5M_30FPS_10BIT_WDR2TO1: + case OV_OS08A20_SLAVE_MIPI_5M_30FPS_10BIT: + case OV_OS08A20_SLAVE_MIPI_5M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_5M_30FPS_12BIT: + case SONY_IMX335_MIPI_5M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_5M_60FPS_10BIT: + *penSize = PIC_2592x1944; + break; + case OV_OS04C10_MIPI_4M_30FPS_12BIT: + case OV_OS04C10_MIPI_4M_30FPS_10BIT_WDR2TO1: + case OV_OS04C10_SLAVE_MIPI_4M_30FPS_12BIT: + case OV_OS04C10_SLAVE_MIPI_4M_30FPS_10BIT_WDR2TO1: + case OV_OV4689_MIPI_4M_30FPS_10BIT: + case SONY_IMX347_MIPI_4M_30FPS_12BIT_WDR2TO1: + case SONY_IMX347_MIPI_4M_60FPS_12BIT: + *penSize = PIC_2688x1520; + break; + case SMS_SC500AI_MIPI_5M_30FPS_10BIT: + case SMS_SC500AI_MIPI_5M_30FPS_10BIT_WDR2TO1: + case SMS_SC501AI_2L_MIPI_5M_30FPS_10BIT: + case SMS_SC531AI_2L_MIPI_5M_30FPS_10BIT: + *penSize = PIC_2880x1620; + break; + case SMS_SC5336_2L_MIPI_5M_30FPS_10BIT: + // *penSize = PIC_2880x1618; + *penSize = PIC_2880x1620; + break; + case OV_OS08A20_MIPI_8M_30FPS_10BIT: + case OV_OS08A20_MIPI_8M_30FPS_10BIT_WDR2TO1: + case OV_OS08A20_SLAVE_MIPI_8M_30FPS_10BIT: + case OV_OS08A20_SLAVE_MIPI_8M_30FPS_10BIT_WDR2TO1: + case SMS_SC850SL_MIPI_8M_30FPS_12BIT: + case SMS_SC850SL_MIPI_8M_30FPS_10BIT_WDR2TO1: + case SMS_SC8238_MIPI_8M_30FPS_10BIT: + case SMS_SC8238_MIPI_8M_15FPS_10BIT_WDR2TO1: + case SONY_IMX334_MIPI_8M_30FPS_12BIT: + case SONY_IMX334_MIPI_8M_30FPS_12BIT_WDR2TO1: + *penSize = PIC_3840x2160; + break; + case OV_OV7251_MIPI_480P_120FPS_10BIT: + case SMS_SC035GS_MIPI_480P_120FPS_12BIT: + case SMS_SC035GS_1L_MIPI_480P_120FPS_10BIT: + case SMS_SC035HGS_MIPI_480P_120FPS_12BIT: + case SMS_SC035HGS_1L_MIPI_480P_120FPS_10BIT: + case GCORE_GC0312_MIPI_480P_20FPS_8BIT: + case GCORE_GC0329_MIPI_480P_10FPS_8BIT: + *penSize = PIC_640x480; + break; + case PICO640_THERMAL_479P: + *penSize = PIC_479P; + break; + case OV_OV6211_MIPI_400P_120FPS_10BIT: + *penSize = PIC_400x400; + break; + case PICO384_THERMAL_384X288: + *penSize = PIC_288P; + break; +#ifdef ARCH_CV183X + case PIXELPLUS_PR2100_2M_2CH_25FPS_8BIT: + *penSize = PIC_3844x1124; + break; + case PIXELPLUS_PR2100_2M_4CH_25FPS_8BIT: + *penSize = PIC_7688x1124; + break; +#else + case PIXELPLUS_PR2100_2M_2CH_25FPS_8BIT: + case PIXELPLUS_PR2100_2M_4CH_25FPS_8BIT: + *penSize = PIC_1080P; + break; +#endif + default: + s32Ret = CVI_FAILURE; + break; + } + return s32Ret; +} +/* + * Brief: get picture size(w*h), according enPicSize + */ +CVI_S32 SAMPLE_COMM_SNS_GetPicSize(PIC_SIZE_E enPicSize, SIZE_S *pstSize) +{ + switch (enPicSize) { + case PIC_CIF: /* 352 * 288 */ + pstSize->u32Width = 352; + pstSize->u32Height = 288; + break; + + case PIC_D1_PAL: /* 720 * 576 */ + pstSize->u32Width = 720; + pstSize->u32Height = 576; + break; + + case PIC_D1_NTSC: /* 720 * 480 */ + pstSize->u32Width = 720; + pstSize->u32Height = 480; + break; + + case PIC_720P: /* 1280 * 720 */ + pstSize->u32Width = 1280; + pstSize->u32Height = 720; + break; + + case PIC_1600x1200: + pstSize->u32Width = 1600; + pstSize->u32Height = 1200; + break; + + case PIC_1080P: /* 1920 * 1080 */ + pstSize->u32Width = 1920; + pstSize->u32Height = 1080; + break; + + case PIC_1088: /* 1920 * 1088*/ + pstSize->u32Width = 1920; + pstSize->u32Height = 1088; + break; + + case PIC_1440P: /* 2560 * 1440 */ + pstSize->u32Width = 2560; + pstSize->u32Height = 1440; + break; + + case PIC_2304x1296: /* 2304 * 1296 */ + pstSize->u32Width = 2304; + pstSize->u32Height = 1296; + break; + + case PIC_2048x1536: + pstSize->u32Width = 2048; + pstSize->u32Height = 1536; + break; + + case PIC_2592x1520: + pstSize->u32Width = 2592; + pstSize->u32Height = 1520; + break; + + case PIC_2560x1600: + pstSize->u32Width = 2560; + pstSize->u32Height = 1600; + break; + + case PIC_2592x1944: + pstSize->u32Width = 2592; + pstSize->u32Height = 1944; + break; + + case PIC_2592x1536: + pstSize->u32Width = 2592; + pstSize->u32Height = 1536; + break; + + case PIC_2688x1520: + pstSize->u32Width = 2688; + pstSize->u32Height = 1520; + break; + + case PIC_2716x1524: + pstSize->u32Width = 2716; + pstSize->u32Height = 1524; + break; + + case PIC_2880x1620: + pstSize->u32Width = 2880; + pstSize->u32Height = 1620; + break; + case PIC_2880x1618: + pstSize->u32Width = 2880; + pstSize->u32Height = 1618; + break; + case PIC_3844x1124: + pstSize->u32Width = 3844; + pstSize->u32Height = 1124; + break; + + case PIC_3840x2160: + pstSize->u32Width = 3840; + pstSize->u32Height = 2160; + break; + + case PIC_3000x3000: + pstSize->u32Width = 3000; + pstSize->u32Height = 3000; + break; + + case PIC_4000x3000: + pstSize->u32Width = 4000; + pstSize->u32Height = 3000; + break; + + case PIC_4096x2160: + pstSize->u32Width = 4096; + pstSize->u32Height = 2160; + break; + + case PIC_3840x8640: + pstSize->u32Width = 3840; + pstSize->u32Height = 8640; + break; + + case PIC_7688x1124: + pstSize->u32Width = 7688; + pstSize->u32Height = 1124; + break; + + case PIC_640x480: + pstSize->u32Width = 640; + pstSize->u32Height = 480; + break; + case PIC_479P: /* 632 * 479 */ + pstSize->u32Width = 632; + pstSize->u32Height = 479; + break; + case PIC_400x400: + pstSize->u32Width = 400; + pstSize->u32Height = 400; + break; + case PIC_288P: /* 384 * 288 */ + pstSize->u32Width = 384; + pstSize->u32Height = 288; + break; + case PIC_CUSTOMIZE: /* env * env */ + { + uint8_t RW_Data[35]; + FILE *fp; + int file_size; + if(access("/kvmapp/kvm/width", F_OK) == 0){ + fp = fopen("/kvmapp/kvm/width", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + RW_Data[file_size] = 0; + pstSize->u32Width = atoi((char*)RW_Data); + } else { + pstSize->u32Width = 1920; + } + if(access("/kvmapp/kvm/height", F_OK) == 0){ + fp = fopen("/kvmapp/kvm/height", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + RW_Data[file_size] = 0; + pstSize->u32Height = atoi((char*)RW_Data); + } else { + pstSize->u32Height = 1080; + } + + // if(getenv("KVM_CSI_HEIGHT") == NULL || getenv("KVM_CSI_WIDTH") == NULL){ + // pstSize->u32Width = 1920; + // pstSize->u32Height = 1080; + // printf("KVM_CSI_H/W = NULL%d\n"); + // } else { + // pstSize->u32Width = atoi(getenv("KVM_CSI_HEIGHT")); + // pstSize->u32Height = atoi(getenv("KVM_CSI_WIDTH")); + // } + + printf("pstSize->u32Width = %d\n", pstSize->u32Width); + printf("pstSize->u32Height = %d\n", pstSize->u32Height); + break; + } + + default: + return CVI_FAILURE; + } + + return CVI_SUCCESS; +} + +/****************************************************************************** + * funciton : Get VI attr info by diffrent sensor + ******************************************************************************/ +CVI_S32 SAMPLE_COMM_SNS_GetDevAttr(SAMPLE_SNS_TYPE_E enSnsType, VI_DEV_ATTR_S *pstViDevAttr) +{ + PIC_SIZE_E enPicSize; + SIZE_S stSize; + + memcpy(pstViDevAttr, &DEV_ATTR_SENSOR_BASE, sizeof(VI_DEV_ATTR_S)); + + SAMPLE_COMM_SNS_GetSize(enSnsType, &enPicSize); + SAMPLE_COMM_SNS_GetPicSize(enPicSize, &stSize); + + pstViDevAttr->stSize.u32Width = stSize.u32Width; + pstViDevAttr->stSize.u32Height = stSize.u32Height; + pstViDevAttr->stWDRAttr.u32CacheLine = stSize.u32Height; + + // WDR mode + if (enSnsType >= SAMPLE_SNS_TYPE_LINEAR_BUTT) + pstViDevAttr->stWDRAttr.enWDRMode = WDR_MODE_2To1_LINE; + + // set synthetic wdr mode + switch (enSnsType) { + case SMS_SC1336_1L_MIPI_1M_30FPS_10BIT_WDR2TO1: + case SMS_SC1336_1L_MIPI_1M_60FPS_10BIT_WDR2TO1: + case SMS_SC1346_1L_MIPI_1M_30FPS_10BIT_WDR2TO1: + case SMS_SC1346_1L_MIPI_1M_60FPS_10BIT_WDR2TO1: + pstViDevAttr->stWDRAttr.bSyntheticWDR = 1; + break; + default: + pstViDevAttr->stWDRAttr.bSyntheticWDR = 0; + break; + } + + // YUV Sensor + switch (enSnsType) { + case NEXTCHIP_N5_1M_2CH_25FPS_8BIT: + case NEXTCHIP_N5_2M_25FPS_8BIT: + case NEXTCHIP_N6_2M_4CH_25FPS_8BIT: + case PICO384_THERMAL_384X288: + case PICO640_THERMAL_479P: + case PIXELPLUS_PR2020_1M_25FPS_8BIT: + case PIXELPLUS_PR2020_1M_30FPS_8BIT: + case PIXELPLUS_PR2020_2M_25FPS_8BIT: + case PIXELPLUS_PR2020_2M_30FPS_8BIT: + case PIXELPLUS_PR2100_2M_25FPS_8BIT: + case PIXELPLUS_PR2100_2M_2CH_25FPS_8BIT: + case PIXELPLUS_PR2100_2M_4CH_25FPS_8BIT: + case VIVO_MCS369_2M_30FPS_12BIT: + case VIVO_MCS369Q_4M_30FPS_12BIT: + case VIVO_MM308M2_2M_25FPS_8BIT: + pstViDevAttr->enDataSeq = VI_DATA_SEQ_YUYV; + pstViDevAttr->enInputDataType = VI_DATA_TYPE_YUV; + pstViDevAttr->enIntfMode = VI_MODE_MIPI_YUV422; + break; + case GCORE_GC2145_MIPI_2M_12FPS_8BIT: + case GCORE_GC0312_MIPI_480P_20FPS_8BIT: + case GCORE_GC0329_MIPI_480P_10FPS_8BIT: + pstViDevAttr->enDataSeq = VI_DATA_SEQ_YUYV; + pstViDevAttr->enInputDataType = VI_DATA_TYPE_YUV; + pstViDevAttr->enIntfMode = VI_MODE_BT601; + break; + case TECHPOINT_TP2863_MIPI_1M_25FPS_8BIT: + case TECHPOINT_TP2863_MIPI_2M_25FPS_8BIT: + case TECHPOINT_TP2863_MIPI_1M_30FPS_8BIT: + case TECHPOINT_TP2863_MIPI_2M_30FPS_8BIT: + pstViDevAttr->enDataSeq = VI_DATA_SEQ_UYVY; + pstViDevAttr->enInputDataType = VI_DATA_TYPE_YUV; + pstViDevAttr->enIntfMode = VI_MODE_MIPI_YUV422; + break; + default: + break; + }; + + // BT601 + switch (enSnsType) { + case GCORE_GC2145_MIPI_2M_12FPS_8BIT: + case GCORE_GC0312_MIPI_480P_20FPS_8BIT: + case GCORE_GC0329_MIPI_480P_10FPS_8BIT: + pstViDevAttr->enIntfMode = VI_MODE_BT601; + break; + default: + break; + }; + + // BT656 + switch (enSnsType) { + case NEXTCHIP_N5_1M_2CH_25FPS_8BIT: + case NEXTCHIP_N5_2M_25FPS_8BIT: + case PIXELPLUS_PR2020_1M_25FPS_8BIT: + case PIXELPLUS_PR2020_1M_30FPS_8BIT: + case PIXELPLUS_PR2020_2M_25FPS_8BIT: + case PIXELPLUS_PR2020_2M_30FPS_8BIT: + pstViDevAttr->enIntfMode = VI_MODE_BT656; + break; + default: + break; + }; + + // BT1120 + switch (enSnsType) { + case VIVO_MCS369_2M_30FPS_12BIT: + case VIVO_MCS369Q_4M_30FPS_12BIT: + case VIVO_MM308M2_2M_25FPS_8BIT: + case TECHPOINT_TP2825_MIPI_2M_30FPS_8BIT: + pstViDevAttr->enDataSeq = VI_DATA_SEQ_YUYV; + pstViDevAttr->enInputDataType = VI_DATA_TYPE_YUV; + pstViDevAttr->enIntfMode = VI_MODE_BT1120_STANDARD; + break; + default: + break; + }; + + // subLVDS + switch (enSnsType) { + case SONY_IMX307_SUBLVDS_2M_30FPS_12BIT: + case SONY_IMX307_SUBLVDS_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_SUBLVDS_2M_60FPS_12BIT: + case SONY_IMX327_SUBLVDS_2M_30FPS_12BIT: + case SONY_IMX327_SUBLVDS_2M_30FPS_12BIT_WDR2TO1: + pstViDevAttr->enIntfMode = VI_MODE_LVDS; + break; + default: + break; + }; + + switch (enSnsType) { + // Sony + case SONY_IMX307_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_SLAVE_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_SLAVE_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_2L_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_2L_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_SUBLVDS_2M_30FPS_12BIT: + case SONY_IMX307_SUBLVDS_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_MIPI_2M_60FPS_12BIT: + case SONY_IMX307_SUBLVDS_2M_60FPS_12BIT: +#ifdef FPGA_PORTING + case SONY_IMX327_MIPI_1M_30FPS_10BIT: + case SONY_IMX327_MIPI_1M_30FPS_10BIT_WDR2TO1: +#endif + case SONY_IMX327_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_SLAVE_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_SLAVE_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_2L_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_2L_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_SUBLVDS_2M_30FPS_12BIT: + case SONY_IMX327_SUBLVDS_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_MIPI_2M_60FPS_12BIT: + case SONY_IMX334_MIPI_8M_30FPS_12BIT: + case SONY_IMX334_MIPI_8M_30FPS_12BIT_WDR2TO1: + case SONY_IMX335_MIPI_2M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_4M_30FPS_12BIT: + case SONY_IMX335_MIPI_4M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_2L_MIPI_4M_30FPS_10BIT: + case SONY_IMX335_MIPI_4M_1600P_30FPS_12BIT: + case SONY_IMX335_MIPI_4M_1600P_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_5M_30FPS_12BIT: + case SONY_IMX335_MIPI_5M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_2M_60FPS_10BIT: + case SONY_IMX335_MIPI_4M_60FPS_10BIT: + case SONY_IMX335_MIPI_5M_60FPS_10BIT: + case SONY_IMX347_MIPI_4M_60FPS_12BIT: + case SONY_IMX347_MIPI_4M_30FPS_12BIT_WDR2TO1: + case SONY_IMX385_MIPI_2M_30FPS_12BIT: + case SONY_IMX385_MIPI_2M_30FPS_12BIT_WDR2TO1: + // GalaxyCore + case GCORE_GC02M1_MIPI_2M_30FPS_10BIT: + case GCORE_GC1054_MIPI_1M_30FPS_10BIT: + case GCORE_GC2053_MIPI_2M_30FPS_10BIT: + case GCORE_GC2053_SLAVE_MIPI_2M_30FPS_10BIT: + case GCORE_GC2053_1L_MIPI_2M_30FPS_10BIT: + case GCORE_GC2083_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_MIPI_2M_60FPS_10BIT: + case GCORE_GC2093_MIPI_2M_30FPS_10BIT_WDR2TO1: + case GCORE_GC2093_SLAVE_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_SLAVE_MIPI_2M_30FPS_10BIT_WDR2TO1: + case GCORE_GC4023_MIPI_4M_30FPS_10BIT: + // cvsens + case CVSENS_CV4001_MIPI_4M_1440P_25FPS_12BIT: + case CVSENS_CV4001_MIPI_4M_1440P_15FPS_WDR2TO1: + pstViDevAttr->enBayerFormat = BAYER_FORMAT_RG; + break; + // brigates + case BRIGATES_BG0808_MIPI_2M_30FPS_10BIT: + case BRIGATES_BG0808_MIPI_2M_30FPS_10BIT_WDR2TO1: + case SMS_SC4336_MIPI_4M_30FPS_10BIT: + case SMS_SC4336P_MIPI_4M_30FPS_10BIT: + pstViDevAttr->enBayerFormat = BAYER_FORMAT_BG; + break; + case GCORE_GC1084_MIPI_1M_30FPS_10BIT: + case GCORE_GC1084_SLAVE_MIPI_1M_30FPS_10BIT: + case GCORE_GC4653_MIPI_4M_30FPS_10BIT: + case GCORE_GC4653_SLAVE_MIPI_4M_30FPS_10BIT: + case TECHPOINT_TP2850_MIPI_2M_30FPS_8BIT: + case TECHPOINT_TP2850_MIPI_4M_30FPS_8BIT: + pstViDevAttr->enBayerFormat = BAYER_FORMAT_GR; + break; + case SOI_K06_MIPI_4M_25FPS_10BIT: + pstViDevAttr->enBayerFormat = BAYER_FORMAT_GB; + break; + case LONTIUM_LT6911_2M_60FPS_8BIT: + pstViDevAttr->enDataSeq = VI_DATA_SEQ_UYVY; + pstViDevAttr->enInputDataType = VI_DATA_TYPE_YUV; + pstViDevAttr->enIntfMode = VI_MODE_MIPI_YUV422; + pstViDevAttr->enBayerFormat = BAYER_FORMAT_BG; + break; + default: + pstViDevAttr->enBayerFormat = BAYER_FORMAT_BG; + break; + }; + + // virtual channel for multi-ch +#ifndef ARCH_CV183X + switch (enSnsType) { + case PIXELPLUS_PR2100_2M_2CH_25FPS_8BIT: + pstViDevAttr->enWorkMode = VI_WORK_MODE_2Multiplex; + break; + case PIXELPLUS_PR2100_2M_4CH_25FPS_8BIT: + pstViDevAttr->enWorkMode = VI_WORK_MODE_4Multiplex; + break; + default: + pstViDevAttr->enWorkMode = VI_WORK_MODE_1Multiplex; + break; + } +#endif + + return CVI_SUCCESS; +} + +CVI_S32 SAMPLE_COMM_SNS_GetYuvBypassSts(SAMPLE_SNS_TYPE_E enSnsType) +{ + CVI_S32 s32Ret = 0; + //Set YUV sensor need bypass isp + switch (enSnsType) { + case GCORE_GC0312_MIPI_480P_20FPS_8BIT: + case GCORE_GC0329_MIPI_480P_10FPS_8BIT: + case GCORE_GC2145_MIPI_2M_12FPS_8BIT: + case PICO640_THERMAL_479P: + case TECHPOINT_TP2850_MIPI_2M_30FPS_8BIT: + case TECHPOINT_TP2850_MIPI_4M_30FPS_8BIT: + case TECHPOINT_TP2825_MIPI_2M_30FPS_8BIT: + case TECHPOINT_TP2863_MIPI_1M_25FPS_8BIT: + case TECHPOINT_TP2863_MIPI_2M_25FPS_8BIT: + case TECHPOINT_TP2863_MIPI_1M_30FPS_8BIT: + case TECHPOINT_TP2863_MIPI_2M_30FPS_8BIT: + case VIVO_MCS369Q_4M_30FPS_12BIT: + case VIVO_MCS369_2M_30FPS_12BIT: + case VIVO_MM308M2_2M_25FPS_8BIT: + case NEXTCHIP_N5_2M_25FPS_8BIT: + case PIXELPLUS_PR2020_1M_25FPS_8BIT: + case PIXELPLUS_PR2020_1M_30FPS_8BIT: + case PIXELPLUS_PR2020_2M_25FPS_8BIT: + case PIXELPLUS_PR2020_2M_30FPS_8BIT: + case PIXELPLUS_PR2100_2M_25FPS_8BIT: + case PIXELPLUS_PR2100_2M_2CH_25FPS_8BIT: + case PIXELPLUS_PR2100_2M_4CH_25FPS_8BIT: + s32Ret = 1; + break; + default: + break; + } + return s32Ret; +} +/****************************************************************************** + * funciton : Get ISP attr info by diffrent sensor + ******************************************************************************/ +CVI_S32 SAMPLE_COMM_SNS_GetIspAttrBySns(SAMPLE_SNS_TYPE_E enSnsType, ISP_PUB_ATTR_S *pstPubAttr) +{ + CVI_S32 s32Ret = CVI_SUCCESS; + PIC_SIZE_E enPicSize; + SIZE_S stSize; + + memcpy(pstPubAttr, &ISP_PUB_ATTR_SAMPLE, sizeof(ISP_PUB_ATTR_S)); + + SAMPLE_COMM_SNS_GetSize(enSnsType, &enPicSize); + SAMPLE_COMM_SNS_GetPicSize(enPicSize, &stSize); + + pstPubAttr->stSnsSize.u32Width = stSize.u32Width; + pstPubAttr->stSnsSize.u32Height = stSize.u32Height; + pstPubAttr->stWndRect.u32Width = stSize.u32Width; + pstPubAttr->stWndRect.u32Height = stSize.u32Height; + + // WDR mode + if (enSnsType >= SAMPLE_SNS_TYPE_LINEAR_BUTT) + pstPubAttr->enWDRMode = WDR_MODE_2To1_LINE; + + // FPS + switch (enSnsType) { + case CVSENS_CV4001_MIPI_4M_1440P_15FPS_WDR2TO1: + pstPubAttr->f32FrameRate = 15; + break; + case SMS_SC035GS_MIPI_480P_120FPS_12BIT: + case SMS_SC035GS_1L_MIPI_480P_120FPS_10BIT: + case SMS_SC035HGS_MIPI_480P_120FPS_12BIT: + case SMS_SC035HGS_1L_MIPI_480P_120FPS_10BIT: + case OV_OV6211_MIPI_400P_120FPS_10BIT: + case OV_OV7251_MIPI_480P_120FPS_10BIT: + pstPubAttr->f32FrameRate = 120; + break; + case SMS_SC1336_1L_MIPI_1M_60FPS_10BIT: + case SMS_SC1336_1L_MIPI_1M_60FPS_10BIT_WDR2TO1: + case SMS_SC1346_1L_MIPI_1M_60FPS_10BIT: + case SMS_SC1346_1L_MIPI_1M_60FPS_10BIT_WDR2TO1: + case SMS_SC1346_1L_SLAVE_MIPI_1M_60FPS_10BIT: + case SONY_IMX307_MIPI_2M_60FPS_12BIT: + case SONY_IMX307_SUBLVDS_2M_60FPS_12BIT: + case SONY_IMX327_MIPI_2M_60FPS_12BIT: + case SONY_IMX335_MIPI_2M_60FPS_10BIT: + case SONY_IMX335_MIPI_4M_60FPS_10BIT: + case SONY_IMX335_MIPI_5M_60FPS_10BIT: + case SONY_IMX347_MIPI_4M_60FPS_12BIT: + case GCORE_GC2093_MIPI_2M_60FPS_10BIT: + pstPubAttr->f32FrameRate = 60; + break; + case TECHPOINT_TP2850_MIPI_2M_30FPS_8BIT: + case TECHPOINT_TP2850_MIPI_4M_30FPS_8BIT: + case TECHPOINT_TP2863_MIPI_1M_30FPS_8BIT: + case TECHPOINT_TP2863_MIPI_2M_30FPS_8BIT: + case SMS_SC1336_1L_MIPI_1M_30FPS_10BIT: + case SMS_SC1336_1L_MIPI_1M_30FPS_10BIT_WDR2TO1: + case SMS_SC1346_1L_MIPI_1M_30FPS_10BIT: + case SMS_SC1346_1L_MIPI_1M_30FPS_10BIT_WDR2TO1: + case SMS_SC1346_1L_SLAVE_MIPI_1M_30FPS_10BIT: + case SMS_SC223A_1L_MIPI_2M_30FPS_10BIT: + case SONY_IMX335_MIPI_2M_30FPS_10BIT_WDR2TO1: + case SONY_IMX347_MIPI_4M_30FPS_12BIT_WDR2TO1: + case OV_OV5647_MIPI_2M_30FPS_10BIT: + case GCORE_GC1084_MIPI_1M_30FPS_10BIT: + case GCORE_GC1084_SLAVE_MIPI_1M_30FPS_10BIT: + case GCORE_GC2083_MIPI_2M_30FPS_10BIT: + case BYD_BF2253L_MIPI_1200P_30FPS_10BIT: + pstPubAttr->f32FrameRate = 30; + break; + case GCORE_GC2145_MIPI_2M_12FPS_8BIT: + pstPubAttr->f32FrameRate = 12; + break; + case GCORE_GC0312_MIPI_480P_20FPS_8BIT: + pstPubAttr->f32FrameRate = 20; + break; + case GCORE_GC0329_MIPI_480P_10FPS_8BIT: + pstPubAttr->f32FrameRate = 10; + break; +#ifdef FPGA_PORTING + case SONY_IMX327_MIPI_1M_30FPS_10BIT: + case SONY_IMX327_MIPI_1M_30FPS_10BIT_WDR2TO1: + pstPubAttr->f32FrameRate = 10; + break; +#endif + case CVSENS_CV4001_MIPI_4M_1440P_25FPS_12BIT: + case TECHPOINT_TP2863_MIPI_1M_25FPS_8BIT: + case TECHPOINT_TP2863_MIPI_2M_25FPS_8BIT: + pstPubAttr->f32FrameRate = 25; + break; + default: + pstPubAttr->f32FrameRate = 25; + break; + } + + switch (enSnsType) { + case SOI_K06_MIPI_4M_25FPS_10BIT: + pstPubAttr->enBayer = BAYER_GBRG; + break; + case LONTIUM_LT6911_2M_60FPS_8BIT: + pstPubAttr->enBayer = BAYER_BGGR; + pstPubAttr->f32FrameRate = 60; + break; + // Sony + case SONY_IMX307_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_SLAVE_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_SLAVE_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_2L_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_2L_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_SUBLVDS_2M_30FPS_12BIT: + case SONY_IMX307_SUBLVDS_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX307_MIPI_2M_60FPS_12BIT: + case SONY_IMX307_SUBLVDS_2M_60FPS_12BIT: +#ifdef FPGA_PORTING + case SONY_IMX327_MIPI_1M_30FPS_10BIT: + case SONY_IMX327_MIPI_1M_30FPS_10BIT_WDR2TO1: +#endif + case SONY_IMX327_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_SLAVE_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_SLAVE_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_2L_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_2L_MIPI_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_SUBLVDS_2M_30FPS_12BIT: + case SONY_IMX327_SUBLVDS_2M_30FPS_12BIT_WDR2TO1: + case SONY_IMX327_MIPI_2M_60FPS_12BIT: + case SONY_IMX334_MIPI_8M_30FPS_12BIT: + case SONY_IMX334_MIPI_8M_30FPS_12BIT_WDR2TO1: + case SONY_IMX335_MIPI_2M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_4M_30FPS_12BIT: + case SONY_IMX335_MIPI_4M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_2L_MIPI_4M_30FPS_10BIT: + case SONY_IMX335_MIPI_4M_1600P_30FPS_12BIT: + case SONY_IMX335_MIPI_4M_1600P_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_5M_30FPS_12BIT: + case SONY_IMX335_MIPI_5M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_2M_60FPS_10BIT: + case SONY_IMX335_MIPI_4M_60FPS_10BIT: + case SONY_IMX335_MIPI_5M_60FPS_10BIT: + case SONY_IMX347_MIPI_4M_60FPS_12BIT: + case SONY_IMX347_MIPI_4M_30FPS_12BIT_WDR2TO1: + case SONY_IMX385_MIPI_2M_30FPS_12BIT: + case SONY_IMX385_MIPI_2M_30FPS_12BIT_WDR2TO1: + // GalaxyCore + case GCORE_GC02M1_MIPI_2M_30FPS_10BIT: + case GCORE_GC1054_MIPI_1M_30FPS_10BIT: + case GCORE_GC2053_MIPI_2M_30FPS_10BIT: + case GCORE_GC2053_SLAVE_MIPI_2M_30FPS_10BIT: + case GCORE_GC2053_1L_MIPI_2M_30FPS_10BIT: + case GCORE_GC2083_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_MIPI_2M_60FPS_10BIT: + case GCORE_GC2093_MIPI_2M_30FPS_10BIT_WDR2TO1: + case GCORE_GC2093_SLAVE_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_SLAVE_MIPI_2M_30FPS_10BIT_WDR2TO1: + case GCORE_GC4023_MIPI_4M_30FPS_10BIT: + case IMGDS_MIS2008_MIPI_2M_1080P_30FPS_12BIT: + case CVSENS_CV4001_MIPI_4M_1440P_25FPS_12BIT: + case CVSENS_CV4001_MIPI_4M_1440P_15FPS_WDR2TO1: + pstPubAttr->enBayer = BAYER_RGGB; + break; + case GCORE_GC1084_MIPI_1M_30FPS_10BIT: + case GCORE_GC1084_SLAVE_MIPI_1M_30FPS_10BIT: + case GCORE_GC4653_MIPI_4M_30FPS_10BIT: + case GCORE_GC4653_SLAVE_MIPI_4M_30FPS_10BIT: + case TECHPOINT_TP2850_MIPI_2M_30FPS_8BIT: + case TECHPOINT_TP2850_MIPI_4M_30FPS_8BIT: + // on licheervnano + pstPubAttr->enBayer = BAYER_RGGB; + //pstPubAttr->enBayer = BAYER_GRBG; + break; +#ifdef ARCH_CV182X + case SOI_F23_MIPI_2M_30FPS_10BIT: + pstPubAttr->enBayer = BAYER_BGRGI; + break; +#endif + default: + pstPubAttr->enBayer = BAYER_BGGR; + break; + }; + + return s32Ret; +} + +/****************************************************************************** + * funciton : Get sns_obj callback + ******************************************************************************/ +CVI_VOID *SAMPLE_COMM_SNS_GetSnsObj(SAMPLE_SNS_TYPE_E enSnsType) +{ + CVI_VOID *pSnsObj; + + switch (enSnsType) { +#if defined(SENSOR_BRIGATES_BG0808) + case BRIGATES_BG0808_MIPI_2M_30FPS_10BIT: + case BRIGATES_BG0808_MIPI_2M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsBG0808_Obj; + break; +#endif +#if defined(SENSOR_BYD_BF2253L) + case BYD_BF2253L_MIPI_1200P_30FPS_10BIT: + pSnsObj = &stSnsBF2253L_Obj; + break; +#endif +#if defined(SENSOR_CVSENS_CV4001) + case CVSENS_CV4001_MIPI_4M_1440P_25FPS_12BIT: + case CVSENS_CV4001_MIPI_4M_1440P_15FPS_WDR2TO1: + return &stSnsCV4001_Obj; +#endif +#if defined(SENSOR_GCORE_GC02M1) + case GCORE_GC02M1_MIPI_2M_30FPS_10BIT: + return &stSnsGc02m1_Obj; +#endif +#if defined(SENSOR_GCORE_GC0312) + case GCORE_GC0312_MIPI_480P_20FPS_8BIT: + return &stSnsGc0312_Obj; +#endif +#if defined(SENSOR_GCORE_GC0329) + case GCORE_GC0329_MIPI_480P_10FPS_8BIT: + return &stSnsGc0329_Obj; +#endif +#if defined(SENSOR_GCORE_GC1054) + case GCORE_GC1054_MIPI_1M_30FPS_10BIT: + return &stSnsGc1054_Obj; +#endif +#if defined(SENSOR_GCORE_GC1084) + case GCORE_GC1084_MIPI_1M_30FPS_10BIT: + return &stSnsGc1084_Obj; +#endif +#if defined(SENSOR_GCORE_GC1084_SLAVE) + case GCORE_GC1084_SLAVE_MIPI_1M_30FPS_10BIT: + return &stSnsGc1084_Slave_Obj; +#endif +#if defined(SENSOR_GCORE_GC2053) + case GCORE_GC2053_MIPI_2M_30FPS_10BIT: + return &stSnsGc2053_Obj; +#endif +#if defined(SENSOR_GCORE_GC2053_SLAVE) + case GCORE_GC2053_SLAVE_MIPI_2M_30FPS_10BIT: + return &stSnsGc2053_Slave_Obj; +#endif +#if defined(SENSOR_GCORE_GC2053_1L) + case GCORE_GC2053_1L_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsGc2053_1l_Obj; + break; +#endif +#if defined(SENSOR_GCORE_GC2083) + case GCORE_GC2083_MIPI_2M_30FPS_10BIT: + return &stSnsGc2083_Obj; +#endif +#if defined(SENSOR_GCORE_GC2093) + case GCORE_GC2093_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_MIPI_2M_60FPS_10BIT: + case GCORE_GC2093_MIPI_2M_30FPS_10BIT_WDR2TO1: + return &stSnsGc2093_Obj; +#endif +#if defined(SENSOR_GCORE_GC2093_SLAVE) + case GCORE_GC2093_SLAVE_MIPI_2M_30FPS_10BIT: + case GCORE_GC2093_SLAVE_MIPI_2M_30FPS_10BIT_WDR2TO1: + return &stSnsGc2093_Slave_Obj; +#endif +#if defined(SENSOR_GCORE_GC2145) + case GCORE_GC2145_MIPI_2M_12FPS_8BIT: + return &stSnsGc2145_Obj; +#endif +#if defined(SENSOR_GCORE_GC4023) + case GCORE_GC4023_MIPI_4M_30FPS_10BIT: + return &stSnsGc4023_Obj; +#endif +#if defined(SENSOR_GCORE_GC4653) + case GCORE_GC4653_MIPI_4M_30FPS_10BIT: + return &stSnsGc4653_Obj; +#endif +#if defined(SENSOR_GCORE_GC4653_SLAVE) + case GCORE_GC4653_SLAVE_MIPI_4M_30FPS_10BIT: + return &stSnsGc4653_Slave_Obj; +#endif +#if defined(SENSOR_ID_MIS2008) + case IMGDS_MIS2008_MIPI_2M_1080P_30FPS_12BIT: + pSnsObj = &stSnsMIS2008_Obj; + break; +#endif +#if defined(SENSOR_NEXTCHIP_N5) + case NEXTCHIP_N5_2M_25FPS_8BIT: + case NEXTCHIP_N5_1M_2CH_25FPS_8BIT: + pSnsObj = &stSnsN5_Obj; + break; +#endif +#if defined(SENSOR_NEXTCHIP_N6) + case NEXTCHIP_N6_2M_4CH_25FPS_8BIT: + pSnsObj = &stSnsN6_Obj; + break; +#endif +#if defined(SENSOR_OV_OS02D10) + case OV_OS02D10_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsOs02d10_Obj; + break; +#endif +#if defined(SENSOR_OV_OS02D10_SLAVE) + case OV_OS02D10_SLAVE_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsOs02d10_Slave_Obj; + break; +#endif +#if defined(SENSOR_OV_OS02K10_SLAVE) + case OV_OS02K10_SLAVE_MIPI_2M_30FPS_12BIT: + pSnsObj = &stSnsOs02k10_Slave_Obj; + break; +#endif +#if defined(SENSOR_OV_OS04A10) + case OV_OS04A10_MIPI_4M_1440P_30FPS_12BIT: + case OV_OS04A10_MIPI_4M_1440P_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsOs04a10_Obj; + break; +#endif +#if defined(SENSOR_OV_OS04C10) + case OV_OS04C10_MIPI_4M_30FPS_12BIT: + case OV_OS04C10_MIPI_4M_1440P_30FPS_12BIT: + case OV_OS04C10_MIPI_4M_30FPS_10BIT_WDR2TO1: + case OV_OS04C10_MIPI_4M_1440P_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsOs04c10_Obj; + break; +#endif +#if defined(SENSOR_OV_OS04C10_SLAVE) + case OV_OS04C10_SLAVE_MIPI_4M_30FPS_12BIT: + case OV_OS04C10_SLAVE_MIPI_4M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsOs04c10_Slave_Obj; + break; +#endif +#if defined(SENSOR_OV_OS08A20) + case OV_OS08A20_MIPI_4M_30FPS_10BIT: + case OV_OS08A20_MIPI_4M_30FPS_10BIT_WDR2TO1: + case OV_OS08A20_MIPI_5M_30FPS_10BIT: + case OV_OS08A20_MIPI_5M_30FPS_10BIT_WDR2TO1: + case OV_OS08A20_MIPI_8M_30FPS_10BIT: + case OV_OS08A20_MIPI_8M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsOs08a20_Obj; + break; +#endif +#if defined(SENSOR_OV_OS08A20_SLAVE) + case OV_OS08A20_SLAVE_MIPI_4M_30FPS_10BIT: + case OV_OS08A20_SLAVE_MIPI_4M_30FPS_10BIT_WDR2TO1: + case OV_OS08A20_SLAVE_MIPI_5M_30FPS_10BIT: + case OV_OS08A20_SLAVE_MIPI_5M_30FPS_10BIT_WDR2TO1: + case OV_OS08A20_SLAVE_MIPI_8M_30FPS_10BIT: + case OV_OS08A20_SLAVE_MIPI_8M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsOs08a20_Slave_Obj; + break; +#endif +#if defined(SENSOR_OV_OV4689) + case OV_OV4689_MIPI_4M_30FPS_10BIT: + pSnsObj = &stSnsOv4689_Obj; + break; +#endif +#if defined(SENSOR_OV_OV5647) + case OV_OV5647_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsOv5647_Obj; + break; +#endif +#if defined(SENSOR_OV_OV6211) + case OV_OV6211_MIPI_400P_120FPS_10BIT: + pSnsObj = &stSnsOv6211_Obj; + break; +#endif +#if defined(SENSOR_OV_OV7251) + case OV_OV7251_MIPI_480P_120FPS_10BIT: + pSnsObj = &stSnsOv7251_Obj; + break; +#endif +#if defined(SENSOR_PICO_384) + case PICO384_THERMAL_384X288: + pSnsObj = &stSnsPICO384_Obj; + break; +#endif +#if defined(SENSOR_PICO_640) + case PICO640_THERMAL_479P: + pSnsObj = &stSnsPICO640_Obj; + break; +#endif +#if defined(SENSOR_PIXELPLUS_PR2020) + case PIXELPLUS_PR2020_1M_25FPS_8BIT: + case PIXELPLUS_PR2020_1M_30FPS_8BIT: + case PIXELPLUS_PR2020_2M_25FPS_8BIT: + case PIXELPLUS_PR2020_2M_30FPS_8BIT: + pSnsObj = &stSnsPR2020_Obj; + break; +#endif +#if defined(SENSOR_PIXELPLUS_PR2100) + case PIXELPLUS_PR2100_2M_25FPS_8BIT: + case PIXELPLUS_PR2100_2M_2CH_25FPS_8BIT: + case PIXELPLUS_PR2100_2M_4CH_25FPS_8BIT: + pSnsObj = &stSnsPR2100_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC035GS) + case SMS_SC035GS_MIPI_480P_120FPS_12BIT: + pSnsObj = &stSnsSC035GS_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC035GS_1L) + case SMS_SC035GS_1L_MIPI_480P_120FPS_10BIT: + pSnsObj = &stSnsSC035GS_1L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC035HGS) + case SMS_SC035HGS_MIPI_480P_120FPS_12BIT: + pSnsObj = &stSnsSC035HGS_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC035HGS_1L) + case SMS_SC035HGS_1L_MIPI_480P_120FPS_10BIT: + pSnsObj = &stSnsSC035HGS_1L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC1336_1L) + case SMS_SC1336_1L_MIPI_1M_30FPS_10BIT: + case SMS_SC1336_1L_MIPI_1M_30FPS_10BIT_WDR2TO1: + case SMS_SC1336_1L_MIPI_1M_60FPS_10BIT: + case SMS_SC1336_1L_MIPI_1M_60FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsSC1336_1L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC1346_1L) + case SMS_SC1346_1L_MIPI_1M_30FPS_10BIT: + case SMS_SC1346_1L_MIPI_1M_30FPS_10BIT_WDR2TO1: + case SMS_SC1346_1L_MIPI_1M_60FPS_10BIT: + case SMS_SC1346_1L_MIPI_1M_60FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsSC1346_1L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC1346_1L_SLAVE) + case SMS_SC1346_1L_SLAVE_MIPI_1M_30FPS_10BIT: + case SMS_SC1346_1L_SLAVE_MIPI_1M_60FPS_10BIT: + pSnsObj = &stSnsSC1346_1L_Slave_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC200AI) + case SMS_SC200AI_MIPI_2M_30FPS_10BIT: + case SMS_SC200AI_MIPI_2M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsSC200AI_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC301IOT) + case SMS_SC301IOT_MIPI_3M_30FPS_10BIT: + pSnsObj = &stSnsSC301IOT_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC401AI) + case SMS_SC401AI_MIPI_4M_30FPS_10BIT: + case SMS_SC401AI_MIPI_3M_30FPS_10BIT: + pSnsObj = &stSnsSC401AI_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC500AI) + case SMS_SC500AI_MIPI_5M_30FPS_10BIT: + case SMS_SC500AI_MIPI_5M_30FPS_10BIT_WDR2TO1: + case SMS_SC500AI_MIPI_4M_30FPS_10BIT: + case SMS_SC500AI_MIPI_4M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsSC500AI_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC501AI_2L) + case SMS_SC501AI_2L_MIPI_5M_30FPS_10BIT: + pSnsObj = &stSnsSC501AI_2L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC531AI_2L) + case SMS_SC531AI_2L_MIPI_5M_30FPS_10BIT: + pSnsObj = &stSnsSC531AI_2L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC850SL) + case SMS_SC850SL_MIPI_8M_30FPS_12BIT: + case SMS_SC850SL_MIPI_8M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsSC850SL_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC3332) + case SMS_SC3332_MIPI_3M_30FPS_10BIT: + pSnsObj = &stSnsSC3332_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC3335) + case SMS_SC3335_MIPI_3M_30FPS_10BIT: + pSnsObj = &stSnsSC3335_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC3335_SLAVE) + case SMS_SC3335_SLAVE_MIPI_3M_30FPS_10BIT: + pSnsObj = &stSnsSC3335_Slave_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC3336) + case SMS_SC3336_MIPI_3M_30FPS_10BIT: + pSnsObj = &stSnsSC3336_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC3336P) + case SMS_SC3336P_MIPI_3M_30FPS_10BIT: + pSnsObj = &stSnsSC3336P_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC2331_1L) + case SMS_SC2331_1L_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC2331_1L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC2331_1L_SLAVE) + case SMS_SC2331_1L_SLAVE_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC2331_1L_Slave_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC2331_1L_SLAVE1) + case SMS_SC2331_1L_SLAVE1_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC2331_1L_Slave1_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC2335) + case SMS_SC2335_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC2335_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC2336) + case SMS_SC2336_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC2336_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC2336_SLAVE) + case SMS_SC2336_SLAVE_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC2336_Slave_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC2336_SLAVE1) + case SMS_SC2336_SLAVE1_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC2336_Slave1_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC2336_1L) + case SMS_SC2336_1L_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC2336_1L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC2336P) + case SMS_SC2336P_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC2336P_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC2336P_1L) + case SMS_SC2336P_1L_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC2336P_1L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC223A_1L) + case SMS_SC223A_1L_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsSC223A_1L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC4210) + case SMS_SC4210_MIPI_4M_30FPS_12BIT: + case SMS_SC4210_MIPI_4M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsSC4210_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC4336) + case SMS_SC4336_MIPI_4M_30FPS_10BIT: + pSnsObj = &stSnsSC4336_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC4336P) + case SMS_SC4336P_MIPI_4M_30FPS_10BIT: + pSnsObj = &stSnsSC4336P_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC5336_2L) + case SMS_SC5336_2L_MIPI_5M_30FPS_10BIT: + pSnsObj = &stSnsSC5336_2L_Obj; + break; +#endif +#if defined(SENSOR_SMS_SC8238) + case SMS_SC8238_MIPI_8M_30FPS_10BIT: + case SMS_SC8238_MIPI_8M_15FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsSC8238_Obj; + break; +#endif +#if defined(SENSOR_SOI_F23) + case SOI_F23_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsF23_Obj; + break; +#endif +#if defined(SENSOR_SOI_F35) + case SOI_F35_MIPI_2M_30FPS_10BIT: + case SOI_F35_MIPI_2M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsF35_Obj; + break; +#endif +#if defined(SENSOR_SOI_F35_SLAVE) + case SOI_F35_SLAVE_MIPI_2M_30FPS_10BIT: + case SOI_F35_SLAVE_MIPI_2M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsF35_Slave_Obj; + break; +#endif +#if defined(SENSOR_SOI_F37P) + case SOI_F37P_MIPI_2M_30FPS_10BIT: + pSnsObj = &stSnsF37P_Obj; + break; +#endif +#if defined(SENSOR_SOI_H65) + case SOI_H65_MIPI_1M_30FPS_10BIT: + pSnsObj = &stSnsH65_Obj; + break; +#endif +#if defined(SENSOR_SOI_K06) + case SOI_K06_MIPI_4M_25FPS_10BIT: + return &stSnsK06_Obj; +#endif +#if defined(SENSOR_SOI_Q03) + case SOI_Q03_MIPI_3M_30FPS_10BIT: + pSnsObj = &stSnsQ03_Obj; + break; +#endif +#if defined(SENSOR_SOI_Q03P) + case SOI_Q03P_MIPI_3M_30FPS_10BIT: + pSnsObj = &stSnsQ03P_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX290_2L) + case SONY_IMX290_MIPI_1M_30FPS_12BIT: + case SONY_IMX290_MIPI_2M_60FPS_12BIT: + pSnsObj = &stSnsImx290_2l_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX307) + case SONY_IMX307_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_MIPI_2M_60FPS_12BIT: + case SONY_IMX307_MIPI_2M_30FPS_12BIT_WDR2TO1: + pSnsObj = &stSnsImx307_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX307_SLAVE) + case SONY_IMX307_SLAVE_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_SLAVE_MIPI_2M_30FPS_12BIT_WDR2TO1: + pSnsObj = &stSnsImx307_Slave_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX307_2L) + case SONY_IMX307_2L_MIPI_2M_30FPS_12BIT: + case SONY_IMX307_2L_MIPI_2M_30FPS_12BIT_WDR2TO1: + pSnsObj = &stSnsImx307_2l_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX307_SUBLVDS) + case SONY_IMX307_SUBLVDS_2M_30FPS_12BIT: + case SONY_IMX307_SUBLVDS_2M_60FPS_12BIT: + case SONY_IMX307_SUBLVDS_2M_30FPS_12BIT_WDR2TO1: + pSnsObj = &stSnsImx307_Sublvds_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX327) + case SONY_IMX327_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_MIPI_2M_60FPS_12BIT: + case SONY_IMX327_MIPI_2M_30FPS_12BIT_WDR2TO1: + pSnsObj = &stSnsImx327_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX327_SLAVE) + case SONY_IMX327_SLAVE_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_SLAVE_MIPI_2M_30FPS_12BIT_WDR2TO1: + pSnsObj = &stSnsImx327_Slave_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX327_2L) + case SONY_IMX327_2L_MIPI_2M_30FPS_12BIT: + case SONY_IMX327_2L_MIPI_2M_30FPS_12BIT_WDR2TO1: + pSnsObj = &stSnsImx327_2l_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX327_FPGA) && defined(FPGA_PORTING) + case SONY_IMX327_MIPI_1M_30FPS_10BIT: + case SONY_IMX327_MIPI_1M_30FPS_10BIT_WDR2TO1: + pSnsObj = &stSnsImx327_fpga_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX327_SUBLVDS) + case SONY_IMX327_SUBLVDS_2M_30FPS_12BIT: + case SONY_IMX327_SUBLVDS_2M_30FPS_12BIT_WDR2TO1: + pSnsObj = &stSnsImx327_Sublvds_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX334) + case SONY_IMX334_MIPI_8M_30FPS_12BIT: + case SONY_IMX334_MIPI_8M_30FPS_12BIT_WDR2TO1: + pSnsObj = &stSnsImx334_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX335) + case SONY_IMX335_MIPI_2M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_4M_30FPS_12BIT: + case SONY_IMX335_MIPI_4M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_2L_MIPI_4M_30FPS_10BIT: + case SONY_IMX335_MIPI_4M_1600P_30FPS_12BIT: + case SONY_IMX335_MIPI_4M_1600P_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_5M_30FPS_12BIT: + case SONY_IMX335_MIPI_5M_30FPS_10BIT_WDR2TO1: + case SONY_IMX335_MIPI_2M_60FPS_10BIT: + case SONY_IMX335_MIPI_4M_60FPS_10BIT: + case SONY_IMX335_MIPI_5M_60FPS_10BIT: + pSnsObj = &stSnsImx335_Obj; + break; +#endif +#if defined(SENSOR_SONY_IMX347) + case SONY_IMX347_MIPI_4M_60FPS_12BIT: + case SONY_IMX347_MIPI_4M_30FPS_12BIT_WDR2TO1: + return &stSnsImx347_Obj; +#endif +#if defined(SENSOR_SONY_IMX385) + case SONY_IMX385_MIPI_2M_30FPS_12BIT: + case SONY_IMX385_MIPI_2M_30FPS_12BIT_WDR2TO1: + pSnsObj = &stSnsImx385_Obj; + break; +#endif +#if defined(SENSOR_TECHPOINT_TP2850) + case TECHPOINT_TP2850_MIPI_2M_30FPS_8BIT: + case TECHPOINT_TP2850_MIPI_4M_30FPS_8BIT: + pSnsObj = &stSnsTP2850_Obj; + break; +#endif +#if defined(SENSOR_TECHPOINT_TP2825) + case TECHPOINT_TP2825_MIPI_2M_30FPS_8BIT: + pSnsObj = &stSnsTP2825_Obj; + break; +#endif +#if defined(SENSOR_TECHPOINT_TP2863) + case TECHPOINT_TP2863_MIPI_1M_30FPS_8BIT: + case TECHPOINT_TP2863_MIPI_2M_30FPS_8BIT: + case TECHPOINT_TP2863_MIPI_1M_25FPS_8BIT: + case TECHPOINT_TP2863_MIPI_2M_25FPS_8BIT: + pSnsObj = &stSnsTP2863_Obj; + break; +#endif +#if defined(SENSOR_VIVO_MCS369) + case VIVO_MCS369_2M_30FPS_12BIT: + pSnsObj = &stSnsMCS369_Obj; + break; +#endif +#if defined(SENSOR_VIVO_MCS369Q) + case VIVO_MCS369Q_4M_30FPS_12BIT: + pSnsObj = &stSnsMCS369Q_Obj; + break; +#endif +#if defined(SENSOR_VIVO_MM308M2) + case VIVO_MM308M2_2M_25FPS_8BIT: + pSnsObj = &stSnsMM308M2_Obj; + break; +#endif +#if defined(SENSOR_LONTIUM_LT6911) + case LONTIUM_LT6911_2M_60FPS_8BIT: + pSnsObj = &stSnsLT6911_Obj; + break; +#endif + default: + pSnsObj = CVI_NULL; + break; + } + return pSnsObj; +} + +/****************************************************************************** + * funciton : Get sns_cfg from sensor_cfg.ini + ******************************************************************************/ + +/*=== Source section parser handler begin === */ +static void parse_source_type(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + (CVI_VOID) param0; + (CVI_VOID) param1; + (CVI_VOID) param2; + + SAMPLE_PRT("source type = %s\n", value); + if (strcmp(value, "SOURCE_USER_FE") == 0) { + cfg->enSource = VI_PIPE_FRAME_SOURCE_USER_FE; + } +} + +static void parse_source_devnum(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + int devno = atoi(value); + + (CVI_VOID) param0; + (CVI_VOID) param1; + (CVI_VOID) param2; + + SAMPLE_PRT("devNum = %s\n", value); + + if (devno >= 1 && devno <= VI_MAX_DEV_NUM) + cfg->devNum = devno; + else + cfg->devNum = 1; +} +/* === Source section parser handler end === */ + +/* === Sensor section parser handler begin === */ +static int parse_lane_id(CVI_S16 *LaneId, const char *value) +{ + char buf[8]; + int offset = 0, idx = 0, k; + + for (k = 0; k < 30; k++) { + /* find next ',' */ + if (value[k] == ',' || value[k] == '\0') { + if (k == offset) { + SAMPLE_PRT("lane_id parse error, is the format correct?\n"); + return -1; + } + memset(buf, 0, sizeof(buf)); + memcpy(buf, &value[offset], k - offset); + buf[k-offset] = '\0'; + LaneId[idx++] = atoi(buf); + offset = k + 1; + } + + if (value[k] == '\0' || idx == 5) + break; + } + + if (k == 30) { + SAMPLE_PRT("lane_id parse error, is the format correct?\n"); + return -1; + } + + return 0; +} + +static int parse_pn_swap(CVI_S8 *PNSwap, const char *value) +{ + char buf[8]; + int offset = 0, idx = 0, k; + + for (k = 0; k < 30; k++) { + /* find next ',' */ + if (value[k] == ',' || value[k] == '\0') { + if (k == offset) { + SAMPLE_PRT("lane_id parse error, is the format correct?\n"); + return -1; + } + memset(buf, 0, sizeof(buf)); + memcpy(buf, &value[offset], k - offset); + buf[k-offset] = '\0'; + PNSwap[idx++] = atoi(buf); + offset = k + 1; + } + + if (value[k] == '\0' || idx == 5) + break; + } + + if (k == 30) { + SAMPLE_PRT("lane_id parse error, is the format correct?\n"); + return -1; + } + + return 0; +} + +static void parse_sensor_name(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ +#define NAME_SIZE 20 + CVI_U32 index = param0; + CVI_U32 i; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("sensor = %s\n", value); + char sensorNameEnv[NAME_SIZE]; + + snprintf(sensorNameEnv, NAME_SIZE, "SENSORNAME%d", index); + setenv(sensorNameEnv, value, 1); + + for (i = 0; i < SAMPLE_SNS_TYPE_BUTT; i++) { + if (strcmp(value, snsr_type_name[i]) == 0) { + cfg->enSnsType[index] = i; + cfg->enWDRMode[index] = (i < SAMPLE_SNS_TYPE_LINEAR_BUTT) ? + WDR_MODE_NONE : WDR_MODE_2To1_LINE; + break; + } + } + if (i == SAMPLE_SNS_TYPE_BUTT) { + cfg->enSnsType[index] = SAMPLE_SNS_TYPE_BUTT; + cfg->enWDRMode[index] = WDR_MODE_NONE; + cfg->u8UseMultiSns = index; + SAMPLE_PRT("ERROR: can not find sensor ini in /mnt/data/\n"); + } +} + +static void parse_sensor_busid(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("bus_id = %s\n", value); + cfg->s32BusId[index] = atoi(value); +} + +static void parse_sensor_i2caddr(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("sns_i2c_addr = %s\n", value); + cfg->s32SnsI2cAddr[index] = atoi(value); +} + +static void parse_sensor_mipidev(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("mipi_dev = %s\n", value); + cfg->MipiDev[index] = atoi(value); +} + +static void parse_sensor_laneid(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("Lane_id = %s\n", value); + parse_lane_id(cfg->as16LaneId[index], value); +} + +static void parse_sensor_pnswap(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("pn_swap = %s\n", value); + parse_pn_swap(cfg->as8PNSwap[index], value); +} + +static void parse_sensor_hwsync(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("hw_sync = %s\n", value); + cfg->u8HwSync[index] = atoi(value); +} + +static void parse_sensor_mclken(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("mclk_en = %s\n", value); + cfg->stMclkAttr[index].bMclkEn = atoi(value); +} + +static void parse_sensor_mclk(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("mclk = %s\n", value); + cfg->stMclkAttr[index].u8Mclk = atoi(value); +} + +static void parse_sensor_orien(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("orien = %s\n", value); + cfg->u8Orien[index] = atoi(value); +} + +static void parse_sensor_muxdev(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("muxdev = %s\n", value); + cfg->u8MuxDev[index] = atoi(value); +} + +static void parse_sensor_attachdev(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("attach_dev = %s\n", value); + cfg->u8AttachDev[index] = atoi(value); +} + +static void parse_sensor_switchgpio(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("switch_gpio = %s\n", value); + cfg->s16SwitchGpio[index] = atoi(value); +} + +static void parse_sensor_switchpol(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2) +{ + CVI_U32 index = param0; + + (CVI_VOID) param1; + (CVI_VOID) param2; + SAMPLE_PRT("switch_pol = %s\n", value); + cfg->u8SwitchPol[index] = atoi(value); +} + + /* === Sensor section parser handler end === */ +typedef CVI_VOID(*parser)(SAMPLE_INI_CFG_S *cfg, const char *value, + CVI_U32 param0, CVI_U32 param1, CVI_U32 param2); + +typedef struct _INI_HDLR_S { + const char name[16]; + CVI_U32 param0; + CVI_U32 param1; + CVI_U32 param2; + parser pfnJob; +} INI_HDLR_S; + +typedef enum _INI_SOURCE_NAME_E { + INI_SOURCE_TYPE = 0, + INI_SOURCE_DEVNUM, + INI_SOURCE_NUM, +} INI_SOURCE_NAME_E; + +typedef enum _INI_SENSOR_NAME_E { + INI_SENSOR_NAME = 0, + INI_SENSOR_BUSID, + INI_SENSOR_I2CADDR, + INI_SENSOR_MIPIDEV, + INI_SENSOR_LANEID, + INI_SENSOR_PNSWAP, + INI_SENSOR_HWSYNC, + INI_SENSOR_MCLKEN, + INI_SENSOR_MCLK, + INI_SENSOR_ORIEN, + INI_SENSOR_MUXDEV, + INI_SENSOR_ATTACHDEV, + INI_SENSOR_SWITCHGPIO, + INI_SENSOR_SWITCHPOL, + INI_SENSOR_NUM, +} INI_SENSOR_NAME_E; + +const INI_HDLR_S stSectionSource[INI_SOURCE_NUM] = { + [INI_SOURCE_TYPE] = {"type", 0, 0, 0, parse_source_type}, + [INI_SOURCE_DEVNUM] = {"dev_num", 0, 0, 0, parse_source_devnum}, +}; + +const INI_HDLR_S stSectionSensor1[INI_SENSOR_NUM] = { + [INI_SENSOR_NAME] = {"name", 0, 0, 0, parse_sensor_name}, + [INI_SENSOR_BUSID] = {"bus_id", 0, 0, 0, parse_sensor_busid}, + [INI_SENSOR_I2CADDR] = {"sns_i2c_addr", 0, 0, 0, parse_sensor_i2caddr}, + [INI_SENSOR_MIPIDEV] = {"mipi_dev", 0, 0, 0, parse_sensor_mipidev}, + [INI_SENSOR_LANEID] = {"lane_id", 0, 0, 0, parse_sensor_laneid}, + [INI_SENSOR_PNSWAP] = {"pn_swap", 0, 0, 0, parse_sensor_pnswap}, + [INI_SENSOR_HWSYNC] = {"hw_sync", 0, 0, 0, parse_sensor_hwsync}, + [INI_SENSOR_MCLKEN] = {"mclk_en", 0, 0, 0, parse_sensor_mclken}, + [INI_SENSOR_MCLK] = {"mclk", 0, 0, 0, parse_sensor_mclk}, + [INI_SENSOR_ORIEN] = {"orien", 0, 0, 0, parse_sensor_orien}, + [INI_SENSOR_MUXDEV] = {"mux_dev", 0, 0, 0, parse_sensor_muxdev}, + [INI_SENSOR_ATTACHDEV] = {"attach_dev", 0, 0, 0, parse_sensor_attachdev}, + [INI_SENSOR_SWITCHGPIO] = {"switch_gpio", 0, 0, 0, parse_sensor_switchgpio}, + [INI_SENSOR_SWITCHPOL] = {"switch_pol", 0, 0, 0, parse_sensor_switchpol}, +}; + +const INI_HDLR_S stSectionSensor2[INI_SENSOR_NUM] = { + [INI_SENSOR_NAME] = {"name", 1, 0, 0, parse_sensor_name}, + [INI_SENSOR_BUSID] = {"bus_id", 1, 0, 0, parse_sensor_busid}, + [INI_SENSOR_I2CADDR] = {"sns_i2c_addr", 1, 0, 0, parse_sensor_i2caddr}, + [INI_SENSOR_MIPIDEV] = {"mipi_dev", 1, 0, 0, parse_sensor_mipidev}, + [INI_SENSOR_LANEID] = {"lane_id", 1, 0, 0, parse_sensor_laneid}, + [INI_SENSOR_PNSWAP] = {"pn_swap", 1, 0, 0, parse_sensor_pnswap}, + [INI_SENSOR_HWSYNC] = {"hw_sync", 1, 0, 0, parse_sensor_hwsync}, + [INI_SENSOR_MCLKEN] = {"mclk_en", 1, 0, 0, parse_sensor_mclken}, + [INI_SENSOR_MCLK] = {"mclk", 1, 0, 0, parse_sensor_mclk}, + [INI_SENSOR_ORIEN] = {"orien", 1, 0, 0, parse_sensor_orien}, + [INI_SENSOR_MUXDEV] = {"mux_dev", 1, 0, 0, parse_sensor_muxdev}, + [INI_SENSOR_ATTACHDEV] = {"attach_dev", 1, 0, 0, parse_sensor_attachdev}, + [INI_SENSOR_SWITCHGPIO] = {"switch_gpio", 1, 0, 0, parse_sensor_switchgpio}, + [INI_SENSOR_SWITCHPOL] = {"switch_pol", 1, 0, 0, parse_sensor_switchpol}, +}; + +const INI_HDLR_S stSectionSensor3[INI_SENSOR_NUM] = { + [INI_SENSOR_NAME] = {"name", 2, 0, 0, parse_sensor_name}, + [INI_SENSOR_BUSID] = {"bus_id", 2, 0, 0, parse_sensor_busid}, + [INI_SENSOR_I2CADDR] = {"sns_i2c_addr", 2, 0, 0, parse_sensor_i2caddr}, + [INI_SENSOR_MIPIDEV] = {"mipi_dev", 2, 0, 0, parse_sensor_mipidev}, + [INI_SENSOR_LANEID] = {"lane_id", 2, 0, 0, parse_sensor_laneid}, + [INI_SENSOR_PNSWAP] = {"pn_swap", 2, 0, 0, parse_sensor_pnswap}, + [INI_SENSOR_HWSYNC] = {"hw_sync", 2, 0, 0, parse_sensor_hwsync}, + [INI_SENSOR_MCLKEN] = {"mclk_en", 2, 0, 0, parse_sensor_mclken}, + [INI_SENSOR_MCLK] = {"mclk", 2, 0, 0, parse_sensor_mclk}, + [INI_SENSOR_ORIEN] = {"orien", 2, 0, 0, parse_sensor_orien}, + [INI_SENSOR_MUXDEV] = {"mux_dev", 2, 0, 0, parse_sensor_muxdev}, + [INI_SENSOR_ATTACHDEV] = {"attach_dev", 2, 0, 0, parse_sensor_attachdev}, + [INI_SENSOR_SWITCHGPIO] = {"switch_gpio", 2, 0, 0, parse_sensor_switchgpio}, + [INI_SENSOR_SWITCHPOL] = {"switch_pol", 2, 0, 0, parse_sensor_switchpol}, +}; + +CVI_S32 SAMPLE_COMM_SNS_SetIniPath(const CVI_CHAR *iniPath) +{ + int ret; + + if (iniPath == NULL) { + SAMPLE_PRT("%s: null ptr\n", __func__); + ret = CVI_FAILURE; + } else if (strlen(iniPath) >= SNSCFGPATH_SIZE) { + SAMPLE_PRT("%s: SNSCFGPATH_SIZE is too small\n", __func__); + ret = CVI_FAILURE; + } else { + strncpy(g_snsCfgPath, iniPath, SNSCFGPATH_SIZE); + ret = CVI_SUCCESS; + } + + return ret; +} + +static int parse_handler(void *user, const char *section, const char *name, const char *value) +{ + SAMPLE_INI_CFG_S *cfg = (SAMPLE_INI_CFG_S *)user; + const INI_HDLR_S *hdler; + int i, size; + + if (strcmp(section, "source") == 0) { + hdler = stSectionSource; + size = INI_SOURCE_NUM; + } else if (strcmp(section, "sensor") == 0) { + hdler = stSectionSensor1; + size = INI_SENSOR_NUM; + } else if (strcmp(section, "sensor2") == 0) { + hdler = stSectionSensor2; + size = INI_SENSOR_NUM; + } else if (strcmp(section, "sensor3") == 0) { + hdler = stSectionSensor3; + size = INI_SENSOR_NUM; + } else { + /* unknown section/name */ + return 1; + } + for (i = 0; i < size; i++) { + if (strcmp(name, hdler[i].name) == 0) { + hdler[i].pfnJob(cfg, value, hdler[i].param0, + hdler[i].param1, hdler[i].param2); + break; + } + } + + return 1; +} + +CVI_S32 SAMPLE_COMM_SNS_ParseIni(SAMPLE_INI_CFG_S *pstIniCfg) +{ + int ret; + + memcpy(pstIniCfg, &stDefIniCfg, sizeof(*pstIniCfg)); + if (g_snsCfgPath[0] != 0) { + SAMPLE_PRT("Parse %s\n", g_snsCfgPath); + ret = ini_parse(g_snsCfgPath, parse_handler, pstIniCfg); + if (ret >= 0) { + return CVI_SUCCESS; + } + if (ret != -1) { + SAMPLE_PRT("Parse %s incomplete, use default cfg\n", INI_FILE_PATH); + return CVI_FAILURE; + } + + SAMPLE_PRT("%s Not Found\n", g_snsCfgPath); + } + SAMPLE_PRT("Parse %s\n", INI_FILE_PATH); + ret = ini_parse(INI_FILE_PATH, parse_handler, pstIniCfg); + if (ret >= 0) { + return CVI_SUCCESS; + } + if (ret != -1) { + SAMPLE_PRT("Parse %s incomplete, use default cfg\n", INI_FILE_PATH); + return CVI_FAILURE; + } + SAMPLE_PRT("%s Not Found\n", INI_FILE_PATH); + SAMPLE_PRT("Parse %s\n", INI_DEF_PATH); + + ret = ini_parse(INI_DEF_PATH, parse_handler, pstIniCfg); + if (ret < 0) { + if (ret == -1) { + SAMPLE_PRT("%s not exist, use default cfg\n", INI_DEF_PATH); + } else { + SAMPLE_PRT("Parse %s incomplete, use default cfg\n", INI_DEF_PATH); + } + + return CVI_FAILURE; + } + + return CVI_SUCCESS; +} diff --git a/support/sg2002/additional/vision/CMakeLists.txt b/support/sg2002/additional/vision/CMakeLists.txt new file mode 100644 index 0000000..c683409 --- /dev/null +++ b/support/sg2002/additional/vision/CMakeLists.txt @@ -0,0 +1,77 @@ +# Config enable component2 or not in Kconfig +################# Add include ################# +list(APPEND ADD_INCLUDE "include" + "include/base" +) +if(PLATFORM_LINUX) + list(APPEND ADD_PRIVATE_INCLUDE "port/linux") +elseif(PLATFORM_MAIXCAM) + list(APPEND ADD_PRIVATE_INCLUDE "port/maixcam") +endif() +list(APPEND ADD_PRIVATE_INCLUDE "include_private") +############################################### + +############## Add source files ############### +# list(APPEND ADD_SRCS "src/lib2.c" +# ) +# FILE(GLOB_RECURSE EXTRA_SRC "src/*.c") +# FILE(GLOB EXTRA_SRC "src/*.c") +# list(APPEND ADD_SRCS ${EXTRA_SRC}) +# aux_source_directory(src ADD_SRCS) # collect all source file in src dir, will set var ADD_SRCS +append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS +if(PLATFORM_LINUX) + append_srcs_dir(ADD_SRCS "port/linux") +elseif(PLATFORM_MAIXCAM) + append_srcs_dir(ADD_SRCS "port/maixcam") +endif() +# list(REMOVE_ITEM COMPONENT_SRCS "src/test.c") +# set(ADD_ASM_SRCS "src/asm.S") +# list(APPEND ADD_SRCS ${ADD_ASM_SRCS}) +# SET_PROPERTY(SOURCE ${ADD_ASM_SRCS} PROPERTY LANGUAGE C) # set .S ASM file as C language +# SET_SOURCE_FILES_PROPERTIES(${ADD_ASM_SRCS} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp -D BBBBB") +############################################### + + +###### Add required/dependent components ###### +list(APPEND ADD_REQUIREMENTS basic opencv opencv_freetype websocket peripheral) +list(APPEND ADD_REQUIREMENTS omv) +list(APPEND ADD_REQUIREMENTS kvm_mmf) +############################################### + +###### Add link search path for requirements/libs ###### +# list(APPEND ADD_LINK_SEARCH_PATH "${CONFIG_TOOLCHAIN_PATH}/lib") +# list(APPEND ADD_REQUIREMENTS pthread m) # add system libs, pthread and math lib for example here +# set (OpenCV_DIR opencv/lib/cmake/opencv4) +# find_package(OpenCV REQUIRED) +############################################### + +############ Add static libs ################## +# list(APPEND ADD_STATIC_LIB "lib/libtest.a") +############################################### + +############ Add dynamic libs ################## +# list(APPEND ADD_DYNAMIC_LIB "lib/arch/v831/libmaix_nn.so" +# "lib/arch/v831/libmaix_cam.so" +# ) +############################################### + +#### Add compile option for this component #### +#### Just for this component, won't affect other +#### modules, including component that depend +#### on this component +# list(APPEND ADD_DEFINITIONS_PRIVATE -DAAAAA=1) + +#### Add compile option for this component +#### and components denpend on this component +# list(APPEND ADD_DEFINITIONS -DAAAAA222=1 +# -DAAAAA333=1) +############################################### + +############ Add static libs ################## +#### Update parent's variables like CMAKE_C_LINK_FLAGS +# set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group libmaix/libtest.a -ltest2 -Wl,--end-group" PARENT_SCOPE) +############################################### + +# register component, DYNAMIC or SHARED flags will make component compiled to dynamic(shared) lib +register_component() + diff --git a/support/sg2002/additional/vision/Kconfig b/support/sg2002/additional/vision/Kconfig new file mode 100644 index 0000000..e69de29 diff --git a/support/sg2002/additional/vision/include/base/maix_camera_base.hpp b/support/sg2002/additional/vision/include/base/maix_camera_base.hpp new file mode 100644 index 0000000..2e4eb79 --- /dev/null +++ b/support/sg2002/additional/vision/include/base/maix_camera_base.hpp @@ -0,0 +1,109 @@ +/** + * @file maix_camera_base.hpp + * @brief Maix camera SDL implementation + * @author neucrack@sipeed.com + * @license Apache 2.0 Sipeed Ltd + * @update date 2023-10-23 Create by neucrack +*/ + +#pragma once + +#include +#include "maix_image.hpp" +#include "maix_err.hpp" + +namespace maix::camera +{ + class CameraBase + { + public: + /** + * @brief Construct a new Camera object + * @param device camera device name, you can get devices by list_devices method, by default(value is NULL(None in MaixPy)) means the first device + * @param width camera width, by default(value is -1) means auto detect, + * if width > max device supported width, will auto set to max device supported width + * @param height camera height, by default(value is -1) means auto detect, + * if height > max device supported height, will auto set to max device supported height + * @param format camera format, by default(value is FMT_RGB888) + * @param buff_num camera buffer number, by default(value is 3) + */ + CameraBase(const char *device = nullptr, int width = -1, int height = -1, image::Format format = image::Format::FMT_RGB888, int buff_num = 3){}; + + /** + * @brief Judge if the given format is supported by the camera + */ + virtual bool is_support_format(image::Format format) = 0; + + /** + * @brief open camera device + * @param width camera width, by default(value is -1) means auto detect, + * if width > max device supported width, will auto set to max device supported width + * @param height camera height, by default(value is -1) means auto detect, + * if height > max device supported height, will auto set to max device supported height + * @param format camera format, by default(value is FMT_RGB888) + * @param buff_num camera buffer number, by default(value is 3) + * @return error code + */ + virtual err::Err open(int width = -1, int height = -1, image::Format format = image::Format::FMT_RGB888, int buff_num = 3) = 0; + + /** + * @brief read a frame from camera + * @param buff buffer to store image data, if NULL, will alloc a new buffer + * @return image data + */ + virtual image::Image *read(void *buff = NULL, size_t buff_size = 0) = 0; + + /** + * @brief close camera device + * @return none + */ + virtual void close() = 0; + + /** + * Add a new channel and return a new Camera object, you can use close() to close this channel. + * @param width camera width, default is -1, means auto, mostly means max width of camera support + * @param height camera height, default is -1, means auto, mostly means max height of camera support + * @param format camera output format, default is RGB888 + * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame, + * more than one buffer will accelerate image read speed, but will cost more memory. + * @return new Camera object + */ + virtual camera::CameraBase *add_channel(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, int buff_num = 3) = 0; + + /** + * @brief clear all buffer + * @return none + */ + virtual void clear_buff() = 0; + + /** + * @brief check camera device is opened or not + * @return opened or not, bool type + */ + virtual bool is_opened() = 0; + + /** + * Get camera supported channels(layers) + */ + virtual int get_ch_nums() = 0; + + /** + * Get channel number of camera. + */ + virtual int get_channel() = 0; + + /** + * Set/Get camera mirror + * @param en enable/disable mirror + */ + virtual int hmirror(int en) = 0; + + /** + * Set/Get camera flip + * @param en enable/disable flip + */ + virtual int vflip(int en) = 0; + }; + +} + diff --git a/support/sg2002/additional/vision/include/maix_camera.hpp b/support/sg2002/additional/vision/include/maix_camera.hpp new file mode 100644 index 0000000..ef15de3 --- /dev/null +++ b/support/sg2002/additional/vision/include/maix_camera.hpp @@ -0,0 +1,305 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include "maix_tensor.hpp" +#include "maix_log.hpp" +#include "maix_image.hpp" +#include "maix_err.hpp" +#include "maix_camera_base.hpp" +#include +#include +#include +#include +#include + +/** + * @brief maix.camera module, access camera device and get image from it + * @maixpy maix.camera +*/ +namespace maix::camera +{ + /** + * List all supported camera devices. + * @return Returns the path to the camera device. + * @maixpy maix.camera.list_devices + */ + std::vector list_devices(); + + /** + * Enable set camera registers, default is false, if set to true, will not set camera registers, you can manually set registers by write_reg API. + * @param enable enable/disable set camera registers + * @maixpy maix.camera.set_regs_enable + */ + void set_regs_enable(bool enable = true); + + /** + * Camera class + * @maixpy maix.camera.Camera + */ + class Camera + { + public: + /** + * @brief Construct a new Camera object. + * Maximum resolution support 2560x1440. + * @param width camera width, default is -1, means auto, mostly means max width of camera support + * @param height camera height, default is -1, means auto, mostly means max height of camera support + * @param format camera output format, default is image.Format.FMT_RGB888 + * @param device camera device path, you can get devices by list_devices method, by default(value is NULL(None in MaixPy)) means the first device + * @param fps camera fps, default is -1, means auto, mostly means max fps of camera support + * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame, + * more than one buffer will accelerate image read speed, but will cost more memory. + * @param open If true, camera will automatically call open() after creation. default is true. + * @maixpy maix.camera.Camera.__init__ + * @maixcdk maix.camera.Camera.Camera + */ + Camera(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, const char *device = nullptr, int fps = -1, int buff_num = 3, bool open = true); + + /** + * @brief Construct a new Camera object. + * @attention CameraBase * parameter need to be set manually, otherwise the operation of this object will be invalid. + * @param device camera device path, you can get devices by list_devices method, by default(value is NULL(None in MaixPy)) means the first device + * @param base basic operation objects. + * @param width camera width, default is -1, means auto, mostly means max width of camera support + * @param height camera height, default is -1, means auto, mostly means max height of camera support + * @param format camera output format, default is image.Format.FMT_RGB888 + * @param fps camera fps, default is -1, means auto, mostly means max fps of camera support + * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame, + * more than one buffer will accelerate image read speed, but will cost more memory. + * @param open If true, camera will automatically call open() after creation. default is true. + * @maixcdk maix.camera.Camera.Camera + */ + Camera(const char *device, CameraBase *base, int width = -1, int height = -1, image::Format format = image::FMT_RGB888, int fps = -1, int buff_num = -1, bool open = true); + + ~Camera(); + + void restart(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, const char *device = nullptr, int fps = -1, int buff_num = 3, bool open = true); + + /** + * Get the number of channels supported by the camera. + * @return Returns the maximum number of channels. + * @maixpy maix.camera.Camera.get_ch_nums + */ + int get_ch_nums(); + + /** + * Open camera and run + * @param width camera width, default is -1, means auto, mostly means max width of camera support + * @param height camera height, default is -1, means auto, mostly means max height of camera support + * @param format camera output format, default same as the constructor's format argument + * @param fps camera fps, default is -1, means auto, mostly means max fps of camera support + * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame, + * more than one buffer will accelerate image read speed, but will cost more memory. + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.camera.Camera.open + */ + err::Err open(int width = -1, int height = -1, image::Format format = image::FMT_INVALID, int fps = -1, int buff_num = -1); + + /** + * Get one frame image from camera buffer, must call open method before read. + * If open method not called, will call it automatically, if open failed, will throw exception! + * So call open method before read is recommended. + * @param buff buffer to store image data, if buff is nullptr, will alloc memory automatically. + * In MaixPy, default to None, you can create a image.Image object, then pass img.data() to buff. + * @param block block read, default is true, means block util read image successfully, + * if set to false, will return nullptr if no image in buffer + * @return image::Image object, if failed, return nullptr, you should delete if manually in C++ + * @maixpy maix.camera.Camera.read + */ + image::Image *read(void *buff = nullptr, size_t buff_size = 0, bool block = true); + + /** + * Clear buff to ensure the next read image is the latest image + * @maixpy maix.camera.Camera.clear_buff + */ + void clear_buff(); + + /** + * Read some frames and drop, this is usually used avoid read not stable image when camera just opened. + * @param num number of frames to read and drop + * @maixpy maix.camera.Camera.skip_frames + */ + void skip_frames(int num); + + /** + * Close camera + * @maixpy maix.camera.Camera.close + */ + void close(); + + /** + * Add a new channel and return a new Camera object, you can use close() to close this channel. + * @param width camera width, default is -1, means auto, mostly means max width of camera support + * @param height camera height, default is -1, means auto, mostly means max height of camera support + * @param format camera output format, default is RGB888 + * @param fps camera fps, default is -1, means auto, mostly means max fps of camera support + * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame, + * more than one buffer will accelerate image read speed, but will cost more memory. + * @param open If true, camera will automatically call open() after creation. default is true. + * @return new Camera object + * @maixpy maix.camera.Camera.add_channel + */ + camera::Camera *add_channel(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, int fps = -1, int buff_num = 3, bool open = true); + + /** + * Check if camera is opened + * @return true if camera is opened, false if not + * @maixpy maix.camera.Camera.is_opened + */ + bool is_opened(); + + /** + * @brief check camera device is closed or not + * @return closed or not, bool type + * @maixpy maix.camera.Camera.is_closed + */ + bool is_closed() { return !is_opened();} + + /** + * Get camera width + * @return camera width + * @maixpy maix.camera.Camera.width + */ + int width() + { + return _width; + } + + /** + * Get camera height + * @return camera height + * @maixpy maix.camera.Camera.height + */ + int height() + { + return _height; + } + + /** + * Get camera fps + * @return camera fps + * @maixpy maix.camera.Camera.fps + */ + int fps() + { + return _fps; + } + + /** + * Get camera output format + * @return camera output format, image::Format object + * @maixpy maix.camera.Camera.format + */ + image::Format format() + { + return _format; + } + + /** + * Get camera buffer number + * @return camera buffer number + * @maixpy maix.camera.Camera.buff_num + */ + int buff_num() + { + return _buff_num; + } + + /** + * Set/Get camera horizontal mirror + * @return camera horizontal mirror + * @maixpy maix.camera.Camera.hmirror + */ + int hmirror(int value = -1); + + /** + * Set/Get camera vertical flip + * @return camera vertical flip + * @maixpy maix.camera.Camera.vflip + */ + int vflip(int value = -1); + + /** + * Get camera device path + * @return camera device path + * @maixpy maix.camera.Camera.device + */ + std::string device() + { + return _device; + } + + /** + * Write camera register + * @param addr register address + * @param data register data + * @param bit_width register data bit width, default is 8 + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.camera.Camera.write_reg + */ + err::Err write_reg(int addr, int data, int bit_width = 8) + { + return err::ERR_NONE; + } + + /** + * Read camera register + * @param addr register address + * @return register data, -1 means failed + * @param bit_width register data bit width, default is 8 + * @maixpy maix.camera.Camera.read_reg + */ + int read_reg(int addr, int bit_width = 8) + { + return -1; + } + + /** + * Camera output color bar image for test + * @param enable enable/disable color bar + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.camera.Camera.show_colorbar + */ + err::Err show_colorbar(bool enable); + + /** + * Get channel of camera + * @return channel number + * @maixpy maix.camera.Camera.get_channel + */ + int get_channel(); + + /** + * Set camera resolution + * @param width new width + * @param height new height + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.camera.Camera.set_resolution + */ + err::Err set_resolution(int width, int height); + + private: + std::string _device; + int _ch; + int _width; + int _height; + int _fps; + int _buff_num; + image::Format _format; + image::Format _format_impl; // used by implement code and need convert to _format + int _hmirror; + int _vflip; + float _exposure; + float _gain; + bool _show_colorbar; + bool _open_set_regs; + CameraBase *_impl; // used by implement code + bool _check_format(image::Format format); + }; +} diff --git a/support/sg2002/additional/vision/include/maix_image.hpp b/support/sg2002/additional/vision/include/maix_image.hpp new file mode 100644 index 0000000..7422bfe --- /dev/null +++ b/support/sg2002/additional/vision/include/maix_image.hpp @@ -0,0 +1,1540 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include "maix_tensor.hpp" +#include "maix_log.hpp" +#include "maix_err.hpp" +#include "maix_fs.hpp" +#include "maix_image_def.hpp" +#include "maix_image_color.hpp" +#include "maix_image_obj.hpp" +#include "maix_type.hpp" +#include + +/** + * @brief maix.image module, image related definition and functions + * @maixpy maix.image + */ +namespace maix::image +{ + + /** + * map point position or rectangle position from one image size to another image size(resize) + * @param int w_in original image width + * @param int h_in original image height + * @param int w_out target image width + * @param int h_out target image height + * @param fit resize method, see maix.image.Fit + * @param x original point x, or rectagle left-top point's x + * @param y original point y, or rectagle left-top point's y + * @param w original rectagle width, can be -1 if not use this arg, default -1. + * @param h original rectagle height, can be -1 if not use this arg, default -1. + * @return list type, [x, y] if map point, [x, y, w, h] if resize rectangle. + * @maixpy maix.image.resize_map_pos + */ + std::vector resize_map_pos(int w_in, int h_in, int w_out, int h_out, image::Fit fit, int x, int y, int w = -1, int h = -1); + + /** + * reverse resize_map_pos method, when we call image.resize method resiz image 'a' to image 'b', we want to known the original position on 'a' whith a knew point on 'b' + * @param int w_in original image width + * @param int h_in original image height + * @param int w_out image width after resized + * @param int h_out image height after resized + * @param fit resize method, see maix.image.Fit + * @param x point on resized image x, or rectagle left-top point's x + * @param y original point y, or rectagle left-top point's y + * @param w original rectagle width, can be -1 if not use this arg, default -1. + * @param h original rectagle height, can be -1 if not use this arg, default -1. + * @return list type, [x, y] if map point, [x, y, w, h] if resize rectangle. + * @maixpy maix.image.resize_map_pos_reverse + */ + std::vector resize_map_pos_reverse(int w_in, int h_in, int w_out, int h_out, image::Fit fit, int x, int y, int w = -1, int h = -1); + + /** + * Image class + * @maixpy maix.image.Image + */ + class Image + { + public: + /** + * Image constructor + * + * @param width image width, should > 0 + * @param height image height, should > 0 + * @param format image format @see image::Format + * @maixpy maix.image.Image.__init__ + * @maixcdk maix.image.Image.Image + */ + Image(int width, int height, image::Format format = image::Format::FMT_RGB888); + // Image(int width, int height, image::Format format = image::Format::FMT_RGB888, Bytes *data = nullptr, bool copy = true); + + /** + * Image constructor + * + * @param width image width, should > 0 + * @param height image height, should > 0 + * @param format image format @see image::Format + * @param data image data, if data is nullptr, will malloc memory for image data + * If the image is in jpeg format, data must be filled in. + * @param data_size image data size, only for compressed format like jpeg png, data_size must be filled in, or should be -1, default is -1. + * @param copy if true and data is not nullptr, will copy data to new buffer, else will use data directly. default is true to avoid memory leak. + * @maixcdk maix.image.Image.Image + */ + Image(int width, int height, image::Format format, uint8_t *data, int data_size, bool copy); + + Image() { + _width = 0; + _height = 0; + _format = image::Format::FMT_INVALID; + _data = nullptr; + _data_size = 0; + _is_malloc = false; + } + ~Image(); + + void operator=(const image::Image &img); + + //************************** get and set basic info **************************// + + /** + * Get image's format @see image.Format + * @maixpy maix.image.Image.format + */ + image::Format format() { return this->_format; } + + /** + * Get image's size, [width, height] + * @maixpy maix.image.Image.size + */ + image::Size size() { return Size(_width, _height); } + + std::vector shape() { return std::vector{_height, _width, (int)fmt_size[_format]}; } + + /** + * Get image's data size + * @maixpy maix.image.Image.data_size + */ + int data_size() { return _data_size; } + + /** + * Get image's width + * @maixpy maix.image.Image.width + */ + int width() { return _width; } + + /** + * Get image's height + * @maixpy maix.image.Image.height + */ + int height() { return _height; } + + /** + * Get image's data pointer. + * In MaixPy is capsule object. + * @maixpy maix.image.Image.data + */ + void *data(){ return _data; } + + /** + * To string method + * @maixpy maix.image.Image.__str__ + */ + std::string __str__(); + + /** + * To string method + * @maixpy maix.image.Image.to_str + */ + std::string to_str() { return __str__(); } + + /** + * Get pixel of image + * @param x pixel's coordinate x. x must less than image's width + * @param y pixel's coordinate y. y must less than image's height + * @param rgbtuple switch return value method. rgbtuple decides whether to split the return or not. default is false. + * @return pixel value, + * According to image format and rgbtuple, return different value: + * format is FMT_RGB888, rgbtuple is true, return [R, G, B]; rgbtuple is false, return [RGB] + * foramt is FMT_BGR888, rgbtuple is true, return [B, G, R]; rgbtuple is false, return [BGR] + * format is FMT_GRAYSCALE, return [GRAY]; + * + * @maixpy maix.image.Image.get_pixel + */ + std::vector get_pixel(int x, int y, bool rgbtuple = false) { + std::vector pixels; + if (!(_format == image::Format::FMT_RGB888 || _format == image::Format::FMT_BGR888 || + _format == image::Format::FMT_RGB565 || _format == image::Format::FMT_BGR565 || + _format == image::Format::FMT_GRAYSCALE)) { + log::error("get_pixel not support format: %d\r\n", _format); + return pixels; + } + + if (x < 0 || x >= _width || y < 0 || y >= _height) { + log::error("get_pixel out of range: (%d, %d)\r\n", x, y); + return pixels; + } + + switch (_format) { + case image::Format::FMT_RGB888: // fall through + case image::Format::FMT_BGR888: + { + uint8_t v0 = ((uint8_t *)_data)[y * _width * 3 + x * 3 + 0]; + uint8_t v1 = ((uint8_t *)_data)[y * _width * 3 + x * 3 + 1]; + uint8_t v2 = ((uint8_t *)_data)[y * _width * 3 + x * 3 + 2]; + if (!rgbtuple) { + int value = v0 << 16 | v1 << 8 | v2; + pixels.push_back(value); + } else { + pixels.push_back(v0); + pixels.push_back(v1); + pixels.push_back(v2); + } + break; + } + case image::Format::FMT_GRAYSCALE: + pixels.push_back(((uint8_t *)_data)[y * _width + x]); + break; + case image::Format::FMT_BGR565: // fall through + case image::Format::FMT_RGB565: + { + if (!rgbtuple) { + int value = ((uint16_t *)_data)[y * _width + x] & 0xffff; + pixels.push_back(value); + } else { + int value = ((uint16_t *)_data)[y * _width + x] & 0xffff; + int v0 = (value >> 11) & 0x1F; + int v1 = (value >> 5) & 0x3F; + int v2 = value & 0x1F; + pixels.push_back(v0); + pixels.push_back(v1); + pixels.push_back(v2); + } + break; + } + default: + log::error("get_pixel not support format: %d\r\n", _format); + break; + } + + return pixels; + } + + /** + * Set pixel of image + * @param x pixel's coordinate x. x must less than image's width + * @param y pixel's coordinate y. y must less than image's height + * @param pixel pixel value, according to image format and size of pixel, has different operation: + * format is FMT_RGB888, pixel size must be 1 or 3, if size is 1, will split pixel[0] to [R, G, B]; if size is 3, will use pixel directly + * format is FMT_BGR888, pixel size must be 1 or 3, if size is 1, will split pixel[0] to [B, G, R]; if size is 3, will use pixel directly + * format is FMT_GRAYSCALE, pixel size must be 1, will use pixel directly + * @return error code, Err::ERR_NONE is ok, other is error + * @maixpy maix.image.Image.set_pixel + */ + err::Err set_pixel(int x, int y, std::vector pixel) { + if (!(_format == image::Format::FMT_RGB888 || _format == image::Format::FMT_BGR888 || + _format == image::Format::FMT_RGB565 || _format == image::Format::FMT_BGR565 || + _format == image::Format::FMT_GRAYSCALE)) { + log::error("get_pixel not support format: %d\r\n", _format); + return err::Err::ERR_RUNTIME; + } + + if (x < 0 || x >= _width || y < 0 || y >= _height) { + log::error("get_pixel out of range: (%d, %d)\r\n", x, y); + return err::Err::ERR_RUNTIME; + } + + switch (_format) { + case image::Format::FMT_RGB888: // fall through + case image::Format::FMT_BGR888: + if (pixel.size() == 1) { + uint8_t v0 = pixel[0]; + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 0] = (v0 >> 16) & 0xFF; + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 1] = (v0 >> 8) & 0xFF; + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 2] = v0 & 0xFF; + } else if (pixel.size() == 3) { + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 0] = pixel[0]; + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 1] = pixel[1]; + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 2] = pixel[2]; + } else { + log::error("set_pixel pixel size must be 1 or 3, but %d\r\n", pixel.size()); + return err::Err::ERR_RUNTIME; + } + break; + case image::Format::FMT_GRAYSCALE: + if (pixel.size() == 1) { + ((uint8_t *)_data)[y * _width + x] = pixel[0]; + } else { + log::error("set_pixel pixel size must be 1, but %d\r\n", pixel.size()); + return err::Err::ERR_RUNTIME; + } + break; + case image::Format::FMT_BGR565: // fall through + case image::Format::FMT_RGB565: + { + if (pixel.size() == 1) { + ((uint16_t *)_data)[y * _width + x] = pixel[0]; + } else if (pixel.size() == 3) { + int num = ((pixel[0] & 0x1F) << 11) | ((pixel[1] & 0x3F) << 5) | (pixel[2] & 0x1F); + ((uint16_t *)_data)[y * _width + x] = num; + } else { + log::error("set_pixel pixel size must be 1 or 3, but %d\r\n", pixel.size()); + return err::Err::ERR_RUNTIME; + } + break; + } + + default: + log::error("get_pixel not support format: %d\r\n", _format); + break; + } + + return err::Err::ERR_NONE; + } + + //************************** convert format **************************// + // more maixpy convert func in MaixPy project's convert_image.hpp + + /** + * Convert Image object to tensor::Tensor object + * @param chw if true, the shape of tensor is [C, H, W], else [H, W, C] + * @param copy if true, will alloc memory for tensor data, else will use the memory of Image object + * @return tensor::Tensor object pointer, an allocated tensor object + * @maixpy maix.image.Image.to_tensor + */ + tensor::Tensor *to_tensor(bool chw = false, bool copy = true); + + /** + * Get image's data and convert to array bytes + * @param copy if true, will alloc memory and copy data to new buffer, + * else will use the memory of Image object, delete bytes object will not affect Image object, + * but delete Image object will make bytes object invalid, it may cause program crash !!!! + * So use this param carefully. + * @return image's data bytes, need be delete by caller in C++. + * @maixpy maix.image.Image.to_bytes + */ + Bytes *to_bytes(bool copy = true); + + /** + * Convert image to specific format + * @param format format want to convert to, @see image::Format, only support RGB888, BGR888, RGBA8888, BGRA8888, GRAYSCALE, JPEG. + * @return new image object. Need be delete by caller in C++. + * @throw err.Exception, if two images' format not support, **or already the format**, will raise exception + * @maixpy maix.image.Image.to_format + */ + image::Image *to_format(const image::Format &format); + + + /** + * Convert image to specific format + * @param format format want to convert to, @see image::Format, only support RGB888, BGR888, RGBA8888, BGRA8888, GRAYSCALE, JPEG. + * @param buff user's buffer, if buff is nullptr, will malloc memory for new image data, else will use buff directly + * @return new image object. Need be delete by caller in C++. + * @throw err.Exception, if two images' format not support, **or already the format**, will raise exception + * @maixcdk maix.image.Image.to_format + */ + image::Image *to_format(const image::Format &format, void *buff, size_t buff_size); + + /** + * Convert image to jpeg + * @param quality the quality of jpg, default is 95. range is (50, 100]. + * @return new image object. Need be delete by caller in C++. + * @throw err.Exception, if two images' format not support, **or already the format**, will raise exception + * @maixpy maix.image.Image.to_jpeg + */ + image::Image *to_jpeg(int quality = 95); + + //************************** draw **************************// + + /** + * Draw image on this image + * @param x left top corner of image point's coordinate x + * @param y left top corner of image point's coordinate y + * @param img image object to draw, the caller's channel must <= the args' channel, + * e.g. caller is RGB888, args is RGBA8888, will throw exception, but caller is RGBA8888, args is RGB888 or RGBA8888 is ok + * @return this image object self + * @maixpy maix.image.Image.draw_image + */ + image::Image *draw_image(int x, int y, image::Image &img); + + /** + * Fill rectangle color to image + * @param x left top corner of rectangle point's coordinate x + * @param y left top corner of rectangle point's coordinate y + * @param w rectangle width + * @param h rectangle height + * @param color rectangle color + * @param thickness rectangle thickness(line width), by default(value is 1), -1 means fill rectangle + * @return this image object self + * @maixpy maix.image.Image.draw_rect + */ + image::Image *draw_rect(int x, int y, int w, int h, const image::Color &color, int thickness = 1); + + /** + * Draw line on image + * @param x1 start point's coordinate x + * @param y1 start point's coordinate y + * @param x2 end point's coordinate x + * @param y2 end point's coordinate y + * @param color line color @see image::Color + * @param thickness line thickness(line width), by default(value is 1) + * @return this image object self + * @maixpy maix.image.Image.draw_line + */ + image::Image *draw_line(int x1, int y1, int x2, int y2, const image::Color &color, int thickness = 1); + + /** + * Draw circle on image + * @param x circle center point's coordinate x + * @param y circle center point's coordinate y + * @param radius circle radius + * @param color circle color @see image::Color + * @param thickness circle thickness(line width), by default(value is 1), -1 means fill circle + * @return this image object self + * @maixpy maix.image.Image.draw_circle + */ + image::Image *draw_circle(int x, int y, int radius, const image::Color &color, int thickness = 1); + + /** + * Draw ellipse on image + * @param x ellipse center point's coordinate x + * @param y ellipse center point's coordinate y + * @param a ellipse major axis length + * @param b ellipse minor axis length + * @param angle ellipse rotation angle + * @param start_angle ellipse start angle + * @param end_angle ellipse end angle + * @param color ellipse color @see image::Color + * @param thickness ellipse thickness(line width), by default(value is 1), -1 means fill ellipse + * @return this image object self + * @maixpy maix.image.Image.draw_ellipse + */ + image::Image *draw_ellipse(int x, int y, int a, int b, float angle, float start_angle, float end_angle, const image::Color &color, int thickness = 1); + + /** + * Draw text on image + * @param x text left top point's coordinate x + * @param y text left top point's coordinate y + * @param string text content + * @param color text color @see image::Color, default is white + * @param scale font scale, by default(value is 1) + * @param thickness text thickness(line width), if negative, the glyph is filled, by default(value is -1) + * @param wrap if true, will auto wrap text to next line if text width > image width, by default(value is true) + * @return this image object self + * @maixpy maix.image.Image.draw_string + */ + image::Image *draw_string(int x, int y, const std::string &textstring, const image::Color &color = image::COLOR_WHITE, float scale = 1, int thickness = -1, + bool wrap = true, int wrap_space = 4, const std::string &font = ""); + + /** + * Draw cross on image + * @param x cross center point's coordinate x + * @param y cross center point's coordinate y + * @param color cross color @see image::Color + * @param size how long the lines of the cross extend, by default(value is 5). So the line length is `2 * size + thickness` + * @param thickness cross thickness(line width), by default(value is 1) + * @maixpy maix.image.Image.draw_cross + */ + image::Image *draw_cross(int x, int y, const image::Color &color, int size = 5, int thickness = 1); + + /** + * Draw arrow on image + * @param x0 start coordinate of the arrow x0 + * @param y0 start coordinate of the arrow y0 + * @param x1 end coordinate of the arrow x1 + * @param y1 end coordinate of the arrow y1 + * @param color cross color @see image::Color + * @param thickness cross thickness(line width), by default(value is 1) + * @return this image object self + * @maixpy maix.image.Image.draw_arrow + */ + image::Image *draw_arrow(int x0, int y0, int x1, int y1, const image::Color &color, int thickness = 1); + + /** + * Draw edges on image + * @param corners edges, [[x0, y0], [x1, y1], [x2, y2], [x3, y3]] + * @param color edges color @see image::Color + * @param size the circle of radius size. TODO: support in the feature + * @param thickness edges thickness(line width), by default(value is 1) + * @param fill if true, will fill edges, by default(value is false) + * @return this image object self + * @maixpy maix.image.Image.draw_edges + */ + image::Image *draw_edges(std::vector> corners, const image::Color &color, int size = 0, int thickness = 1, bool fill = false); + + /** + * Draw keypoints on image + * @param keypoints keypoints, [x1, y1, x2, y2...] or [x, y, rotation_andle_in_degrees, x2, y2, rotation_andle_in_degrees2](TODO: rotation_andle_in_degrees support in the feature) + * @param color keypoints color @see image::Color + * @param size size of keypoints + * @param thickness keypoints thickness(line width), by default(value is -1 means fill circle) + * @return this image object self + * @maixpy maix.image.Image.draw_keypoints + */ + image::Image *draw_keypoints(std::vector keypoints, const image::Color &color, int size = 10, int thickness = -1); + + //************************** image operations **************************// + + /** + * Resize image, will create a new resized image object + * @param width new width, if value is -1, will use height to calculate aspect ratio + * @param height new height, if value is -1, will use width to calculate aspect ratio + * @param object_fit fill, contain, cover, by default is fill + * @param method resize method, by default is bilinear + * @return Always return a new resized image object even size not change, So in C++ you should take care of the return value to avoid memory leak. + * And it's better to judge whether the size has changed before calling this function to make the program more efficient. + * e.g. + * if img->width() != width || img->height() != height: + * img = img->resize(width, height); + * @maixpy maix.image.Image.resize + */ + image::Image *resize(int width, int height, image::Fit object_fit = image::Fit::FIT_FILL, image::ResizeMethod method = image::ResizeMethod::NEAREST); + + /** + * Affine transform image, will create a new transformed image object + * @param src_points three source points, [x1, y1, x2, y2, x3, y3] + * @param dst_points three destination points, [x1, y1, x2, y2, x3, y3] + * @param width new width, if value is -1, will use height to calculate aspect ratio + * @param height new height, if value is -1, will use width to calculate aspect ratio + * @param method resize method, by default is bilinear + * @return new transformed image object + * @maixpy maix.image.Image.affine + */ + image::Image *affine(std::vector src_points, std::vector dst_points, int width = -1, int height = -1, image::ResizeMethod method = image::ResizeMethod::BILINEAR); + + /** + * Copy image, will create a new copied image object + * @return new copied image object + * @maixpy maix.image.Image.copy + */ + image::Image *copy(); + + /** + * Crop image, will create a new cropped image object + * @param x left top corner of crop rectangle point's coordinate x + * @param y left top corner of crop rectangle point's coordinate y + * @param w crop rectangle width + * @param h crop rectangle height + * @return new cropped image object + * @maixpy maix.image.Image.crop + */ + image::Image *crop(int x, int y, int w, int h); + + /** + * Rotate image, will create a new rotated image object + * @param angle anti-clock wise rotate angle, if angle is 90 or 270, and width or height is -1, will swap width and height, or will throw exception + * @param width new width, if value is -1, will use height to calculate aspect ratio + * @param height new height, if value is -1, will use width to calculate aspect ratio + * @param method resize method, by default is bilinear + * @return new rotated image object + * @maixpy maix.image.Image.rotate + */ + image::Image *rotate(float angle, int width = -1, int height = -1, image::ResizeMethod method = image::ResizeMethod::BILINEAR); + + /** + * @brief Finds the mean of x_div * y_div squares in the image and returns the modified image composed of the mean of each square. + * @param x_div The width of the squares. + * @param y_div The height of the squares. + * @param copy Select whether to return a new image or modify the original image. default is false. + * If true, returns a new image composed of the mean of each square; If false, returns the modified image composed of the mean of each square. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mean_pool + */ + image::Image *mean_pool(int x_div, int y_div, bool copy = false); + + /** + * @brief Finds the midpoint of x_div * y_div squares in the image and returns the modified image composed of the mean of each square. + * @param x_div The width of the squares. + * @param y_div The height of the squares. + * @param bias The bias of the midpoint. default is 0.5. + * midpoint value is equal to (max * bias + min * (1 - bias)) + * @param copy Select whether to return a new image or modify the original image. default is false. + * If true, returns a new image composed of the midpoint of each square; If false, returns the modified image composed of the midpoint of each square. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.midpoint_pool + */ + image::Image *midpoint_pool(int x_div, int y_div, double bias = 0.5, bool copy = false); + + /** + * @brief JPEG compresses the image in place, the same as to_jpeg functioin, it's recommend to use to_jpeg instead. + * @param quality The quality of the compressed image. default is 95. + * @return Returns the compressed JPEG image + * @maixpy maix.image.Image.compress + */ + image::Image *compress(int quality = 95); + + /** + * @brief Sets all pixels in the image to zero + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.clear + */ + image::Image *clear(image::Image *mask = nullptr); + + /** + * @brief Zeros a rectangular part of the image. If no arguments are supplied this method zeros the center of the image. + * @param x The x coordinate of the top left corner of the rectangle. + * @param y The y coordinate of the top left corner of the rectangle. + * @param w The width of the rectangle. + * @param h The height of the rectangle. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mask_rectange + */ + image::Image *mask_rectange(int x = -1, int y = -1, int w = -1, int h = -1); + + /** + * @brief Zeros a circular part of the image. If no arguments are supplied this method zeros the center of the image. + * @param x The x coordinate of the center of the circle. + * @param y The y coordinate of the center of the circle. + * @param radius The radius of the circle. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mask_circle + */ + image::Image *mask_circle(int x = -1, int y = -1, int radius = -1); + + /** + * @brief Zeros a ellipse part of the image. If no arguments are supplied this method zeros the center of the image. + * @param x The x coordinate of the center of the ellipse. + * @param y The y coordinate of the center of the ellipse. + * @param radius_x The radius of the ellipse in the x direction. + * @param radius_y The radius of the ellipse in the y direction. + * @param rotation_angle_in_degrees The rotation angle of the ellipse in degrees. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mask_ellipse + */ + image::Image *mask_ellipse(int x = -1, int y = -1, int radius_x = -1, int radius_y = -1, float rotation_angle_in_degrees = 0); + + /** + * @brief Sets all pixels in the image to black or white depending on if the pixel is inside of a threshold in the threshold list thresholds or not. + * @param thresholds You can define multiple thresholds. + * For GRAYSCALE format, you can use {{Lmin, Lmax}, ...} to define one or more thresholds. + * For RGB888 format, you can use {{Lmin, Lmax, Amin, Amax, Bmin, Bmax}, ...} to define one or more thresholds. + * Where the upper case L,A,B represent the L,A,B channels of the LAB image format, and min, max represent the minimum and maximum values of the corresponding channels. + * @param invert If true, the thresholds will be inverted before the operation. default is false. + * @param zero If zero is true, the image will be set the pixels within the threshold to 0, other pixels remain unchanged. If zero is false, the image will be set to black or white. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @param to_bitmap If true, the image will be converted to a bitmap image before thresholding. default is false. TODO: support in the feature + * @param copy Select whether to return a new image or modify the original image. default is false. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.binary + */ + image::Image *binary(std::vector> thresholds = std::vector>(), bool invert = false, bool zero = false, image::Image *mask = nullptr, bool to_bitmap = false, bool copy = false); + + /** + * @brief Inverts the image in place. + * @return Returns the image after the operation is completed + * @maixpy maix.image.Image.invert + */ + image::Image *invert(); + + /** + * @brief Performs a bitwise and operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_and + */ + image::Image *b_and(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs a bitwise nand operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_nand + */ + image::Image *b_nand(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs a bitwise or operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_or + */ + image::Image *b_or(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs a bitwise nor operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_nor + */ + image::Image *b_nor(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs a bitwise xor operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_xor + */ + image::Image *b_xor(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs a bitwise xnor operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_xnor + */ + image::Image *b_xnor(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs an auto white balance operation on the image. TODO: support in the feature + * @param max if True uses the white-patch algorithm instead. default is false. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.awb + */ + image::Image *awb(bool max = false); + + /** + * @brief Multiples the passed (3x3) or (4x3) floating-point color-correction-matrix with the image. + * note: Grayscale format is not support. + * @param matrix The color correction matrix to use. 3x3 or 4x3 matrix. + * Weights may either be positive or negative, and the sum of each column in the 3x3 matrix should generally be 1. + * example: + * { + * 1, 0, 0, + * 0, 1, 0, + * 0, 0, 1, + * } + * + * Where the last row of the 4x3 matrix is an offset per color channel. If you add an offset you may wish to make the + * weights sum to less than 1 to account for the offset. + * example: + * { + * 1, 0, 0, + * 0, 1, 0, + * 0, 0, 1, + * 0, 0, 0, + * } + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.ccm + */ + image::Image *ccm(std::vector &matrix); + + /** + * @brief Quickly changes the image gamma, contrast, and brightness. Create a array whose size is usually 255, + * and use the parameters gamma, contrast, and brightness to calculate the value of the array, and then map the + * image pixel value through the value of the array. + * The calculation method for array is: array[array_idx] = (powf((array_idx / 255.0), (1 / gamma)) * contrast + brightness) * scale, + * `powf` is a function used to calculate floating point power. + * `array` is the array used for mapping. + * `array_idx` is the index of the array, the maximum value is determined according to the image format, usually 255. + * `scale` is a constant, the value is determined by the image format, usually 255. + * Mapping method: + * Assume that a pixel value in the image is 128, then map the pixel value to the value of array[128] + * Users can adjust the value of the array through the gamma, contrast, and brightness parameters. + * @param gamma The contrast gamma greater than 1.0 makes the image darker in a non-linear manner while less than 1.0 makes the image brighter. default is 1.0. + * @param contrast The contrast value greater than 1.0 makes the image brighter in a linear manner while less than 1.0 makes the image darker. default is 1.0. + * @param brightness The brightness value greater than 0.0 makes the image brighter in a constant manner while less than 0.0 makes the image darker. default is 0.0. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.gamma + */ + image::Image *gamma(double gamma = 1.0, double contrast = 1.0, double brightness = 0.0); + + /** + * @brief Alias for Image.gamma. + * @param gamma The contrast gamma greater than 1.0 makes the image darker in a non-linear manner while less than 1.0 makes the image brighter. default is 1.0. + * @param contrast The contrast value greater than 1.0 makes the image brighter in a linear manner while less than 1.0 makes the image darker. default is 1.0. + * @param brightness The brightness value greater than 0.0 makes the image brighter in a constant manner while less than 0.0 makes the image darker. default is 0.0. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.gamma_corr + */ + image::Image *gamma_corr(double gamma, double contrast = 1.0, double brightness = 0.0); + + /** + * @brief Flips (numerically inverts) all pixels values in an image + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.negate + */ + image::Image *negate(); + + /** + * @brief Replaces all pixels in the image with the corresponding pixels in the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param hmirror If true, the image will be horizontally mirrored before the operation. default is false. + * @param vflip If true, the image will be vertically flipped before the operation. default is false. + * @param transpose If true, the image can be used to rotate 90 degrees or 270 degrees. + * hmirror = false, vflip = false, transpose = false, the image will not be rotated. + * hmirror = false, vflip = true, transpose = true, the image will be rotated 90 degrees. + * hmirror = true, vflip = true, transpose = false, the image will be rotated 180 degrees. + * hmirror = true, vflip = false, transpose = true, the image will be rotated 270 degrees. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.replace + */ + image::Image *replace(image::Image *other = nullptr, bool hmirror = false, bool vflip = false, bool transpose = false, image::Image *mask = nullptr); + + /** + * @brief Alias for Image::replace. + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param hmirror If true, the image will be horizontally mirrored before the operation. default is false. + * @param vflip If true, the image will be vertically flipped before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.set + */ + image::Image *set(image::Image *other, bool hmirror = false, bool vflip = false, bool transpose = false, image::Image *mask = nullptr); + + /** + * @brief Adds the other image to the image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.add + */ + image::Image *add(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Subtracts the other image from the image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param reverse If true, the image will be reversed before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.sub + */ + image::Image *sub(image::Image *other, bool reverse = false, image::Image *mask = nullptr); + + /** + * @brief Multiplies the image by the other image. + * Note: This method is meant for image blending and cannot multiply the pixels in the image by a scalar like 2. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param invert If true, the image will be change the multiplication operation from a*b to 1/((1/a)*(1/b)). + * In particular, this lightens the image instead of darkening it (e.g. multiply versus burn operations). default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mul + */ + image::Image *mul(image::Image *other, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Divides the image by the other image. + * This method is meant for image blending and cannot divide the pixels in the image by a scalar like 2. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param invert If true, the image will be change the division direction from a/b to b/a. default is false. + * @param mod If true, the image will be change the division operation to the modulus operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.div + */ + image::Image *div(image::Image *other, bool invert = false, bool mod = false, image::Image *mask = nullptr); + + /** + * @brief Caculate the minimum of each pixel in the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.min + */ + image::Image *min(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Caculate the maximum of each pixel in the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.max + */ + image::Image *max(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Caculate the absolute value of the difference between each pixel in the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.difference + */ + image::Image *difference(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Blends the image with the other image. + * res = alpha * this_img / 256 + (256 - alpha) * other_img / 256 + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param alpha The alpha value of the blend, the value range is [0, 256],default is 128. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.blend + */ + image::Image *blend(image::Image *other, int alpha = 128, image::Image *mask = nullptr); + + /** + * @brief Runs the histogram equalization algorithm on the image. + * @param adaptive If true, an adaptive histogram equalization method will be run on the image instead which as generally better results than non-adaptive histogram qualization but a longer run time. default is false. + * @param clip_limit Provides a way to limit the contrast of the adaptive histogram qualization. Use a small value for this, like 10, to produce good histogram equalized contrast limited images. default is -1. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.histeq + */ + image::Image *histeq(bool adaptive = false, int clip_limit = -1, image::Image *mask = nullptr); + + /** + * @brief Standard mean blurring filter using a box filter. + * The parameters offset and invert are valid when threshold is True. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mean + */ + image::Image *mean(int size, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Runs the median filter on the image. The median filter is the best filter for smoothing surfaces while preserving edges but it is very slow. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param percentile This parameter controls the percentile of the value used in the kernel. You can set this to 0 for a min filter, 0.25 for a lower quartile filter, 0.75 for an upper quartile filter, and 1.0 for a max filter. default is 0.5. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.median + */ + image::Image *median(int size, double percentile = 0.5, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Runs the mode filter on the image by replacing each pixel with the mode of their neighbors. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mode + */ + image::Image *mode(int size, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Runs the midpoint filter on the image.This filter finds the midpoint (max * bias + min * (1 - bias)) of each pixel neighborhood in the image. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param bias The bias of the midpoint. default is 0.5. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.midpoint + */ + image::Image *midpoint(int size, double bias = 0.5, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Convolves the image by a filter kernel. This allows you to do general purpose convolutions on an image. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param kernel The kernel used for convolution. The kernel should be a list of lists of numbers. The kernel should be the same size as the actual kernel size. + * @param mul This parameter is used to multiply the convolved pixel results. default is auto. + * @param add This parameter is the value to be added to each convolution pixel result. default is 0.0. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.morph + */ + image::Image *morph(int size, std::vector kernel, float mul = -1, float add = 0.0, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Convolves the image by a smoothing guassian kernel. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param unsharp If true, this method will perform an unsharp mask operation instead of gaussian filtering operation, this improves the clarity of image edges. default is false. + * @param mul This parameter is used to multiply the convolved pixel results. default is auto. + * @param add This parameter is the value to be added to each convolution pixel result. default is 0.0. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.gaussian + */ + image::Image *gaussian(int size, bool unsharp = false, float mul = -1, float add = 0.0, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Convolves the image by a edge detecting laplacian kernel. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param sharpen If True, this method will sharpen the image instead of an unthresholded edge detection image. Then increase the kernel size to improve image clarity. default is false. + * @param mul This parameter is used to multiply the convolved pixel results. default is auto. + * @param add This parameter is the value to be added to each convolution pixel result. default is 0.0. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.laplacian + */ + image::Image *laplacian(int size, bool sharpen = false, float mul = -1, float add = 0.0, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Convolves the image by a bilateral filter. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param color_sigma Controls how closely colors are matched using the bilateral filter. default is 0.1. + * @param space_sigma Controls how closely pixels space-wise are blurred with each other. default is 1. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.bilateral + */ + image::Image *bilateral(int size, double color_sigma = 0.1, double space_sigma = 1, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Re-project’s and image from cartessian coordinates to linear polar coordinates. + * @param reverse If true, the image will be reverse polar transformed. default is false. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.linpolar + */ + image::Image *linpolar(bool reverse = false); + + /** + * @brief Re-project’s and image from cartessian coordinates to log polar coordinates. + * @param reverse If true, the image will be reverse polar transformed. default is false. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.logpolar + */ + image::Image *logpolar(bool reverse = false); + + /** + * @brief Performs a lens correction operation on the image. TODO: support in the feature + * @param strength The strength of the lens correction. default is 1.8. + * @param zoom The zoom of the lens correction. default is 1.0. + * @param x_corr The x correction of the lens correction. default is 0.0. + * @param y_corr The y correction of the lens correction. default is 0.0. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.lens_corr + */ + image::Image *lens_corr(double strength = 1.8, double zoom = 1.0, double x_corr = 0.0, double y_corr = 0.0); + + /** + * @brief Performs a rotation correction operation on the image. TODO: support in the feature + * @param x_rotation The x rotation of the rotation correction. default is 0.0. + * @param y_rotation The y rotation of the rotation correction. default is 0.0. + * @param z_rotation The z rotation of the rotation correction. default is 0.0. + * @param x_translation The x translation of the rotation correction. default is 0.0. + * @param y_translation The y translation of the rotation correction. default is 0.0. + * @param zoom The zoom of the rotation correction. default is 1.0. + * @param fov The fov of the rotation correction. default is 60.0. + * @param corners The corners of the rotation correction. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.rotation_corr + */ + image::Image *rotation_corr(double x_rotation = 0.0, double y_rotation = 0.0, double z_rotation = 0.0, double x_translation = 0.0, double y_translation = 0.0, double zoom = 1.0, double fov = 60.0, std::vector corners = std::vector()); + + /** + * @brief Gets the histogram of the image. + * @param thresholds You can define multiple thresholds. + * For GRAYSCALE format, you can use {{Lmin, Lmax}, ...} to define one or more thresholds. + * For RGB888 format, you can use {{Lmin, Lmax, Amin, Amax, Bmin, Bmax}, ...} to define one or more thresholds. + * Where the upper case L,A,B represent the L,A,B channels of the LAB image format, and min, max represent the minimum and maximum values of the corresponding channels. + * @param invert If true, the thresholds will be inverted before the operation. default is false. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param bins The number of bins to use for the histogram. + * In GRAYSCALE format, setting range is [2, 256], default is 100. + * In rgb888 format, setting range is [2, 100], default is 100. + * @param l_bins The number of bins to use for the l channel of the histogram. Only valid in RGB888 format. + * If an invalid value is set, bins will be used instead. The setting range is [2, 100], default is 100. + * @param a_bins The number of bins to use for the a channel of the histogram. + * Only valid in RGB888 format.The setting range is [2, 256], default is 256. + * @param b_bins The number of bins to use for the b channel of the histogram. + * Only valid in RGB888 format. The setting range is [2, 256], default is 256. + * @param difference difference may be set to an image object to cause this method to operate on the difference image between the current image and the difference image object. + * default is None. + * @return Returns the histogram of the image + * @maixpy maix.image.Image.get_histogram + */ + std::map> get_histogram(std::vector> thresholds = std::vector>(), bool invert = false, std::vector roi = std::vector(), int bins = -1, int l_bins = 100, int a_bins = 256, int b_bins = 256, image::Image *difference = nullptr); + + /** + * @brief Gets the statistics of the image. TODO: support in the feature + * @param thresholds You can define multiple thresholds. + * For GRAYSCALE format, you can use {{Lmin, Lmax}, ...} to define one or more thresholds. + * For RGB888 format, you can use {{Lmin, Lmax, Amin, Amax, Bmin, Bmax}, ...} to define one or more thresholds. + * Where the upper case L,A,B represent the L,A,B channels of the LAB image format, and min, max represent the minimum and maximum values of the corresponding channels. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param bins The number of bins to use for the statistics. default is -1. + * @param l_bins The number of bins to use for the l channel of the statistics. default is -1. + * @param a_bins The number of bins to use for the a channel of the statistics. default is -1. + * @param b_bins The number of bins to use for the b channel of the statistics. default is -1. + * @param difference The difference image to use for the statistics. default is None. + * @return Returns the statistics of the image + * @maixpy maix.image.Image.get_statistics + */ + image::Statistics get_statistics(std::vector> thresholds = std::vector>(), bool invert = false, std::vector roi = std::vector(), int bins = -1, int l_bins = -1, int a_bins = -1, int b_bins = -1, image::Image *difference = nullptr); + + /** + * @brief Gets the regression of the image. + * @param thresholds You can define multiple thresholds. + * For GRAYSCALE format, you can use {{Lmin, Lmax}, ...} to define one or more thresholds. + * For RGB888 format, you can use {{Lmin, Lmax, Amin, Amax, Bmin, Bmax}, ...} to define one or more thresholds. + * Where the upper case L,A,B represent the L,A,B channels of the LAB image format, and min, max represent the minimum and maximum values of the corresponding channels. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param x_stride The x stride to use for the regression. default is 2. + * @param y_stride The y stride to use for the regression. default is 1. + * @param area_threshold The area threshold to use for the regression. default is 10. + * @param pixels_threshold The pixels threshold to use for the regression. default is 10. + * @param robust If true, the regression will be robust. default is false. + * @return Returns the regression of the image + * @maixpy maix.image.Image.get_regression + */ + std::vector get_regression(std::vector> thresholds = std::vector>(), bool invert = false, std::vector roi = std::vector(), int x_stride = 2, int y_stride = 1, int area_threshold = 10, int pixels_threshold = 10, bool robust = false); + + //************************** image with filesystem **************************// + /** + * Save image to file + * @param path file path + * @param quality image quality, by default(value is 95), support jpeg and png format + * @return error code, err::ERR_NONE is ok, other is error + * @maixpy maix.image.Image.save + */ + err::Err save(const char *path, int quality = 95); + + //************************** application algorithms **************************// + /** + * @brief Flood fills a region of the image starting from location x, y. + * @param x The x coordinate of the seed point. + * @param y The y coordinate of the seed point. + * @param seed_threshold The seed_threshold value controls how different any pixel in the fill area may be from the original starting pixel. default is 0.05. + * @param floating_threshold The floating_threshold value controls how different any pixel in the fill area may be from any neighbor pixels. default is 0.05. + * @param color The color to fill the region with. default is white. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param clear_background If true, the background will be cleared before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. FIXME: the mask image works abnormally + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.flood_fill + */ + image::Image *flood_fill(int x, int y, float seed_threshold = 0.05, float floating_threshold = 0.05, image::Color color = image::COLOR_WHITE, bool invert = false, bool clear_background = false, image::Image *mask = nullptr); + + /** + * @brief Erodes the image in place. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold The number of pixels in the kernel that are not 0. If it is less than or equal to the threshold, set the center pixel to black. default is (kernel_size - 1). + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.erode + */ + image::Image *erode(int size, int threshold = -1, image::Image *mask = nullptr); + + /** + * @brief Dilates the image in place. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold The number of pixels in the kernel that are not 0. If it is greater than or equal to the threshold, set the center pixel to white. default is 0. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.dilate + */ + image::Image *dilate(int size, int threshold = 0, image::Image *mask = nullptr); + + /** + * @brief Performs erosion and dilation on an image in order. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold As the threshold for erosion and dilation, the actual threshold for erosion is (kernel_size - 1 - threshold), the actual threshold for dialation is threshold. default is 0. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.open + */ + image::Image *open(int size, int threshold = 0, image::Image *mask = nullptr); + + /** + * @brief Performs dilation and erosion on an image in order. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold As the threshold for erosion and dilation, the actual threshold for erosion is (kernel_size - 1 - threshold), the actual threshold for dialation is threshold. default is 0. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.close + */ + image::Image *close(int size, int threshold = 0, image::Image *mask = nullptr); + + /** + * @brief Returns the image difference of the image and Image.open()’ed image. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold As the threshold for open method. default is 0. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.top_hat + */ + image::Image *top_hat(int size, int threshold = 0, image::Image *mask = nullptr); + + /** + * @brief Returns the image difference of the image and Image.close()’ed image. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold As the threshold for close method. default is 0. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.black_hat + */ + image::Image *black_hat(int size, int threshold = 0, image::Image *mask = nullptr); + + /** + * Finds all blobs in the image and returns a list of image.Blob class which describe each Blob. + * Please see the image.Blob object more more information. + * + * @param thresholds You can define multiple thresholds. + * For GRAYSCALE format, you can use {{Lmin, Lmax}, ...} to define one or more thresholds. + * For RGB888 format, you can use {{Lmin, Lmax, Amin, Amax, Bmin, Bmax}, ...} to define one or more thresholds. + * Where the upper case L,A,B represent the L,A,B channels of the LAB image format, and min, max represent the minimum and maximum values of the corresponding channels. + * @param invert if true, will invert thresholds before find blobs, default is false + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param x_stride x stride is the number of x pixels to skip when doing the hough transform. default is 2 + * @param y_stride y_stride is the number of y pixels to skip when doing the hough transform. default is 1 + * @param area_threshold area threshold, if the blob area is smaller than area_threshold, the blob is not returned, default is 10 + * @param pixels_threshold pixels threshold, if the blob pixels is smaller than area_threshold, the blob is not returned,, default is 10. + * when x_stride and y_stride is equal to 1, pixels_threshold is equivalent to area_threshold + * @param merge if True merges all not filtered out blobs whos bounding rectangles intersect each other. default is false + * @param margin margin can be used to increase or decrease the size of the bounding rectangles for blobs during the intersection test. + * For example, with a margin of 1 blobs whos bounding rectangles are 1 pixel away from each other will be merged. default is 0 + * @param x_hist_bins_max if set to non-zero populates a histogram buffer in each blob object with an x_histogram projection of all columns in the object. This value then sets the number of bins for that projection. + * @param y_hist_bins_max if set to non-zero populates a histogram buffer in each blob object with an y_histogram projection of all rows in the object. This value then sets the number of bins for that projection. + * + * @return Return the blob when found blobs, format is (blob1, blob2, ...), you can use blob class methods to do more operations. + * @maixpy maix.image.Image.find_blobs + */ + std::vector find_blobs(std::vector> thresholds = std::vector>(), bool invert = false, std::vector roi = std::vector(), int x_stride = 2, int y_stride = 1, int area_threshold = 10, int pixels_threshold = 10, bool merge = false, int margin = 0, int x_hist_bins_max = 0, int y_hist_bins_max = 0); + + /** + * Find lines in image + * + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param x_stride x stride is the number of x pixels to skip when doing the hough transform. default is 2 + * @param y_stride y_stride is the number of y pixels to skip when doing the hough transform. default is 1 + * @param threshold threshold threshold controls what lines are detected from the hough transform. Only lines with a magnitude greater than or equal to threshold are returned. + * The right value of threshold for your application is image dependent. default is 1000. + * @param theta_margin theta_margin controls the merging of detected lines. default is 25. + * @param rho_margin rho_margin controls the merging of detected lines. default is 25. + * + * @return Return the line when found lines, format is (line1, line2, ...), you can use line class methods to do more operations + * @maixpy maix.image.Image.find_lines + */ + std::vector find_lines(std::vector roi = std::vector(), int x_stride = 2, int y_stride = 1, double threshold = 1000, double theta_margin = 25, double rho_margin = 25); + + /** + * @brief Finds all line segments in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param merge_distance The maximum distance between two lines to merge them. default is 0. + * @param max_theta_difference The maximum difference between two lines to merge them. default is 15. + * @return Return the line when found lines, format is (line1, line2, ...), you can use line class methods to do more operations + * @maixpy maix.image.Image.find_line_segments + */ + std::vector find_line_segments(std::vector roi = std::vector(), int merge_distance = 0, int max_theta_difference = 15); + + /** + * Find circles in image + * + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param x_stride x stride is the number of x pixels to skip when doing the hough transform. default is 2 + * @param y_stride y_stride is the number of y pixels to skip when doing the hough transform. default is 1 + * @param threshold threshold controls what circles are detected from the hough transform. Only circles with a magnitude greater than or equal to threshold are returned. + * The right value of threshold for your application is image dependent. + * @param x_margin x_margin controls the merging of detected circles. Circles which are x_margin, y_margin, and r_margin pixels apart are merged. default is 10 + * @param y_margin y_margin controls the merging of detected circles. Circles which are x_margin, y_margin, and r_margin pixels apart are merged. default is 10 + * @param r_margin r_margin controls the merging of detected circles. Circles which are x_margin, y_margin, and r_margin pixels apart are merged. default is 10 + * @param r_min r_min controls the minimum circle radius detected. Increase this to speed up the algorithm. default is 2 + * @param r_max r_max controls the maximum circle radius detected. Decrease this to speed up the algorithm. default is min(roi.w / 2, roi.h / 2) + * @param r_step r_step controls how to step the radius detection by. default is 2. + * + * @return Return the circle when found circles, format is (circle1, circle2, ...), you can use circle class methods to do more operations + * @maixpy maix.image.Image.find_circles + */ + std::vector find_circles(std::vector roi = std::vector(), int x_stride = 2, int y_stride = 1, int threshold = 2000, int x_margin = 10, int y_margin = 10, int r_margin = 10, int r_min = 2, int r_max = -1, int r_step = 2); + + /** + * @brief Finds all rects in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param threshold The threshold to use for the rects. default is 10000. + * @return Returns the rects of the image + * @maixpy maix.image.Image.find_rects + */ + std::vector find_rects(std::vector roi = std::vector(), int threshold = 10000); + + /** + * @brief Finds all qrcodes in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @return Returns the qrcodes of the image + * @maixpy maix.image.Image.find_qrcodes + */ + std::vector find_qrcodes(std::vector roi = std::vector()); + + /** + * @brief Finds all apriltags in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param families The families to use for the apriltags. default is TAG36H11. + * @param fx The camera X focal length in pixels, default is -1. + * @param fy The camera Y focal length in pixels, default is -1. + * @param cx The camera X center in pixels, default is image.width / 2. + * @param cy The camera Y center in pixels, default is image.height / 2. + * @return Returns the apriltags of the image + * @maixpy maix.image.Image.find_apriltags + */ + std::vector find_apriltags(std::vector roi = std::vector(), image::ApriltagFamilies families = image::ApriltagFamilies::TAG36H11, float fx = -1, float fy = -1, int cx = -1, int cy = -1); + + /** + * @brief Finds all datamatrices in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param effort Controls how much time to spend trying to find data matrix matches. default is 200. + * @return Returns the datamatrices of the image + * @maixpy maix.image.Image.find_datamatrices + */ + std::vector find_datamatrices(std::vector roi = std::vector(), int effort = 200); + + /** + * @brief Finds all barcodes in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @return Returns the barcodes of the image + * @maixpy maix.image.Image.find_barcodes + */ + std::vector find_barcodes(std::vector roi = std::vector()); + + /** + * @brief Finds the displacement between the image and the template. TODO: support in the feature + * note: this method must be used on power-of-2 image sizes + * @param template_image The template image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param template_roi The region-of-interest rectangle (x, y, w, h) to work in. If not specified, it is equal to the image rectangle. + * @param logpolar If true, it will instead find rotation and scale changes between the two images. default is false. + * @return Returns the displacement of the image + * @maixpy maix.image.Image.find_displacement + */ + image::Displacement find_displacement(image::Image &template_image, std::vector roi = std::vector(), std::vector template_roi = std::vector(), bool logpolar = false); + + /** + * @brief Finds the template in the image. + * @param template_image The template image. + * @param threshold Threshold is floating point number (0.0-1.0) where a higher threshold prevents false positives while lowering the detection rate while a lower threshold does the opposite. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. Only valid in SEARCH_EX mode. + * @param step The step size to use for the template. default is 2. Only valid in SEARCH_EX mode + * @param search The search method to use for the template. default is SEARCH_EX. + * @return Returns a bounding box tuple (x, y, w, h) for the matching location otherwise None. + * @maixpy maix.image.Image.find_template + */ + std::vector find_template(image::Image &template_image, float threshold, std::vector roi = std::vector(), int step = 2, image::TemplateMatch search = image::TemplateMatch::SEARCH_EX); + + /** + * @brief Finds the features in the image. TODO: support in the feature + * @param cascade The cascade to use for the features. default is CASCADE_FRONTALFACE_ALT. + * @param threshold The threshold to use for the features. default is 0.5. + * @param scale The scale to use for the features. default is 1.5. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @return Returns the features of the image + * @maixpy maix.image.Image.find_features + */ + std::vector find_features(int cascade, float threshold = 0.5, float scale = 1.5, std::vector roi = std::vector()); + + /** + * @brief Finds the lbp in the image. TODO: support in the feature. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @return Returns the lbp of the image + * @maixpy maix.image.Image.find_lbp + */ + image::LBPKeyPoint find_lbp(std::vector roi = std::vector()); + + /** + * @brief Finds the keypoints in the image. TODO: support in the feature. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param threshold The threshold to use for the keypoints. default is 20. + * @param normalized If true, the image will be normalized before the operation. default is false. + * @param scale_factor The scale factor to use for the keypoints. default is 1.5. + * @param max_keypoints The maximum number of keypoints to use for the keypoints. default is 100. + * @param corner_detector The corner detector to use for the keypoints. default is CORNER_AGAST. + * @return Returns the keypoints of the image + * @maixpy maix.image.Image.find_keypoints + */ + image::ORBKeyPoint find_keypoints(std::vector roi = std::vector(), int threshold = 20, bool normalized = false, float scale_factor = 1.5, int max_keypoints = 100, image::CornerDetector corner_detector = image::CornerDetector::CORNER_AGAST); + + /** + * @brief Finds the edges in the image. + * @param edge_type The edge type to use for the edges. default is EDGE_CANNY. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param threshold The threshold to use for the edges. default is 20. + * @return Returns the edges of the image + * @maixpy maix.image.Image.find_edges + */ + image::Image* find_edges(image::EdgeDetector edge_type, std::vector roi = std::vector(), std::vector threshold = std::vector({100, 200})); + + /** + * @brief Finds the hog in the image. TODO: support in the feature + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param size The size to use for the hog. default is 8. + * @return Returns the hog of the image + * @maixpy maix.image.Image.find_hog + */ + image::Image* find_hog(std::vector roi = std::vector(), int size = 8); + + /** + * @brief Matches the lbp descriptor of the image. TODO: support in the feature + * @param desc1 The descriptor to use for the match. + * @param desc2 The descriptor to use for the match. + * @return Returns the match of the image + * @maixpy maix.image.Image.match_lbp_descriptor + */ + int match_lbp_descriptor(image::LBPKeyPoint &desc1, image::LBPKeyPoint &desc2); + + /** + * @brief Matches the orb descriptor of the image. TODO: support in the feature + * @param desc1 The descriptor to use for the match. + * @param desc2 The descriptor to use for the match. + * @param threshold The threshold to use for the match. default is 95. + * @param filter_outliers If true, the image will be filter_outliers before the operation. default is false. + * @return Returns the match of the image + * @maixpy maix.image.Image.match_orb_descriptor + */ + image::KPTMatch match_orb_descriptor(image::ORBKeyPoint &desc1, image::ORBKeyPoint &desc2, int threshold = 95, bool filter_outliers = false); + + /** + * map point position or rectangle position from this image size to another image size(resize) + * @param int w_out target image width + * @param int h_out target image height + * @param fit resize method, see maix.image.Fit + * @param x original point x, or rectagle left-top point's x + * @param y original point y, or rectagle left-top point's y + * @param w original rectagle width, can be -1 if not use this arg, default -1. + * @param h original rectagle height, can be -1 if not use this arg, default -1. + * @return list type, [x, y] if map point, [x, y, w, h] if resize rectangle. + * @maixpy maix.image.resize_map_pos + */ + std::vector resize_map_pos(int w_out, int h_out, image::Fit fit, int x, int y, int w = -1, int h = -1) + { + return image::resize_map_pos(_width, _height, w_out, h_out, fit, x, y, w, h); + } + + private: + void *_actual_data; + void *_data; + int _width; + int _height; + int _data_size; + Format _format; + bool _is_malloc; + + int _get_cv_pixel_num(image::Format &format); + std::vector _get_available_roi(std::vector roi, std::vector other_roi = std::vector()); + void _create_image(int width, int height, image::Format format, uint8_t *data, int data_size, bool copy); + }; // class Image + + /** + * Load image from file, and convert to Image object + * @param path image file path + * @param format read as this format, if not match, will convert to this format, by default is RGB888 + * @return Image object, if load failed, will return None(nullptr in C++), so you should care about it. + * @maixpy maix.image.load + */ + image::Image *load(const char *path, image::Format format = image::Format::FMT_RGB888); + + /** + * Create image from bytes + * @param width image width + * @param height image height + * @param format image format + * @param data image data, if data is None, will malloc memory for image data + * If the image is in jpeg format, data must be filled in. + * @param copy if true and data is not None, will copy data to new buffer, else will use data directly. default is true to avoid memory leak. + * Use it carefully!!! + * @return Image object + * @maixpy maix.image.from_bytes + */ + image::Image *from_bytes(int width, int height, image::Format format, Bytes *data, bool copy = true); + + /** + * Load font from file + * @param name font name, used to identify font + * @param path font file path, support ttf, ttc, otf + * @param size font size, font height, by default is 16 + * @return error code, err::ERR_NONE is ok, other is error + * @maixpy maix.image.load_font + */ + err::Err load_font(const std::string &name, const char *path, int size = 16); + + /** + * Set default font, if not call this method, default is hershey_plain + * @param name font name, supported names can be get by fonts() + * @return error code, err::ERR_NONE is ok, other is error + * @maixpy maix.image.set_default_font + */ + err::Err set_default_font(const std::string &name); + + /** + * Get all loaded fonts + * @return all loaded fonts, string list type + * @maixpy maix.image.fonts + */ + std::vector *fonts(); + + /** + * Get text rendered width and height + * @param string text content + * @param scale font scale, by default(value is 1) + * @param thickness text thickness(line width), by default(value is 1) + * @return text rendered width and height, [width, height] + * @maixpy maix.image.string_size + */ + image::Size string_size(std::string string, float scale = 1, int thickness = 1, const std::string &font = ""); +} // namespace maix::image diff --git a/support/sg2002/additional/vision/include/maix_image_color.hpp b/support/sg2002/additional/vision/include/maix_image_color.hpp new file mode 100644 index 0000000..18de6f0 --- /dev/null +++ b/support/sg2002/additional/vision/include/maix_image_color.hpp @@ -0,0 +1,296 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include "maix_image_def.hpp" +#include "maix_log.hpp" + +namespace maix::image +{ + /** + * Color class + * @maixpy maix.image.Color + */ + class Color + { + public: + /** + * Color constructor + * @param alpha alpha channel, value range: 0 ~ 1 + * @maixpy maix.image.Color.__init__ + */ + Color(uint8_t ch1, uint8_t ch2 = 0, uint8_t ch3 = 0, float alpha = 0, image::Format format = image::FMT_GRAYSCALE) + { + if(alpha > 1 || alpha < 0) + throw std::runtime_error("alpha value range: 0 ~ 1"); + this->format = format; + switch (format) + { + case image::FMT_RGB888: + r = ch1; + g = ch2; + b = ch3; + this->alpha = 1; + break; + case image::FMT_BGR888: + b = ch1; + g = ch2; + r = ch3; + this->alpha = 1; + break; + case image::FMT_GRAYSCALE: + gray = ch1; + break; + case image::FMT_BGRA8888: + b = ch1; + g = ch2; + r = ch3; + this->alpha = alpha; + break; + case image::FMT_RGBA8888: + r = ch1; + g = ch2; + b = ch3; + this->alpha = alpha; + break; + default: + throw std::runtime_error("not support format"); + break; + } + } + + /** + * Color red channel + * @maixpy maix.image.Color.r + */ + uint8_t r; + + /** + * Color green channel + * @maixpy maix.image.Color.g + */ + uint8_t g; + + /** + * Color blue channel + * @maixpy maix.image.Color.b + */ + uint8_t b; + + /** + * Color alpha channel, value from 0.0 to 1.0, float value + * @maixpy maix.image.Color.alpha + */ + float alpha; + + /** + * Color gray channel + * @maixpy maix.image.Color.gray + */ + uint8_t gray; + + /** + * Color format + * @maixpy maix.image.Color.format + */ + image::Format format; + + /** + * Get color's hex value + * @maixpy maix.image.Color.hex + */ + uint32_t hex() + { + uint32_t hex = 0; + switch (format) + { + case image::FMT_RGB888: + hex = r | (g << 8) | (b << 16); + break; + case image::FMT_BGR888: + hex = b | (g << 8) | (r << 16); + break; + case image::FMT_GRAYSCALE: + hex = gray; + break; + case image::FMT_BGRA8888: + hex = b | (g << 8) | (r << 16) | ((uint8_t)(alpha*255) << 24); + break; + case image::FMT_RGBA8888: + hex = r | (g << 8) | (b << 16) | ((uint8_t)(alpha*255) << 24); + break; + default: + throw std::runtime_error("not support format"); + break; + } + return hex; + } + + /** + * Create Color object from RGB channels + * @maixpy maix.image.Color.from_rgb + */ + static image::Color from_rgb(uint8_t r, uint8_t g, uint8_t b) + { + return Color(r, g, b, 1, image::Format::FMT_RGB888); + } + + /** + * Create Color object from BGR channels + * @maixpy maix.image.Color.from_bgr + */ + static image::Color from_bgr(uint8_t b, uint8_t g, uint8_t r) + { + return Color(b, g, r, 1, image::Format::FMT_BGR888); + } + + /** + * Create Color object from gray channel + * @maixpy maix.image.Color.from_gray + */ + static image::Color from_gray(uint8_t gray) + { + return Color(gray); + } + + /** + * Create Color object from RGBA channels + * @param alpha alpha channel, float value, value range: 0 ~ 1 + * @maixpy maix.image.Color.from_rgba + */ + static image::Color from_rgba(uint8_t r, uint8_t g, uint8_t b, float alpha) + { + return Color(r, g, b, alpha, image::Format::FMT_RGBA8888); + } + + /** + * Create Color object from BGRA channels + * * @param alpha alpha channel, float value, value range: 0 ~ 1 + * @maixpy maix.image.Color.from_bgra + */ + static image::Color from_bgra(uint8_t b, uint8_t g, uint8_t r, float alpha) + { + return Color(b, g, r, alpha, image::Format::FMT_BGRA8888); + } + + /** + * Create Color object from hex value + * @param hex hex value, e.g. 0x0000FF00, lower address if first channel + * @param format color format, @see image::Format + * @maixpy maix.image.Color.from_hex + */ + static image::Color from_hex(uint32_t hex, image::Format &format) + { + return Color(hex & 0xFF, hex & 0xFF00, hex & 0xFF0000, (hex & 0xFF000000)/255.0, format); + } + + /** + * Convert Color format + * @param format format want to convert to, @see image::Format, only support RGB888, BGR888, RGBA8888, BGRA8888, GRAYSCALE. + * @maixpy maix.image.Color.to_format + */ + void to_format(const image::Format &format) + { + if(!(format == image::FMT_RGB888 || format == image::FMT_BGR888 || + format == image::FMT_RGBA8888 || format == image::FMT_BGRA8888 || + format == image::FMT_GRAYSCALE)) + { + log::error("convert format failed, not support format %d\n", format); + return; + } + if(this->format == format) + return; + if((this->format == image::FMT_RGB888 || this->format == image::FMT_BGR888) && + (format == image::FMT_RGBA8888 || format == image::FMT_BGRA8888)) + { + this->alpha = 1; + } + else if(this->format == image::FMT_GRAYSCALE && format != image::FMT_GRAYSCALE) + { + this->r = this->gray; + this->g = this->gray; + this->b = this->gray; + this->alpha = 1; + } + else if((this->format == image::FMT_RGBA8888 || this->format == image::FMT_BGRA8888) && + (format == image::FMT_RGB888 || format == image::FMT_BGR888)) + { + this->alpha = 0; + } + else if(this->format != image::FMT_GRAYSCALE && format == image::FMT_GRAYSCALE) + { + this->gray = (this->r + this->g + this->b) / 3; + this->r = this->gray; + this->g = this->gray; + this->b = this->gray; + this->alpha = 0; + } + this->format = format; + } + + /** + * Convert color format and return a new Color object + * @param format format want to convert to, @see image::Format, only support RGB888, BGR888, RGBA8888, BGRA8888, GRAYSCALE. + * @return new Color object, you need to delete it manually in C++. + * @maixpy maix.image.Color.to_format2 + */ + image::Color *to_format2(const image::Format &format) + { + image::Color *color = new image::Color(*this); + color->to_format(format); + return color; + } + }; + + /** + * Predefined color white + * @maixpy maix.image.COLOR_WHITE + */ + const image::Color COLOR_WHITE = image::Color::from_rgb(255, 255, 255); + /** + * Predefined color black + * @maixpy maix.image.COLOR_BLACK + */ + const image::Color COLOR_BLACK = image::Color::from_rgb(0, 0, 0); + /** + * Predefined color red + * @maixpy maix.image.COLOR_RED + */ + const image::Color COLOR_RED = image::Color::from_rgb(255, 0, 0); + /** + * Predefined color green + * @maixpy maix.image.COLOR_GREEN + */ + const image::Color COLOR_GREEN = image::Color::from_rgb(0, 255, 0); + /** + * Predefined color blue + * @maixpy maix.image.COLOR_BLUE + */ + const image::Color COLOR_BLUE = image::Color::from_rgb(0, 0, 255); + /** + * Predefined color yellow + * @maixpy maix.image.COLOR_YELLOW + */ + const image::Color COLOR_YELLOW = image::Color::from_rgb(255, 255, 0); + /** + * Predefined color purple + * @maixpy maix.image.COLOR_PURPLE + */ + const image::Color COLOR_PURPLE = image::Color::from_rgb(143, 0, 255); + /** + * Predefined color orange + * @maixpy maix.image.COLOR_ORANGE + */ + const image::Color COLOR_ORANGE = image::Color::from_rgb(255, 127, 0); + /** + * Predefined color gray + * @maixpy maix.image.COLOR_GRAY + */ + const image::Color COLOR_GRAY = image::Color::from_rgb(127, 127, 127); + +} diff --git a/support/sg2002/additional/vision/include/maix_image_def.hpp b/support/sg2002/additional/vision/include/maix_image_def.hpp new file mode 100644 index 0000000..26feb9a --- /dev/null +++ b/support/sg2002/additional/vision/include/maix_image_def.hpp @@ -0,0 +1,313 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace maix::image +{ + + /** + * Image formats + * @attention for MaixPy firmware developers, update this enum will also need to update the fmt_size and fmt_names too !!! + * @maixpy maix.image.Format + */ + enum Format + { + FMT_RGB888 = 0, // RGBRGB...RGB, R at the lowest address + FMT_BGR888, // BGRBGR...BGR, B at the lowest address + FMT_RGBA8888, // RGBARGBA...RGBA, R at the lowest address + FMT_BGRA8888, // BGRABGRA...BGRA, B at the lowest address + FMT_RGB565, + FMT_BGR565, + FMT_YUV422SP, // YYY...UVUVUV...UVUV + FMT_YUV422P, // YYY...UUU...VVV + FMT_YVU420SP, // YYY...VUVUVU...VUVU, NV21 + FMT_YUV420SP, // YYY...UVUVUV...UVUV, NV12 + FMT_YVU420P, // YYY...VVV...UUU + FMT_YUV420P, // YYY...UUU...VVV + FMT_GRAYSCALE, + FMT_BGGR6, // 6-bit Bayer format with a BGGR pattern. + FMT_GBRG6, // 6-bit Bayer format with a GBRG pattern. + FMT_GRBG6, // 6-bit Bayer format with a GRBG pattern. + FMT_RGGB6, // 6-bit Bayer format with a RGGB pattern. + FMT_BGGR8, // 8-bit Bayer format with a BGGR pattern. + FMT_GBRG8, // 8-bit Bayer format with a GBRG pattern. + FMT_GRBG8, // 8-bit Bayer format with a GRBG pattern. + FMT_RGGB8, // 8-bit Bayer format with a RGGB pattern. + FMT_BGGR10, // 10-bit Bayer format with a BGGR pattern. + FMT_GBRG10, // 10-bit Bayer format with a GBRG pattern. + FMT_GRBG10, // 10-bit Bayer format with a GRBG pattern. + FMT_RGGB10, // 10-bit Bayer format with a RGGB pattern. + FMT_BGGR12, // 12-bit Bayer format with a BGGR pattern. + FMT_GBRG12, // 12-bit Bayer format with a GBRG pattern. + FMT_GRBG12, // 12-bit Bayer format with a GRBG pattern. + FMT_RGGB12, // 12-bit Bayer format with a RGGB pattern. + FMT_UNCOMPRESSED_MAX, + + // compressed format below, not compressed should define upper + FMT_COMPRESSED_MIN, + FMT_JPEG, + FMT_PNG, + FMT_COMPRESSED_MAX, + + FMT_INVALID = 0xFF // format not valid + }; // !!!! update this section please update fmt_size and fmt_names too !!!! + + /** + * Image format size in bytes + * @attention It's a copy of this variable in MaixPy, + * so change it in C++ (e.g. update var in hello function) will not take effect the var inMaixPy. + * So we add const for this var to avoid this mistake. + * @maixpy maix.image.fmt_size + */ + const std::vector fmt_size = { + 3, + 3, + 4, + 4, + 2, + 2, + 2, + 2, + 1.5, + 1.5, + 1.5, + 1.5, + 1, // grayscale + 0.75, // 6-bit Bayer format + 0.75, // 6-bit Bayer format + 0.75, // 6-bit Bayer format + 0.75, // 6-bit Bayer format + 1, // 8-bit Bayer format + 1, // 8-bit Bayer format + 1, // 8-bit Bayer format + 1, // 8-bit Bayer format + 1.25, // 10-bit Bayer format + 1.25, // 10-bit Bayer format + 1.25, // 10-bit Bayer format + 1.25, // 10-bit Bayer format + 1.5, // 12-bit Bayer format + 1.5, // 12-bit Bayer format + 1.5, // 12-bit Bayer format + 1.5, // 12-bit Bayer format + 0, // uncompereed_max + 0, // compressed_min + 1, // jpeg + 1, // png + 0, // compressed_max + 0 // invalid + }; + + /** + * Image format string + * @maixpy maix.image.fmt_names + */ + const std::vector fmt_names = { + "RGB888", + "BGR888", + "RGBA8888", + "BGRA8888", + "RGB565", + "BGR565", + "YUV422SP", + "YUV422P", + "YVU420SP", + "YUV420SP", + "YVU420P", + "YUV420P", + "GRAYSCALE", + "BGGR6", + "GBRG6", + "GRBG6", + "RG6B6", + "BGGR8", + "GBRG8", + "GRBG8", + "RG6B8", + "BGGR10", + "GBRG10", + "GRBG10", + "RG6B10", + "BGGR12", + "GBRG12", + "GRBG12", + "RG6B12", + "UNCOMPRESSED_MAX", + "COMPRESSED_MIN", + "JPEG", + "PNG", + "COMPRESSED_MAX", + "INVALID" + }; + + /** + * Image size type + * @maixpy maix.image.Size + */ + class Size + { + public: + /** + * Construct a new Size object + * @param width image width + * @param height image height + * @maixpy maix.image.Size.__init__ + */ + Size(int width = 0, int height = 0) + { + this->_width = width; + this->_height = height; + } + + /** + * width of size + * @param width set new width, if not set, only return current width + * @maixpy maix.image.Size.width + */ + int width(int width = -1) + { + if(width != -1) + { + this->_width = width; + } + return this->_width; + } + + /** + * height of size + * @param height set new height, if not set, only return current height + * @maixpy maix.image.Size.height + */ + int height(int height = -1) + { + if(height != -1) + { + this->_height = height; + } + return this->_height; + } + + /** + * Subscript operator + * @param index 0 for width, 1 for height + * @return int& width or height + * @maixpy maix.image.Size.__getitem__ + * @maixcdk maix.image.Size.operator[] + */ + int &operator[](int index) + { + if (index == 0) + return _width; + else if (index == 1) + return _height; + else + throw std::out_of_range("Size index out of range"); + } + + /** + * to string + * @maixpy maix.image.Size.__str__ + */ + std::string __str__() + { + return "Size(" + std::to_string(_width) + "x" + std::to_string(_height) + ")"; + } + private: + int _width; + int _height; + }; + + /** + * Object fit method + * @maixpy maix.image.Fit + */ + enum Fit + { + FIT_NONE = -1, // no object fit, keep original + FIT_FILL = 0, // width to new width, height to new height, may be stretch + FIT_CONTAIN, // keep aspect ratio, fill blank area with black color + FIT_COVER, // keep aspect ratio, crop image to fit new size + FIT_MAX + }; + + /** + * Resize method + * @maixpy maix.image.ResizeMethod + */ + enum ResizeMethod + { + NEAREST = 0, + BILINEAR, + BICUBIC, + AREA, + LANCZOS, + HAMMING, + RESIZE_METHOD_MAX + }; + + /** + * Family of apriltag + * @maixpy maix.image.ApriltagFamilies + */ + enum ApriltagFamilies + { + TAG16H5 = 1, + TAG25H7 = 2, + TAG25H9 = 4, + TAG36H10 = 8, + TAG36H11 = 16, + ARTOOLKIT = 32 + }; + + /** + * Template match method + * @maixpy maix.image.TemplateMatch + */ + enum TemplateMatch + { + SEARCH_EX, // Exhaustive search + SEARCH_DS, // Diamond search + }; + + /** + * CornerDetector class + * @maixpy maix.image.CornerDetector + */ + enum CornerDetector + { + CORNER_FAST, + CORNER_AGAST + }; + + /** + * EdgeDetector class + * @maixpy maix.image.EdgeDetector + */ + enum EdgeDetector + { + EDGE_CANNY, + EDGE_SIMPLE, + }; + + /** + * FlipDir + * @maixpy maix.image.FlipDir + */ + enum class FlipDir + { + X, + Y, + XY + }; + +} diff --git a/support/sg2002/additional/vision/include/maix_image_obj.hpp b/support/sg2002/additional/vision/include/maix_image_obj.hpp new file mode 100644 index 0000000..d5a2a2c --- /dev/null +++ b/support/sg2002/additional/vision/include/maix_image_obj.hpp @@ -0,0 +1,2543 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include "maix_err.hpp" +#include +#include + +namespace maix::image +{ + /** + * Line class + * @maixpy maix.image.Line + */ + class Line + { + private: + int _x1; + int _y1; + int _x2; + int _y2; + int _length; + int _magnitude; + int _theta; + int _rho; + + public: + /** + * Line constructor + * + * @param x1 coordinate x1 of the straight line + * @param y1 coordinate y1 of the straight line + * @param x2 coordinate x2 of the straight line + * @param y2 coordinate y2 of the straight line + * @param magnitude magnitude of the straight line after Hough transformation + * @param theta angle of the straight line after Hough transformation + * @param rho p-value of the straight line after Hough transformation + * @maixpy maix.image.Line.__init__ + */ + Line(int x1, int y1, int x2, int y2, int magnitude = 0, int theta = 0, int rho = 0) + { + _x1 = x1; + _y1 = y1; + _x2 = x2; + _y2 = y2; + int x_diff = x2 - x1; + int y_diff = y2 - y1; + _length = (int)sqrtf(x_diff * x_diff + y_diff * y_diff); + _magnitude = magnitude; + _theta = theta; + _rho = rho; + } + ~Line(){}; + + /** + * Subscript operator + * @param index + * [0] get x1 of line + * [1] get y1 of line + * [2] get x2 of line + * [3] get y2 of line + * [4] get length of line + * [5] get magnitude of the straight line after Hough transformation + * [6] get angle of the straight line after Hough transformation (0-179 degrees) + * [7] get p-value of the straight line after Hough transformation + * @return int& + * @maixpy maix.image.Line.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x1; + case 1: return _y1; + case 2: return _x2; + case 3: return _y2; + case 4: return _length; + case 5: return _magnitude; + case 6: return _theta; + case 7: return _rho; + default:throw std::out_of_range("Line index out of range"); + } + } + + /** + * @brief get x1 of line + * @return return x1 of the line, type is int + * @maixpy maix.image.Line.x1 + */ + int x1() + { + return _x1; + }; + + /** + * @brief get y1 of line + * @return return y1 of the line, type is int + * @maixpy maix.image.Line.y1 + */ + int y1() + { + return _y1; + }; + + /** + * @brief get x2 of line + * @return return x2 of the line, type is int + * @maixpy maix.image.Line.x2 + */ + int x2() + { + return _x2; + }; + + /** + * @brief get y2 of line + * @return return y2 of the line, type is int + * @maixpy maix.image.Line.y2 + */ + int y2() + { + return _y2; + }; + + /** + * @brief get length of line + * @return return length of the line, type is int + * @maixpy maix.image.Line.length + */ + int length() + { + return _length; + }; + + /** + * @brief get magnitude of the straight line after Hough transformation + * @return return magnitude, type is int + * @maixpy maix.image.Line.magnitude + */ + int magnitude() + { + return _magnitude; + }; + + /** + * @brief get angle of the straight line after Hough transformation (0-179 degrees) + * @return return angle, type is int + * @maixpy maix.image.Line.theta + */ + int theta() + { + return _theta; + }; + + /** + * @brief get p-value of the straight line after Hough transformation + * @return return p-value, type is int + * @maixpy maix.image.Line.rho + */ + int rho() + { + return _rho; + }; + }; + + /** + * Rect class + * @maixpy maix.image.Rect + */ + class Rect + { + private: + int _x; + int _y; + int _w; + int _h; + int _magnitude; + std::vector> _corners; + public: + /** + * Rect constructor + * @param corners corners of rect + * @param x coordinate x of the straight line + * @param y coordinate y of the straight line + * @param w coordinate w of the straight line + * @param h coordinate h of the straight line + * @param magnitude magnitude of the straight line after Hough transformation + * @maixpy maix.image.Rect.__init__ + */ + Rect(std::vector> &corners, int x, int y, int w, int h, int magnitude = 0) + { + _x = x; + _y = y; + _w = w; + _h = h; + _magnitude = magnitude; + _corners = corners; + } + ~Rect(){}; + + /** + * Subscript operator + * @param index + * [0] get x of rect + * [1] get y of rect + * [2] get w of rect + * [3] get h of rect + * [4] get magnitude of the straight line after Hough transformation + * @return int& + * @maixpy maix.image.Rect.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: return _magnitude; + default:throw std::out_of_range("Rect index out of range"); + } + } + + + /** + * @brief get corners of rect + * @return return the coordinate of the rect. + * @maixpy maix.image.Rect.corners + */ + std::vector> corners() { + return _corners; + } + + /** + * @brief get rectangle of rect + * @return return the rectangle of the rect. format is {x, y, w, h}, type is std::vector + * @maixpy maix.image.Rect.rect + */ + std::vector rect() { + std::vector coord = {_x, _y, _w, _h}; + return coord; + }; + + /** + * @brief get x of rect + * @return return x of the rect, type is int + * @maixpy maix.image.Rect.x + */ + int x() { + return _x; + }; + + /** + * @brief get y of rect + * @return return y of the rect, type is int + * @maixpy maix.image.Rect.y + */ + int y() { + return _y; + } + + /** + * @brief get w of rect + * @return return w of the rect, type is int + * @maixpy maix.image.Rect.w + */ + int w() { + return _w; + } + + /** + * @brief get h of rect + * @return return h of the rect, type is int + * @maixpy maix.image.Rect.h + */ + int h() { + return _h; + } + + /** + * @brief get magnitude of the straight line after Hough transformation + * @return return magnitude, type is int + * @maixpy maix.image.Rect.magnitude + */ + int magnitude() { + return _magnitude; + } + }; + + /** + * circle class + * @maixpy maix.image.Circle + */ + class Circle + { + private: + int _x; + int _y; + int _r; + int _magnitude; + public: + /** + * Circle constructor + * + * @param x coordinate x of the circle + * @param y coordinate y of the circle + * @param r coordinate r of the circle + * @param magnitude coordinate y2 of the straight line + * @maixpy maix.image.Circle.__init__ + */ + Circle(int x, int y, int r, int magnitude) + { + _x = x; + _y = y; + _r = r; + _magnitude = magnitude; + } + ~Circle(){}; + + /** + * Subscript operator + * @param index + * [0] get x of circle + * [1] get y of circle + * [2] get r of circle + * [3] get magnitude of the circle after Hough transformation + * @return int& + * @maixpy maix.image.Circle.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _r; + case 3: return _magnitude; + default:throw std::out_of_range("Circle index out of range"); + } + } + + /** + * @brief get x of circle + * @return return x of the circle, type is int + * @maixpy maix.image.Circle.x + */ + int x() + { + return _x; + }; + + /** + * @brief get y of circle + * @return return y of the circle, type is int + * @maixpy maix.image.Circle.y + */ + int y() + { + return _y; + }; + + /** + * @brief get r of circle + * @return return r of the circle, type is int + * @maixpy maix.image.Circle.r + */ + int r() + { + return _r; + }; + + /** + * @brief get magnitude of the circle after Hough transformation + * @return return magnitude, type is int + * @maixpy maix.image.Circle.magnitude + */ + int magnitude() + { + return _magnitude; + }; + }; + + /** + * Blob class + * @maixpy maix.image.Blob + */ + class Blob + { + private: + int _x; + int _y; + int _w; + int _h; + int _cx; + int _cy; + int _rotation; + float _cxf; + float _cyf; + int _pixels; + float _rotation_f; + int _code; + int _count; + int _merge_cnt; + int _perimeter; + int _roundness; + std::vector _x_hist_bins; + std::vector _y_hist_bins; + std::vector> _corners; + std::vector> _mini_corners; + public: + // /** + // * Blob constructor + // * + // * @param rect blob rect, type is + // * @attention this constructor will be optimized in the future + // */ + // Blob(cv::RotatedRect &rect, int merge_cnt = 0) + // { + // cv::Point2f box_points[4]; + // rect.points(box_points); + // _x = box_points[0].x; + // _y = box_points[0].y; + // _w = rect.size.width; + // _h = rect.size.height; + // _cxf = rect.center.x; + // _cyf = rect.center.y; + // _cx = (int)rect.center.x; + // _cy = (int)rect.center.y; + // _rotation_f = rect.angle; + // _rotation = (int)_rotation_f; + // _merge_cnt = merge_cnt; + // for (int i = 0; i < 4; i++) + // { + // _corners.push_back({(int)box_points[i].x, (int)box_points[i].y}); + // } + // } + + /** + * Blob constructor + * + * @param rect blob rect, type is std::vector + * @param corners blob corners, type is std::vector> + * @param mini_corners blob mini_corners, type is std::vector> + * @param cx blob center x, type is float + * @param cy blob center y, type is float + * @param pixels blob pixels, type is int + * @param rotation blob rotation, type is float + * @param code blob code, type is int + * @param count blob count, type is int + * @param perimeter blob perimeter, type is int + * @param roundness blob roundness, type is float + * @param x_hist_bins blob x_hist_bins, type is std::vector + * @param y_hist_bins blob y_hist_bins, type is std::vector + * @maixpy maix.image.Blob.__init__ + */ + Blob(std::vector &rect, std::vector> &corners, std::vector> &mini_corners,float cx, float cy, int pixels, float rotation, int code, int count, int perimeter, float roundness, std::vector &x_hist_bins, std::vector &y_hist_bins) + { + _x = rect[0]; + _y = rect[1]; + _w = rect[2]; + _h = rect[3]; + _cxf = cx; + _cyf = cy; + _cx = (int)cx; + _cy = (int)cy; + _pixels = pixels; + _rotation_f = rotation; + _rotation = (int)_rotation_f; + _code = code; + _count = count; + _perimeter = perimeter; + _roundness = roundness; + _x_hist_bins = x_hist_bins; + _y_hist_bins = y_hist_bins; + _corners = corners; + _mini_corners = mini_corners; + } + + ~Blob(){}; + + /** + * Subscript operator + * @param index + * [0] Returns the blob’s bounding box x coordinate + * [1] Returns the blob’s bounding box y coordinate + * [2] Returns the blob’s bounding box w coordinate + * [3] Returns the blob’s bounding box h coordinate + * [4] Returns the number of pixels that are part of this blob + * [5] Returns the centroid x position of the blob + * [6] Returns the centroid y position of the blob + * @return int& width or height + * @maixpy maix.image.Blob.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: return _pixels; + case 5: return _cx; + case 6: return _cy; + case 7: return _rotation; + default:throw std::out_of_range("Blob index out of range"); + } + } + + /** + * @brief get blob corners + * @return Returns a list of 4 (x,y) tuples of the 4 corners of the object. + * (x0, y0)___________(x1, y1) + * | | + * | | + * | | + * |___________| + * (x3, y3) (x2, y2) + * note: the order of corners may change + * @maixpy maix.image.Blob.corners + */ + std::vector> corners() + { + return _corners; + } + + /** + * @brief get blob mini corners + * @return Returns a list of 4 (x,y) tuples of the 4 corners than bound the min area rectangle of the blob. + * (x0, y0)___________(x1, y1) + * | | + * | | + * | | + * |___________| + * (x3, y3) (x2, y2) + * note: the order of corners may change + * @maixpy maix.image.Blob.mini_corners + */ + std::vector> mini_corners() + { + return _mini_corners; + } + + /** + * @brief get blob rect + * @return Returns the center coordinates and width and height of the rectangle. format is (x, y, w, h) + * w + * (x, y) ___________ + * | | + * | | h + * | | + * |___________| + * + * @maixpy maix.image.Blob.rect + */ + std::vector rect() + { + std::vector rect = {_x, _y, _w, _h}; + return rect; + }; + + /** + * @brief get blob x of the upper left coordinate + * @return Returns the x coordinate of the upper left corner of the rectangle. + * @maixpy maix.image.Blob.x + */ + int x() + { + return _x; + }; + + /** + * @brief get blob y of the upper left coordinate + * @return Returns the y coordinate of the upper left corner of the rectangle. + * @maixpy maix.image.Blob.y + */ + int y() + { + return _y; + }; + + /** + * @brief get blob width + * @return Returns the blob’s bounding box w coordinate + * @maixpy maix.image.Blob.w + */ + int w() + { + return _w; + }; + + /** + * @brief get blob height + * @return Returns the blob’s bounding box h coordinate + * @maixpy maix.image.Blob.h + */ + int h() + { + return _h; + }; + + /** + * @brief get blob pixels + * @return Returns the number of pixels that are part of this blob. + * @maixpy maix.image.Blob.pixels + */ + int pixels() + { + return _pixels; + }; + + /** + * @brief get blob center x + * @return Returns the centroid x position of the blob + * @maixpy maix.image.Blob.cx + */ + int cx() + { + return _cx; + }; + + /** + * @brief get blob center y + * @return Returns the centroid y position of the blob + * @maixpy maix.image.Blob.cy + */ + int cy() + { + return _cy; + }; + + /** + * @brief get blob center x + * @return Returns the centroid x position of the blob + * @maixpy maix.image.Blob.cxf + */ + float cxf() + { + return _cxf; + }; + + /** + * @brief get blob center y + * @return Returns the centroid y position of the blob + * @maixpy maix.image.Blob.cyf + */ + float cyf() + { + return _cyf; + }; + + /** + * @brief get blob rotation + * @return Returns the rotation of the blob in radians (float). If the blob is like a pencil or pen this value will be unique for 0-180 degrees. + * @maixpy maix.image.Blob.rotation + */ + float rotation() + { + return _rotation_f; + }; + + /** + * @brief get blob rotation_rad + * @return Returns the rotation of the blob in radians + * @maixpy maix.image.Blob.rotation_rad + */ + float rotation_rad() + { + return _rotation_f; + }; + + /** + * @brief get blob rotation_deg + * @return Returns the rotation of the blob in degrees. + * @maixpy maix.image.Blob.rotation_deg + */ + int rotation_deg() + { + return _rotation * 180 / 3.1415926; + }; + + /** + * @brief get blob code + * @return Returns a 32-bit binary number with a bit set in it for each color threshold that’s part of this blob + * @maixpy maix.image.Blob.code + */ + int code() { + return _code; + } + + /** + * @brief get blob count + * @return Returns the number of blobs merged into this blob. + * @maixpy maix.image.Blob.count + */ + int count() { + return _count; + } + + /** + * @brief get blob merge_cnt + * @return Returns the number of pixels on this blob’s perimeter. + * @maixpy maix.image.Blob.perimeter + */ + int perimeter() { + return _perimeter; + } + + /** + * @brief get blob roundness + * @return Returns a value between 0 and 1 representing how round the object is + * @maixpy maix.image.Blob.roundness + */ + float roundness() { + return _roundness; + } + + /** + * @brief get blob elongation + * @returnReturns a value between 0 and 1 representing how long (not round) the object is + * @maixpy maix.image.Blob.elongation + */ + float elongation() { + return 1 - _roundness; + } + + /** + * @brief get blob area + * @return Returns the area of the bounding box around the blob + * @maixpy maix.image.Blob.area + */ + int area() + { + return _w * _h; + }; + + /** + * @brief get blob density + * @return Returns the density ratio of the blob + * @maixpy maix.image.Blob.density + */ + float density() { + int area = _w * _h; + if (area == 0) { + return 0; + } + return (float)_pixels / (float)(_w * _h); + } + + /** + * @brief Alias for blob.density() + * @return Returns the density ratio of the blob + * @maixpy maix.image.Blob.extent + */ + float extent() { + return this->density(); + } + + /** + * @brief get blob compactness + * @return Returns the compactness ratio of the blob + * @maixpy maix.image.Blob.compactness + */ + float compactness() { + float perimeter = _perimeter; + if (perimeter == 0) { + return 0; + } + return (_pixels * 4 * 3.1415926) / (perimeter * perimeter); + } + + /** + * @brief get blob solidity + * @return Returns the solidity ratio of the blob + * @maixpy maix.image.Blob.solidity + */ + float solidity() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + float min_area = (((x0 * y1) + (x1 * y2) + (x2 * y3) + (x3 * y0)) - ((y0 * x1) + (y1 * x2) + (y2 * x3) + (y3 * x0))) / 2.0f; + if (min_area == 0) { + return 0; + } + int pixels = _pixels; + float solidity = (float)pixels / min_area; + return solidity > 1 ? 1 : solidity; + } + + /** + * @brief get blob convexity + * @return Returns a value between 0 and 1 representing how convex the object is + * @maixpy maix.image.Blob.convexity + */ + float convexity() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + + float d0 = sqrtf((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1)); + float d1 = sqrtf((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); + float d2 = sqrtf((x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3)); + float d3 = sqrtf((x3 - x0) * (x3 - x0) + (y3 - y0) * (y3 - y0)); + int perimeter = _perimeter; + if (perimeter == 0) { + return 0; + } + float convexity = (d0 + d1 + d2 + d3) / perimeter; + return convexity > 1 ? convexity : 1; + } + + /** + * @brief get blob x_hist_bins + * @return Returns the x_hist_bins of the blob + * @maixpy maix.image.Blob.x_hist_bins + */ + std::vector x_hist_bins() { + return _x_hist_bins; + } + + /** + * @brief get blob y_hist_bins + * @return Returns the y_hist_bins of the blob + * @maixpy maix.image.Blob.y_hist_bins + */ + std::vector y_hist_bins() { + return _y_hist_bins; + } + + /** + * @brief get blob major_axis_line + * @return Returns a line tuple (x1, y1, x2, y2) of the minor axis of the blob. + * @maixpy maix.image.Blob.major_axis_line + */ + std::vector major_axis_line() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + float l0 = sqrtf((m0x - m2x) * (m0x - m2x) + (m0y - m2y) * (m0y - m2y)); + float l1 = sqrtf((m1x - m3x) * (m1x - m3x) + (m1y - m3y) * (m1y - m3y)); + + if (l0 > l1) { + return {m0x, m0y, m2x, m2y}; + } else { + return {m1x, m1y, m3x, m3y}; + } + } + + /** + * @brief get blob minor_axis_line + * @return Returns a line tuple (x1, y1, x2, y2) of the minor axis of the blob. + * @maixpy maix.image.Blob.minor_axis_line + */ + std::vector minor_axis_line() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + float l0 = sqrtf((m0x - m2x) * (m0x - m2x) + (m0y - m2y) * (m0y - m2y)); + float l1 = sqrtf((m1x - m3x) * (m1x - m3x) + (m1y - m3y) * (m1y - m3y)); + + if (l0 < l1) { + return {m0x, m0y, m2x, m2y}; + } else { + return {m1x, m1y, m3x, m3y}; + } + } + + /** + * @brief get blob enclosing_circle + * @return Returns a circle tuple (x, y, r) of the circle that encloses the min area rectangle of a blob. + * @maixpy maix.image.Blob.enclosing_circle + */ + std::vector enclosing_circle() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + + int cx = (x0 + x1 + x2 + x3) / 4; + int cy = (y0 + y1 + y2 + y3) / 4; + + float d0 = sqrtf((x0 - cx) * (x0 - cx) + (y0 - cy) * (y0 - cy)); + float d1 = sqrtf((x1 - cx) * (x1 - cx) + (y1 - cy) * (y1 - cy)); + float d2 = sqrtf((x2 - cx) * (x2 - cx) + (y2 - cy) * (y2 - cy)); + float d3 = sqrtf((x3 - cx) * (x3 - cx) + (y3 - cy) * (y3 - cy)); + float d = std::max(d0, std::max(d1, std::max(d2, d3))); + + return {cx, cy, (int)d}; + } + + /** + * @brief get blob enclosed_ellipse + * @return Returns an ellipse tuple (x, y, rx, ry, rotation) of the ellipse that fits inside of the min area rectangle of a blob. + * @maixpy maix.image.Blob.enclosed_ellipse + */ + std::vector enclosed_ellipse() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + int cx = (x0 + x1 + x2 + x3) / 4; + int cy = (y0 + y1 + y2 + y3) / 4; + + float d0 = sqrtf((m0x - cx) * (m0x - cx) + (m0y - cy) * (m0y - cy)); + float d1 = sqrtf((m1x - cx) * (m1x - cx) + (m1y - cy) * (m1y - cy)); + float d2 = sqrtf((m2x - cx) * (m2x - cx) + (m2y - cy) * (m2y - cy)); + float d3 = sqrtf((m3x - cx) * (m3x - cx) + (m3y - cy) * (m3y - cy)); + float a = std::min(d0, d2); + float b = std::min(d1, d3); + + float l0 = sqrtf((m0x - m2x) * (m0x - m2x) + (m0y - m2y) * (m0y - m2y)); + float l1 = sqrtf((m1x - m3x) * (m1x - m3x) + (m1y - m3y) * (m1y - m3y)); + + float r; + if (l0 >= l1) { + if (m0x - m2x == 0) + r = 0; + else + r = atan2f(m0y - m2y, m0x - m2x); + } else { + if (m1x - m3x == 0) + r = 3.1415926 / 2; + else + r = atan2f(m1y - m3y, m1x - m3x) + 3.1415926 / 2; + } + return {cx, cy, (int)a, (int)b, (int)r}; + } + }; // class Blob + + /** + * QRCode class + * @maixpy maix.image.QRCode + */ + class QRCode + { + private: + int _x; + int _y; + int _w; + int _h; + std::string _payload; + int _version; + int _ecc_level; + int _mask; + int _data_type; + int _eci; + std::vector> _corners; + public: + /** + * QRCode constructor + * + * @param rect rect of corners, type is std::vector + * @param corners corners of QRCode + * @param payload payload of the QRCode + * @param version version of the QRCode + * @param ecc_level ecc_level of the QRCode + * @param mask mask of the QRCode + * @param data_type data_type of the QRCode + * @param eci eci of the QRCode + * @maixpy maix.image.QRCode.__init__ + */ + QRCode(std::vector &rect, std::vector> &corners, std::string &payload, int version, int ecc_level, int mask, int data_type, int eci) + { + _x = rect[0]; + _y = rect[1]; + _w = rect[2]; + _h = rect[3]; + _payload = payload; + _version = version; + _ecc_level = ecc_level; + _mask = mask; + _data_type = data_type; + _eci = eci; + _corners = corners; + } + + ~QRCode(){}; + + /** + * Subscript operator + * @param index + * [0] Returns the qrcode’s bounding box x coordinate + * [1] Returns the qrcode’s bounding box y coordinate + * [2] Returns the qrcode’s bounding box w coordinate + * [3] Returns the qrcode’s bounding box h coordinate + * [4] Not support this index, try to use payload() method + * [5] Returns the version of qrcode + * [6] Returns the error correction level of qrcode + * [7] Returns the mask of qrcode + * [8] Returns the datatype of qrcode + * [9] Returns the eci of qrcode + * @return int& + * @maixpy maix.image.QRCode.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: throw err::Exception("Not support this index, try to use payload() method"); + case 5: return _version; + case 6: return _ecc_level; + case 7: return _mask; + case 8: return _data_type; + case 9: return _eci; + default:throw std::out_of_range("QRcode index out of range"); + } + } + + /** + * @brief get coordinate of QRCode + * @return return the coordinate of the QRCode. + * @maixpy maix.image.QRCode.corners + */ + std::vector> corners() { + return _corners; + } + + /** + * @brief get rectangle of QRCode + * @return return the rectangle of the QRCode. format is {x, y, w, h}, type is std::vector + * @maixpy maix.image.QRCode.rect + */ + std::vector rect() { + std::vector coord = {_x, _y, _w, _h}; + return coord; + } + + /** + * @brief get x of QRCode + * @return return x of the QRCode, type is int + * @maixpy maix.image.QRCode.x + */ + int x() { + return _x; + } + + /** + * @brief get y of QRCode + * @return return y of the QRCode, type is int + * @maixpy maix.image.QRCode.y + */ + int y() { + return _y; + } + + /** + * @brief get w of QRCode + * @return return w of the QRCode, type is int + * @maixpy maix.image.QRCode.w + */ + int w() { + return _w; + } + + /** + * @brief get h of QRCode + * @return return h of the QRCode, type is int + * @maixpy maix.image.QRCode.h + */ + int h() { + return _h; + } + + /** + * @brief get QRCode payload + * @return return area of the QRCode + * @maixpy maix.image.QRCode.payload + */ + std::string payload() { + return _payload; + } + + /** + * @brief get QRCode version + * @return return version of the QRCode + * @maixpy maix.image.QRCode.version + */ + int version() { + return _version; + } + + /** + * @brief get QRCode error correction level + * @return return error correction level of the QRCode + * @maixpy maix.image.QRCode.ecc_level + */ + int ecc_level() { + return _ecc_level; + } + + /** + * @brief get QRCode mask + * @return return mask of the QRCode + * @maixpy maix.image.QRCode.mask + */ + int mask() { + return _mask; + } + + /** + * @brief get QRCode dataType + * @return return mask of the QRCode + * @maixpy maix.image.QRCode.data_type + */ + int data_type() { + return _data_type; + } + + /** + * @brief get QRCode eci + * @return return data of the QRCode + * @maixpy maix.image.QRCode.eci + */ + int eci() { + return _eci; + } + + /** + * @brief check QRCode is numeric + * @return return true if the result type of the QRCode is numeric + * @maixpy maix.image.QRCode.is_numeric + */ + bool is_numeric() { + return _data_type == 1; + } + + /** + * @brief check QRCode is alphanumeric + * @return return true if the result type of the QRCode is alphanumeric + * @maixpy maix.image.QRCode.is_alphanumeric + */ + bool is_alphanumeric() { + return _data_type == 2; + } + + /** + * @brief check QRCode is binary + * @return return true if the result type of the QRCode is binary + * @maixpy maix.image.QRCode.is_binary + */ + bool is_binary() { + return _data_type == 4; + } + + /** + * @brief check QRCode is kanji + * @return return true if the result type of the QRCode is kanji + * @maixpy maix.image.QRCode.is_kanji + */ + bool is_kanji() { + return _data_type == 8; + } + }; // class QRCode + + /** + * AprilTag class + * @maixpy maix.image.AprilTag + */ + class AprilTag + { + private: + int _x; + int _y; + int _w; + int _h; + int _id; + std::vector> _corners; + int _family; + float _cx; + float _cy; + float _rotation; + float _decision_margin; + int _hamming; + float _goodness; + float _x_translation; + float _y_translation; + float _z_translation; + float _x_rotation; + float _y_rotation; + float _z_rotation; + public: + /** + * AprilTag constructor + * + * @param rect Inlucdes the top-left corner and the width and height of the rectangle. format is {x, y, w, h}, type is std::vector + * @param corners Includes the four corners of the rectangle. format is {{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}, type is std::vector> + * @param id The id of the AprilTag + * @param famliy The family of the AprilTag + * @param centroid_x The x coordinate of the center of the AprilTag + * @param centroid_y The y coordinate of the center of the AprilTag + * @param rotation The rotation of the AprilTag + * @param decision_margin The decision_margin of the AprilTag + * @param hamming The hamming of the AprilTag + * @param goodness The goodness of the AprilTag + * @param x_translation The x_translation of the AprilTag + * @param y_translation The y_translation of the AprilTag + * @param z_translation The z_translation of the AprilTag + * @param x_rotation The x_rotation of the AprilTag + * @param y_rotation The y_rotation of the AprilTag + * @param z_rotation The z_rotation of the AprilTag + * @maixpy maix.image.AprilTag.__init__ + */ + AprilTag(std::vector &rect, std::vector> &corners, int id, int famliy, float centroid_x, float centroid_y, float rotation, float decision_margin, int hamming, float goodness, float x_translation, float y_translation, float z_translation, float x_rotation, float y_rotation, float z_rotation) + { + _x = rect[0]; + _y = rect[1]; + _w = rect[2]; + _h = rect[3]; + _corners = corners; + _id = id; + _family = famliy; + _cx = centroid_x; + _cy = centroid_y; + _rotation = rotation; + _decision_margin = decision_margin; + _hamming = hamming; + _goodness = goodness; + _x_translation = x_translation; + _y_translation = y_translation; + _z_translation = z_translation; + _x_rotation = x_rotation; + _y_rotation = y_rotation; + _z_rotation = z_rotation; + } + + ~AprilTag(){}; + + /** + * Subscript operator + * @param index + * [0] Returns the apriltag’s bounding box x coordinate + * [1] Returns the apriltag’s bounding box y coordinate + * [2] Returns the apriltag’s bounding box w coordinate + * [3] Returns the apriltag’s bounding box h coordinate + * [4] Returns the apriltag’s id + * [5] Returns the apriltag’s family + * [6] Not support + * [7] Not support + * [8] Not support + * [9] Not support + * [10] Returns the apriltag’s hamming + * [11] Not support + * [12] Not support + * [13] Not support + * [14] Not support + * [15] Not support + * [16] Not support + * [17] Not support + * @return int& + * @maixpy maix.image.AprilTag.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: return _id; + case 5: return _family; + case 6: throw err::Exception("Not support this index, try to use cxf() method"); + case 7: throw err::Exception("Not support this index, try to use cyf() method"); + case 8: throw err::Exception("Not support this index, try to use rotation() method"); + case 9: throw err::Exception("Not support this index, try to use decision_margin() method"); + case 10: return _hamming; + case 11: throw err::Exception("Not support this index, try to use goodness() method"); + case 12: throw err::Exception("Not support this index, try to use x_translation() method"); + case 13: throw err::Exception("Not support this index, try to use y_translation() method"); + case 14: throw err::Exception("Not support this index, try to use z_translation() method"); + case 15: throw err::Exception("Not support this index, try to use x_rotation() method"); + case 16: throw err::Exception("Not support this index, try to use y_rotation() method"); + case 17: throw err::Exception("Not support this index, try to use z_rotation() method"); + default:throw std::out_of_range("Apriltag index out of range"); + } + } + + /** + * @brief get coordinate of AprilTag + * @return return the coordinate of the AprilTag. + * @maixpy maix.image.AprilTag.corners + */ + std::vector> corners() { + return _corners; + } + + /** + * @brief get rectangle of AprilTag + * @return return the rectangle of the AprilTag. format is {x, y, w, h}, type is std::vector + * @maixpy maix.image.AprilTag.rect + */ + std::vector rect() { + return {_x, _y, _w, _h}; + } + + /** + * @brief get x of AprilTag + * @return return x of the AprilTag, type is int + * @maixpy maix.image.AprilTag.x + */ + int x() { + return _x; + } + + /** + * @brief get y of AprilTag + * @return return y of the AprilTag, type is int + * @maixpy maix.image.AprilTag.y + */ + int y() { + return _y; + } + + /** + * @brief get w of AprilTag + * @return return w of the AprilTag, type is int + * @maixpy maix.image.AprilTag.w + */ + int w() { + return _w; + } + + /** + * @brief get h of AprilTag + * @return return h of the AprilTag, type is int + * @maixpy maix.image.AprilTag.h + */ + int h() { + return _h; + } + + /** + * @brief get id of AprilTag + * @return return id of the AprilTag, type is int + * @maixpy maix.image.AprilTag.id + */ + int id() { + return _id; + } + + /** + * @brief get family of AprilTag + * @return return family of the AprilTag, type is int + * @maixpy maix.image.AprilTag.family + */ + int family() { + return _family; + } + + /** + * @brief get cx of AprilTag + * @return return cx of the AprilTag, type is int + * @maixpy maix.image.AprilTag.cx + */ + int cx() { + return (int)_cx; + } + + /** + * @brief get cxf of AprilTag + * @return return cxf of the AprilTag, type is float + * @maixpy maix.image.AprilTag.cxf + */ + float cxf() { + return _cx; + } + + /** + * @brief get cy of AprilTag + * @return return cy of the AprilTag, type is int + * @maixpy maix.image.AprilTag.cy + */ + int cy() { + return (int)_cy; + } + + /** + * @brief get cyf of AprilTag + * @return return cyf of the AprilTag, type is float + * @maixpy maix.image.AprilTag.cyf + */ + float cyf() { + return _cy; + } + + /** + * @brief get rotation of AprilTag + * @return return rotation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.rotation + */ + float rotation() { + return _rotation; + } + + /** + * @brief Get decision_margin of AprilTag + * @return Returns the quality of the apriltag match (0.0 - 1.0) where 1.0 is the best. + * @maixpy maix.image.AprilTag.decision_margin + */ + float decision_margin() { + return _decision_margin; + } + + /** + * @brief get hamming of AprilTag + * @return Returns the number of accepted bit errors for this tag. + * return 0, means 0 bit errors will be accepted. + * 1 is TAG25H7, means up to 1 bit error may be accepted + * 2 is TAG25H9, means up to 3 bit errors may be accepted + * 3 is TAG36H10, means up to 3 bit errors may be accepted + * 4 is TAG36H11, means up to 4 bit errors may be accepted + * 5 is ARTOOLKIT, means 0 bit errors will be accepted + * @maixpy maix.image.AprilTag.hamming + */ + int hamming() { + return _hamming; + } + + /** + * @brief get goodness of AprilTag + * @return return goodness of the AprilTag, type is float + * Note: This value is always 0.0 for now. + * @maixpy maix.image.AprilTag.goodness + */ + float goodness() { + return _goodness; + } + + /** + * @brief get x_translation of AprilTag + * @return return x_translation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.x_translation + */ + float x_translation() { + return _x_translation; + } + + /** + * @brief get y_translation of AprilTag + * @return return y_translation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.y_translation + */ + float y_translation() { + return _y_translation; + } + + /** + * @brief get z_translation of AprilTag + * @return return z_translation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.z_translation + */ + float z_translation() { + return _z_translation; + } + + /** + * @brief get x_rotation of AprilTag + * @return return x_rotation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.x_rotation + */ + float x_rotation() { + return _x_rotation; + } + + /** + * @brief get y_rotation of AprilTag + * @return return y_rotation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.y_rotation + */ + float y_rotation() { + return _y_rotation; + } + + /** + * @brief get z_rotation of AprilTag + * @return return z_rotation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.z_rotation + */ + float z_rotation() { + return _z_rotation; + } + }; + + /** + * DataMatrix class + * @maixpy maix.image.DataMatrix + */ + class DataMatrix + { + private: + int _x; + int _y; + int _w; + int _h; + std::vector> _corners; + std::string _payload; + float _rotation; + int _rows; + int _columns; + int _capacity; + int _padding; + public: + /** + * DataMatrix constructor + * + * @param rect Inlucdes the top-left corner and the width and height of the rectangle. format is {x, y, w, h}, type is std::vector + * @param corners Includes the four corners of the rectangle. format is {{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}, type is std::vector> + * @param payload The payload of the DataMatrix + * @param rotation The rotation of the DataMatrix + * @param rows The rows of the DataMatrix + * @param columns The columns of the DataMatrix + * @param capacity The capacity of the DataMatrix + * @param padding The padding of the DataMatrix + * @maixpy maix.image.DataMatrix.__init__ + */ + DataMatrix(std::vector &rect, std::vector> &corners, std::string &payload, float rotation, int rows, int columns, int capacity, int padding) + { + _x = rect[0]; + _y = rect[1]; + _w = rect[2]; + _h = rect[3]; + _corners = corners; + _payload = payload; + _rotation = rotation; + _rows = rows; + _columns = columns; + _capacity = capacity; + _padding = padding; + } + + ~DataMatrix(){}; + + /** + * Subscript operator + * @param index + * [0] get x of DataMatrix + * [1] get y of DataMatrix + * [2] get w of DataMatrix + * [3] get h of DataMatrix + * [4] Not support this index, try to use payload() method + * [5] Not support this index, try to use rotation() method + * [6] get rows of DataMatrix + * [7] get columns of DataMatrix + * [8] get capacity of DataMatrix + * [9] get padding of DataMatrix + * @return int& + * @maixpy maix.image.DataMatrix.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: throw err::Exception("Not support this index, try to use payload() method"); + case 5: throw err::Exception("Not support this index, try to use ratation() method"); + case 6: return _rows; + case 7: return _columns; + case 8: return _capacity; + case 9: return _padding; + default:throw std::out_of_range("DataMatrix index out of range"); + } + } + + /** + * @brief get coordinate of DataMatrix + * @return return the coordinate of the DataMatrix. + * @maixpy maix.image.DataMatrix.corners + */ + std::vector> corners() { + return _corners; + } + + /** + * @brief get rectangle of DataMatrix + * @return return the rectangle of the DataMatrix. format is {x, y, w, h}, type is std::vector + * @maixpy maix.image.DataMatrix.rect + */ + std::vector rect() { + return {_x, _y, _w, _h}; + } + + /** + * @brief get x of DataMatrix + * @return return x of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.x + */ + int x() { + return _x; + } + + /** + * @brief get y of DataMatrix + * @return return y of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.y + */ + int y() { + return _y; + } + + /** + * @brief get w of DataMatrix + * @return return w of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.w + */ + int w() { + return _w; + } + + /** + * @brief get h of DataMatrix + * @return return h of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.h + */ + int h() { + return _h; + } + + /** + * @brief get payload of DataMatrix + * @return return payload of the DataMatrix, type is std::string + * @maixpy maix.image.DataMatrix.payload + */ + std::string payload() { + return _payload; + } + + /** + * @brief get rotation of DataMatrix + * @return return rotation of the DataMatrix, type is float + * @maixpy maix.image.DataMatrix.rotation + */ + float rotation() { + return _rotation; + } + + /** + * @brief get rows of DataMatrix + * @return return rows of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.rows + */ + int rows() { + return _rows; + } + + /** + * @brief get columns of DataMatrix + * @return return columns of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.columns + */ + int columns() { + return _columns; + } + + /** + * @brief get capacity of DataMatrix + * @return returns how many characters could fit in this data matrix, type is int + * @maixpy maix.image.DataMatrix.capacity + */ + int capacity() { + return _capacity; + } + + /** + * @brief get padding of DataMatrix + * @return returns how many unused characters are in this data matrix, type is int + * @maixpy maix.image.DataMatrix.padding + */ + int padding() { + return _padding; + } + }; + + /** + * BarCode class + * @maixpy maix.image.BarCode + */ + class BarCode { + private: + int _x; + int _y; + int _w; + int _h; + std::vector> _corners; + std::string _payload; + int _type; + float _rotation; + int _quality; + public: + /** + * BarCode constructor + * + * @param rect Inlucdes the top-left corner and the width and height of the rectangle. format is {x, y, w, h}, type is std::vector + * @param corners Includes the four corners of the rectangle. format is {{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}, type is std::vector> + * @param payload The payload of the BarCode + * @param type The type of the BarCode + * @param rotation The rotation of the BarCode + * @param quality The quality of the BarCode + * @maixpy maix.image.BarCode.__init__ + */ + BarCode(std::vector &rect, std::vector> &corners, std::string &payload, int type, float rotation, int quality) + { + _x = rect[0]; + _y = rect[1]; + _w = rect[2]; + _h = rect[3]; + _corners = corners; + _payload = payload; + _type = type; + _rotation = rotation; + _quality = quality; + } + + ~BarCode(){}; + + /** + * Subscript operator + * @param index + * [0] get x of BarCode + * [1] get y of BarCode + * [2] get w of BarCode + * [3] get h of BarCode + * [4] Not support this index, try to use payload() method + * [5] get type of BarCode + * [6] Not support this index, try to use rotation() method + * [7] get quality of BarCode + * @return int& + * @maixpy maix.image.BarCode.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: throw err::Exception("Not support this index, try to use payload() method"); + case 5: return _type; + case 6: throw err::Exception("Not support this index, try to use rotation() method"); + case 7: return _quality; + default:throw std::out_of_range("BarCode index out of range"); + } + } + + /** + * @brief get coordinate of BarCode + * @return return the coordinate of the BarCode. + * @maixpy maix.image.BarCode.corners + */ + std::vector> corners() { + return _corners; + } + + /** + * @brief get rectangle of BarCode + * @return return the rectangle of the BarCode. format is {x, y, w, h}, type is std::vector + * @maixpy maix.image.BarCode.rect + */ + std::vector rect() { + return {_x, _y, _w, _h}; + } + + /** + * @brief get x of BarCode + * @return return x of the BarCode, type is int + * @maixpy maix.image.BarCode.x + */ + int x() { + return _x; + } + + /** + * @brief get y of BarCode + * @return return y of the BarCode, type is int + * @maixpy maix.image.BarCode.y + */ + int y() { + return _y; + } + + /** + * @brief get w of BarCode + * @return return w of the BarCode, type is int + * @maixpy maix.image.BarCode.w + */ + int w() { + return _w; + } + + /** + * @brief get h of BarCode + * @return return h of the BarCode, type is int + * @maixpy maix.image.BarCode.h + */ + int h() { + return _h; + } + + /** + * @brief get payload of BarCode + * @return return payload of the BarCode, type is std::string + * @maixpy maix.image.BarCode.payload + */ + std::string payload() { + return _payload; + } + + /** + * @brief get type of BarCode + * @return return type of the BarCode, type is int + * @maixpy maix.image.BarCode.type + */ + int type() { + return _type; + } + + /** + * @brief get rotation of BarCode + * @return return rotation of the BarCode, type is float. FIXME: always return 0.0 + * @maixpy maix.image.BarCode.rotation + */ + float rotation() { + return _rotation; + } + + /** + * @brief get quality of BarCode + * @return return quality of the BarCode, type is int + * @maixpy maix.image.BarCode.quality + */ + int quality() { + return _quality; + } + }; + + /** + * Statistics class + * @maixpy maix.image.Statistics + */ + class Statistics { + private: + int _l_mean; + int _l_median; + int _l_mode; + int _l_std_dev; + int _l_min; + int _l_max; + int _l_lq; + int _l_uq; + + int _a_mean; + int _a_median; + int _a_mode; + int _a_std_dev; + int _a_min; + int _a_max; + int _a_lq; + int _a_uq; + + int _b_mean; + int _b_median; + int _b_mode; + int _b_std_dev; + int _b_min; + int _b_max; + int _b_lq; + int _b_uq; + + image::Format _format; + public: + /** + * Statistics constructor + * + * @param format The statistics source image format + * @param l_statistics The statistics of the L channel. format is {mean, median, mode, std_dev, min, max, lq, uq}, type is std::vector + * @param a_statistics The statistics of the A channel. format is {mean, median, mode, std_dev, min, max, lq, uq}, type is std::vector + * @param b_statistics The statistics of the B channel. format is {mean, median, mode, std_dev, min, max, lq, uq}, type is std::vector + * @maixpy maix.image.Statistics.__init__ + */ + Statistics(image::Format format, std::vector &l_statistics, std::vector &a_statistics, std::vector &b_statistics) + { + err::check_bool_raise((l_statistics.size() == 8) || (a_statistics.size() == 8) || (b_statistics.size() == 8), "statistics size must be 8"); + + _l_mean = l_statistics[0]; + _l_median = l_statistics[1]; + _l_mode = l_statistics[2]; + _l_std_dev = l_statistics[3]; + _l_min = l_statistics[4]; + _l_max = l_statistics[5]; + _l_lq = l_statistics[6]; + _l_uq = l_statistics[7]; + + _a_mean = a_statistics[0]; + _a_median = a_statistics[1]; + _a_mode = a_statistics[2]; + _a_std_dev = a_statistics[3]; + _a_min = a_statistics[4]; + _a_max = a_statistics[5]; + _a_lq = a_statistics[6]; + _a_uq = a_statistics[7]; + + _b_mean = b_statistics[0]; + _b_median = b_statistics[1]; + _b_mode = b_statistics[2]; + _b_std_dev = b_statistics[3]; + _b_min = b_statistics[4]; + _b_max = b_statistics[5]; + _b_lq = b_statistics[6]; + _b_uq = b_statistics[7]; + + _format = format; + } + Statistics(){}; + ~Statistics(){}; + + /** + * Subscript operator + * @param index array index + * @return int& + * @maixpy maix.image.Statistics.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _l_mean; + case 1: return _l_median; + case 2: return _l_mode; + case 3: return _l_std_dev; + case 4: return _l_min; + case 5: return _l_max; + case 6: return _l_lq; + case 7: return _l_uq; + case 8: return _a_mean; + case 9: return _a_median; + case 10: return _a_mode; + case 11: return _a_std_dev; + case 12: return _a_min; + case 13: return _a_max; + case 14: return _a_lq; + case 15: return _a_uq; + case 16: return _b_mean; + case 17: return _b_median; + case 18: return _b_mode; + case 19: return _b_std_dev; + case 20: return _b_min; + case 21: return _b_max; + case 22: return _b_lq; + case 23: return _b_uq; + default:throw std::out_of_range("Statistics index out of range"); + } + } + + /** + * @brief get format of Statistics source image + * @return return format of the Statistics source image, type is image::Format + * @maixpy maix.image.Statistics.format + */ + image::Format format() { + return _format; + } + + /** + * @brief get L channel mean + * @return return L channel mean, type is int + * @maixpy maix.image.Statistics.l_mean + */ + int l_mean() { + return _l_mean; + } + + /** + * @brief get L channel median + * @return return L channel median, type is int + * @maixpy maix.image.Statistics.l_median + */ + int l_median() { + return _l_median; + } + + /** + * @brief get L channel mode + * @return return L channel mode, type is int + * @maixpy maix.image.Statistics.l_mode + */ + int l_mode() { + return _l_mode; + } + + /** + * @brief get L channel std_dev + * @return return L channel std_dev, type is int + * @maixpy maix.image.Statistics.l_std_dev + */ + int l_std_dev() { + return _l_std_dev; + } + + /** + * @brief get L channel min + * @return return L channel min, type is int + * @maixpy maix.image.Statistics.l_min + */ + int l_min() { + return _l_min; + } + + /** + * @brief get L channel max + * @return return L channel max, type is int + * @maixpy maix.image.Statistics.l_max + */ + int l_max() { + return _l_max; + } + + /** + * @brief get L channel lq + * @return return L channel lq, type is int + * @maixpy maix.image.Statistics.l_lq + */ + int l_lq() { + return _l_lq; + } + + /** + * @brief get L channel uq + * @return return L channel uq, type is int + * @maixpy maix.image.Statistics.l_uq + */ + int l_uq() { + return _l_uq; + } + + /** + * @brief get A channel mean + * @return return A channel mean, type is int + * @maixpy maix.image.Statistics.a_mean + */ + int a_mean() { + return _a_mean; + } + + /** + * @brief get A channea median + * @return return A channel median, type is int + * @maixpy maix.image.Statistics.a_median + */ + int a_median() { + return _a_median; + } + + /** + * @brief get A channel mode + * @return return A channel mode, type is int + * @maixpy maix.image.Statistics.a_mode + */ + int a_mode() { + return _a_mode; + } + + /** + * @brief get A channel std_dev + * @return return A channel std_dev, type is int + * @maixpy maix.image.Statistics.a_std_dev + */ + int a_std_dev() { + return _a_std_dev; + } + + /** + * @brief get A channel min + * @return return A channel min, type is int + * @maixpy maix.image.Statistics.a_min + */ + int a_min() { + return _a_min; + } + + /** + * @brief get A channel max + * @return return A channel max, type is int + * @maixpy maix.image.Statistics.a_max + */ + int a_max() { + return _a_max; + } + + /** + * @brief get A channel lq + * @return return A channel lq, type is int + * @maixpy maix.image.Statistics.a_lq + */ + int a_lq() { + return _a_lq; + } + + /** + * @brief get A channel uq + * @return return A channel uq, type is int + * @maixpy maix.image.Statistics.a_uq + */ + int a_uq() { + return _a_uq; + } + + /** + * @brief get B channel mean + * @return return B channel mean, type is int + * @maixpy maix.image.Statistics.b_mean + */ + int b_mean() { + return _b_mean; + } + + /** + * @brief get B channea median + * @return return B channel median, type is int + * @maixpy maix.image.Statistics.b_median + */ + int b_median() { + return _b_median; + } + + /** + * @brief get B channel mode + * @return return B channel mode, type is int + * @maixpy maix.image.Statistics.b_mode + */ + int b_mode() { + return _b_mode; + } + + /** + * @brief get B channel std_dev + * @return return B channel std_dev, type is int + * @maixpy maix.image.Statistics.b_std_dev + */ + int b_std_dev() { + return _b_std_dev; + } + + /** + * @brief get B channel min + * @return return B channel min, type is int + * @maixpy maix.image.Statistics.b_min + */ + int b_min() { + return _b_min; + } + + /** + * @brief get B channel max + * @return return B channel max, type is int + * @maixpy maix.image.Statistics.b_max + */ + int b_max() { + return _b_max; + } + + /** + * @brief get B channel lq + * @return return B channel lq, type is int + * @maixpy maix.image.Statistics.b_lq + */ + int b_lq() { + return _b_lq; + } + + /** + * @brief get B channel uq + * @return return B channel uq, type is int + * @maixpy maix.image.Statistics.b_uq + */ + int b_uq() { + return _b_uq; + } + }; + + + /** + * Displacement class + * @maixpy maix.image.Displacement + */ + class Displacement { + private: + float _x_translation; + float _y_translation; + float _rotation; + float _scale; + float _response; + public: + Displacement(){}; + /** + * Displacement constructor + * + * @param x_translation The x_translation of the Displacement + * @param y_translation The y_translation of the Displacement + * @param rotation The rotation of the Displacement + * @param scale The scale of the Displacement + * @param response The response of the Displacement + * @maixpy maix.image.Displacement.__init__ + */ + Displacement(float x_translation, float y_translation, float rotation, float scale, float response) + { + _x_translation = x_translation; + _y_translation = y_translation; + _rotation = rotation; + _scale = scale; + _response = response; + } + + ~Displacement(){}; + + /** + * Subscript operator + * @param index array index + * @return int& + * @maixpy maix.image.Displacement.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: throw err::Exception("Not support this index, try to use x_translation() method"); + case 1: throw err::Exception("Not support this index, try to use y_translation() method"); + case 2: throw err::Exception("Not support this index, try to use rotation() method"); + case 3: throw err::Exception("Not support this index, try to use scale() method"); + case 4: throw err::Exception("Not support this index, try to use response() method"); + default:throw std::out_of_range("Displacement index out of range"); + } + } + + /** + * @brief get x_translation of Displacement + * @return return x_translation of the Displacement, type is float + * @maixpy maix.image.Displacement.x_translation + */ + float x_translation() { + return _x_translation; + } + + /** + * @brief get y_translation of Displacement + * @return return y_translation of the Displacement, type is float + * @maixpy maix.image.Displacement.y_translation + */ + float y_translation() { + return _y_translation; + } + + /** + * @brief get rotation of Displacement + * @return return rotation of the Displacement, type is float + * @maixpy maix.image.Displacement.rotation + */ + float rotation() { + return _rotation; + } + + /** + * @brief get scale of Displacement + * @return return scale of the Displacement, type is float + * @maixpy maix.image.Displacement.scale + */ + float scale() { + return _scale; + } + + /** + * @brief get response of Displacement + * @return return response of the Displacement, type is float + * @maixpy maix.image.Displacement.response + */ + float response() { + return _response; + } + }; + + /** + * LBPKeyPoint class + * @maixpy maix.image.LBPKeyPoint + */ + class LBPKeyPoint { + private: + std::valarray data; + public: + LBPKeyPoint(){}; + + /** + * LBPKeyPoint constructor + * + * @param data The data of the LBPKeyPoint + * @maixpy maix.image.LBPKeyPoint.__init__ + */ + LBPKeyPoint(std::valarray &data) + { + this->data = data; + } + + ~LBPKeyPoint(){}; + + std::valarray get_data() { + return data; + } + }; + + /** + * KeyPoint class + * @maixpy maix.image.KeyPoint + */ + class KeyPoint { + private: + uint16_t _x; + uint16_t _y; + uint16_t _score; + uint16_t _octave; + uint16_t _angle; + uint16_t _matched; + std::vector desc; + public: + /** + * KeyPoint constructor + * + * @param x The x of the KeyPoint + * @param y The y of the KeyPoint + * @param score The score of the KeyPoint + * @param octave The octave of the KeyPoint + * @param angle The angle of the KeyPoint + * @param matched The matched of the KeyPoint + * @param desc The desc of the KeyPoint + * @maixpy maix.image.KeyPoint.__init__ + */ + KeyPoint(uint16_t x, uint16_t y, uint16_t score, uint16_t octave, uint16_t angle, uint16_t matched, std::vector &desc) + { + _x = x; + _y = y; + _score = score; + _octave = octave; + _angle = angle; + _matched = matched; + this->desc = desc; + } + + ~KeyPoint(){}; + }; + + /** + * KPTMatch class + * @maixpy maix.image.KPTMatch + */ + class KPTMatch { + private: + int cx; + int cy; + int x; + int y; + int w; + int h; + int score; + int theta; + int match; + public: + KPTMatch() {} + /** + * KPTMatch constructor + * + * @param cx The cx of the KPTMatch + * @param cy The cy of the KPTMatch + * @param x The x of the KPTMatch + * @param y The y of the KPTMatch + * @param w The w of the KPTMatch + * @param h The h of the KPTMatch + * @param score The score of the KPTMatch + * @param theta The theta of the KPTMatch + * @param match The match of the KPTMatch + * @maixpy maix.image.KPTMatch.__init__ + */ + KPTMatch(int cx, int cy, int x, int y, int w, int h, int score, int theta, int match) + { + this->cx = cx; + this->cy = cy; + this->x = x; + this->y = y; + this->w = w; + this->h = h; + this->score = score; + this->theta = theta; + this->match = match; + } + + ~KPTMatch(){}; + }; + + /** + * ORBKeyPoint class + * @maixpy maix.image.ORBKeyPoint + */ + class ORBKeyPoint { + private: + std::vector data; + int threshold; + bool normalized; + public: + ORBKeyPoint(){}; + /** + * ORBKeyPoint constructor + * + * @param data The data of the ORBKeyPoint + * @param threshold The threshold of the ORBKeyPoint + * @param normalized The normalized of the ORBKeyPoint + * @maixpy maix.image.ORBKeyPoint.__init__ + */ + ORBKeyPoint(std::vector &data, int threshold, bool normalized) + { + this->data = data; + this->threshold = threshold; + this->normalized = normalized; + } + + ~ORBKeyPoint(){}; + + /** + * @brief get data of ORBKeyPoint + * @return return data of the ORBKeyPoint, type is std::vector + * @maixpy maix.image.ORBKeyPoint.get_data + */ + std::vector get_data() { + return data; + } + }; + + /** + * HaarCascade class + * @maixpy maix.image.HaarCascade + */ + class HaarCascade { + private: + int std; // Image standard deviation. + int step; // Image scanning factor. + float threshold; // Detection threshold. + float scale_factor; // Image scaling factor. + int n_stages; // Number of stages in the cascade. + int n_features; // Number of features in the cascade. + int n_rectangles; // Number of rectangles in the cascade. + std::vector window; // Detection window size. + // image::Image *img; // Grayscale image. + std::vector sum; // Integral image. + // sum:[0] width, [1] height, [2] y_offs, [3] x_ratio, [4] y_ratio, [5] uint32_t **data, [6] uint32_t **swap + std::vector ssq; // Squared integral image. + // ssq:[0] width, [1] height, [2] y_offs, [3] x_ratio, [4] y_ratio, [5] uint32_t **data, [6] uint32_t **swap + uint8_t *stages_array; // Number of features per stage. + int16_t *stages_thresh_array; // Stages thresholds. + int16_t *tree_thresh_array; // Features threshold (1 per feature). + int16_t *alpha1_array; // Alpha1 array (1 per feature). + int16_t *alpha2_array; // Alpha2 array (1 per feature). + int8_t *num_rectangles_array; // Number of rectangles per features (1 per feature). + int8_t *weights_array; // Rectangles weights (1 per rectangle). + int8_t *rectangles_array; // Rectangles array. + public: + /** + * HaarCascade constructor + * + * @param data The data of the HaarCascade + * @param threshold The threshold of the HaarCascade + * @param normalized The normalized of the HaarCascade + * @maixpy maix.image.HaarCascade.__init__ + */ + HaarCascade() + { + + } + + ~HaarCascade(){}; + }; +} diff --git a/support/sg2002/additional/vision/include/maix_vision.hpp b/support/sg2002/additional/vision/include/maix_vision.hpp new file mode 100644 index 0000000..4040ff5 --- /dev/null +++ b/support/sg2002/additional/vision/include/maix_vision.hpp @@ -0,0 +1,4 @@ +#include "maix_image.hpp" +#include "maix_display.hpp" +#include "maix_camera.hpp" +#include "maix_video.hpp" diff --git a/support/sg2002/additional/vision/include_private/maix_image_util.hpp b/support/sg2002/additional/vision/include_private/maix_image_util.hpp new file mode 100644 index 0000000..c7c85dd --- /dev/null +++ b/support/sg2002/additional/vision/include_private/maix_image_util.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "maix_image.hpp" +#include "omv.hpp" + +namespace maix::image +{ + /** + * Convert image to openmv image format + * @param image image + * @param imlib_image openmv image + * @return + */ + extern void convert_to_imlib_image(image::Image *image, image_t *imlib_image); + extern void _convert_to_lab_thresholds(std::vector> &in, list_t *out); +} + diff --git a/support/sg2002/additional/vision/port/maixcam/maix_camera_mmf.hpp b/support/sg2002/additional/vision/port/maixcam/maix_camera_mmf.hpp new file mode 100644 index 0000000..7de5bd8 --- /dev/null +++ b/support/sg2002/additional/vision/port/maixcam/maix_camera_mmf.hpp @@ -0,0 +1,317 @@ +/** + * @author lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + + +#pragma once + +#include +#include "maix_err.hpp" +#include "maix_basic.hpp" +#include "maix_log.hpp" +#include "maix_image.hpp" +#include "maix_time.hpp" +#include "maix_camera_base.hpp" +#include "kvm_mmf.hpp" +#include + +static void try_deinit_mmf() +{ + static uint8_t is_called = 0; + if (!is_called) { + mmf_try_deinit(true); + is_called = 1; + } +} + +static void signal_handle(int signal) +{ + const char *signal_msg = NULL; + switch (signal) { + case SIGILL: signal_msg = "SIGILL"; break; + case SIGTRAP: signal_msg = "SIGTRAP"; break; + case SIGABRT: signal_msg = "SIGABRT"; break; + case SIGBUS: signal_msg = "SIGBUS"; break; + case SIGFPE: signal_msg = "SIGFPE"; break; + case SIGKILL: signal_msg = "SIGKILL"; break; + case SIGSEGV: signal_msg = "SIGSEGV"; break; + default: signal_msg = "UNKNOWN"; break; + } + + maix::log::error("Trigger signal, code:%s(%d)!\r\n", signal_msg, signal); + try_deinit_mmf(); + exit(1); +} + +// FIXME: move this function to port/maix_vision_maixcam.cpp ? +static __attribute__((constructor)) void maix_vision_register_signal(void) +{ + signal(SIGILL, signal_handle); + signal(SIGTRAP, signal_handle); + signal(SIGABRT, signal_handle); + signal(SIGBUS, signal_handle); + signal(SIGFPE, signal_handle); + signal(SIGKILL, signal_handle); + signal(SIGSEGV, signal_handle); + + maix::util::register_exit_function(try_deinit_mmf); +} + +namespace maix::camera +{ + class CameraCviMmf final : public CameraBase + { + public: + CameraCviMmf(const std::string device, int width, int height, image::Format format, int buff_num) + { + this->device = device; + this->format = format; + this->width = width; + this->height = height; + this->buffer_num = buff_num; + this->ch = -1; + + if (0 != mmf_init()) { + err::check_raise(err::ERR_RUNTIME, "mmf init failed"); + } + + if (0 != mmf_vi_init()) { + err::check_raise(err::ERR_RUNTIME, "mmf vi init failed"); + } + } + + CameraCviMmf(const std::string device, int ch, int width, int height, image::Format format, int buff_num) + { + this->device = device; + this->format = format; + this->width = width; + this->height = height; + this->buffer_num = buff_num; + this->ch = ch; + + if (0 != mmf_init()) { + err::check_raise(err::ERR_RUNTIME, "mmf init failed"); + } + + if (0 != mmf_vi_init()) { + err::check_raise(err::ERR_RUNTIME, "mmf vi init failed"); + } + } + + ~CameraCviMmf() + { + mmf_del_vi_channel(this->ch); + if (0 != mmf_vi_deinit()) { + err::check_raise(err::ERR_RUNTIME, "mmf vi init failed"); + } + mmf_try_deinit(true); + } + + err::Err open(int width, int height, image::Format format, int buff_num) + { + if (format == image::FMT_GRAYSCALE) { + format = image::FMT_YVU420SP; + } + int ch = mmf_get_vi_unused_channel(); + if (ch < 0) { + log::error("camera open: mmf get vi channel failed"); + return err::ERR_RUNTIME; + } + if (0 != mmf_add_vi_channel(ch, width, height, mmf_invert_format_to_mmf(format))) { + log::error("camera open: mmf add vi channel failed"); + return err::ERR_RUNTIME; + } + + this->ch = ch; + this->width = (width == -1) ? this->width : width; + this->height = (height == -1) ? this->height : height; + this->align_width = mmf_vi_aligned_width(this->ch); + this->align_need = (this->width % this->align_width == 0) ? false : true; // Width need align only + return err::ERR_NONE; + } // open + + bool is_support_format(image::Format format) + { + if(format == image::Format::FMT_RGB888 || format == image::Format::FMT_BGR888 + || format == image::Format::FMT_YVU420SP|| format == image::Format::FMT_GRAYSCALE) + return true; + return false; + } + + void close() + { + if (mmf_vi_chn_is_open(this->ch) == true) { + if (0 != mmf_del_vi_channel(this->ch)) { + log::error("mmf del vi channel failed"); + } + } + } // close + + // read + image::Image *read(void *buff = NULL, size_t buff_size = 0) + { + // printf("Func: maix_image.cpp read\r\n"); + image::Image *img = NULL; + + void *buffer = NULL; + int buffer_len = 0, width = 0, height = 0, format = 0; + + if (0 == mmf_vi_frame_pop(this->ch, &buffer, &buffer_len, &width, &height, &format)) { + if (buffer == NULL) { + mmf_vi_frame_free(this->ch); + printf("mmf_vi_frame_free error\r\n"); + return NULL; + } + if(buff) + { + if(buff_size < (size_t)buffer_len) + { + log::error("camera read: buff size not enough, need %d, but %d", buffer_len, buff_size); + mmf_vi_frame_free(this->ch); + return NULL; + } + img = new image::Image(width, height, this->format, (uint8_t*)buff, buff_size, false); + } + else + { + img = new image::Image(this->width, this->height, this->format); + } + void *image_data = img->data(); + switch (img->format()) { + case image::Format::FMT_GRAYSCALE: + if (this->align_need) { + for (int h = 0; h < this->height; h ++) { + memcpy((uint8_t *)image_data + h * this->width, (uint8_t *)buffer + h * width, this->width); + } + } else { + memcpy(image_data, buffer, this->width * this->height); + } + break; + case image::Format::FMT_BGR888: // fall through + case image::Format::FMT_RGB888: + if (this->align_need) { + for (int h = 0; h < this->height; h++) { + memcpy((uint8_t *)image_data + h * this->width * 3, (uint8_t *)buffer + h * width * 3, this->width * 3); + } + } else { + memcpy(image_data, buffer, this->width * this->height * 3); + } + break; + case image::Format::FMT_YVU420SP: + if (this->align_need) { + for (int h = 0; h < this->height * 3 / 2; h ++) { + memcpy((uint8_t *)image_data + h * this->width, (uint8_t *)buffer + h * width, this->width); + } + } else { + memcpy(image_data, buffer, this->width * this->height * 3 / 2); + } + break; + default: + printf("unknown format\n"); + delete img; + mmf_vi_frame_free(this->ch); + printf("switch (img->format()\r\n"); + return NULL; + } + mmf_vi_frame_free(this->ch); + // printf("mmf_vi_frame_free\r\n"); + return img; + } + + return img; + } // read + + camera::CameraCviMmf *add_channel(int width, int height, image::Format format, int buff_num) + { + int new_channel = mmf_get_vi_unused_channel(); + if (new_channel < 0) { + log::error("Support not more channel!\r\n"); + return NULL; + } + return new camera::CameraCviMmf(this->device, new_channel, width, height, format, buff_num); + } + + void clear_buff() + { + + } + + bool is_opened() { + return mmf_vi_chn_is_open(this->ch); + } + + int get_ch_nums() { + return 1; + } + + int get_channel() { + return this->ch; + } + + int hmirror(int en) + { + bool out; + if (en == -1) { + mmf_get_vi_hmirror(this->ch, &out); + } else { + bool need_open = false; + if (this->is_opened()) { + this->close(); + need_open = true; + } + + mmf_set_vi_hmirror(this->ch, en); + + if (need_open) { + err::check_raise(this->open(this->width, this->height, this->format, this->buffer_num), "Open failed"); + } + out = en; + } + + return out; + } + + int vflip(int en) + { + bool out; + if (en == -1) { + mmf_get_vi_vflip(this->ch, &out); + } else { + bool need_open = false; + if (this->is_opened()) { + this->close(); + need_open = true; + } + + mmf_set_vi_vflip(this->ch, en); + + if (need_open) { + err::check_raise(this->open(this->width, this->height, this->format, this->buffer_num), "Open failed"); + } + out = en; + } + return out; + } + + private: + std::string device; + image::Format format; + int fd; + int ch; + uint32_t raw_format; + std::vector buffers; + std::vector buffers_len; + int buffer_num; + int queue_id; // user directly used buffer id + int width; + int height; + void *buff; + bool buff_alloc; + int align_width; + bool align_need; + }; + +} // namespace maix::camera diff --git a/support/sg2002/additional/vision/src/maix_camera.cpp b/support/sg2002/additional/vision/src/maix_camera.cpp new file mode 100644 index 0000000..8dab65c --- /dev/null +++ b/support/sg2002/additional/vision/src/maix_camera.cpp @@ -0,0 +1,425 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + + +#include "maix_camera.hpp" +#include +#ifdef PLATFORM_LINUX + #include "maix_camera_v4l2.hpp" +#endif +#ifdef PLATFORM_MAIXCAM + #include "maix_camera_mmf.hpp" +#endif + +namespace maix::camera +{ + static bool set_regs_flag = false; + + std::vector list_devices() + { + // find to /dev/video* + std::vector devices; + std::string path = "/dev"; + DIR *dir = opendir(path.c_str()); + if (dir == NULL) + { + return devices; + } + struct dirent *ptr; + while ((ptr = readdir(dir)) != NULL) + { + if (ptr->d_type == DT_CHR) + { + std::string name = ptr->d_name; + if (name.find("video") != std::string::npos) + { + devices.push_back( path + "/" + name ); + } + } + } + closedir(dir); + // sort devices with name + std::sort(devices.begin(), devices.end()); + // print devices + for (size_t i = 0; i < devices.size(); i++) + { + log::debug("find device: %s\n", devices[i].c_str()); + } + return devices; + } + + void set_regs_enable(bool enable) { + set_regs_flag = enable; + } + + err::Err Camera::show_colorbar(bool enable) + { + // only set variable now + // should control camera to show colorbar + _show_colorbar = enable; + return err::ERR_NONE; + } + + static void generate_colorbar(image::Image &img) + { + int width = img.width(); + int height = img.height(); + int step = width / 8; + int color_step = 255 / 7; + int color = 0; + uint8_t colors[8][3] = { + {255, 255, 255}, + {255, 0, 0}, + {255, 127, 0}, + {255, 255, 0}, + {0, 255, 0}, + {0, 0, 255}, + {143, 0, 255}, + {0, 0, 0}, + }; + for (int i = 0; i < 8; i++) + { + image::Color _color(colors[i][0], colors[i][1], colors[i][2], 0, image::FMT_RGB888); + img.draw_rect(i * step, 0, step, height, _color, -1); + color += color_step; + } + } +#ifdef PLATFORM_LINUX + static char * _get_device(const char *device) + { + if (device) + { + return (char *)device; + } + else + { + std::vector devices = list_devices(); + err::check_bool_raise(devices.size() > 0, "No camera device"); + return (char *)devices[0].c_str(); + } + } +#endif + + Camera::Camera(int width, int height, image::Format format, const char *device, int fps, int buff_num, bool open) + { + err::Err e; + err::check_bool_raise(_check_format(format), "Format not support"); + + if (format == image::Format::FMT_RGB888 && width * height * 3 > 640 * 640 * 3) { + log::warn("Note that we do not recommend using large resolution RGB888 images, which can take up a lot of memory!\r\n"); + } + + _width = (width == -1) ? 640 : width; + _height = (height == -1) ? 480 : height; + _format = format; + _fps = (fps == -1) ? 30 : fps; + _buff_num = buff_num; + + _show_colorbar = false; + _open_set_regs = set_regs_flag; + _impl = NULL; + +#ifdef PLATFORM_LINUX + _device = _get_device(device); + _impl = new CameraV4L2(_device, _width, _height, _format, _buff_num); +#endif + +#ifdef PLATFORM_MAIXCAM + _device = ""; + _impl = new CameraCviMmf(_device, _width, _height, _format, _buff_num); +#endif + + if (open) { + e = this->open(_width, _height, _format, _buff_num); + + // err::check_raise(e, "camera open failed"); + } + } + + Camera::Camera(const char *device, CameraBase *base, int width, int height, image::Format format, int fps, int buff_num, bool open) + { + err::Err e; + err::check_bool_raise(_check_format(format), "Format not support"); + + _width = (width == -1) ? 640 : width; + _height = (height == -1) ? 480 : height; + _format = format; + _fps = (fps == -1) ? 30 : fps; + _buff_num = buff_num; + + _show_colorbar = false; + _open_set_regs = set_regs_flag; + _impl = base; + + if (open) { + e = this->open(_width, _height, _format, _buff_num); + err::check_raise(e, "camera open failed"); + } + } + + Camera::~Camera() + { + if (this->is_opened()) { + this->close(); + } +#ifdef PLATFORM_LINUX + delete (CameraV4L2*)_impl; +#endif + +#ifdef PLATFORM_MAIXCAM + delete (CameraCviMmf*)_impl; +#endif + } + + void Camera::restart(int width, int height, image::Format format, const char *device, int fps, int buff_num, bool open) + { + if (this->is_opened()) { + this->close(); + } +#ifdef PLATFORM_LINUX + delete (CameraV4L2*)_impl; +#endif + +#ifdef PLATFORM_MAIXCAM + delete (CameraCviMmf*)_impl; +#endif + + err::Err e; + err::check_bool_raise(_check_format(format), "Format not support"); + + if (format == image::Format::FMT_RGB888 && width * height * 3 > 640 * 640 * 3) { + log::warn("Note that we do not recommend using large resolution RGB888 images, which can take up a lot of memory!\r\n"); + } + + _width = (width == -1) ? 640 : width; + _height = (height == -1) ? 480 : height; + _format = format; + _fps = (fps == -1) ? 30 : fps; + _buff_num = buff_num; + + _show_colorbar = false; + _open_set_regs = set_regs_flag; + _impl = NULL; + +#ifdef PLATFORM_LINUX + _device = _get_device(device); + _impl = new CameraV4L2(_device, _width, _height, _format, _buff_num); +#endif + +#ifdef PLATFORM_MAIXCAM + _device = ""; + _impl = new CameraCviMmf(_device, _width, _height, _format, _buff_num); +#endif + + if (open) { + e = this->open(_width, _height, _format, _buff_num); + + // err::check_raise(e, "camera open failed"); + } + } + + int Camera::get_ch_nums() + { + return 2; + } + + int Camera::get_channel() + { + if (_impl == NULL) + return err::ERR_NOT_INIT; + + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + return _impl->get_channel(); + } + + bool Camera::_check_format(image::Format format) { + if (format == image::FMT_RGB888 || format == image::FMT_BGR888 + || format == image::FMT_RGBA8888 || format == image::FMT_BGRA8888 + || format == image::FMT_YVU420SP || format == image::FMT_GRAYSCALE) { + return true; + } else { + return false; + } + } + + err::Err Camera::open(int width, int height, image::Format format, int fps, int buff_num) + { + if (_impl == NULL) + return err::Err::ERR_RUNTIME; + + int width_tmp = (width == -1) ? _width : width; + int height_tmp = (height == -1) ? _height : height; + image::Format format_tmp = (format == image::FMT_INVALID) ? _format : format; + int fps_tmp = (fps == -1) ? 30 : fps; + int buff_num_tmp =( buff_num == -1) ? _buff_num : buff_num; + + err::check_bool_raise(_check_format(format_tmp), "Format not support"); + + if (this->is_opened()) { + if (width == width_tmp && height == height_tmp && format == format_tmp && fps == fps_tmp && buff_num == buff_num_tmp) { + return err::ERR_NONE; + } + this->close(); // Get new param, close and reopen + } + + _width = width_tmp; + _height = height_tmp; + _fps = fps_tmp; + _buff_num = buff_num_tmp; + _format = format_tmp; + _format_impl = _format; + if(!_impl->is_support_format(_format)) + { + if(_impl->is_support_format(image::FMT_RGB888)) + _format_impl = image::FMT_RGB888; + else if(_impl->is_support_format(image::FMT_BGR888)) + _format_impl = image::FMT_BGR888; + else if(_impl->is_support_format(image::FMT_YVU420SP)) + _format_impl = image::FMT_YVU420SP; + else if(_impl->is_support_format(image::FMT_YUV420SP)) + _format_impl = image::FMT_YUV420SP; + else if(_impl->is_support_format(image::FMT_RGBA8888)) + _format_impl = image::FMT_RGBA8888; + else if(_impl->is_support_format(image::FMT_BGRA8888)) + _format_impl = image::FMT_BGRA8888; + else if(_impl->is_support_format(image::FMT_GRAYSCALE)) + _format_impl = image::FMT_GRAYSCALE; + else + return err::ERR_ARGS; + } + + return _impl->open(_width, _height, _format_impl, _buff_num);; + } + + void Camera::close() + { + if (this->is_closed()) + return; + + _impl->close(); + } + + camera::Camera *Camera::add_channel(int width, int height, image::Format format, int fps, int buff_num, bool open) + { + err::check_bool_raise(_check_format(format), "Format not support"); + + int width_tmp = (width == -1) ? _width : width; + int height_tmp = (height == -1) ? _height : height; + image::Format format_tmp = (format == image::Format::FMT_INVALID) ? _format : format; + int fps_tmp = (fps == -1) ? _fps : fps; + int buff_num_tmp = buff_num == -1 ? _buff_num : buff_num; + + Camera *cam = NULL; + if (_impl) { + CameraBase *cam_base = _impl->add_channel(width_tmp, height_tmp, format_tmp, buff_num); + err::check_bool_raise(cam_base, "Unable to add a new channel. Please check the maximum number of supported channels."); + cam = new Camera(_device.c_str(), cam_base, width_tmp, height_tmp, format_tmp, fps_tmp, buff_num_tmp, open); + } + return cam; + } + + bool Camera::is_opened() + { + if (_impl == NULL) + return false; + + return _impl->is_opened(); + } + + image::Image *Camera::read(void *buff, size_t buff_size, bool block) + { + if (!this->is_opened()) { + // err::Err e = open(_width, _height, _format, _buff_num); + // err::check_raise(e, "open camera failed"); + return NULL; + } + + if (_show_colorbar) { + image::Image *img = new image::Image(_width, _height); + generate_colorbar(*img); + err::check_null_raise(img, "camera read failed"); + return img; + } else { + // it's better all done by impl to faster read, but if impl not support, we have to convert it + if(_format_impl == _format) + { + image::Image *img = _impl->read(buff, buff_size); + // err::check_null_raise(img, "camera read failed"); + return img; + } + else + { + image::Image *img = _impl->read(); + image::Image *img2 = img->to_format(_format, buff, buff_size); + delete img; + // err::check_null_raise(img2, "camera read failed"); + return img2; + } + } + } + + void Camera::clear_buff() + { + if (_impl == NULL) + return; + _impl->clear_buff(); + } + + void Camera::skip_frames(int num) + { + if (_impl == NULL) + return; + for(int i = 0; i < num; i++) + { + image::Image *img = _impl->read(); + delete img; + } + } + + err::Err Camera::set_resolution(int width, int height) + { + err::Err e; + if (_impl == NULL) + return err::ERR_NOT_INIT; + + if (this->is_opened()) { + this->close(); + } + + _width = width; + _height = height; + e = this->open(_width, _height, _format, _buff_num); + err::check_raise(e, "camera open failed"); + return err::ERR_NONE; + } + + int Camera::hmirror(int value) { + if (_impl == NULL) + return err::ERR_NOT_INIT; + + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + return _impl->hmirror(value); + } + + int Camera::vflip(int value) { + if (_impl == NULL) + return err::ERR_NOT_INIT; + + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + return _impl->vflip(value); + } +} + diff --git a/support/sg2002/additional/vision/src/maix_image.cpp b/support/sg2002/additional/vision/src/maix_image.cpp new file mode 100644 index 0000000..9405b4a --- /dev/null +++ b/support/sg2002/additional/vision/src/maix_image.cpp @@ -0,0 +1,1654 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#include "maix_image.hpp" +#include "opencv2/opencv.hpp" +#include "opencv2/freetype.hpp" +#include +#include +#include +#include +#include +#ifdef PLATFORM_MAIXCAM +#include "kvm_mmf.hpp" +#endif + +namespace maix::image +{ + + static void _get_cv_format_color(image::Format _format, const image::Color &color_in, int *ch_format, cv::Scalar &cv_color); + + static uint8_t _get_char_size(const uint8_t c) + { + if ((c & 0x80) == 0x00) { + // 0xxxxxxx: 1 byte character + return 1; + } else if ((c & 0xE0) == 0xC0) { + // 110xxxxx: 2 byte character + return 2; + } else if ((c & 0xF0) == 0xE0) { + // 1110xxxx: 3 byte character + return 3; + } else if ((c & 0xF8) == 0xF0) { + // 11110xxx: 4 byte character + return 4; + } else { + // Invalid UTF-8 start byte, default to 1 + return 1; + } + } + + + image::Image *load(const char *path, image::Format format) + { + cv::Mat mat; + if (format == image::FMT_BGR888 || format == image::FMT_RGB888) + { + mat = cv::imread(path); + if (mat.empty()) + return nullptr; + if (format == image::FMT_RGB888) + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + } + else + { + mat = cv::imread(path, cv::IMREAD_UNCHANGED); + if (mat.empty()) + return nullptr; + if (mat.channels() == 1) + { + switch (format) + { + case image::FMT_GRAYSCALE: + break; + case image::FMT_RGB888: + cv::cvtColor(mat, mat, cv::COLOR_GRAY2RGB); + break; + case image::FMT_RGBA8888: + cv::cvtColor(mat, mat, cv::COLOR_GRAY2RGBA); + break; + case image::FMT_BGR888: + cv::cvtColor(mat, mat, cv::COLOR_GRAY2BGR); + break; + case image::FMT_BGRA8888: + cv::cvtColor(mat, mat, cv::COLOR_GRAY2BGRA); + break; + default: + log::error("load image failed, can't convert grayscale to format %d\n", format); + return nullptr; + } + } + else if (mat.channels() == 3) + { + switch (format) + { + case image::FMT_GRAYSCALE: + cv::cvtColor(mat, mat, cv::COLOR_BGR2GRAY); + break; + case image::FMT_RGB888: + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + break; + case image::FMT_RGBA8888: + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGBA); + break; + case image::FMT_BGR888: + break; + case image::FMT_BGRA8888: + cv::cvtColor(mat, mat, cv::COLOR_BGR2BGRA); + break; + default: + log::error("load image failed, can't convert bgr to format %d\n", format); + return nullptr; + } + } + else if (mat.channels() == 4) + { + switch (format) + { + case image::FMT_GRAYSCALE: + cv::cvtColor(mat, mat, cv::COLOR_BGRA2GRAY); + break; + case image::FMT_RGB888: + cv::cvtColor(mat, mat, cv::COLOR_BGRA2RGB); + break; + case image::FMT_RGBA8888: + cv::cvtColor(mat, mat, cv::COLOR_BGRA2RGBA); + break; + case image::FMT_BGR888: + cv::cvtColor(mat, mat, cv::COLOR_BGRA2BGR); + break; + case image::FMT_BGRA8888: + break; + default: + log::error("load image failed, can't convert bgra to format %d\n", format); + return nullptr; + } + } + else + { + log::error("load image failed, channels not support: %d\n", mat.channels()); + return nullptr; + } + } + image::Image *img = new image::Image(mat.cols, mat.rows, format); + memcpy(img->data(), mat.data, mat.cols * mat.rows * mat.channels()); + return img; + } + + image::Image *from_bytes(int width, int height, image::Format format, Bytes *data, bool copy) + { + // _create_image(width, height, format, data->data, data->size(), copy); + return new image::Image(width, height, format, data->data, data->size(), copy); + } + + static std::map> fonts_info; + static std::map fonts_size_info; + static std::string curr_font_name = "hershey_plain"; + static int curr_font_id = cv::FONT_HERSHEY_PLAIN; // -1 if user custom font, else opencv's HersheyFonts id + + static void add_default_fonts(std::map> &fonts_info) + { + if (!fonts_info.empty()) + return; + fonts_info["hershey_simplex"] = cv::Ptr(); + fonts_info["hershey_plain"] = cv::Ptr(); + fonts_info["hershey_duplex"] = cv::Ptr(); + fonts_info["hershey_complex"] = cv::Ptr(); + fonts_info["hershey_triplex"] = cv::Ptr(); + fonts_info["hershey_complex_small"] = cv::Ptr(); + fonts_info["hershey_script_simplex"] = cv::Ptr(); + } + + static int get_default_fonts_id(const std::string &name) + { + if (name == "hershey_simplex") + return cv::FONT_HERSHEY_SIMPLEX; + else if (name == "hershey_plain") + return cv::FONT_HERSHEY_PLAIN; + else if (name == "hershey_duplex") + return cv::FONT_HERSHEY_DUPLEX; + else if (name == "hershey_complex") + return cv::FONT_HERSHEY_COMPLEX; + else if (name == "hershey_triplex") + return cv::FONT_HERSHEY_TRIPLEX; + else if (name == "hershey_complex_small") + return cv::FONT_HERSHEY_COMPLEX_SMALL; + else if (name == "hershey_script_simplex") + return cv::FONT_HERSHEY_SCRIPT_SIMPLEX; + else + return -1; + } + + err::Err load_font(const std::string &name, const char *path, int size) + { + add_default_fonts(fonts_info); + // use opencv to load freetype font and store object in global variable + cv::Ptr ft2 = cv::freetype::createFreeType2(); + if (!ft2) + { + log::error("load font failed\n"); + return err::ERR_ARGS; + } + ft2->loadFontData(path, 0); + fonts_info[name] = ft2; + fonts_size_info[name] = size; + return err::ERR_NONE; + } + + err::Err set_default_font(const std::string &name) + { + // if name in fonts_info + if (fonts_info.find(name) == fonts_info.end()) + { + log::error("font %d not load\n", name.c_str()); + return err::ERR_ARGS; + } + curr_font_name = name; + curr_font_id = get_default_fonts_id(name); + return err::ERR_NONE; + } + + std::vector *fonts() + { + std::vector *fonts = new std::vector; + add_default_fonts(fonts_info); + for (auto &font : fonts_info) + { + fonts->push_back(font.first); + } + return fonts; + } + + static void _get_text_size(cv::Size &size, const std::string &text, const std::string &font_name, int font_id, float scale, int thickness) + { + int baseLine = 0; + if (font_id == -1) + { + int font_height = fonts_size_info[font_name]; + cv::Ptr ft2 = fonts_info[font_name]; + if (ft2 == cv::Ptr()) + { + log::error("font %d not load\n", font_name.c_str()); + throw std::runtime_error("font not load"); + } + size = ft2->getTextSize(text, scale * font_height, thickness, &baseLine); + if (thickness > 0) + baseLine += thickness; + size.height = size.height + baseLine; + } + else + { + size = cv::getTextSize(text, font_id, scale, thickness > 0 ? thickness : -thickness, &baseLine); + baseLine += baseLine > 0 ? 0 : -thickness; + size.height += baseLine; + } + } + + static void _put_text(cv::Mat &img, const std::string &text, const cv::Point &point, + const cv::Scalar &color, float scale, int thickness, const std::string &font_name, int font_id) + { + if (font_id == -1) + { + cv::Ptr ft2 = fonts_info[font_name]; + if (ft2 == cv::Ptr()) + { + log::error("font %d not load\n", font_name.c_str()); + throw std::runtime_error("font not load"); + } + // point from left top to left center + const std::string text_tmp(text, 0, 1); + cv::Point point_tmp(point.x, point.y + ft2->getTextSize(text, scale * fonts_size_info[font_name], thickness, nullptr).height); + ft2->putText(img, text, point_tmp, scale * fonts_size_info[font_name], color, thickness, cv::LINE_AA, true); + } + else + { + // left top corner to point left bottom corner by first char height + const std::string text_tmp(text, 0, 1); + int baseLine = 0; + cv::Point point_tmp(point.x, point.y + cv::getTextSize(text_tmp, font_id, scale, thickness > 0 ? thickness : -thickness, &baseLine).height); + cv::putText(img, text, point_tmp, font_id, scale, color, thickness > 0 ? thickness : -thickness, cv::LINE_AA, false); + } + } + + void Image::_create_image(int width, int height, image::Format format, uint8_t *data, int data_size, bool copy) + { + _format = format; + _width = width; + _height = height; + if (width <= 0 || height <= 0) + throw err::Exception(err::ERR_ARGS, "image width and height should > 0"); + + if (_format > Format::FMT_COMPRESSED_MIN) { + if (!data || data_size < 0) + throw err::Exception(err::ERR_ARGS,"image data and data_size are incorrect"); + _data_size = data_size; + } else { + // not use data_size, uncompressed image only use fiexed size. + int size_calc = width * height * image::fmt_size[format]; + if(data_size > 0 && data_size != size_calc) + { + log::error("data_size not match image content size, data_size: %d, image content size: %d\n", data_size, size_calc); + throw err::Exception(err::ERR_ARGS, "data_size not match image content size"); + } + _data_size = size_calc; + } + + if (!data) + { + _actual_data = malloc(_data_size + 0x1000); + if (!_actual_data) + throw err::Exception(err::ERR_NO_MEM, "malloc image data failed"); + _data = (void *)(((uint64_t)_actual_data + 0x1000) & ~0xFFF); + // log::debug("malloc image data\n"); + _is_malloc = true; + } + else + { + if(!copy) + { + _data = data; + _actual_data = _data; + _is_malloc = false; + } + else + { + _actual_data = malloc(_data_size + 0x1000); + if (!_actual_data) + throw std::bad_alloc(); + _data = (void *)(((uint64_t)_actual_data + 0x1000) & ~0xFFF); + memcpy(_data, data, _data_size); + // log::debug("malloc image data\n"); + _is_malloc = true; + } + } + } + + Image::Image(int width, int height, image::Format format, uint8_t *data, int data_size, bool copy) + { + _create_image(width, height, format, data, data_size, copy); + } + + Image::Image(int width, int height, image::Format format) + // Image::Image(int width, int height, image::Format format, Bytes *data, bool copy) + { + // if(!data) + // { + _create_image(width, height, format, nullptr, 0, false); + // return; + // } + // _create_image(width, height, format, data->data, data->size(), copy); + } + + Image::~Image() + { + if (_is_malloc) + { + // log::debug("free image data\n"); + free(_actual_data); + _actual_data = NULL; + _data = NULL; + } + } + + void Image::operator=(const image::Image &img) + { + _format = img._format; + _width = img._width; + _height = img._height; + _data = malloc(_width * _height * image::fmt_size[_format]); + if (!_data) + throw std::bad_alloc(); + memcpy(_data, img._data, _width * _height * image::fmt_size[_format]); + } + + std::string Image::__str__() + { + char buf[128]; + sprintf(buf, "Image(%d, %d, %s), data size: %d", _width, _height, fmt_names[_format].c_str(), (int)(_width * _height * fmt_size[_format])); + return std::string(buf); + } + + Bytes *Image::to_bytes(bool copy) + { + if(copy) + return new Bytes((uint8_t *)_data, _data_size, true, true); + return new Bytes((uint8_t *)_data, _data_size, false, false); + } + + tensor::Tensor *Image::to_tensor(bool chw, bool copy) + { + void *data = _data; + std::vector shape; + tensor::Tensor *t = nullptr; + if (_format == image::FMT_GRAYSCALE) + shape = {_height, _width}; + else if (_format < image::FMT_YUV422SP) + shape = chw ? (std::vector){(int)image::fmt_size[_format], _height, _width} : (std::vector){_height, _width, (int)image::fmt_size[_format]}; + else if (_format == image::FMT_YUV422SP || _format == image::FMT_YUV422P) + shape = chw ? (std::vector){2, _height, _width} : (std::vector){_height, _width, 2}; + else if (_format == image::FMT_YUV420SP || _format == image::FMT_YUV420P || _format == image::FMT_YVU420SP || _format == image::FMT_YVU420P) + shape = {(int)(_height * 1.5), _width}; + else + throw std::runtime_error("not support format"); + if (copy) + { + t = new tensor::Tensor(shape, tensor::UINT8, nullptr); + memcpy(t->data(), data, t->size_int() * tensor::dtype_size[t->dtype()]); + } + else + { + t = new tensor::Tensor(shape, tensor::UINT8, data); + } + return t; + } + + image::Image *Image::to_format(const image::Format &format, void *buff, size_t buff_size) + { + if (_format == format) + { + log::error("convert format failed, already the format %d\n", format); + throw err::Exception(err::ERR_ARGS, "convert format failed, already the format"); + } + + cv::Mat src(_height, _width, CV_8UC((int)image::fmt_size[_format]), _data); + cv::ColorConversionCodes cvt_code; + if(format == image::FMT_JPEG) // compress + { + switch (_format) { +#ifdef PLATFORM_MAIXCAM + case image::FMT_GRAYSCALE: + { + image::Image *img = nullptr; + if (!mmf_enc_jpg_push_with_quality(0, (uint8_t *)_data, _width, _height, mmf_invert_format_to_mmf(image::FMT_GRAYSCALE), 95)) { + uint8_t *data; + int data_size; + if (!mmf_enc_jpg_pop(0, &data, &data_size)) { + img = new image::Image(_width, _height, format, data, data_size, true); + mmf_enc_jpg_free(0); + } + } + return img; + break; + } + case image::FMT_YVU420SP: + { + image::Image *img = nullptr; + if (!mmf_enc_jpg_push_with_quality(0, (uint8_t *)_data, _width, _height, mmf_invert_format_to_mmf(image::FMT_YVU420SP), 95)) { + uint8_t *data; + int data_size; + if (!mmf_enc_jpg_pop(0, &data, &data_size)) { + img = new image::Image(_width, _height, format, data, data_size, true); + mmf_enc_jpg_free(0); + } + } + return img; + break; + } +#endif + default: + { +#ifdef PLATFORM_MAIXCAM + image::Image *p_img = nullptr; + image::Image *img = nullptr; + bool src_alloc = false; + if(_format != Format::FMT_RGB888) + { + p_img = to_format(image::FMT_RGB888); + src_alloc = true; + } else { + p_img = this; + } + + if (!mmf_enc_jpg_push_with_quality(0, (uint8_t *)p_img->data(), _width, _height, mmf_invert_format_to_mmf(image::FMT_RGB888), 80)) { + uint8_t *data; + int data_size; + if (!mmf_enc_jpg_pop(0, &data, &data_size)) { + img = new image::Image(_width, _height, format, data, data_size, true); + mmf_enc_jpg_free(0); + } + } + + if (src_alloc) + { + delete p_img; + } + return img; +#else + image::Image *p_img = nullptr; + cv::Mat *p_src = &src; + bool src_alloc = false; + if(_format != Format::FMT_BGR888 && _format != Format::FMT_BGRA8888) + { + p_img = to_format(image::FMT_BGR888); + p_src = new cv::Mat(p_img->_height, p_img->_width, CV_8UC((int)image::fmt_size[image::FMT_BGR888]), p_img->_data); + src_alloc = true; + } + cv::InputArray input(*p_src); + std::vector jpeg_buff; + std::vector params; + params.push_back(cv::IMWRITE_JPEG_QUALITY); + params.push_back(95); + cv::imencode(".jpg", input, jpeg_buff, params); + image::Image *img; + if(buff) + { + if(buff_size < jpeg_buff.size()) + { + log::error("convert format failed, buffer size not enough\n"); + throw err::Exception(err::ERR_ARGS, "convert format failed, buffer size not enough"); + } + memcpy(buff, jpeg_buff.data(), jpeg_buff.size()); + img = new image::Image(src.cols, src.rows, format, (uint8_t*)buff, jpeg_buff.size(), false); + } + else + img = new image::Image(src.cols, src.rows, format, (uint8_t*)jpeg_buff.data(), jpeg_buff.size(), true); + if(src_alloc) + { + delete p_img; + delete p_src; + } + return img; +#endif + break; + } + } + + } + else if(format == image::FMT_PNG) + { + image::Image *p_img = nullptr; + cv::Mat *p_src = &src; + bool src_alloc = false; + if(_format != Format::FMT_BGR888 && _format != Format::FMT_BGRA8888) + { + p_img = to_format(image::FMT_BGRA8888); + p_src = new cv::Mat(p_img->_height, p_img->_width, CV_8UC((int)image::fmt_size[image::FMT_BGRA8888]), p_img->_data); + src_alloc = true; + } + cv::InputArray input(*p_src); + std::vector png_buff; + std::vector params; + params.push_back(cv::IMWRITE_PNG_COMPRESSION); + params.push_back(3); + cv::imencode(".png", input, png_buff, params); + image::Image *img; + if(buff) + { + if(buff_size < png_buff.size()) + { + log::error("convert format failed, buffer size not enough\n"); + throw err::Exception(err::ERR_ARGS, "convert format failed, buffer size not enough"); + } + memcpy(buff, png_buff.data(), png_buff.size()); + img = new image::Image(src.cols, src.rows, format, (uint8_t*)buff, png_buff.size(), false); + } + else + img = new image::Image(src.cols, src.rows, format, png_buff.data(), png_buff.size(), true); + if(src_alloc) + { + delete p_img; + delete p_src; + } + return img; + } + switch (_format) + { + case image::FMT_RGB888: + switch (format) + { + case image::FMT_GRAYSCALE: + cvt_code = cv::COLOR_RGB2GRAY; + break; + case image::FMT_BGR888: + cvt_code = cv::COLOR_RGB2BGR; + break; + case image::FMT_RGBA8888: + cvt_code = cv::COLOR_RGB2RGBA; + break; + case image::FMT_BGRA8888: + cvt_code = cv::COLOR_RGB2BGRA; + break; + case image::FMT_YVU420SP: + cvt_code = cv::COLOR_RGB2YUV_YV12; + break; + default: + log::error("convert format failed, can't convert rgb to format %d\n", format); + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + break; + case image::FMT_BGR888: + switch (format) + { + case image::FMT_GRAYSCALE: + cvt_code = cv::COLOR_BGR2GRAY; + break; + case image::FMT_RGB888: + cvt_code = cv::COLOR_BGR2RGB; + break; + case image::FMT_RGBA8888: + cvt_code = cv::COLOR_BGR2RGBA; + break; + case image::FMT_BGRA8888: + cvt_code = cv::COLOR_BGR2BGRA; + break; + case image::FMT_YVU420SP: + cvt_code = cv::COLOR_BGR2YUV_YV12; + break; + default: + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + break; + case image::FMT_RGBA8888: + switch (format) + { + case image::FMT_GRAYSCALE: + cvt_code = cv::COLOR_RGBA2GRAY; + break; + case image::FMT_RGB888: + cvt_code = cv::COLOR_RGBA2RGB; + break; + case image::FMT_BGR888: + cvt_code = cv::COLOR_RGBA2BGR; + break; + case image::FMT_BGRA8888: + cvt_code = cv::COLOR_RGBA2BGRA; + break; + case image::FMT_YVU420SP: + cvt_code = cv::COLOR_BGR2YUV_YV12; + break; + default: + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + break; + case image::FMT_BGRA8888: + switch (format) + { + case image::FMT_GRAYSCALE: + cvt_code = cv::COLOR_BGRA2GRAY; + break; + case image::FMT_RGB888: + cvt_code = cv::COLOR_BGRA2RGB; + break; + case image::FMT_BGR888: + cvt_code = cv::COLOR_BGRA2BGR; + break; + case image::FMT_RGBA8888: + cvt_code = cv::COLOR_BGRA2RGBA; + break; + case image::FMT_YVU420SP: + cvt_code = cv::COLOR_BGR2YUV_YV12; + break; + default: + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + break; + case image::FMT_GRAYSCALE: + switch (format) + { + case image::FMT_RGB888: + cvt_code = cv::COLOR_GRAY2RGB; + break; + case image::FMT_BGR888: + cvt_code = cv::COLOR_GRAY2BGR; + break; + case image::FMT_RGBA8888: + cvt_code = cv::COLOR_GRAY2RGBA; + break; + case image::FMT_BGRA8888: + cvt_code = cv::COLOR_GRAY2BGRA; + break; + case image::FMT_YVU420SP: + cvt_code = cv::COLOR_BGR2YUV_YV12; + break; + default: + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + break; + default: + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + image::Image *img; + if(buff) + { + if(buff_size < image::fmt_size[format]) + { + log::error("convert format failed, buffer size not enough, need %d, but %d\n", image::fmt_size[format], buff_size); + throw err::Exception(err::ERR_ARGS, "convert format failed, buffer size not enough"); + } + img = new image::Image(src.cols, src.rows, format, (uint8_t*)buff, src.cols * src.rows * image::fmt_size[format], false); + } + else + img = new image::Image(src.cols, src.rows, format); + + if (_format == image::FMT_GRAYSCALE && format == image::FMT_YVU420SP) { + cv::Mat dst(src.rows * 3 / 2, src.cols, CV_8UC((int)image::fmt_size[format]), img->data()); + int nv_len = src.cols * src.rows / 2; + int offset = src.cols * src.rows; + memcpy(dst.data, src.data, src.cols * src.rows); + memset(dst.data + offset, 128, nv_len); + } else if (format == image::FMT_YVU420SP) { + cv::Mat dst(src.rows * 3 / 2, src.cols, CV_8UC((int)image::fmt_size[format]), img->data()); + cv::cvtColor(src, dst, cvt_code); + int nv_len = src.cols * src.rows / 2; + int half_len = nv_len / 2; + int offset = src.cols * src.rows; + uint8_t *nv21 = (uint8_t *)img->data(); + uint8_t *uv_temp = (uint8_t *)malloc(nv_len); + err::check_null_raise(uv_temp, "malloc uv_temp failed"); + memcpy(uv_temp, nv21 + offset, nv_len); + for (int i = 0; i < half_len; i ++) { + nv21[offset + i * 2] = uv_temp[i]; // V + nv21[offset + i * 2 + 1] = uv_temp[half_len + i]; // U + } + if (uv_temp) + free(uv_temp); + } else if (_format == image::FMT_RGB888 && format == image::FMT_GRAYSCALE) { + int height = _width; + int width = _height; + uint8_t *src = (uint8_t *)_data; + uint8_t *dst = (uint8_t *)img->data(); + for (int i = 0; i < height; i ++) { + for (int j = 0; j < width; j ++) { + dst[i * width + j] = (src[(i * width + j) * 3 + 0] * 38 + src[(i * width + j) * 3 + 1] * 75 + src[(i * width + j) * 3 + 2] * 15) >> 7; + } + } + } else { + cv::Mat dst(src.rows, src.cols, CV_8UC((int)image::fmt_size[format]), img->data()); + cv::cvtColor(src, dst, cvt_code); + } + + return img; + } + + + image::Image *Image::to_format(const image::Format &format) + { + return to_format(format, nullptr, 0); + } + + image::Image *Image::to_jpeg(int quality) + { + image::Format format = image::Format::FMT_JPEG; + cv::Mat src(_height, _width, CV_8UC((int)image::fmt_size[_format]), _data); + switch (_format) { + case image::FMT_YVU420SP: + { +#ifdef PLATFORM_MAIXCAM + image::Image *img = nullptr; + if (!mmf_enc_jpg_push_with_quality(0, (uint8_t *)_data, _width, _height, mmf_invert_format_to_mmf(image::FMT_YVU420SP), quality)) { + uint8_t *data; + int data_size; + if (!mmf_enc_jpg_pop(0, &data, &data_size)) { + img = new image::Image(_width, _height, format, data, data_size, true); + mmf_enc_jpg_free(0); + } + } + return img; +#endif + break; + } + default: + { +#ifdef PLATFORM_MAIXCAM + image::Image *p_img = nullptr; + image::Image *img = nullptr; + bool src_alloc = false; + if(_format != Format::FMT_RGB888) + { + p_img = to_format(image::FMT_RGB888); + src_alloc = true; + } else { + p_img = this; + } + + if (!mmf_enc_jpg_push_with_quality(0, (uint8_t *)p_img->data(), _width, _height, mmf_invert_format_to_mmf(image::FMT_RGB888), quality)) { + uint8_t *data; + int data_size; + if (!mmf_enc_jpg_pop(0, &data, &data_size)) { + img = new image::Image(_width, _height, format, data, data_size, true); + mmf_enc_jpg_free(0); + } + } + + if (src_alloc) + { + delete p_img; + } + return img; +#else + image::Image *p_img = nullptr; + cv::Mat *p_src = &src; + bool src_alloc = false; + if(_format != Format::FMT_BGR888 && _format != Format::FMT_BGRA8888) + { + p_img = to_format(image::FMT_BGR888); + p_src = new cv::Mat(p_img->_height, p_img->_width, CV_8UC((int)image::fmt_size[image::FMT_BGR888]), p_img->_data); + src_alloc = true; + } + cv::InputArray input(*p_src); + std::vector jpeg_buff; + std::vector params; + params.push_back(cv::IMWRITE_JPEG_QUALITY); + params.push_back(60); // 压缩率 + cv::imencode(".jpg", input, jpeg_buff, params); + image::Image *img; + img = new image::Image(src.cols, src.rows, format, (uint8_t*)jpeg_buff.data(), jpeg_buff.size(), true); + if(src_alloc) + { + delete p_img; + delete p_src; + } + return img; +#endif + break; + } + } + + return nullptr; + } + + image::Image *Image::draw_image(int x, int y, image::Image &img) + { + image::Format fmt = img.format(); + if (!(fmt == image::FMT_GRAYSCALE || fmt == image::FMT_RGB888 || fmt == image::FMT_BGR888 || + fmt == image::FMT_RGBA8888 || fmt == image::FMT_BGRA8888)) + throw std::runtime_error("image format not support"); + // check format + if (image::fmt_size[img.format()] > image::fmt_size[_format]) + throw std::runtime_error("image format not match"); + cv::Mat src(img.height(), img.width(), CV_8UC((int)image::fmt_size[img.format()]), img.data()); + cv::Mat dst(_height, _width, CV_8UC((int)image::fmt_size[_format]), _data); + cv::Rect rect(x, y, img.width(), img.height()); + if (_format == fmt && image::fmt_size[fmt] < 4) + { + src.copyTo(dst(rect)); + } + else + { + // convert format + bool img_alloc = false; + image::Image *img_new = nullptr; + if (_format != fmt) + { + img_new = img.to_format(_format); + if (!img_new) + { + return img_new; + } + img_alloc = true; + } + else + img_new = &img; + cv::Mat src_new(img_new->height(), img_new->width(), CV_8UC((int)image::fmt_size[_format]), img_new->data()); + if (image::fmt_size[_format] < 4) + { + src_new.copyTo(dst(rect)); + } + else // merge two BGRA images + { + for (int y = 0; y < src_new.rows; y++) + { + if(y + rect.y >= dst.rows) + break; + for (int x = 0; x < src_new.cols; x++) + { + if(x + rect.x >= dst.cols) + break; + cv::Vec4b &pixel = src_new.at(y, x); + // ignore transparent pixel + if (pixel[3] == 0) + continue; + cv::Vec4b &pixel_dst = dst.at(y + rect.y, x + rect.x); + // alpha is 255, just copy + if (pixel[3] == 255) + { + pixel_dst[0] = pixel[0]; + pixel_dst[1] = pixel[1]; + pixel_dst[2] = pixel[2]; + pixel_dst[3] = pixel[3]; + } + // alpha > 0 and < 255, blend + else + { + // TODO: optimize blend algorithm + pixel_dst[0] = (uint32_t)((pixel[0] * pixel[3] + pixel_dst[0] * (255 - pixel[3]))) >> 8; + pixel_dst[1] = (uint32_t)((pixel[1] * pixel[3] + pixel_dst[1] * (255 - pixel[3]))) >> 8; + pixel_dst[2] = (uint32_t)((pixel[2] * pixel[3] + pixel_dst[2] * (255 - pixel[3]))) >> 8; + pixel_dst[3] = 255 - (255 - pixel[3]) * (255 - pixel_dst[3]); + } + } + } + } + if (img_alloc) + delete img_new; + } + return this; + } + + image::Image *Image::draw_rect(int x, int y, int w, int h, const image::Color &color, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::Rect rect(x, y, w, h); + if( color.alpha != 1 && (_format == Format::FMT_RGBA8888 || _format == Format::FMT_BGRA8888)) + { + cv::Mat roi = img(rect); + cv::Mat color_mat(roi.size(), roi.type(), cv_color); + cv::addWeighted(color_mat, color.alpha, roi, 1 - color.alpha, 0, roi); + } + else + cv::rectangle(img, rect, cv_color, thickness); + return this; + } + + image::Image *Image::draw_line(int x1, int y1, int x2, int y2, const image::Color &color, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::line(img, cv::Point(x1, y1), cv::Point(x2, y2), cv_color, thickness); + return this; + } + + image::Image *Image::draw_circle(int x, int y, int radius, const image::Color &color, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::circle(img, cv::Point(x, y), radius, cv_color, thickness); + return this; + } + + image::Image *Image::draw_ellipse(int x, int y, int a, int b, float angle, float start_angle, float end_angle, const image::Color &color, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::ellipse(img, cv::Point(x, y), cv::Size(a, b), angle, start_angle, end_angle, cv_color, thickness); + return this; + } + + image::Image *image::Image::draw_string(int x, int y, const std::string &text, const image::Color &color, float scale, int thickness, + bool wrap, int wrap_space, const std::string &font) + { + int ch_format = 0; + cv::Scalar cv_color; + add_default_fonts(fonts_info); + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::Point point(x, y); + const std::string *final_font = &curr_font_name; + int final_font_id = curr_font_id; + // if cont is not empty, use it as font path + if (!font.empty()) + { + // if name in fonts_info + if (fonts_info.find(font) == fonts_info.end()) + { + log::error("font %d not load\n", font.c_str()); + throw std::runtime_error("font not load"); + } + final_font = &font; + final_font_id = get_default_fonts_id(font); + } + // auto wrap if text width > image width + if (!wrap) + { + _put_text(img, text, point, cv_color, scale, thickness, *final_font, final_font_id); + } + else + { + cv::Size size; + _get_text_size(size, text, *final_font, final_font_id, scale, thickness); + int text_width = size.width; + int text_height = size.height; + int text_max_width = _width - x; + bool wrap_now = false; + if(strstr(text.c_str(), "\n") != NULL || strstr(text.c_str(), "\r") != NULL) + wrap_now = true; + + // auto wrap + if (wrap_now || text_width > text_max_width) + { + // loop to calculate text width, if reach to image width, draw text on image, and move to next line go on + size_t idx = 0; + std::string text_tmp; + cv::Size size_tmp; + uint8_t char_size = 1; + bool last_is_r = false; + wrap_now = false; + while (idx < text.length()) + { + char c = text[idx]; + wrap_now = false; + if(c == '\r') + { + last_is_r = true; + wrap_now = true; + } + else if(c == '\n') + { + if(last_is_r) + { + last_is_r = false; + ++idx; + continue; + } + wrap_now = true; + last_is_r = false; + } + else + { + last_is_r = false; + } + if(wrap_now) + { + _put_text(img, text_tmp, point, cv_color, scale, thickness, *final_font, final_font_id); + point.x = x; + point.y += text_height + wrap_space; + text_tmp.clear(); + if (point.y + text_height >= _height) + break; + ++idx; + continue; + } + char_size = _get_char_size(c); + text_tmp += text.substr(idx, char_size); + cv::Size size_tmp; + _get_text_size(size_tmp, text_tmp, *final_font, final_font_id, scale, thickness); + if (size_tmp.width >= text_max_width) + { + if (size_tmp.width > text_max_width) + { + text_tmp.erase(text_tmp.length() - char_size, char_size); + } + _put_text(img, text_tmp, point, cv_color, scale, thickness, *final_font, final_font_id); + point.x = x; + point.y += text_height + wrap_space; + text_tmp.clear(); + if (point.y + text_height >= _height) + break; + if (size_tmp.width > text_max_width) + continue; + } + idx += char_size; + } + // draw last line + if (!text_tmp.empty()) + { + _put_text(img, text_tmp, point, cv_color, scale, thickness, *final_font, final_font_id); + } + } + else + { + _put_text(img, text, point, cv_color, scale, thickness, *final_font, final_font_id); + } + } + return this; + } + + image::Image *Image::draw_cross(int x, int y, const image::Color &color, int size, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::line(img, cv::Point(x - size, y), cv::Point(x + size, y), cv_color, thickness); + cv::line(img, cv::Point(x, y - size), cv::Point(x, y + size), cv_color, thickness); + return this; + } + + image::Image *Image::draw_arrow(int x0, int y0, int x1, int y1, const image::Color &color, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::Point start(x0, y0); + cv::Point end(x1, y1); + cv::arrowedLine(img, start, end, cv_color, thickness); + return this; + } + + image::Image *Image::draw_edges(std::vector> corners, const image::Color &color, int size, int thickness, bool fill) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + + if (corners.size() < 4) + { + throw std::runtime_error("corners size must >= 4"); + return nullptr; + } + + if (fill) { + thickness = -1; + } + + cv::Point topLeft(corners[0][0], corners[0][1]); + cv::Point bottomRight(corners[2][0], corners[2][1]); + cv::rectangle(img, topLeft, bottomRight, cv_color, thickness); + return this; + } + + image::Image *Image::draw_keypoints(std::vector keypoints, const image::Color &color, int size, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + + if (keypoints.size() < 2 || keypoints.size() % 2 != 0) { + throw std::runtime_error("keypoints size must >= 2 and multiple of 2"); + return nullptr; + } + for(size_t i=0; idata()); + _cv_rgb_nv21(resize_rgb, dst, width, height); + } else { + dst = cv::Mat(height, width, pixel_num, ret->data()); + cv::resize(img, dst, cv::Size(width, height), 0, 0, inter_method); + } + } + else if (object_fit == image::Fit::FIT_CONTAIN) + { + cv::Mat tmp; + float scale = std::min((float)width / _width, (float)height / _height); + cv::resize(img, tmp, cv::Size(), scale, scale, inter_method); + dst = cv::Mat(cv_dst_h, width, pixel_num, ret->data()); + cv::Rect rect((width - tmp.cols) / 2, (height - tmp.rows) / 2, tmp.cols, tmp.rows); + tmp.copyTo(dst(rect)); + // fill black color + if (tmp.cols < width) + { + cv::Mat black(height, (width - tmp.cols) / 2, pixel_num, cv::Scalar(0, 0, 0)); + // left black area + cv::Rect rect(0, 0, (width - tmp.cols) / 2, height); + black.copyTo(dst(rect)); + // right black area + rect.x = (width - tmp.cols) / 2 + tmp.cols; + black.copyTo(dst(rect)); + if (rect.width != width - tmp.cols - rect.width) + { + // one line + rect.x = dst.cols - 1; + rect.width = 1; + cv::Mat black2(height, 1, pixel_num, cv::Scalar(0, 0, 0)); + black2.copyTo(dst(rect)); + } + } + if (tmp.rows < height) + { + cv::Mat black((height - tmp.rows) / 2, width, pixel_num, cv::Scalar(0, 0, 0)); + // top black area + cv::Rect rect(0, 0, width, (height - tmp.rows) / 2); + black.copyTo(dst(rect)); + // bottom black area + rect.y = (height - tmp.rows) / 2 + tmp.rows; + black.copyTo(dst(rect)); + if (rect.height != height - tmp.rows - rect.height) + { + // one line + rect.y = dst.rows - 1; + rect.height = 1; + cv::Mat black2(1, width, pixel_num, cv::Scalar(0, 0, 0)); + black2.copyTo(dst(rect)); + } + } + } + else if (object_fit == image::Fit::FIT_COVER) + { + cv::Mat tmp; + float scale = std::max((float)width / _width, (float)height / _height); + cv::resize(img, tmp, cv::Size(), scale, scale, inter_method); + dst = cv::Mat(cv_dst_h, width, pixel_num, ret->data()); + cv::Rect rect((tmp.cols - width) / 2, (tmp.rows - height) / 2, width, height); + tmp(rect).copyTo(dst); + } + else + { + throw std::runtime_error("not support object fit"); + } + return ret; + } + + image::Image *Image::affine(std::vector src_points, std::vector dst_points, int width, int height, image::ResizeMethod method) + { + if (width < 0 && height < 0) + { + throw std::runtime_error("width and height can't both be -1"); + } + int pixel_num = _get_cv_pixel_num(_format); + /// calculate size if width or height is -1 + if (width == -1) + { + width = height * _width / _height; + } + else if (height == -1) + { + height = width * _height / _width; + } + image::Image *ret = new image::Image(width, height, _format); + ; + cv::Mat img(_height, _width, pixel_num, _data); + cv::Mat dst(height, width, pixel_num, ret->data()); + cv::Point2f srcTri[3]; + cv::Point2f dstTri[3]; + for (int i = 0; i < 3; i++) + { + srcTri[i] = cv::Point2f(src_points[i * 2], src_points[i * 2 + 1]); + dstTri[i] = cv::Point2f(dst_points[i * 2], dst_points[i * 2 + 1]); + } + cv::Mat warp_mat = cv::getAffineTransform(srcTri, dstTri); + cv::warpAffine(img, dst, warp_mat, dst.size(), (cv::InterpolationFlags)method); + return ret; + } + + image::Image *Image::copy() + { + image::Image *ret = new image::Image(_width, _height, _format); + ; + memcpy(ret->data(), _data, _width * _height * image::fmt_size[_format]); + return ret; + } + + image::Image *Image::crop(int x, int y, int w, int h) + { + image::Image *ret = new image::Image(w, h, _format); + ; + int pixel_num = _get_cv_pixel_num(_format); + cv::Mat img(_height, _width, pixel_num, _data); + cv::Mat dst(h, w, pixel_num, ret->data()); + cv::Rect rect(x, y, w, h); + img(rect).copyTo(dst); + return ret; + } + + image::Image *Image::rotate(float angle, int width, int height, image::ResizeMethod method) + { + int pixel_num = _get_cv_pixel_num(_format); + if (width < 0 && height < 0) + { + if (angle == 90 || angle == 270) + { + width = _height; + height = _width; + } + } + /// calculate size if width or height is -1 + if (width < 0) + { + // new_H = int(w * fabs(sin(radians(angle))) + h * fabs(cos(radians(angle)))) + width = _width * fabs(sin(angle * M_PI / 180)) + _height * fabs(cos(angle * M_PI / 180)); + } + if (height < 0) + { + // new_W = int(h * fabs(sin(radians(angle))) + w * fabs(cos(radians(angle)))) + height = _height * fabs(sin(angle * M_PI / 180)) + _width * fabs(cos(angle * M_PI / 180)); + } + image::Image *ret = new image::Image(width, height, _format); + ; + cv::Mat img(_height, _width, pixel_num, _data); + cv::Mat dst(height, width, pixel_num, ret->data()); + cv::Point2f center((float)_width / 2.0, (float)_height / 2.0); + cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, 1.0); + // M[0, 2] += (width - w) / 2 + // M[1, 2] += (height - h) / 2 + rot_mat.at(0, 2) += (width - _width) / 2.0; + rot_mat.at(1, 2) += (height - _height) / 2.0; + cv::warpAffine(img, dst, rot_mat, dst.size(), (cv::InterpolationFlags)method); + return ret; + } + + err::Err Image::save(const char *path, int quality) + { + if (_height <= 0 || _width <= 0) + { + log::error("save image failed, image size is invalid\n"); + return err::ERR_ARGS; + } + cv::Mat img(_height, _width, CV_8UC((int)image::fmt_size[_format]), _data); + std::vector params; + if (quality >= 0 && quality <= 100) + { + params.push_back(cv::IMWRITE_JPEG_QUALITY); + params.push_back(quality); + } + else if (quality >= -1 && quality <= 9) + { + params.push_back(cv::IMWRITE_PNG_COMPRESSION); + params.push_back(quality); + } + else + { + return err::ERR_ARGS; + } + log::debug("save image to %s\n", path); + // not not dir exists, create it + std::string dir = path; + size_t pos = dir.find_last_of('/'); + if (pos != std::string::npos) + { + dir = dir.substr(0, pos); + if (!fs::exists(dir)) + { + if (fs::mkdir(dir) < 0) + { + log::error("create dir %s failed\n", dir.c_str()); + return err::ERR_IO; + } + } + } + // save, img may RGB format + if (_format == image::FMT_RGB888) + { + cv::cvtColor(img, img, cv::COLOR_RGB2BGR); + } + else if (_format == image::FMT_RGBA8888) + { + cv::cvtColor(img, img, cv::COLOR_RGBA2BGRA); + } + cv::imwrite(path, img, params); + // convert back + if (_format == image::FMT_RGB888) + { + cv::cvtColor(img, img, cv::COLOR_RGB2BGR); + } + else if (_format == image::FMT_RGBA8888) + { + cv::cvtColor(img, img, cv::COLOR_RGBA2BGRA); + } + return err::ERR_NONE; + } + + image::Size string_size(std::string text, float scale, int thickness, const std::string &font) + { + const std::string *final_font = &curr_font_name; + int final_font_id = curr_font_id; + // if cont is not empty, use it as font path + if (!font.empty()) + { + // if name in fonts_info + if (fonts_info.find(font) == fonts_info.end()) + { + log::error("font %d not load\n", font.c_str()); + throw std::runtime_error("font not load"); + } + final_font = &font; + final_font_id = get_default_fonts_id(font); + } + cv::Size size; + _get_text_size(size, text, *final_font, final_font_id, scale, thickness); + return Size(size.width, size.height); + } + + std::vector resize_map_pos(int w_in, int h_in, int w_out, int h_out, image::Fit fit, int x, int y, int w, int h) { + float scale_x = static_cast(w_out) / w_in; + float scale_y = static_cast(h_out) / h_in; + std::vector result; + + if (fit == image::Fit::FIT_FILL) { + x = static_cast(x * scale_x); + y = static_cast(y * scale_y); + if (w > 0) { + w = static_cast(w * scale_x); + } + if (h > 0) { + h = static_cast(h * scale_y); + } + } else if (fit == image::Fit::FIT_CONTAIN) { + float scale = std::min(scale_x, scale_y); + if (w_out > h_out) { + x = static_cast((w_out - w_in * scale) / 2 + x * scale); + y = static_cast(y * scale); + } else { + x = static_cast(x * scale); + y = static_cast((h_out - h_in * scale) / 2 + y * scale); + } + if (w > 0) { + w = static_cast(w * scale); + h = static_cast(h * scale); + } + } else if (fit == image::Fit::FIT_COVER) { + float scale = std::max(scale_x, scale_y); + if (w_out > h_out) { + x = static_cast(x * scale); + y = static_cast((h_out - h_in * scale) / 2 + y * scale); + } else { + x = static_cast((w_out - w_in * scale) / 2 + x * scale); + y = static_cast(y * scale); + } + if (w > 0) { + w = static_cast(w * scale); + h = static_cast(h * scale); + } + } else { + throw err::Exception(err::ERR_ARGS, "Unsupported fit mode"); + } + + // Populate the result vector + result.push_back(x); + result.push_back(y); + if (w > 0 && h > 0) { + result.push_back(w); + result.push_back(h); + } + return result; + } + + std::vector resize_map_pos_reverse(int w_in, int h_in, int w_out, int h_out, image::Fit fit, int x, int y, int w, int h) { + float scale_x = static_cast(w_out) / w_in; + float scale_y = static_cast(h_out) / h_in; + std::vector result; + + if (fit == image::Fit::FIT_FILL) { + x = static_cast(x / scale_x); + y = static_cast(y / scale_y); + if (w > 0) { + w = static_cast(w / scale_x); + } + if (h > 0) { + h = static_cast(h / scale_y); + } + } else if (fit == image::Fit::FIT_CONTAIN) { + float scale = std::min(scale_x, scale_y); + if (w_out > h_out) { + x = static_cast((x - (w_out - w_in * scale) / 2) / scale); + y = static_cast(y / scale); + } else { + x = static_cast(x / scale); + y = static_cast((y - (h_out - h_in * scale) / 2) / scale); + } + if (w > 0) { + w = static_cast(w / scale); + h = static_cast(h / scale); + } + } else if (fit == image::Fit::FIT_COVER) { + float scale = std::max(scale_x, scale_y); + if (w_out > h_out) { + x = static_cast(x / scale); + y = static_cast((y - (h_out - h_in * scale) / 2) / scale); + } else { + x = static_cast((x - (w_out - w_in * scale) / 2) / scale); + y = static_cast(y * scale); + } + if (w > 0) { + w = static_cast(w / scale); + h = static_cast(h / scale); + } + } else { + throw err::Exception(err::ERR_ARGS, "Unsupported fit mode"); + } + + // Populate the result vector + result.push_back(x); + result.push_back(y); + if (w > 0 && h > 0) { + result.push_back(w); + result.push_back(h); + } + return result; + } + + void _get_cv_format_color(image::Format _format, const image::Color &color_in, int *ch_format, cv::Scalar &cv_color) + { + bool color_alloc = false; + image::Color *color = (image::Color *)&color_in; + if (color->format != _format) + { + color = color->to_format2(_format); + color_alloc = true; + } + switch (_format) + { + case image::FMT_RGB888: + *ch_format = CV_8UC3; + cv_color = cv::Scalar(color->r, color->g, color->b); + break; + case image::FMT_BGR888: + *ch_format = CV_8UC3; + cv_color = cv::Scalar(color->b, color->g, color->r); + break; + case image::FMT_GRAYSCALE: + *ch_format = CV_8UC1; + cv_color = cv::Scalar(color->gray); + break; + case image::FMT_BGRA8888: + *ch_format = CV_8UC4; + cv_color = cv::Scalar(color->b, color->g, color->r, color->alpha * 255); + break; + case image::FMT_RGBA8888: + *ch_format = CV_8UC4; + cv_color = cv::Scalar(color->r, color->g, color->b, color->alpha * 255); + break; + default: + throw std::runtime_error("not support format"); + break; + } + if (color_alloc) + delete color; + } + + int Image::_get_cv_pixel_num(image::Format &format) + { + int pixel_num = 0; + switch (_format) + { + case image::FMT_RGB888: + case image::FMT_BGR888: + pixel_num = CV_8UC3; + break; + case image::FMT_GRAYSCALE: + pixel_num = CV_8UC1; + break; + case image::FMT_BGRA8888: + case image::FMT_RGBA8888: + pixel_num = CV_8UC4; + break; + case image::FMT_RGB565: + case image::FMT_BGR565: + pixel_num = CV_8UC2; + break; + default: + throw std::runtime_error("not support format"); + break; + } + return pixel_num; + } + + std::vector Image::_get_available_roi(std::vector roi, std::vector other_roi) { + int src_x, src_y, src_w, src_h; + + if (other_roi.size() == 0) { + src_x = 0; + src_y = 0; + src_w = _width; + src_h = _height; + } else if (other_roi.size() == 4) { + src_x = other_roi[0]; + src_y = other_roi[1]; + src_w = other_roi[2]; + src_h = other_roi[3]; + } else { + throw err::Exception("other_roi size must be 4"); + } + + if (roi.size() == 0) { + return std::vector{0, 0, _width, _height}; + } else if (roi.size() != 4) { + throw err::Exception("roi size must be 4"); + } + + if (roi[2] <= 0 || roi[3] <= 0) { + throw err::Exception("roi width and height must > 0"); + } + + if ((roi[0] >= (src_x + src_w)) || (roi[1] >= (src_y + src_h)) || (roi[0] + roi[2] <= src_x) || (roi[1] + roi[3] <= src_y)) { + throw err::Exception("roi does not overlap on the image!"); + } + + int x = std::max(roi[0], src_x); + int y = std::max(roi[1], src_y); + int w = std::min(roi[0] + roi[2], src_x + src_w) - x; + int h = std::min(roi[1] + roi[3], src_y + src_h) - y; + return std::vector{x, y, w, h}; + } +} // namespace maix::image diff --git a/support/sg2002/additional/vision/src/maix_image_ops.cpp b/support/sg2002/additional/vision/src/maix_image_ops.cpp new file mode 100644 index 0000000..dd3eb17 --- /dev/null +++ b/support/sg2002/additional/vision/src/maix_image_ops.cpp @@ -0,0 +1,1209 @@ +/** + * @author lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#include "maix_image.hpp" +#include "maix_image_util.hpp" +#include "maix_err.hpp" +#include +#include + +namespace maix::image { + void convert_to_imlib_image(image::Image *image, image_t *imlib_image) { + if (!image || !imlib_image) { + return; + } + + pixformat_t imlib_format; + switch (image->format()) { + case Format::FMT_GRAYSCALE: + imlib_format = PIXFORMAT_GRAYSCALE; + break; + case Format::FMT_RGB565: + imlib_format = PIXFORMAT_RGB565; + break; + case Format::FMT_BGR888: // fall through + case Format::FMT_RGB888: + imlib_format = PIXFORMAT_RGB888; + break; + default: + log::error("convert_to_imlib_image format not support: %d", image->format()); + return; + } + + image_init(imlib_image, image->width(), image->height(), imlib_format, image->data_size(), image->data()); + } + + image::Image *Image::mean_pool(int x_div, int y_div, bool copy) { + err::check_bool_raise(x_div > 0 && x_div <= _width && y_div > 0 && y_div <= _height, "mean pool get invalid param"); + + image_t src_img, out_img; + uint8_t *buffer = NULL; + convert_to_imlib_image(this, &src_img); + + out_img.w = src_img.w / x_div; + out_img.h = src_img.h / y_div; + out_img.pixfmt = src_img.pixfmt; + if (copy) { + buffer = (uint8_t *)malloc(out_img.w * out_img.h * image::fmt_size[_format]); + if (!buffer) { + log::error("mean pool malloc failed\r\n"); + return nullptr; + } + out_img.pixels = buffer; + } else { + out_img.pixels = src_img.pixels; + } + + imlib_mean_pool(&src_img, &out_img, x_div, y_div); + + if (copy) { + image::Image *dst = new image::Image(out_img.w, out_img.h, _format, out_img.pixels, -1, true); + return dst; + } else { + _width = out_img.w; + _height = out_img.h; + } + return this; + } + + image::Image *Image::midpoint_pool(int x_div, int y_div, double bias, bool copy) { + if (x_div <= 0 || x_div > _width || y_div <= 0 || y_div > _height) { + log::warn("midpoint pool invalid div: %d, %d", x_div, y_div); + return nullptr; + } + + if (!(_format == Format::FMT_RGB888 || _format == Format::FMT_BGR888 || _format == Format::FMT_GRAYSCALE)) { + log::warn("midpoint pool not support format: %d", _format); + return nullptr; + } + + image::Image *dst; + int _dst_width = _width / x_div; + int _dst_height = _height / y_div; + if (copy) { + dst = new image::Image(_dst_width, _dst_height, _format); + } else { + dst = this; + } + + bias *= 256; + int min_bias = 256 - bias; + int max_bias = bias; + + switch (_format) { + case Format::FMT_GRAYSCALE: + for (int y = 0, yy = _dst_height, yyy = (_height % y_div) / 2; y < yy; y++) { + for (int x = 0, xx = _dst_width, xxx = (_width % x_div) / 2; x < xx; x++) { + int min = 255, max = 0; + uint8_t *src_data = (uint8_t *)_data; + uint8_t *dst_data = (uint8_t *)dst->data(); + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + int pixel = src_data[(yyy + (y * y_div) + i) * _width + (xxx + (x * x_div) + j)]; + min = std::min(min, pixel); + max = std::max(max, pixel); + } + } + dst_data[y * _dst_width + x] = (min_bias * min + max_bias * max) >> 8; + } + } + break; + case Format::FMT_RGB888: // fall through + case Format::FMT_BGR888: + for (int y = 0, yy = _dst_height, yyy = (_height % y_div) / 2; y < yy; y++) { + for (int x = 0, xx = _dst_width, xxx = (_width % x_div) / 2; x < xx; x++) { + int v0_min = 255, v0_max = 0; + int v1_min = 255, v1_max = 0; + int v2_min = 255, v2_max = 0; + uint8_t *src_data = (uint8_t *)_data; + uint8_t *dst_data = (uint8_t *)dst->data(); + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + int v0 = src_data[((yyy + (y * y_div) + i) * _width + (xxx + (x * x_div) + j)) * 3]; + int v1 = src_data[((yyy + (y * y_div) + i) * _width + (xxx + (x * x_div) + j)) * 3 + 1]; + int v2 = src_data[((yyy + (y * y_div) + i) * _width + (xxx + (x * x_div) + j)) * 3 + 2]; + v0_min = std::min(v0_min, v0); + v0_max = std::max(v0_max, v0); + v1_min = std::min(v1_min, v1); + v1_max = std::max(v1_max, v1); + v2_min = std::min(v2_min, v2); + v2_max = std::max(v2_max, v2); + } + } + + dst_data[(y * _dst_width + x) * 3] = ((v0_min * min_bias) + (v0_max * max_bias)) >> 8; + dst_data[(y * _dst_width + x) * 3 + 1] =((v1_min * min_bias) + (v1_max * max_bias)) >> 8; + dst_data[(y * _dst_width + x) * 3 + 2] = ((v2_min * min_bias) + (v2_max * max_bias)) >> 8; + } + } + break; + default: + // should not be here + break; + } + + if (!copy) { + _width = _dst_width; + _height = _dst_height; + } + return dst; + } + + image::Image *Image::compress(int quality) { + return to_jpeg(quality); + } + + err::Err image_zero(image::Image &src, image::Image &mask, bool invert) { + image_t src_img, mask_img; + convert_to_imlib_image(&src, &src_img); + convert_to_imlib_image(&mask, &mask_img); + imlib_zero(&src_img, &mask_img, invert); + + return err::Err::ERR_NONE; + } + + image::Image *Image::clear(image::Image *mask) { + if (!mask) { + memset(_data, 0, _data_size); + } else { + image_zero(*this, *mask, false); + } + + return this; + } + + image::Image *Image::mask_rectange(int x, int y, int w, int h) { + int use_default_setting = 0; + if (x < 0 || y < 0 || w < 0 || h < 0) { + use_default_setting = 1; + } + + if (use_default_setting) { + x = _width / 4; + y = _height / 4; + w = _width / 2; + h = _height / 2; + } + + draw_rect(x, y, w, h, image::COLOR_BLACK, -1); + return this; + } + + image::Image *Image::mask_circle(int x, int y, int radius) { + int use_default_setting = 0; + if (x < 0 || y < 0 || radius < 0) { + use_default_setting = 1; + } + + if (use_default_setting) { + x = _width / 2; + y = _height / 2; + radius = std::min(_width, _height) / 2; + } + + draw_circle(x, y, radius, image::COLOR_BLACK, -1); + return this; + } + + image::Image *Image::mask_ellipse(int x, int y, int radius_x, int radius_y, float rotation_angle_in_degrees) { + int use_default_setting = 0; + if (x < 0 || y < 0 || radius_x < 0 || radius_y < 0) { + use_default_setting = 1; + } + + if (use_default_setting) { + x = _width / 2; + y = _height / 2; + radius_x = _width / 2; + radius_y = _height / 2; + rotation_angle_in_degrees = 0; + } + + draw_ellipse(x, y, radius_x, radius_y, rotation_angle_in_degrees, 0, 360, image::COLOR_BLACK, -1); + return this; + } + + image::Image *Image::binary(std::vector> thresholds, bool invert, bool zero, image::Image *mask, bool to_bitmap, bool copy) { + err::check_bool_raise(thresholds.size() != 0, "You need to set thresholds"); + err::check_bool_raise(to_bitmap == false, "Parameter to_bitmap is not supported"); + + list_t thresholds_list; + list_init(&thresholds_list, sizeof(color_thresholds_list_lnk_data_t)); + _convert_to_lab_thresholds(thresholds, &thresholds_list); + + image_t src_img, mask_img, out_img; + image::Image *dst = nullptr; + if (copy) { + dst = new image::Image(_width, _height, _format); + } else { + dst = this; + } + + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(dst, &out_img); + if (mask) { + convert_to_imlib_image(mask, &mask_img); + } + + imlib_binary(&out_img, &src_img, &thresholds_list, invert, zero, mask ? &mask_img : NULL); + list_free(&thresholds_list); + + return dst; + } + + image::Image *Image::invert() { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_data = (uint32_t *)_data; + for (int i = 0; i < u32_len; i ++) { + u32_data[i] = ~u32_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_data[i] = ~remain_data[i]; + } + + return this; + } + + image::Image *Image::b_and(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_and(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] & u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] & remain_other_data[i]; + } + } + + return this; + } + + image::Image *Image::b_nand(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_nand(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] & ~u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] & ~remain_other_data[i]; + } + } + + return this; + } + + image::Image *Image::b_or(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_or(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] | u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] | remain_other_data[i]; + } + } + + + return this; + } + + image::Image *Image::b_nor(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_nor(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] | ~u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] | ~remain_other_data[i]; + } + } + + + return this; + } + + image::Image *Image::b_xor(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_xor(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] ^ u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] ^ remain_other_data[i]; + } + } + + return this; + } + + image::Image *Image::b_xnor(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_xnor(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] ^ ~u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] ^ ~remain_other_data[i]; + } + } + + return this; + } + + image::Image *Image::awb(bool max) { + image_t src_img; + Image *rgb565_img = nullptr; + if (_format == image::FMT_RGB888 || _format == image::FMT_BGR888) { + rgb565_img = to_format(image::FMT_RGB565); + convert_to_imlib_image(rgb565_img, &src_img); + } else { + log::warn("awb not support format: %d", _format); + return this; + } + + imlib_awb(&src_img, max); + + if (_format == image::FMT_RGB888 || _format == image::FMT_BGR888) { + Image *rgb888_img = rgb565_img->to_format(image::FMT_RGB888); + memcpy(_data, rgb888_img->data(), _data_size); + delete rgb888_img; + delete rgb565_img; + } + return this; + } + + image::Image *Image::ccm(std::vector &matrix) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + + float ccm[12] = {}; + float *matrix_data = (float *)matrix.data(); + size_t len = matrix.size(); + if (len != 9 && len != 12) { + log::error("ccm matrix size not match: %d", len); + return this; + } + + for (size_t i = 0; i < len; i ++) { + ccm[i] = matrix_data[i]; + } + + imlib_ccm(&src_img, ccm, len == 12); + return this; + } + + image::Image *Image::gamma(double gamma, double contrast, double brightness) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + + imlib_gamma(&src_img, gamma, contrast, brightness); + return this; + } + + image::Image *Image::gamma_corr(double gamma, double contrast, double brightness) { + return this->gamma(gamma, contrast, brightness); + } + + image::Image *Image::negate(void) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + imlib_negate(&src_img); + return this; + } + + image::Image *Image::replace(image::Image *other, bool hmirror, bool vflip, bool transpose, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (other) { + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + convert_to_imlib_image(other, &other_img); + } + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + } + + if (other && mask) { + imlib_replace(&src_img, NULL, &other_img, 0, hmirror, vflip, transpose, &mask_img); + } else if (other && !mask) { + imlib_replace(&src_img, NULL, &other_img, 0, hmirror, vflip, transpose, NULL); + } else if (!other && mask) { + imlib_replace(&src_img, NULL, &src_img, 0, hmirror, vflip, transpose, &mask_img); + } else { + imlib_replace(&src_img, NULL, &src_img, 0, hmirror, vflip, transpose, NULL); + } + + _width = src_img.w; + _height = src_img.h; + + return this; + } + + image::Image *Image::set(image::Image *other, bool hmirror, bool vflip, bool transpose, image::Image *mask) { + return this->replace(other, hmirror, vflip, transpose, mask); + } + + image::Image *Image::add(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_add(&src_img, NULL, &other_img, 0, &mask_img); + } else { + imlib_add(&src_img, NULL, &other_img, 0, NULL); + } + return this; + } + + image::Image *Image::sub(image::Image *other, bool reverse, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_sub(&src_img, NULL, &other_img, 0, reverse, &mask_img); + } else { + imlib_sub(&src_img, NULL, &other_img, 0, reverse, NULL); + } + return this; + } + + image::Image *Image::mul(image::Image *other, bool invert, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_mul(&src_img, NULL, &other_img, 0, invert, &mask_img); + } else { + imlib_mul(&src_img, NULL, &other_img, 0, invert, NULL); + } + return this; + } + + image::Image *Image::div(image::Image *other, bool invert, bool mod, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_div(&src_img, NULL, &other_img, 0, invert, mod, &mask_img); + } else { + imlib_div(&src_img, NULL, &other_img, 0, invert, mod, NULL); + } + return this; + } + + image::Image *Image::min(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_min(&src_img, NULL, &other_img, 0, &mask_img); + } else { + imlib_min(&src_img, NULL, &other_img, 0, NULL); + } + return this; + } + + image::Image *Image::max(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_max(&src_img, NULL, &other_img, 0, &mask_img); + } else { + imlib_max(&src_img, NULL, &other_img, 0, NULL); + } + return this; + } + + image::Image *Image::difference(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_difference(&src_img, NULL, &other_img, 0, &mask_img); + } else { + imlib_difference(&src_img, NULL, &other_img, 0, NULL); + } + return this; + } + + image::Image *Image::blend(image::Image *other, int alpha, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (alpha < 0 || alpha > 256) { + log::error("alpha value not valid: %d", alpha); + return this; + } + + float alpha_f = alpha / 256.0; + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_blend(&src_img, NULL, &other_img, 0, alpha_f, &mask_img); + } else { + imlib_blend(&src_img, NULL, &other_img, 0, alpha_f, NULL); + } + return this; + } + + image::Image *Image::histeq(bool adaptive, int clip_limit, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (adaptive) { + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_clahe_histeq(&src_img, clip_limit, &mask_img); + } else { + imlib_clahe_histeq(&src_img, clip_limit, NULL); + } + + } else { + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_histeq(&src_img, &mask_img); + } else { + imlib_histeq(&src_img, NULL); + } + } + return this; + } + + image::Image *Image::mean(int size, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_mean_filter(&src_img, size, threshold, offset, invert, &mask_img); + } else { + imlib_mean_filter(&src_img, size, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::median(int size, double percentile, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_median_filter(&src_img, size, percentile, threshold, offset, invert, &mask_img); + } else { + imlib_median_filter(&src_img, size, percentile, threshold, offset, invert, NULL); + } + + return this; + } + + image::Image *Image::mode(int size, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_mode_filter(&src_img, size, threshold, offset, invert, &mask_img); + } else { + imlib_mode_filter(&src_img, size, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::midpoint(int size, double bias, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_midpoint_filter(&src_img, size, bias, threshold, offset, invert, &mask_img); + } else { + imlib_midpoint_filter(&src_img, size, bias, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::morph(int size, std::vector kernel, float mul, float add, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + int *kernel_data = (int *)kernel.data(); + size_t len = kernel.size(); + + err::check_bool_raise(len != 0, "You need to config values of kernel"); + err::check_bool_raise(len == ((size * 2) + 1) * ((size * 2) + 1), "Kernel size not match"); + + int m = 0; + for (size_t i = 0; i < len; i++) { + m += kernel_data[i]; + } + if (m == 0) { + m = 1; + } + + if (mul < 0) { + mul = 1.0f / m; + } + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_morph(&src_img, size, kernel_data, mul, add, threshold, offset, invert, &mask_img); + } else { + imlib_morph(&src_img, size, kernel_data, mul, add, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::gaussian(int size, bool unsharp, float mul, float add, bool threshold, int offset, bool invert, image::Image *mask) { + std::vector pascal; + std::vector kernel; + int m = 0; + int k_2 = size * 2; + int n = k_2 + 1; + + pascal.resize(n); + pascal[0] = 1; + for (int i = 0; i < k_2; i ++) { + pascal[i + 1] = (pascal[i] * (k_2 - i)) / (i + 1); + } + + kernel.resize(n * n); + for (int i = 0; i < n; i ++) { + for (int j = 0; j < n; j ++) { + int temp = pascal[i] * pascal[j]; + kernel[(i * n) + j] = temp; + m += temp; + } + } + + if (unsharp == false) { + kernel[((n / 2) * n) + (n / 2)] -= m * 2; + m = -m; + } + + if (mul < 0) { + mul = 1.0f / m; + } + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_morph(&src_img, size, kernel.data(), mul, add, threshold, offset, invert, &mask_img); + } else { + imlib_morph(&src_img, size, kernel.data(), mul, add, threshold, offset, invert, NULL); + } + + return this; + } + + image::Image *Image::laplacian(int size, bool sharpen, float mul, float add, bool threshold, int offset, bool invert, image::Image *mask) { + std::vector pascal; + std::vector kernel; + int m = 0; + int k_2 = size * 2; + int n = k_2 + 1; + + pascal.resize(n); + pascal[0] = 1; + for (int i = 0; i < k_2; i ++) { + pascal[i + 1] = (pascal[i] * (k_2 - i)) / (i + 1); + } + + kernel.resize(n * n); + for (int i = 0; i < n; i ++) { + for (int j = 0; j < n; j ++) { + int temp = pascal[i] * pascal[j]; + kernel[(i * n) + j] = -temp; + m += temp; + } + } + + kernel[((n / 2) * n) + (n / 2)] += m; + m = kernel[((n / 2) * n) + (n / 2)]; + + if (sharpen == false) { + kernel[((n / 2) * n) + (n / 2)] += m; + } + + if (mul < 0) { + mul = 1.0f / m; + } + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_morph(&src_img, size, kernel.data(), mul, add, threshold, offset, invert, &mask_img); + } else { + imlib_morph(&src_img, size, kernel.data(), mul, add, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::bilateral(int size, double color_sigma, double space_sigma, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_bilateral_filter(&src_img, size, color_sigma, space_sigma, threshold, offset, invert, &mask_img); + } else { + imlib_bilateral_filter(&src_img, size, color_sigma, space_sigma, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::linpolar(bool reverse) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + imlib_logpolar(&src_img, true, reverse); + return this; + } + + image::Image *Image::logpolar(bool reverse) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + imlib_logpolar(&src_img, false, reverse); + return this; + } + + image::Image *Image::lens_corr(double strength, double zoom, double x_corr, double y_corr) { + if (_width % 2 || _height % 2) { + log::error("lens_corr image size must be even"); + return this; + } + + image_t src_img; + convert_to_imlib_image(this, &src_img); + imlib_lens_corr(&src_img, strength, zoom, x_corr, y_corr); + return this; + } + + image::Image *Image::rotation_corr(double x_rotation, double y_rotation, double z_rotation, double x_translation, double y_translation, double zoom, double fov, std::vector corners) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + imlib_rotation_corr(&src_img, x_rotation, y_rotation, z_rotation, x_translation, y_translation, zoom, fov, (float *)corners.data()); + return this; + } + + std::map> Image::get_histogram(std::vector> thresholds, bool invert, std::vector roi, int bins, int l_bins, int a_bins, int b_bins, image::Image *difference) { + std::map> result = std::map>(); + image_t src_img, *other_img = NULL; + convert_to_imlib_image(this, &src_img); + if (difference) { + other_img = (image_t *)malloc(sizeof(image_t)); + if (!other_img) { + log::error("malloc image_t failed"); + return result; + } + convert_to_imlib_image(difference, other_img); + } + + rectangle_t roi_rect; + std::vector avail_roi = _get_available_roi(roi); + roi_rect.x = avail_roi[0]; + roi_rect.y = avail_roi[1]; + roi_rect.w = avail_roi[2]; + roi_rect.h = avail_roi[3]; + + list_t thresholds_list; + list_init(&thresholds_list, sizeof(color_thresholds_list_lnk_data_t)); + _convert_to_lab_thresholds(thresholds, &thresholds_list); + + histogram_t hist; + switch (_format) { + case image::FMT_GRAYSCALE: + // bins must be greater than 1 + bins = bins >= 2 ? bins : COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1; + hist.LBinCount = bins; + hist.ABinCount = 0; + hist.BBinCount = 0; + hist.LBins = (float *)malloc(hist.LBinCount * sizeof(float)); + hist.ABins = NULL; + hist.BBins = NULL; + imlib_get_histogram(&hist, &src_img, &roi_rect, &thresholds_list, invert, other_img); + break; + case image::FMT_RGB888: + bins = bins >= 2 ? bins : COLOR_L_MAX - COLOR_L_MIN + 1; + l_bins = l_bins >= 2 ? l_bins : bins; + a_bins = a_bins >= 2 ? a_bins : COLOR_A_MAX - COLOR_A_MIN + 1; + b_bins = b_bins >= 2 ? b_bins : COLOR_B_MAX - COLOR_B_MIN + 1; + hist.LBinCount = l_bins; + hist.ABinCount = a_bins; + hist.BBinCount = b_bins; + hist.LBins = (float *)malloc(hist.LBinCount * sizeof(float)); + hist.ABins = (float *)malloc(hist.ABinCount * sizeof(float)); + hist.BBins = (float *)malloc(hist.BBinCount * sizeof(float)); + imlib_get_histogram(&hist, &src_img, &roi_rect, &thresholds_list, invert, other_img); + break; + default: + log::error("format not support: %d", _format); + return result; + } + + std::vector l_bins_data(hist.LBins, hist.LBins + hist.LBinCount); + std::vector a_bins_data(hist.ABins, hist.ABins + hist.ABinCount); + std::vector b_bins_data(hist.BBins, hist.BBins + hist.BBinCount); + result["L"] = l_bins_data; + result["A"] = a_bins_data; + result["B"] = b_bins_data; + + list_free(&thresholds_list); + if (difference && other_img) free(other_img); + if (hist.LBins) free(hist.LBins); + if (hist.ABins) free(hist.ABins); + if (hist.BBins) free(hist.BBins); + return result; + } + + image::Statistics Image::get_statistics(std::vector> thresholds, bool invert, std::vector roi, int bins, int l_bins, int a_bins, int b_bins, image::Image *difference) { + image::Statistics result = image::Statistics(); + image_t src_img, *other_img = NULL; + convert_to_imlib_image(this, &src_img); + if (difference) { + other_img = (image_t *)malloc(sizeof(image_t)); + if (!other_img) { + log::error("malloc image_t failed"); + return result; + } + convert_to_imlib_image(difference, other_img); + } + + rectangle_t roi_rect; + std::vector avail_roi = _get_available_roi(roi); + roi_rect.x = avail_roi[0]; + roi_rect.y = avail_roi[1]; + roi_rect.w = avail_roi[2]; + roi_rect.h = avail_roi[3]; + + list_t thresholds_list; + list_init(&thresholds_list, sizeof(color_thresholds_list_lnk_data_t)); + _convert_to_lab_thresholds(thresholds, &thresholds_list); + + histogram_t hist = {0}; + switch (_format) { + case image::FMT_GRAYSCALE: + // bins must be greater than 1 + bins = bins >= 2 ? bins : COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1; + hist.LBinCount = bins; + hist.ABinCount = 0; + hist.BBinCount = 0; + hist.LBins = (float *)malloc(hist.LBinCount * sizeof(float)); + hist.ABins = NULL; + hist.BBins = NULL; + imlib_get_histogram(&hist, &src_img, &roi_rect, &thresholds_list, invert, other_img); + break; + case image::FMT_RGB888: + bins = bins >= 2 ? bins : COLOR_L_MAX - COLOR_L_MIN + 1; + l_bins = l_bins >= 2 ? l_bins : bins; + a_bins = a_bins >= 2 ? a_bins : COLOR_A_MAX - COLOR_A_MIN + 1; + b_bins = b_bins >= 2 ? b_bins : COLOR_B_MAX - COLOR_B_MIN + 1; + hist.LBinCount = l_bins; + hist.ABinCount = a_bins; + hist.BBinCount = b_bins; + hist.LBins = (float *)malloc(hist.LBinCount * sizeof(float)); + hist.ABins = (float *)malloc(hist.ABinCount * sizeof(float)); + hist.BBins = (float *)malloc(hist.BBinCount * sizeof(float)); + imlib_get_histogram(&hist, &src_img, &roi_rect, &thresholds_list, invert, other_img); + break; + default: + log::error("format not support: %d", _format); + return result; + } + + statistics_t stats = {0}; + imlib_get_statistics(&stats, (pixformat_t)src_img.pixfmt, &hist); + + std::vector l_statistics = {stats.LMean, stats.LMedian, stats.LMode, stats.LSTDev, stats.LMin, stats.LMax, stats.LLQ, stats.LUQ}; + std::vector a_statistics = {stats.AMean, stats.AMedian, stats.AMode, stats.ASTDev, stats.AMin, stats.AMax, stats.LLQ, stats.AUQ}; + std::vector b_statistics = {stats.BMean, stats.BMedian, stats.BMode, stats.BSTDev, stats.BMin, stats.BMax, stats.LLQ, stats.BUQ}; + result = image::Statistics( _format, + l_statistics, + a_statistics, + b_statistics); + + list_free(&thresholds_list); + if (difference && other_img) free(other_img); + if (hist.LBins) free(hist.LBins); + if (hist.ABins) free(hist.ABins); + if (hist.BBins) free(hist.BBins); + return result; + } + + std::vector Image::get_regression(std::vector> thresholds, bool invert, std::vector roi, int x_stride, int y_stride, int area_threshold, int pixels_threshold, bool robust) { + std::vector lines = std::vector(); + image_t src_img; + if (_format != image::FMT_GRAYSCALE && _format != image::FMT_RGB888 && _format != image::FMT_RGB565) { + log::error("get_regression only support GRAYSCALE RGB888 RGB565 format!\n"); + } + convert_to_imlib_image(this, &src_img); + + rectangle_t roi_rect; + std::vector avail_roi = _get_available_roi(roi); + roi_rect.x = avail_roi[0]; + roi_rect.y = avail_roi[1]; + roi_rect.w = avail_roi[2]; + roi_rect.h = avail_roi[3]; + + list_t thresholds_list; + list_init(&thresholds_list, sizeof(color_thresholds_list_lnk_data_t)); + _convert_to_lab_thresholds(thresholds, &thresholds_list); + + find_lines_list_lnk_data_t out; + bool res = imlib_get_regression(&out, &src_img, &roi_rect, x_stride, + y_stride, &thresholds_list, invert, area_threshold, pixels_threshold, robust); + if (true == res) { + Line line = Line(out.line.x1, out.line.y1, out.line.x2, out.line.y2, out.magnitude, out.theta, out.rho); + lines.push_back(line); + } + + list_free(&thresholds_list); + return lines; + } + + image::Image *Image::flood_fill(int x, int y, float seed_threshold, float floating_threshold, image::Color color , bool invert, bool clear_background, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_flood_fill(&src_img, x, y, seed_threshold, floating_threshold, color.hex(), invert, clear_background, &mask_img); + } else { + imlib_flood_fill(&src_img, x, y, seed_threshold, floating_threshold, color.hex(), invert, clear_background, NULL); + } + return this; + } + + image::Image *Image::erode(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "erode size must be greater than 0"); + err::check_bool_raise(threshold == -1 || threshold >= 0, "erode threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (threshold == -1) { + threshold = ((size * 2) + 1) * ((size * 2) + 1) - 1; + } + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_erode(&src_img, size, threshold, &mask_img); + } else { + imlib_erode(&src_img, size, threshold, NULL); + } + return this; + } + + image::Image *Image::dilate(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "dilate size must be greater than 0"); + err::check_bool_raise(threshold >= 0, "dilate threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_dilate(&src_img, size, threshold, &mask_img); + } else { + imlib_dilate(&src_img, size, threshold, NULL); + } + return this; + } + + image::Image *Image::open(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "open size must be greater than 0"); + err::check_bool_raise(threshold >= 0, "open threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_open(&src_img, size, threshold, &mask_img); + } else { + imlib_open(&src_img, size, threshold, NULL); + } + return this; + } + + image::Image *Image::close(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "close size must be greater than 0"); + err::check_bool_raise(threshold >= 0, "close threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_close(&src_img, size, threshold, &mask_img); + } else { + imlib_close(&src_img, size, threshold, NULL); + } + return this; + } + + image::Image *Image::top_hat(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "top_hat size must be greater than 0"); + err::check_bool_raise(threshold >= 0, "top_hat threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_top_hat(&src_img, size, threshold, &mask_img); + } else { + imlib_top_hat(&src_img, size, threshold, NULL); + } + return this; + } + + image::Image *Image::black_hat(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "black_hat size must be greater than 0"); + err::check_bool_raise(threshold >= 0, "black_hat threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_black_hat(&src_img, size, threshold, &mask_img); + } else { + imlib_black_hat(&src_img, size, threshold, NULL); + } + return this; + } +} \ No newline at end of file diff --git a/support/sg2002/build b/support/sg2002/build new file mode 100644 index 0000000..1579c74 --- /dev/null +++ b/support/sg2002/build @@ -0,0 +1,178 @@ +#!/bin/bash + +export MAIXCDK_PATH=~/MaixCDK +export NanoKVM_PATH=~/NanoKVM + +Project_Name="None" +Project_PATH="None" +Project_Action="None" +KVMAPP_PATH="$NanoKVM_PATH/kvmapp" + +maixcdk_build() { + local input_value="$1" + + DIST_PATH="./dist" + rm -rf $DIST_PATH + + if [ "$input_value" = "from_zero" ] + then + maixcdk build < +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qr.h" +#include "system_ctrl.h" +#include "system_state.h" +#include "system_init.h" +#include "oled_ctrl.h" +#include "oled_ui.h" +#include "hdmi.h" + +#define IP_Change_time 5000 +#define QR_Change_time 5000 +#define STATE_DELAY 1000 +#define OLED_DELAY 1000 +#define KEY_DELAY 100 +#define KEY_LONG_PRESS 1500 +#define KEY_LONGLONG_PRESS 9000 +#define WIFI_CONNECTION_DELAY 5000 +#define OLED_SLEEP_DELAY_MIN 10 +#define OLED_SLEEP_DELAY_DEFAULT 30 +#define KVM_WD_COUNT_MAX 10 +#define RM_Watchdog_times 60 + +typedef struct { + int8_t page = 0; + int8_t sub_page = 0; + uint8_t eth_route[16] = {0}; // route ip + uint8_t wifi_route[16] = {0}; // route ip + uint8_t eth_addr[16] = {0}; // ETH ip + uint8_t wifi_addr[16] = {0}; // WiFi ip + uint8_t tail_addr[16] = {0}; // Tailscale ip + uint8_t rndis_addr[16] = {0}; // RNDIS ip + int8_t eth_state = -1; // cat /sys/class/net/eth0/carrier + int8_t wifi_state = -1; // cat /sys/class/net/wlan0/carrier + int8_t tail_state = -1; // ifconfig tailscale0 | grep 'inet addr' | awk '{print $2}' + int8_t hdmi_state = -1; // cat /proc/cvitek/vi_dbg | grep VIFPS | awk '{print $3}' (1s) + int8_t usb_state = -1; // cat /sys/class/udc/4340000.usb/state + int8_t hid_state = -1; // (exist?) /sys/kernel/config/usb_gadget/g0/configs/c.1/hid.GSn(n=012) + int8_t rndis_state = -1; // (exist?) /sys/kernel/config/usb_gadget/g0/configs/c.1/rndis.usb0 + int8_t udisk_state = -1; // (exist?) /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0 + int8_t host_pwr_state = -1; // cat /sys/class/gpio/gpio504/value + int16_t hdmi_width = 0; // cat /kvmapp/kvm/width + int16_t hdmi_height = 0; // cat /kvmapp/kvm/height + int8_t type = 0; // cat /kvmapp/kvm/type + int8_t now_fps = 0; // cat /kvmapp/kvm/now_fps + int16_t qlty = 0; // cat /kvmapp/kvm/qlty + int8_t oled_thread_running = 0; + int8_t key_thread_running = 0; + int8_t sys_thread_running = 0; + int8_t wifi_config_process = -1; // 1:QR;2:Test;3:IP; + char wifi_ap_pass[9] = {0}; + uint8_t oled_sleep_state = 0; // 0:wakeup; 1:sleep; + int8_t reconvery_update = 0; // 0:Undetected; 1:Needs Update; 2:Update finish; -1:not need to update + uint8_t ping_allow = 1; +} kvm_sys_state_t; + +typedef struct { + int8_t page = -1; + int8_t sub_page = -1; + uint8_t eth_route[16] = {0}; // route ip + uint8_t wifi_route[16] = {0}; // route ip + uint8_t eth_addr[16] = {0}; // ETH ip + uint8_t wifi_addr[16] = {0}; // WiFi ip + uint8_t tail_addr[16] = {0}; // Tailscale ip + uint8_t rndis_addr[16] = {0}; // RNDIS ip + int8_t eth_state = -1; // cat /sys/class/net/eth0/carrier + int8_t wifi_state = -1; // cat /sys/class/net/wlan0/carrier + int8_t tail_state = -1; // ifconfig tailscale0 | grep inet\ addr | awk '{print $2}' + int8_t hdmi_state = -1; // cat /proc/cvitek/vi_dbg | grep VIFPS | awk '{print $3}' (1s) + int8_t usb_state = -1; // cat /sys/class/udc/4340000.usb/state + int8_t hid_state = -1; // (exist?) /sys/kernel/config/usb_gadget/g0/configs/c.1/hid.GSn(n=012) + int8_t rndis_state = -1; // (exist?) /sys/kernel/config/usb_gadget/g0/configs/c.1/rndis.usb0 + int8_t udisk_state = -1; // (exist?) /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0 + int8_t host_pwr_state = -1; // cat /sys/class/gpio/gpio504/value + int16_t hdmi_width = -1; // cat /kvmapp/kvm/width + int16_t hdmi_height = -1; // cat /kvmapp/kvm/height + int8_t type = -1; // cat /kvmapp/kvm/type + int8_t now_fps = -1; // cat /kvmapp/kvm/now_fps + int16_t qlty = -1; // cat /kvmapp/kvm/qlty + uint8_t oled_sleep_param = 0; + uint8_t oled_sleep_state = 0; // 0:wakeup; 1:sleep; + uint64_t oled_sleep_start = 0; + uint64_t ue_patch_state = 0; +} kvm_oled_state_t; + +#endif // CONFIG_H_ diff --git a/support/sg2002/kvm_system/main/lib/hdmi/hdmi.cpp b/support/sg2002/kvm_system/main/lib/hdmi/hdmi.cpp new file mode 100644 index 0000000..0868e77 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/hdmi/hdmi.cpp @@ -0,0 +1,238 @@ +#include "hdmi.h" + +using namespace maix; +using namespace maix::sys; +using namespace maix::peripheral; +i2c::I2C LT6911_i2c(4, i2c::Mode::MASTER); + +void lt6911_enable() +{ + uint8_t buf[2]; + buf[0] = 0xff; + buf[1] = 0x80; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0xee; + buf[1] = 0x01; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); +} + +void lt6911_disable() +{ + uint8_t buf[2]; + buf[0] = 0xff; + buf[1] = 0x80; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0xee; + buf[1] = 0x00; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); +} + +void lt6911_start() +{ + uint8_t buf[2]; + + buf[0] = 0xff; + buf[1] = 0x80; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0x5A; + buf[1] = 0x80; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); +} + +void lt6911_stop() +{ + uint8_t buf[2]; + + buf[0] = 0xff; + buf[1] = 0x80; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0x5A; + buf[1] = 0x88; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); +} + +void lt6911_reset() +{ + lt6911_stop(); + time::sleep_ms(1); + lt6911_start(); +} + +void lt6911_get_hdmi_errer() +{ + uint8_t buf[6]; + + buf[0] = 0xff; + buf[1] = 0xC0; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0x20; + buf[1] = 0x01; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + time::sleep_ms(100); + + buf[0] = 0x24; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + + maix::Bytes *dat = LT6911_i2c.readfrom(LT6911_ADDR, 6); + + buf[0] = 0x20; + buf[1] = 0x07; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + for(int i = 0; i < 6; i++){ + buf[i] = (uint8_t)dat->data[i]; + } + delete dat; + + printf("hdmi_errer_code = %x, %x, %x, %x, %x, %x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); +} + +uint8_t lt6911_get_hdmi_res() +{ + uint8_t buf[2]; + uint8_t revbuf[4]; + uint16_t Vactive; + uint16_t Hactive; + + buf[0] = 0xff; + buf[1] = 0xd2; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0x83; + buf[1] = 0x11; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + time::sleep_ms(100); + + // Vactive + buf[0] = 0x96; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 2); + + // Hactive + buf[0] = 0x8b; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat1 = LT6911_i2c.readfrom(LT6911_ADDR, 2); + + revbuf[0] = (uint8_t)dat0->data[0]; + revbuf[1] = (uint8_t)dat0->data[1]; + revbuf[2] = (uint8_t)dat1->data[0]; + revbuf[3] = (uint8_t)dat1->data[1]; + + Vactive = (revbuf[0] << 8)|revbuf[1]; + Hactive = (revbuf[2] << 8)|revbuf[3]; + Hactive *= 2; + + printf("HDMI res modification event\n"); + printf("new res: %d * %d\n", Hactive, Vactive); + + delete dat0; + delete dat1; + + if (Vactive != 0 && Hactive != 0){ + return 1; + } else { + return 0; + } +} + +void lt6911_get_hdmi_clk() +{ + uint8_t buf[2]; + uint8_t revbuf[3]; + uint32_t clk; + + buf[0] = 0xff; + buf[1] = 0xa0; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0x34; + buf[1] = 0x0b; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + time::sleep_ms(50); + + // clk + buf[0] = 0xff; + buf[1] = 0xb8; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + buf[0] = 0xb1; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 3); + + revbuf[0] = (uint8_t)dat0->data[0]; + revbuf[1] = (uint8_t)dat0->data[1]; + revbuf[2] = (uint8_t)dat0->data[2]; + revbuf[0] &= 0x07; + + clk = revbuf[0]; + clk <<= 8; + clk |= revbuf[1]; + clk <<= 8; + clk |= revbuf[2]; + + printf("HDMI CLK = %d\n", clk); + + delete dat0; +} + +uint8_t lt6911_get_csi_res() +{ + uint8_t ret = 0; + uint8_t buf[2]; + uint8_t revbuf[4]; + static uint16_t old_Vactive; + static uint16_t old_Hactive; + uint16_t Vactive; + uint16_t Hactive; + char Cmd[100]={0}; + + buf[0] = 0xff; + buf[1] = 0xc2; + LT6911_i2c.writeto(LT6911_ADDR, buf, 2); + + // Vactive + buf[0] = 0x06; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat0 = LT6911_i2c.readfrom(LT6911_ADDR, 2); + + // Hactive + buf[0] = 0x38; + LT6911_i2c.writeto(LT6911_ADDR, buf, 1); + maix::Bytes *dat1 = LT6911_i2c.readfrom(LT6911_ADDR, 2); + + revbuf[0] = (uint8_t)dat0->data[0]; + revbuf[1] = (uint8_t)dat0->data[1]; + revbuf[2] = (uint8_t)dat1->data[0]; + revbuf[3] = (uint8_t)dat1->data[1]; + + Vactive = (revbuf[0] << 8)|revbuf[1]; + Hactive = (revbuf[2] << 8)|revbuf[3]; + + if(old_Hactive != Hactive || old_Vactive != Vactive){ + old_Hactive = Hactive; + old_Vactive = Vactive; + ret = 1; + } + + printf("CSI res: %d * %d\n", Hactive, Vactive); + // setenv("KVM_CSI_HEIGHT", to_string(Hactive).c_str(), 1); + // setenv("KVM_CSI_WIDTH", to_string(Vactive).c_str(), 1); + + sprintf(Cmd, "echo %d > /kvmapp/kvm/width", Hactive); + system(Cmd); + sprintf(Cmd, "echo %d > /kvmapp/kvm/height", Vactive); + system(Cmd); + + delete dat0; + delete dat1; + + return ret; +} \ No newline at end of file diff --git a/support/sg2002/kvm_system/main/lib/hdmi/hdmi.h b/support/sg2002/kvm_system/main/lib/hdmi/hdmi.h new file mode 100644 index 0000000..7fa9008 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/hdmi/hdmi.h @@ -0,0 +1,29 @@ +#ifndef HDMI_H_ +#define HDMI_H_ + +#include "maix_basic.hpp" +#include "maix_time.hpp" +#include "maix_gpio.hpp" +#include "maix_pinmap.hpp" +#include "maix_i2c.hpp" +#include +#include +#include +#include +#include + +#define LT6911_ADDR 0x2B +#define LT6911_READ 0xFF +#define LT6911_WRITE 0x00 + +void lt6911_enable(); +void lt6911_disable(); +void lt6911_start(); +void lt6911_stop(); +void lt6911_reset(); +void lt6911_get_hdmi_errer(); +uint8_t lt6911_get_hdmi_res(); +void lt6911_get_hdmi_clk(); +uint8_t lt6911_get_csi_res(); + +#endif // HDMI_H_ \ No newline at end of file diff --git a/support/sg2002/kvm_system/main/lib/libqr/CMakeLists.txt b/support/sg2002/kvm_system/main/lib/libqr/CMakeLists.txt new file mode 100644 index 0000000..b60d6fa --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/CMakeLists.txt @@ -0,0 +1,59 @@ +project(qr) +cmake_minimum_required(VERSION 2.6.0) + +set(QR_VERSION "1.0.0") +set(QR_SOVERSION "1") + +set(QR_COMMAND_SOURCES qrcmd.c) +set(QR_LIBRARY_SOURCES + qr.c qrcnv.c qrcnv_bmp.c qrcnv_png.c qrcnv_svg.c qrcnv_tiff.c +) +set(QR_PUBLIC_HEADERS qr.h) + +set(bindir bin) +set(incdir include) +set(libdir lib) + +set(CMAKE_SKIP_BUILD_RPATH OFF) +set(CMAKE_BUILD_WITH_INSTALL_RPATH OFF) +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${libdir}") +set(CMAKE_INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/${libdir}") + +find_package(ZLIB) + +add_definitions(-Wall -Wextra) + +include_directories(${ZLIB_INCLUDE_DIRS}) + +add_executable(qrcmd ${QR_COMMAND_SOURCES}) +add_executable(qrcmd_multi ${QR_COMMAND_SOURCES}) + +add_library(libqr_shared SHARED ${QR_LIBRARY_SOURCES}) +add_library(libqr_static STATIC ${QR_LIBRARY_SOURCES}) + +target_link_libraries(qrcmd libqr_shared) +target_link_libraries(qrcmd_multi libqr_shared) +target_link_libraries(libqr_shared m ${ZLIB_LIBRARIES}) + +set_target_properties(qrcmd PROPERTIES + OUTPUT_NAME qr +) +set_target_properties(qrcmd_multi PROPERTIES + OUTPUT_NAME qrs + COMPILE_FLAGS -DQRCMD_STRUCTURED_APPEND +) +set_target_properties(libqr_shared PROPERTIES + OUTPUT_NAME qr + VERSION ${QR_VERSION} + SOVERSION ${QR_SOVERSION} +) +set_target_properties(libqr_static PROPERTIES + OUTPUT_NAME qr +) + +install(TARGETS qrcmd qrcmd_multi libqr_shared libqr_static + RUNTIME DESTINATION ${bindir} + LIBRARY DESTINATION ${libdir} + ARCHIVE DESTINATION ${libdir} +) +install(FILES ${QR_PUBLIC_HEADERS} DESTINATION ${incdir}) diff --git a/support/sg2002/kvm_system/main/lib/libqr/LICENSE b/support/sg2002/kvm_system/main/lib/libqr/LICENSE new file mode 100644 index 0000000..85a58e3 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/LICENSE @@ -0,0 +1,19 @@ +All rights reserved. + +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/support/sg2002/kvm_system/main/lib/libqr/README b/support/sg2002/kvm_system/main/lib/libqr/README new file mode 100644 index 0000000..17700bc --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/README @@ -0,0 +1 @@ +This is a C library and a command line tool to make a QR Code. diff --git a/support/sg2002/kvm_system/main/lib/libqr/TODO b/support/sg2002/kvm_system/main/lib/libqr/TODO new file mode 100644 index 0000000..c649aa2 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/TODO @@ -0,0 +1,8 @@ +TODO: +- test on Win32 +- test on Cygwin (if I could) +- write tests +- write manpage of qr(1), qrs(1) and libqr(3) +- localization support by ICU4C +- pkg-config (.pc file) support +- rcfile support for command line tool diff --git a/support/sg2002/kvm_system/main/lib/libqr/crc.h b/support/sg2002/kvm_system/main/lib/libqr/crc.h new file mode 100644 index 0000000..e1983b7 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/crc.h @@ -0,0 +1,63 @@ +/* + * This code is taken from: + * PNG (Portable Network Graphics) Specification, Version 1.1 + * 15. Appendix: Sample CRC Code + * http://www.libpng.org/pub/png/spec/1.1/PNG-CRCAppendix.html + */ + +#ifdef uint32_t +typedef uint32_t crc_t; +#else +typedef unsigned long crc_t; +#endif + +/* Table of CRCs of all 8-bit messages. */ +static crc_t crc_table[256]; + +/* Flag: has the table been computed? Initially false. */ +static int crc_table_computed = 0; + +/* Make the table for a fast CRC. */ +static void make_crc_table(void) +{ + crc_t c; + int n, k; + + for (n = 0; n < 256; n++) { + c = (crc_t) n; + for (k = 0; k < 8; k++) { + if (c & 1) { + c = 0xedb88320L ^ (c >> 1); + } else { + c = c >> 1; + } + } + crc_table[n] = c; + } + crc_table_computed = 1; +} + +/* Update a running CRC with the bytes buf[0..len-1]--the CRC + should be initialized to all 1's, and the transmitted value + is the 1's complement of the final running CRC (see the + crc() routine below)). */ + +static crc_t update_crc(crc_t crc, const unsigned char *buf, int len) +{ + crc_t c = crc; + int n; + + if (!crc_table_computed) { + make_crc_table(); + } + for (n = 0; n < len; n++) { + c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); + } + return c; +} + +/* Return the CRC of the bytes buf[0..len-1]. */ +static crc_t crc(const unsigned char *buf, int len) +{ + return update_crc(0xffffffffL, buf, len) ^ 0xffffffffL; +} diff --git a/support/sg2002/kvm_system/main/lib/libqr/qr.c b/support/sg2002/kvm_system/main/lib/libqr/qr.c new file mode 100644 index 0000000..07ca444 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/qr.c @@ -0,0 +1,2611 @@ +/* + * QR Code Generator Library + * + * Core routines were originally written by Junn Ohta. + * Based on qr.c Version 0.1: 2004/4/3 (Public Domain) + * + * @package libqr + * @author Ryusuke SEKIYAMA + * @copyright 2006-2013 Ryusuke SEKIYAMA + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "qr.h" +#include "qr_util.h" +#include "qr_private.h" +#include "qr_dwtable.h" + +#define qrIsData(qr, i, j) (((qr)->symbol[i][j] & QR_MM_DATA) != 0) +#define qrIsFunc(qr, i, j) (((qr)->symbol[i][j] & QR_MM_FUNC) != 0) + +QR_API const char *(*qrGetCurrentFunctionName)(void) = NULL; + +/* + * ライブラリのバージョンを返す + */ +QR_API const char * +qrVersion(void) +{ + return LIBQR_VERSION; +} + +/* + * QRCodeオブジェクトを生成する + */ +QR_API QRCode * +qrInit(int version, int mode, int eclevel, int masktype, int *errcode) +{ + QRCode *qr = NULL; + + /* + * メモリを確保する + */ + qr = (QRCode *)calloc(1, sizeof(QRCode)); + if (qr == NULL) { + *errcode = QR_ERR_MEMORY_EXHAUSTED; + return NULL; + } + qr->dataword = (qr_byte_t *)calloc(1, QR_DWD_MAX); + qr->ecword = (qr_byte_t *)calloc(1, QR_ECW_MAX); + qr->codeword = (qr_byte_t *)calloc(1, QR_CWD_MAX); + if (qr->dataword == NULL || qr->ecword == NULL || qr->codeword == NULL) { + *errcode = QR_ERR_MEMORY_EXHAUSTED; + qrDestroy(qr); + return NULL; + } + + /* + * 内部状態を初期化する + */ + qr->_symbol = NULL; + qr->symbol = NULL; + qr->source = NULL; + qr->srcmax = 0; + qr->srclen = 0; + qr->enclen = 0; + qr->delta1 = 0; + qr->delta2 = 0; + qr->errcode = QR_ERR_NONE; + qr->state = QR_STATE_BEGIN; + + /* + * 型番を設定する + */ + if (version == -1 || (version >= 1 && version <= QR_VER_MAX)) { + qr->param.version = version; + } else { + *errcode = QR_ERR_INVALID_VERSION; + qrDestroy(qr); + return NULL; + } + + /* + * 符号化モードを設定する + */ + if (mode == QR_EM_AUTO || (mode >= QR_EM_NUMERIC && mode < QR_EM_COUNT)) { + qr->param.mode = mode; + } else { + *errcode = QR_ERR_INVALID_MODE; + qrDestroy(qr); + return NULL; + } + + /* + * 誤り訂正レベルを設定する + */ + if (eclevel >= QR_ECL_L && eclevel < QR_EM_COUNT) { + qr->param.eclevel = eclevel; + } else { + *errcode = QR_ERR_INVALID_ECL; + qrDestroy(qr); + return NULL; + } + + /* + * マスクパターンを設定する + */ + if (masktype == -1 || (masktype >= 0 && masktype < QR_MPT_MAX)) { + qr->param.masktype = masktype; + } else { + *errcode = QR_ERR_INVALID_MPT; + qrDestroy(qr); + return NULL; + } + + return qr; +} + +/* + * QRStructuredオブジェクトを生成する + */ +QR_API QRStructured * +qrsInit(int version, int mode, int eclevel, int masktype, int maxnum, int *errcode) +{ + QRStructured *st = NULL; + + /* + * メモリを確保する + */ + st = (QRStructured *)calloc(1, sizeof(QRStructured)); + if (st == NULL) { + *errcode = QR_ERR_MEMORY_EXHAUSTED; + return NULL; + } + + /* + * 内部状態を初期化する + */ + st->parity = 0; + st->state = QR_STATE_BEGIN; + + /* + * 最大シンボル数を設定する + */ + if (maxnum >= 2 && masktype <= QR_STA_MAX) { + st->max = maxnum; + } else { + *errcode = QR_ERR_INVALID_MAXNUM; + qrsDestroy(st); + return NULL; + } + + /* + * 型番を設定する + */ + if (version >= 1 && version <= QR_VER_MAX) { + st->param.version = version; + } else { + *errcode = QR_ERR_INVALID_VERSION; + qrsDestroy(st); + return NULL; + } + + /* + * 符号化モードを設定する + */ + if (mode == QR_EM_AUTO || (mode >= QR_EM_NUMERIC && mode < QR_EM_COUNT)) { + st->param.mode = mode; + } else { + *errcode = QR_ERR_INVALID_MODE; + qrsDestroy(st); + return NULL; + } + + /* + * 誤り訂正レベルを設定する + */ + if (eclevel >= QR_ECL_L && eclevel < QR_EM_COUNT) { + st->param.eclevel = eclevel; + } else { + *errcode = QR_ERR_INVALID_ECL; + qrsDestroy(st); + return NULL; + } + + /* + * マスクパターンを設定する + */ + if (masktype == -1 || (masktype >= 0 && masktype < QR_MPT_MAX)) { + st->param.masktype = masktype; + } else { + *errcode = QR_ERR_INVALID_MPT; + qrsDestroy(st); + return NULL; + } + + /* + * 一つめのQRコードオブジェクトを初期化する + */ + st->qrs[0] = qrInit(st->param.version, st->param.mode, + st->param.eclevel, st->param.masktype, errcode); + if (st->qrs[0] == NULL) { + qrsDestroy(st); + return NULL; + } + st->cur = st->qrs[0]; + st->num = 1; + + return st; +} + +/* + * QRCodeオブジェクトを複製する + */ +QR_API QRCode * +qrClone(const QRCode *qr, int *errcode) +{ + QRCode *cp = NULL; + + /* + * QRCodeオブジェクト用のメモリを確保し、複製する + */ + cp = (QRCode *)malloc(sizeof(QRCode)); + if (cp == NULL) { + *errcode = QR_ERR_MEMORY_EXHAUSTED; + return NULL; + } + memcpy(cp, qr, sizeof(QRCode)); + + /* + * 動的に確保されるメンバをいったんNULLにする + */ + cp->dataword = NULL; + cp->ecword = NULL; + cp->codeword = NULL; + cp->_symbol = NULL; + cp->symbol = NULL; + cp->source = NULL; + + /* + * ファイナライズ後ならシンボル、ファイナライズ前なら計算用領域を複製 + */ + if (cp->state == QR_STATE_FINAL) { + int i, dim; + + dim = qr_vertable[cp->param.version].dimension; + + cp->_symbol = (qr_byte_t *)calloc((size_t)dim, (size_t)dim); + if (cp->_symbol == NULL) { + *errcode = QR_ERR_MEMORY_EXHAUSTED; + qrDestroy(cp); + return NULL; + } + memcpy(cp->_symbol, qr->_symbol, (size_t)(dim * dim)); + + cp->symbol = (qr_byte_t **)malloc(sizeof(qr_byte_t *) * (size_t)dim); + if (cp->symbol == NULL) { + *errcode = QR_ERR_MEMORY_EXHAUSTED; + qrDestroy(cp); + return NULL; + } + for (i = 0; i < dim; i++) { + cp->symbol[i] = cp->_symbol + dim * i; + } + } else { + cp->dataword = (qr_byte_t *)malloc(QR_DWD_MAX); + cp->ecword = (qr_byte_t *)malloc(QR_ECW_MAX); + cp->codeword = (qr_byte_t *)malloc(QR_CWD_MAX); + if (cp->dataword == NULL || cp->ecword == NULL || cp->codeword == NULL) { + *errcode = QR_ERR_MEMORY_EXHAUSTED; + qrDestroy(cp); + return NULL; + } + memcpy(cp->dataword, qr->dataword, QR_DWD_MAX); + memcpy(cp->ecword , qr->ecword , QR_ECW_MAX); + memcpy(cp->codeword, qr->codeword, QR_CWD_MAX); + } + + /* + * 入力データを複製 + */ + if (cp->srcmax > 0 && qr->source != NULL) { + cp->source = (qr_byte_t *)malloc(cp->srcmax); + if (cp->source == NULL) { + *errcode = QR_ERR_MEMORY_EXHAUSTED; + qrDestroy(cp); + return NULL; + } + memcpy(cp->source, qr->source, cp->srclen); + } + + return cp; +} + +/* + * QRStructuredオブジェクトを複製する + */ +QR_API QRStructured * +qrsClone(const QRStructured *st, int *errcode) +{ + QRStructured *cps = NULL; + int i = 0; + + /* + * QRStructuredオブジェクト用のメモリを確保し、複製する + */ + cps = (QRStructured *)malloc(sizeof(QRStructured)); + if (cps == NULL) { + *errcode = QR_ERR_MEMORY_EXHAUSTED; + return NULL; + } + memcpy(cps, st, sizeof(QRStructured)); + + /* + * 保持しているQRCodeオブジェクトを複製する + */ + while (i < cps->num) { + QRCode *cp = qrClone(st->qrs[i], errcode); + if (cp == NULL) { + while (i > 0) { + qrDestroy(cps->qrs[--i]); + free(cps); + } + return NULL; + } + cps->qrs[i++] = cp; + } + while (i < QR_STA_MAX) { + cps->qrs[i++] = NULL; + } + cps->cur = cps->qrs[0] + (st->cur - st->qrs[0]); + + return cps; +} + +/* + * QRCodeオブジェクトを開放する + */ +QR_API void +qrDestroy(QRCode *qr) +{ + if (qr == NULL) { + return; + } + qrFree(qr->source); + qrFree(qr->dataword); + qrFree(qr->ecword); + qrFree(qr->codeword); + qrFree(qr->symbol); + qrFree(qr->_symbol); + free(qr); +} + +/* + * QRStructuredオブジェクトを開放する + */ +QR_API void +qrsDestroy(QRStructured *st) +{ + int i; + if (st == NULL) { + return; + } + for (i = 0; i < st->num; i++) { + qrDestroy(st->qrs[i]); + } + free(st); +} + +/* + * 出力形式に対応するMIMEタイプを返す + */ +QR_API const char * +qrMimeType(int format) +{ + switch (format) { + case QR_FMT_PNG: return "image/png"; + case QR_FMT_BMP: return "image/bmp"; + case QR_FMT_TIFF: return "image/tiff"; + case QR_FMT_PBM: return "image/x-portable-bitmap"; + case QR_FMT_SVG: return "image/svg+xml"; + case QR_FMT_JSON: return "application/json"; + case QR_FMT_DIGIT: return "text/plain"; + case QR_FMT_ASCII: return "text/plain"; + default: return NULL; + } +} + +/* + * 出力形式に対応する拡張子 (ピリオドなし) を返す + */ +QR_API const char * +qrExtension(int format) +{ + switch (format) { + case QR_FMT_PNG: return "png"; + case QR_FMT_BMP: return "bmp"; + case QR_FMT_TIFF: return "tiff"; + case QR_FMT_PBM: return "pbm"; + case QR_FMT_SVG: return "svg"; + case QR_FMT_JSON: return "json"; + case QR_FMT_DIGIT: return "txt"; + case QR_FMT_ASCII: return "txt"; + default: return NULL; + } +} + +/* + * QRコードオブジェクトに登録されているエラー番号を返す + */ +QR_API int +qrGetErrorCode(QRCode *qr) +{ + return qr->errcode; +} + +/* + * QRコードオブジェクトに登録されているエラー情報を返す + */ +QR_API char * +qrGetErrorInfo(QRCode *qr) +{ + return &(qr->errinfo[0]); +} + +/* + * 構造的連接の最後のQRコードオブジェクトに登録されているエラー番号を返す + */ +QR_API int +qrsGetErrorCode(QRStructured *st) +{ + return st->cur->errcode; +} + +/* + * 構造的連接の最後のQRコードオブジェクトに登録されているエラー情報を返す + */ +QR_API char * +qrsGetErrorInfo(QRStructured *st) +{ + return &(st->cur->errinfo[0]); +} + +/* + * エラー番号に対応したエラー情報を返す + */ +QR_API const char * +qrStrError(int errcode) +{ + switch (errcode) { + /* wide use errors */ + case QR_ERR_NONE: + case QR_ERR_USAGE: + return ""; + + case QR_ERR_SEE_ERRNO: + return "For more information, check for errno"; + + case QR_ERR_NOT_IMPL: + return "Not yet implemented"; + + case QR_ERR_STATE: + return "Not allowed in the current state"; + + case QR_ERR_FOPEN: + return "Failed to open file"; + + case QR_ERR_FREAD: + return "Failed to read data"; + + case QR_ERR_FWRITE: + return "Failed to write data"; + + case QR_ERR_MEMORY_EXHAUSTED: + return "Memory exhausted"; + + /* invalid parameter errors */ + case QR_ERR_INVALID_ARG: + return "Invalid argument"; + + case QR_ERR_INVALID_VERSION: + return "Invalid version number"; + + case QR_ERR_INVALID_MODE: + return "Invalid encoding mode"; + + case QR_ERR_INVALID_ECL: + return "Invalid error correction level"; + + case QR_ERR_INVALID_MPT: + return "Invalid mask pattern type"; + + case QR_ERR_INVALID_MAG: + return "Invalid pixel magnifying ratio"; + + case QR_ERR_INVALID_SEP: + return "Invalid separator width"; + + case QR_ERR_INVALID_SIZE: + return "Invalid output size"; + + case QR_ERR_INVALID_FMT: + return "Invalid output format"; + + case QR_ERR_INVALID_OUT: + return "Invalid output pathname"; + + case QR_ERR_INVALID_MAXNUM: + return "Invalid maximum symbol number"; + + case QR_ERR_UNSUPPORTED_FMT: + return "Unsupported output format"; + + case QR_ERR_EMPTY_PARAM: + return "Parameter required"; + + /* input data size/type errors */ + case QR_ERR_EMPTY_SRC: + return "Input data is empty"; + + case QR_ERR_LARGE_SRC: + return "Input data too large"; + + case QR_ERR_NOT_NUMERIC: + return "Non decimal characters found"; + + case QR_ERR_NOT_ALNUM: + return "Non alphanumeric characters found"; + + case QR_ERR_NOT_KANJI: + return "Non JIS X 0208 kanji sequence found"; + + /* imaging related errors */ + case QR_ERR_IMAGE_TOO_LARGE: + return "Output image size too large"; + + case QR_ERR_WIDTH_TOO_LARGE: + return "Output image width too large"; + + case QR_ERR_HEIGHT_TOO_LARGE: + return "Output image height too large"; + + case QR_ERR_IMAGECREATE: + return "Failed to create image"; + + case QR_ERR_IMAGEFORMAT: + return "Failed to convert image"; + + case QR_ERR_IMAGEFRAME: + return "Failed to create frame"; + + /* zlib related errors */ + case QR_ERR_DEFLATE: + return "Failed to deflate"; + + /* unknown error(s) */ + case QR_ERR_UNKNOWN: + default: + return "Unknown error"; + } +} + +/* + * libqrのエラー番号からエラー情報を設定する + */ +QR_API void +qrSetErrorInfo(QRCode *qr, int errnum, const char *param) +{ + qr->errcode = errnum; + if (param != NULL) { + snprintf(&(qr->errinfo[0]), QR_ERR_MAX, "%s: %s", param, qrStrError(errnum)); + } else { + snprintf(&(qr->errinfo[0]), QR_ERR_MAX, "%s", qrStrError(errnum)); + } +} + +/* + * システム標準のエラー番号からエラー情報を設定する + */ +QR_API void +qrSetErrorInfo2(QRCode *qr, int errnum, const char *param) +{ + char *info; + int size = 0; + info = &(qr->errinfo[0]); + qr->errcode = QR_ERR_SEE_ERRNO; + if (param != NULL) { + size = snprintf(info, QR_ERR_MAX, "%s: ", param); + if (size < 0 || size >= QR_ERR_MAX) { + return; + } + info += size; + } +#ifdef WIN32 + snprintf(info, (size_t)(QR_ERR_MAX - size), "%s", strerror(errnum)); +#else + strerror_r(errnum, info, (size_t)(QR_ERR_MAX - size)); +#endif +} + +/* + * libqrのエラー番号と可変長パラメータからエラー情報を設定する + */ +QR_API void +qrSetErrorInfo3(QRCode *qr, int errnum, const char *fmt, ...) +{ + char info[QR_ERR_MAX]; + va_list ap; + + qr->errcode = errnum; + va_start(ap, fmt); + vsnprintf(&(info[0]), QR_ERR_MAX, fmt, ap); + va_end(ap); + snprintf(&(qr->errinfo[0]), QR_ERR_MAX, "%s%s", qrStrError(errnum), info); +} + +/* + * 最適な符号化方法を調べる + */ +QR_API int +qrDetectDataType(const qr_byte_t *source, int size) +{ + if (qrStrPosNotNumeric(source, size) == -1) { + return QR_EM_NUMERIC; + } + if (qrStrPosNotAlnum(source, size) == -1) { + return QR_EM_ALNUM; + } + if (qrStrPosNotKanji(source, size) == -1) { + return QR_EM_KANJI; + } + return QR_EM_8BIT; +} + +/* + * 数字以外のデータが現れる位置を調べる + */ +QR_API int +qrStrPosNotNumeric(const qr_byte_t *source, int size) +{ + int p = 0; + + while (p < size) { + if (source[p] < '0' || source[p] > '9') { + return p; + } + p++; + } + return -1; +} + +/* + * 英数字以外のデータが現れる位置を調べる + */ +QR_API int +qrStrPosNotAlnum(const qr_byte_t *source, int size) +{ + int p = 0; + + while (p < size) { + if (qr_alnumtable[source[p]] == -1) { + return p; + } + p++; + } + return -1; +} + +/* + * JIS X 0208漢字以外のデータが現れる位置を調べる + */ +QR_API int +qrStrPosNotKanji(const qr_byte_t *source, int size) +{ + qr_byte_t x, y; + int p = 0; + + while (p < size - 1) { + x = source[p++]; + if (x >= 0x81 && x <= 0x9f) { + x -= 0x81; + } else if (x >= 0xe0 && x <= 0xea) { + x -= 0xc1; + } else { + /* JIS X 0208漢字の1バイトめでない */ + return p - 1; + } + y = source[p++]; + if (y >= 0x40 && y <= 0xfc) { + y -= 0x40; + } else { + /* JIS X 0208漢字の2バイトめでない */ + return p - 1; + } + if (qr_dwtable_kanji[x][y] == -1) { + /* JIS X 0208漢字の未定義領域 */ + return p - 2; + } + } + if (p < size) { + return p; + } + return -1; +} + +/* + * 英数字もしくはJIS X 0208漢字のデータが現れる位置を調べる + */ +QR_API int +qrStrPosNot8bit(const qr_byte_t *source, int size) +{ + qr_byte_t x, y; + int p = 0; + + while (p < size) { + x = source[p++]; + if (qr_alnumtable[x] != -1) { + return p - 1; + } + if (p < size && ((x >= 0x81 && x <= 0x9f) || (x >= 0xe0 && x <= 0xea))) { + if (x < 0xa0) { + x -= 0x81; + } else { + x -= 0xc1; + } + y = source[p]; + if (y >= 0x40 && y <= 0xfc && qr_dwtable_kanji[x][y - 0x40] != -1) { + return p - 1; + } + } + } + return -1; +} + +/* + * デフォルトの符号化モードでsizeバイト符号化したときのビット長を返す + */ +QR_API int +qrGetEncodedLength(QRCode *qr, int size) +{ + return qrGetEncodedLength2(qr, size, qr->param.mode); +} + +/* + * 特定の符号化モードでsizeバイト符号化したときのビット長を返す + */ +QR_API int +qrGetEncodedLength2(QRCode *qr, int size, int mode) +{ + int n, v; + + /* + * モード指示子と文字数指示子のサイズ + */ + v = (qr->param.version == -1) ? QR_VER_MAX : qr->param.version; + n = 4 + qr_vertable[v].nlen[mode]; + + /* + * 符号化モードごとのデータサイズ + */ + switch (mode) { + case QR_EM_NUMERIC: + /* + * 数字モード: 3桁ごとに10ビット + * (余りは1桁なら4ビット, 2桁なら7ビット) + */ + n += (size / 3) * 10; + switch (size % 3) { + case 1: + n += 4; + break; + case 2: + n += 7; + break; + } + break; + case QR_EM_ALNUM: + /* + * 英数字モード: 2桁ごとに11ビット + * (余りは1桁につき6ビット) + */ + n += (size / 2) * 11; + if (size % 2 == 1) { + n += 6; + } + break; + case QR_EM_8BIT: + /* + * 8ビットバイトモード: 1桁ごとに8ビット + */ + n += size * 8; + break; + case QR_EM_KANJI: + /* + * 漢字モード: 1文字(2バイト)ごとに13ビット + */ + n += (size / 2) * 13; + break; + default: + qrSetErrorInfo(qr, QR_ERR_INVALID_MODE, NULL); + return -1; + } + + return n; +} + +/* + * デフォルトの符号化モードでsizeビットを上限として符号化可能な最大のバイト長を返す + */ +QR_API int +qrGetEncodableLength(QRCode *qr, int size) +{ + return qrGetEncodableLength2(qr, size, qr->param.mode); +} + +/* + * 特定の符号化モードでsizeビットを上限として符号化可能な最大のバイト長を返す + */ +QR_API int +qrGetEncodableLength2(QRCode *qr, int size, int mode) +{ + int l, m, n, v; + + /* + * モード指示子と文字数指示子のサイズ + */ + v = (qr->param.version == -1) ? QR_VER_MAX : qr->param.version; + n = size - 4 - qr_vertable[v].nlen[mode]; + if (n <= 0) { + return 0; + } + + /* + * 符号化モードごとのデータサイズ + */ + switch (mode) { + case QR_EM_NUMERIC: + /* + * 数字モード: 3桁ごとに10ビット + * (余りは1桁なら4ビット, 2桁なら7ビット) + */ + l = (n / 10) * 3; + m = n % 10; + if (m >= 7) { + l += 2; + } else if (m >= 4) { + l += 1; + } + break; + case QR_EM_ALNUM: + /* + * 英数字モード: 2桁ごとに11ビット + * (余りは1桁につき6ビット) + */ + l = (n / 11) * 2; + m = n % 11; + if (m >= 6) { + l += 1; + } + break; + case QR_EM_8BIT: + /* + * 8ビットバイトモード: 1桁ごとに8ビット + */ + l = n / 8; + break; + case QR_EM_KANJI: + /* + * 漢字モード: 1文字(2バイト)ごとに13ビット + */ + l = (n / 13) * 2; + break; + default: + qrSetErrorInfo(qr, QR_ERR_INVALID_MODE, NULL); + return -1; + } + + return l; +} + +/* + * データを追加する + */ +QR_API int +qrAddData(QRCode *qr, const qr_byte_t *source, int size) +{ + if (qr->state == QR_STATE_FINAL) { + qrSetErrorInfo(qr, QR_ERR_STATE, _QR_FUNCTION); + return FALSE; + } + return qrAddData2(qr, source, size, qr->param.mode); +} + +/* + * 符号化モードを指定してデータを追加する + */ +QR_API int +qrAddData2(QRCode *qr, const qr_byte_t *source, int size, int mode) +{ + int enclen, maxlen; + int version; + int pos, err; + + if (qr->state == QR_STATE_FINAL) { + qrSetErrorInfo(qr, QR_ERR_STATE, _QR_FUNCTION); + return FALSE; + } + + if (size <= 0) { + qrSetErrorInfo(qr, QR_ERR_EMPTY_SRC, NULL); + return FALSE; + } + + /* + * 入力データに最適な符号化モードを選ぶ + */ + if (mode == QR_EM_AUTO) { + mode = qrDetectDataType(source, size); + } else if (mode < QR_EM_NUMERIC || mode >= QR_EM_COUNT) { + qrSetErrorInfo(qr, QR_ERR_INVALID_MODE, NULL); + return FALSE; + } + + + /* + * 符号化後のデータ長を計算する + */ + enclen = qrGetEncodedLength2(qr, size, mode); + if (enclen == -1) { + return FALSE; + } + version = (qr->param.version == -1) ? QR_VER_MAX : qr->param.version; + maxlen = 8 * qr_vertable[version].ecl[qr->param.eclevel].datawords; + if (qr->enclen + enclen > maxlen) { + qrSetErrorInfo3(qr, QR_ERR_LARGE_SRC, ", %d total encoded bits" + " (max %d bits on version=%d, ecl=%s)", + qr->enclen + enclen, maxlen, version, qr_eclname[qr->param.eclevel]); + return FALSE; + } + if (qr->param.version == -1) { + qr->delta1 += qr_vertable[QR_VER_MAX].nlen[mode] - qr_vertable[VERPOINT1].nlen[mode]; + qr->delta2 += qr_vertable[QR_VER_MAX].nlen[mode] - qr_vertable[VERPOINT2].nlen[mode]; + } + + /* + * 型番が指定されていれば、入力データをバッファリングせず直接エンコードする + */ + if (qr->param.version != -1) { + qr->enclen += enclen; + if (!qrHasData(qr)) { + qrInitDataWord(qr); + } + if (qrEncodeDataWord(qr, source, size, mode) == TRUE) { + qr->state = QR_STATE_SET; + return TRUE; + } + return FALSE; + } + + /* + * 入力データを検証する + */ + pos = -1; + err = QR_ERR_NONE; + switch (mode) { + case QR_EM_NUMERIC: + pos = qrStrPosNotNumeric(source, size); + err = QR_ERR_NOT_NUMERIC; + break; + case QR_EM_ALNUM: + pos = qrStrPosNotAlnum(source, size); + err = QR_ERR_NOT_ALNUM; + break; + case QR_EM_KANJI: + pos = qrStrPosNotKanji(source, size); + err = QR_ERR_NOT_KANJI; + break; + } + if (pos != -1) { + qrSetErrorInfo3(qr, err, " at offset %d", pos); + return FALSE; + } + qr->enclen += enclen; + + /* + * バッファの容量が足りないときは追加で確保する + */ + while (qr->srcmax < qr->srclen + size + 6) { + qr->srcmax += QR_SRC_MAX; + qr->source = (qr_byte_t *)realloc(qr->source, qr->srcmax); + if (qr->source == NULL) { + qr->srcmax = 0; + qrSetErrorInfo2(qr, QR_ERR_MEMORY_EXHAUSTED, _QR_FUNCTION); + return FALSE; + } + } + + /* + * バッファにデータを保存する + */ + qr->source[qr->srclen++] = (qr_byte_t)(mode | 0x80); + qr->source[qr->srclen++] = (qr_byte_t)((size >> 24) & 0x7F); + qr->source[qr->srclen++] = (qr_byte_t)((size >> 16) & 0xFF); + qr->source[qr->srclen++] = (qr_byte_t)((size >> 8) & 0xFF); + qr->source[qr->srclen++] = (qr_byte_t)(size & 0xFF); + memcpy(&(qr->source[qr->srclen]), source, (size_t)size); + qr->srclen += size; + qr->source[qr->srclen] = '\0'; + + qr->state = QR_STATE_SET; + return TRUE; +} + +/* + * 構造的連接の最後のQRコードオブジェクトにデータを追加する + */ +QR_API int +qrsAddData(QRStructured *st, const qr_byte_t *source, int size) +{ + if (st->state == QR_STATE_FINAL) { + qrSetErrorInfo(st->cur, QR_ERR_STATE, _QR_FUNCTION); + return FALSE; + } + return qrsAddData2(st, source, size, st->param.mode); +} + +/* + * 構造的連接の最後のQRコードオブジェクトに符号化モードを指定してデータを追加する + */ +QR_API int +qrsAddData2(QRStructured *st, const qr_byte_t *source, int size, int mode) +{ + int enclen, maxlen, limit, remain; + int sizes[QR_STA_MAX] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int i, j; + int p; + + if (st->state == QR_STATE_FINAL) { + qrSetErrorInfo(st->cur, QR_ERR_STATE, _QR_FUNCTION); + return FALSE; + } + + if (size <= 0) { + qrSetErrorInfo(st->cur, QR_ERR_EMPTY_SRC, NULL); + return FALSE; + } + + /* + * 入力データに最適な符号化モードを選ぶ + */ + if (mode == QR_EM_AUTO) { + mode = qrDetectDataType(source, size); + } + + /* + * 残りデータ容量を計算する + */ + maxlen = 8 * qr_vertable[st->param.version].ecl[st->param.eclevel].datawords; + limit = maxlen - QR_STA_LEN; + if (!qrHasData(st->cur)) { + remain = limit; + } else { + remain = qrRemainedDataBits(st->cur); + } + + /* + * 符号化後のデータ長を計算する + */ + enclen = qrGetEncodedLength2(st->cur, size, mode); + if (enclen == -1) { + return FALSE; + } + j = 0; + if (enclen > remain) { + int l, r, s; + r = remain; + s = size; + for (i = 0; i <= st->max - st->num; i++) { + j++; + l = qrGetEncodableLength2(st->cur, r, mode); + if (s <= l) { + sizes[i] = s; + s = 0; + break; + } else { + sizes[i] = l; + s -= l; + r = limit; + } + } + if (s > 0) { + int snum, reqlen; + snum = (enclen + maxlen - 1) / maxlen - (st->num - 1); + reqlen = maxlen * (st->num - 1); + reqlen += maxlen - remain; + reqlen += enclen; + reqlen += qr_vertable[st->param.version].nlen[mode] * (snum - 1) + QR_STA_LEN * snum; + qrSetErrorInfo3(st->cur, QR_ERR_LARGE_SRC, ", %d total encoded bits" + " (max %d bits on version=%d, ecl=%s, num=%d)", + reqlen, maxlen * st->max, + st->param.version, qr_eclname[st->param.eclevel], st->max); + return FALSE; + } + } else { + j = 1; + sizes[0] = size; + } + + /* + * 入力データをエンコードする + */ + p = 0; + i = 0; + while (i < j) { + if (sizes[i] == 0 && i != 0) { + break; + } + if (!qrHasData(st->cur)) { + /* + * データコード語を初期化し、仮の構造的連接ヘッダを追加する + */ + qrInitDataWord(st->cur); + qrAddDataBits(st->cur, 4, 0); + qrAddDataBits(st->cur, 4, 0); + qrAddDataBits(st->cur, 4, 0); + qrAddDataBits(st->cur, 8, 0); + } + if (sizes[i] != 0) { + st->cur->enclen += qrGetEncodedLength2(st->cur, sizes[i], mode); + if (qrEncodeDataWord(st->cur, source + p, sizes[i], mode) == TRUE) { + st->cur->state = QR_STATE_SET; + st->state = QR_STATE_SET; + } else { + return FALSE; + } + p += sizes[i]; + } + i++; + if (i < j && sizes[i] > 0) { + /* + * 次のQRコードオブジェクトを初期化する + */ + int errcode; + st->qrs[st->num] = qrInit(st->param.version, st->param.mode, + st->param.eclevel, st->param.masktype, &errcode); + if (st->qrs[st->num] == NULL) { + qrSetErrorInfo(st->cur, errcode, NULL); + return FALSE; + } + st->cur = st->qrs[st->num]; + st->num++; + } + } + + /* + * パリティを計算する + */ + p = 0; + while (p < size) { + st->parity ^= source[p++]; + } + + return TRUE; +} + +/* + * データコード語を初期化する + */ +static int +qrInitDataWord(QRCode *qr) +{ + /* + * データコード語領域をゼロクリアする + */ + memset(qr->dataword, '\0', QR_DWD_MAX); + + /* + * 追加位置をバイト0の最上位ビットにする + */ + qr->dwpos = 0; + qr->dwbit = 7; + + return TRUE; +} + +/* + * データコード語をエンコードする + */ +static int +qrEncodeDataWord(QRCode *qr, const qr_byte_t *source, int size, int mode) +{ + int p = 0; + int e = QR_ERR_NONE; + int n = 0; + int word = 0; + int dwpos = qr->dwbit; + int dwbit = qr->dwpos; + + if (mode < QR_EM_NUMERIC || mode >= QR_EM_COUNT) { + e = QR_ERR_INVALID_MODE; + goto err; + } + + /* + * モード指示子(4ビット)を追加する + */ + qrAddDataBits(qr, 4, qr_modeid[mode]); + + /* + * 文字数指示子(8〜16ビット)を追加する + * ビット数は型番とモードによって異なる + */ + if (mode == QR_EM_KANJI) { + qrAddDataBits(qr, qr_vertable[qr->param.version].nlen[mode], size / 2); + } else { + qrAddDataBits(qr, qr_vertable[qr->param.version].nlen[mode], size); + } + + /* + * 入力データを符号化する + */ + switch (mode) { + case QR_EM_NUMERIC: + /* + * 数字モード + * 3桁ずつ10ビットの2進数に変換する + * 余りは1桁なら4ビット、2桁なら7ビットにする + */ + while (p < size) { + qr_byte_t q = source[p]; + if (q < '0' || q > '9') { + /* 数字でない */ + e = QR_ERR_NOT_NUMERIC; + goto err; + } + word = word * 10 + (q - '0'); + /* + * 3桁たまったら10ビットで追加する + */ + if (++n >= 3) { + qrAddDataBits(qr, 10, word); + n = 0; + word = 0; + } + p++; + } + /* + * 余りの桁を追加する + */ + if (n == 1) { + qrAddDataBits(qr, 4, word); + } else if (n == 2) { + qrAddDataBits(qr, 7, word); + } + break; + + case QR_EM_ALNUM: + /* + * 英数字モード + * 2桁ずつ11ビットの2進数に変換する + * 余りは6ビットとして変換する + */ + while (p < size) { + signed char q = qr_alnumtable[source[p]]; + if (q == -1) { + /* 符号化可能な英数字でない */ + e = QR_ERR_NOT_ALNUM; + goto err; + } + word = word * 45 + (int)q; + /* + * 2桁たまったら11ビットで追加する + */ + if (++n >= 2) { + qrAddDataBits(qr, 11, word); + n = 0; + word = 0; + } + p++; + } + /* + * 余りの桁を追加する + */ + if (n == 1) { + qrAddDataBits(qr, 6, word); + } + break; + + case QR_EM_8BIT: + /* + * 8ビットバイトモード + * 各バイトを直接8ビット値として追加する + */ + while (p < size) { + qrAddDataBits(qr, 8, (int)source[p++]); + } + break; + + case QR_EM_KANJI: + /* + * 漢字モード + * 2バイトを13ビットに変換して追加する + */ + while (p < size - 1) { + qr_byte_t x, y; + /* + * 第1バイトの処理 + * 0x81-0x9fなら0x81を引く + * 0xe0,0xeaなら0xc1を引く + */ + x = source[p++]; + if (x >= 0x81 && x <= 0x9f) { + x -= 0x81; + } else if (x >= 0xe0 && x <= 0xea) { + x -= 0xc1; + } else { + /* JIS X 0208漢字の1バイトめでない */ + p -= 1; + e = QR_ERR_NOT_KANJI; + goto err; + } + /* + * 第2バイトの処理 + * 0x40を引く + */ + y = source[p++]; + if (y >= 0x40 && y <= 0xfc) { + y -= 0x40; + } else { + /* JIS X 0208漢字の2バイトめでない */ + p -= 1; + e = QR_ERR_NOT_KANJI; + goto err; + } + /* + * 結果を13ビットの値として追加する + */ + word = (int)qr_dwtable_kanji[x][y]; + if (word == -1) { + /* JIS X 0208漢字の未定義領域 */ + p -= 2; + e = QR_ERR_NOT_KANJI; + goto err; + } + qrAddDataBits(qr, 13, word); + } + if (p < size) { + /* + * 末尾に余分なバイトがある + */ + e = QR_ERR_NOT_KANJI; + goto err; + } + break; + + default: + e = QR_ERR_INVALID_MODE; + goto err; + } + + return TRUE; + + err: + qr->dwpos = dwpos; + qr->dwbit = dwbit; + if (e == QR_ERR_INVALID_MODE) { + qrSetErrorInfo(qr, e, NULL); + } else { + qrSetErrorInfo3(qr, e, " at offset %d", p); + } + return FALSE; +} + +/* + * データコード語の余りを埋める + */ +static int +qrFinalizeDataWord(QRCode *qr) +{ + int n, m; + int word; + + /* + * 終端パターンを追加する(最大4ビットの0) + */ + n = qrRemainedDataBits(qr); + if (n < 4) { + qrAddDataBits(qr, n, 0); + n = 0; + } else { + qrAddDataBits(qr, 4, 0); + n -= 4; + } + /* + * 末尾のデータコード語の全ビットが埋まっていなければ + * 余りを埋め草ビット(0)で埋める + */ + m = n % 8; + if (m > 0) { + qrAddDataBits(qr, m, 0); + n -= m; + } + + /* + * 残りのデータコード語に埋め草コード語1,2を交互に埋める + */ + word = PADWORD1; + while (n >= 8) { + qrAddDataBits(qr, 8, word); + if (word == PADWORD1) { + word = PADWORD2; + } else { + word = PADWORD1; + } + n -= 8; + } + + return TRUE; +} + +/* + * データコード語にビット列を追加する + */ +static void +qrAddDataBits(QRCode *qr, int n, int word) +{ + /* + * 上位ビットから順に処理(ビット単位で処理するので遅い) + */ + while (n-- > 0) { + /* + * ビット追加位置にデータの下位からnビットめをORする + */ + qr->dataword[qr->dwpos] |= ((word >> n) & 1) << qr->dwbit; + /* + * 次のビット追加位置に進む + */ + if (--qr->dwbit < 0) { + qr->dwpos++; + qr->dwbit = 7; + } + } +} + +/* + * データコード語の残りビット数を返す + */ +QR_API int +qrRemainedDataBits(QRCode *qr) +{ + int version; + version = (qr->param.version == -1) ? QR_VER_MAX : qr->param.version; + return (qr_vertable[version].ecl[qr->param.eclevel].datawords - qr->dwpos) * 8 - (7 - qr->dwbit); +} + +/* + * RSブロックごとに誤り訂正コード語を計算する + */ +static int +qrComputeECWord(QRCode *qr) +{ + int i, j, k, m; + int ecwtop, dwtop, nrsb, rsbnum; + qr_byte_t rswork[QR_RSD_MAX]; + + /* + * データコード語をRSブロックごとに読み出し、 + * それぞれについて誤り訂正コード語を計算する + * RSブロックは長さによってnrsb種類に分かれ、 + * それぞれの長さについてrsbnum個のブロックがある + */ + dwtop = 0; + ecwtop = 0; + nrsb = qr_vertable[qr->param.version].ecl[qr->param.eclevel].nrsb; +#define rsb qr_vertable[qr->param.version].ecl[qr->param.eclevel].rsb +#define gfvector qr_gftable[ecwlen] + for (i = 0; i < nrsb; i++) { + int dwlen, ecwlen; + /*unsigned char *gfvector;*/ + /*qr_rsblock_t *rsbp;*/ + /* + * この長さのRSブロックの個数(rsbnum)と + * RSブロック内のデータコード語の長さ(dwlen)、 + * 誤り訂正コード語の長さ(ecwlen)を求める + * また誤り訂正コード語の長さから、使われる + * 誤り訂正生成多項式(gfvector)を選ぶ + */ + /*rsbp = &(qr_vertable[qr->param.version].ecl[qr->param.eclevel].rsb[i]); + rsbnum = rsbp->rsbnum; + dwlen = rsbp->datawords; + ecwlen = rsbp->totalwords - rsbp->datawords;*/ + rsbnum = rsb[i].rsbnum; + dwlen = rsb[i].datawords; + ecwlen = rsb[i].totalwords - rsb[i].datawords; + /*gfvector = qr_gftable[ecwlen];*/ + /* + * それぞれのRSブロックについてデータコード語を + * 誤り訂正生成多項式で除算し、結果を誤り訂正 + * コード語とする + */ + for (j = 0; j < rsbnum; j++) { + /* + * RS符号計算用作業領域をクリアし、 + * 当該RSブロックのデータコード語を + * 多項式係数とみなして作業領域に入れる + * (作業領域の大きさはRSブロックの + * データコード語と誤り訂正コード語の + * いずれか長いほうと同じだけ必要) + */ + memset(&(rswork[0]), '\0', QR_RSD_MAX); + memcpy(&(rswork[0]), &(qr->dataword[dwtop]), (size_t)dwlen); + /* + * 多項式の除算を行う + * (各次数についてデータコード語の初項係数から + * 誤り訂正生成多項式への乗数を求め、多項式 + * どうしの減算により剰余を求めることをくり返す) + */ + for (k = 0; k < dwlen; k++) { + int e; + if (rswork[0] == 0) { + /* + * 初項係数がゼロなので、各項係数を + * 左にシフトして次の次数に進む + */ + for (m = 0; m < QR_RSD_MAX-1; m++) { + rswork[m] = rswork[m+1]; + } + rswork[QR_RSD_MAX-1] = 0; + continue; + } + /* + * データコード語の初項係数(整数表現)から + * 誤り訂正生成多項式への乗数(べき表現)を求め、 + * 残りの各項について剰余を求めるために + * データコード語の各項係数を左にシフトする + */ + e = qr_fac2exp[rswork[0]]; + for (m = 0; m < QR_RSD_MAX-1; m++) { + rswork[m] = rswork[m+1]; + } + rswork[QR_RSD_MAX-1] = 0; + /* + * 誤り訂正生成多項式の各項係数に上で求めた + * 乗数を掛け(べき表現の加算により求める)、 + * データコード語の各項から引いて(整数表現の + * 排他的論理和により求める)、剰余を求める + */ + for (m = 0; m < ecwlen; m++) { + rswork[m] ^= qr_exp2fac[(gfvector[m] + e) % 255]; + } + } + /* + * 多項式除算の剰余を当該RSブロックの + * 誤り訂正コードとする + */ + memcpy(&(qr->ecword[ecwtop]), &(rswork[0]), (size_t)ecwlen); + /* + * データコード語の読み出し位置と + * 誤り訂正コード語の書き込み位置を + * 次のRSブロック開始位置に移動する + */ + dwtop += dwlen; + ecwtop += ecwlen; + } + } +#undef rsb +#undef gfvector + return TRUE; +} + +/* + * データコード語と誤り訂正コード語から最終的なコード語を作る + */ +static int +qrMakeCodeWord(QRCode *qr) +{ + int i, j, k, cwtop, pos; + int dwlenmax, ecwlenmax; + int dwlen, ecwlen, nrsb; + /*qr_rsblock_t *rsb;*/ + + /* + * RSブロックのサイズ種類数(nrsb)および + * 最大RSブロックのデータコード語数(dwlenmax)、 + * 誤り訂正コード語数(ecwlenmax)を得る + */ + nrsb = qr_vertable[qr->param.version].ecl[qr->param.eclevel].nrsb; + /*rsb = qr_vertable[qr->param.version].ecl[qr->param.eclevel].rsb;*/ +#define rsb qr_vertable[qr->param.version].ecl[qr->param.eclevel].rsb + dwlenmax = rsb[nrsb-1].datawords; + ecwlenmax = rsb[nrsb-1].totalwords - rsb[nrsb-1].datawords; + /* + * 各RSブロックから順にデータコード語を取り出し + * コード語領域(qr->codeword)に追加する + */ + cwtop = 0; + for (i = 0; i < dwlenmax; i++) { + pos = i; + /* + * RSブロックのサイズごとに処理を行う + */ + for (j = 0; j < nrsb; j++) { + dwlen = rsb[j].datawords; + /* + * 同じサイズのRSブロックは順に処理する + */ + for (k = 0; k < rsb[j].rsbnum; k++) { + /* + * 各RSブロックのiバイトめのデータ + * コード語をコード語領域に追加する + * (すでにすべてのデータコード語を + * 取り出したRSブロックは飛ばす) + */ + if (i < dwlen) { + qr->codeword[cwtop++] = qr->dataword[pos]; + } + /* + * 次のRSブロックのiバイトめに進む + */ + pos += dwlen; + } + } + } + /* + * 各RSブロックから順に誤り訂正コード語を取り出し + * コード語領域(qr->codeword)に追加する + */ + for (i = 0; i < ecwlenmax; i++) { + pos = i; + /* + * RSブロックのサイズごとに処理を行う + */ + for (j = 0; j < nrsb; j++) { + ecwlen = rsb[j].totalwords - rsb[j].datawords; + /* + * 同じサイズのRSブロックは順に処理する + */ + for (k = 0; k < rsb[j].rsbnum; k++) { + /* + * 各RSブロックのiバイトめの誤り訂正 + * コード語をコード語領域に追加する + * (すでにすべての誤り訂正コード語を + * 取り出したRSブロックは飛ばす) + */ + if (i < ecwlen) { + qr->codeword[cwtop++] = qr->ecword[pos]; + } + /* + * 次のRSブロックのiバイトめに進む + */ + pos += ecwlen; + } + } + } +#undef rsb +#undef nrsb + return TRUE; +} + +/* + * シンボルを初期化し、機能パターンを配置する + */ +static int +qrFillFunctionPattern(QRCode *qr) +{ + int i, j, n, dim, xpos, ypos; + + /* + * シンボルの1辺の長さを求める + */ + dim = qr_vertable[qr->param.version].dimension; + /* + * シンボル全体をクリアする + */ + qrFree(qr->symbol); + qrFree(qr->_symbol); + qr->_symbol = (qr_byte_t *)calloc((size_t)dim, (size_t)dim); + if (qr->_symbol == NULL) { + return FALSE; + } + qr->symbol = (qr_byte_t **)malloc(sizeof(qr_byte_t *) * (size_t)dim); + if (qr->symbol == NULL) { + free(qr->_symbol); + return FALSE; + } + for (i = 0; i < dim; i++) { + qr->symbol[i] = qr->_symbol + dim * i; + } + /* + * 左上、右上、左下の隅に位置検出パターンを配置する + */ + for (i = 0; i < QR_DIM_FINDER; i++) { + for (j = 0; j < QR_DIM_FINDER; j++) { + qr->symbol[i][j] = qr_finderpattern[i][j]; + qr->symbol[i][dim-1-j] = qr_finderpattern[i][j]; + qr->symbol[dim-1-i][j] = qr_finderpattern[i][j]; + } + } + /* + * 位置検出パターンの分離パターンを配置する + */ + for (i = 0; i < QR_DIM_FINDER+1; i++) { + qr->symbol[i][QR_DIM_FINDER] = QR_MM_FUNC; + qr->symbol[QR_DIM_FINDER][i] = QR_MM_FUNC; + qr->symbol[i][dim-1-QR_DIM_FINDER] = QR_MM_FUNC; + qr->symbol[dim-1-QR_DIM_FINDER][i] = QR_MM_FUNC; + qr->symbol[dim-1-i][QR_DIM_FINDER] = QR_MM_FUNC; + qr->symbol[QR_DIM_FINDER][dim-1-i] = QR_MM_FUNC; + } + /* + * 位置合わせパターンを配置する + */ + n = qr_vertable[qr->param.version].aplnum; + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + int x, y, x0, y0, xcenter, ycenter; + /* + * 位置合わせパターンの中心と左上の座標を求める + */ + ycenter = qr_vertable[qr->param.version].aploc[i]; + xcenter = qr_vertable[qr->param.version].aploc[j]; + y0 = ycenter - QR_DIM_ALIGN / 2; + x0 = xcenter - QR_DIM_ALIGN / 2; + if (qrIsFunc(qr, ycenter, xcenter)) { + /* + * 位置検出パターンと重なるときは配置しない + */ + continue; + } + for (y = 0; y < QR_DIM_ALIGN; y++) { + for (x = 0; x < QR_DIM_ALIGN; x++) { + qr->symbol[y0+y][x0+x] = qr_alignpattern[y][x]; + } + } + } + } + /* + * タイミングパターンを配置する + */ + for (i = QR_DIM_FINDER; i < dim-1-QR_DIM_FINDER; i++) { + qr->symbol[i][QR_DIM_TIMING] = QR_MM_FUNC; + qr->symbol[QR_DIM_TIMING][i] = QR_MM_FUNC; + if ((i & 1) == 0) { + qr->symbol[i][QR_DIM_TIMING] |= QR_MM_BLACK; + qr->symbol[QR_DIM_TIMING][i] |= QR_MM_BLACK; + } + } + /* + * 形式情報の領域を予約する + */ + for (i = 0; i < 2; i++) { + for (j = 0; j < QR_FIN_MAX; j++) { + xpos = (qr_fmtinfopos[i][j].xpos + dim) % dim; + ypos = (qr_fmtinfopos[i][j].ypos + dim) % dim; + qr->symbol[ypos][xpos] |= QR_MM_FUNC; + } + } + xpos = (qr_fmtblackpos.xpos + dim) % dim; + ypos = (qr_fmtblackpos.ypos + dim) % dim; + qr->symbol[ypos][xpos] |= QR_MM_FUNC; + /* + * 型番情報が有効(型番7以上)なら + * 型番情報の領域を予約する + */ + if (qr_verinfo[qr->param.version] != -1L) { + for (i = 0; i < 2; i++) { + for (j = 0; j < QR_VIN_MAX; j++) { + xpos = (qr_verinfopos[i][j].xpos + dim) % dim; + ypos = (qr_verinfopos[i][j].ypos + dim) % dim; + qr->symbol[ypos][xpos] |= QR_MM_FUNC; + } + } + } + + return TRUE; +} + +/* + * シンボルに符号化されたコード語を配置する + */ +static int +qrFillCodeWord(QRCode *qr) +{ + int i, j; + + /* + * シンボル右下隅から開始する + */ + qrInitPosition(qr); + /* + * コード語領域のすべてのバイトについて... + */ + for (i = 0; i < qr_vertable[qr->param.version].totalwords; i++) { + /* + * 最上位ビットから順に各ビットを調べ... + */ + for (j = 7; j >= 0; j--) { + /* + * そのビットが1なら黒モジュールを置く + */ + if ((qr->codeword[i] & (1 << j)) != 0) { + qr->symbol[qr->ypos][qr->xpos] |= QR_MM_DATA; + } + /* + * 次のモジュール配置位置に移動する + */ + qrNextPosition(qr); + } + } + + return TRUE; +} + +/* + * モジュール配置の初期位置と配置方向を決める + */ +static void +qrInitPosition(QRCode *qr) +{ + /* + * シンボルの右下隅から配置を始める + */ + qr->xpos = qr->ypos = qr_vertable[qr->param.version].dimension - 1; + /* + * 最初の移動方向は左向き、次に上向き + */ + qr->xdir = -1; + qr->ydir = -1; +} + +/* + * 次のモジュール配置位置を決める + */ +static void +qrNextPosition(QRCode *qr) +{ + do { + /* + * qr->xdir方向に1モジュール移動して + * qr->xdirの向きを逆にする + * 右に移動したときはqr->ydir方向にも + * 1モジュール移動する + */ + qr->xpos += qr->xdir; + if (qr->xdir > 0) { + qr->ypos += qr->ydir; + } + qr->xdir = -qr->xdir; + /* + * y方向にシンボルをはみ出すようなら + * y方向ではなくx方向に2モジュール左に移動し、 + * かつqr->ydirの向きを逆にする + * qr->xposが縦のタイミングパターン上なら + * さらに1モジュール左に移動する + */ + if (qr->ypos < 0 || qr->ypos >= qr_vertable[qr->param.version].dimension) { + qr->ypos -= qr->ydir; + qr->ydir = -qr->ydir; + qr->xpos -= 2; + if (qr->xpos == QR_DIM_TIMING) { + qr->xpos--; + } + } + /* + * 新しい位置が機能パターン上なら + * それをよけて次の候補位置を探す + */ + } while (qrIsFunc(qr, qr->ypos, qr->xpos)); +} + +/* + * シンボルを最適なマスクパターンでマスクする + */ +static int +qrSelectMaskPattern(QRCode *qr) +{ + int type; + long penalty, xpenalty; + + if (qr->param.masktype >= 0) { + /* + * マスクパターンが引数で指定されていたので + * そのパターンでマスクして終了 + */ + return qrApplyMaskPattern(qr); + } + /* + * すべてのマスクパターンを評価する + */ + xpenalty = -1L; + for (type = 0; type < QR_MPT_MAX; type++) { + /* + * 当該マスクパターンでマスクして評価する + */ + qrApplyMaskPattern2(qr, type); + penalty = qrEvaluateMaskPattern(qr); + /* + * 失点がこれまでより低かったら記録する + */ + if (xpenalty == -1L || penalty < xpenalty) { + qr->param.masktype = type; + xpenalty = penalty; + } + } + /* + * 失点が最低のパターンでマスクする + */ + return qrApplyMaskPattern(qr); +} + +/* + * 設定済みの参照子のマスクパターンでシンボルをマスクする + */ +static int +qrApplyMaskPattern(QRCode *qr) +{ + return qrApplyMaskPattern2(qr, qr->param.masktype); +} + +/* + * 指定した参照子のマスクパターンでシンボルをマスクする + */ +static int +qrApplyMaskPattern2(QRCode *qr, int type) +{ + + int i, j, dim; + + if (type < 0 || type >= QR_MPT_MAX) { + qrSetErrorInfo3(qr, QR_ERR_INVALID_MPT, "%d", type); + return FALSE; + } + + dim = qr_vertable[qr->param.version].dimension; + /* + * 以前のマスクパターンをクリアし、 + * 符号化済みデータを初期パターンとする + */ + for (i = 0; i < dim; i++) { + for (j = 0; j < dim; j++) { + /* + * 機能パターン領域の印字黒モジュールは残す + */ + if (qrIsFunc(qr, i, j)) { + continue; + } + /* + * 符号化データ領域は符号化データの + * 黒モジュールを印字黒モジュールにする + */ + if (qrIsData(qr, i, j)) { + qr->symbol[i][j] |= QR_MM_BLACK; + } else { + qr->symbol[i][j] &= ~QR_MM_BLACK; + } + } + } + /* + * i行j列のモジュールについて... + */ + for (i = 0; i < dim; i++) { + for (j = 0; j < dim; j++) { + if (qrIsFunc(qr, i, j)) { + /* + * 機能パターン領域(および形式情報、 + * 型番情報)はマスク対象から除外する + */ + continue; + } + /* + * 指定された条件を満たすモジュールを反転する + */ + if ((type == 0 && (i + j) % 2 == 0) || + (type == 1 && i % 2 == 0) || + (type == 2 && j % 3 == 0) || + (type == 3 && (i + j) % 3 == 0) || + (type == 4 && ((i / 2) + (j / 3)) % 2 == 0) || + (type == 5 && (i * j) % 2 + (i * j) % 3 == 0) || + (type == 6 && ((i * j) % 2 + (i * j) % 3) % 2 == 0) || + (type == 7 && ((i * j) % 3 + (i + j) % 2) % 2 == 0)) + { + qr->symbol[i][j] ^= QR_MM_BLACK; + } + } + } + + return TRUE; +} + +/* + * マスクパターンを評価し評価値を返す + */ +static long +qrEvaluateMaskPattern(QRCode *qr) +{ + int i, j, m, n, dim; + long penalty; + + /* + * 評価値をpenaltyに積算する + * マスクは符号化領域に対してのみ行うが + * 評価はシンボル全体について行われる + */ + penalty = 0L; + dim = qr_vertable[qr->param.version].dimension; + /* + * 特徴: 同色の行/列の隣接モジュール + * 評価条件: モジュール数 = (5+i) + * 失点: 3+i + */ + for (i = 0; i < dim; i++) { + n = 0; + for (j = 0; j < dim; j++) { + if (j > 0 && qrIsBlack(qr, i, j) == qrIsBlack(qr, i, j-1)) { + /* + * すぐ左と同色のモジュール + * 同色列の長さを1増やす + */ + n++; + } else { + /* + * 色が変わった + * 直前で終わった同色列の長さを評価する + */ + if (n >= 5) { + penalty += (long)(3 + (n - 5)); + } + n = 1; + } + } + /* + * 列が尽きた + * 直前で終わった同色列の長さを評価する + */ + if (n >= 5) { + penalty += (long)(3 + (n - 5)); + } + } + for (i = 0; i < dim; i++) { + n = 0; + for (j = 0; j < dim; j++) { + if (j > 0 && qrIsBlack(qr, j, i) == qrIsBlack(qr, j-1, i)) { + /* + * すぐ上と同色のモジュール + * 同色列の長さを1増やす + */ + n++; + } else { + /* + * 色が変わった + * 直前で終わった同色列の長さを評価する + */ + if (n >= 5) { + penalty += (long)(3 + (n - 5)); + } + n = 1; + } + } + /* + * 列が尽きた + * 直前で終わった同色列の長さを評価する + */ + if (n >= 5) { + penalty += (long)(3 + (n - 5)); + } + } + /* + * 特徴: 同色のモジュールブロック + * 評価条件: ブロックサイズ = 2×2 + * 失点: 3 + */ + for (i = 0; i < dim - 1; i++) { + for (j = 0; j < dim - 1; j++) { + if (qrIsBlack(qr, i, j) == qrIsBlack(qr, i, j+1) && + qrIsBlack(qr, i, j) == qrIsBlack(qr, i+1, j) && + qrIsBlack(qr, i, j) == qrIsBlack(qr, i+1, j+1)) + { + /* + * 2×2の同色のブロックがあった + */ + penalty += 3L; + } + } + } + /* + * 特徴: 行/列における1:1:3:1:1比率(暗:明:暗:明:暗)のパターン + * に続いて比率4の幅以上の明パターン + * 失点: 40 + * 前後はシンボル境界外か明モジュールである必要がある + * 2:2:6:2:2のようなパターンにも失点を与えるべきかは + * JIS規格からは読み取れない。ここでは与えていない + */ + for (i = 0; i < dim; i++) { + for (j = 0; j < dim - 6; j++) { + if ((j == 0 || !qrIsBlack(qr, i, j-1)) && + qrIsBlack(qr, i, j+0) && + !qrIsBlack(qr, i, j+1) && + qrIsBlack(qr, i, j+2) && + qrIsBlack(qr, i, j+3) && + qrIsBlack(qr, i, j+4) && + !qrIsBlack(qr, i, j+5) && + qrIsBlack(qr, i, j+6)) + { + int k, l; + l = 1; + for (k = 0; k < dim - j - 7 && k < 4; k++) { + if (qrIsBlack(qr, i, j + k + 7)) { + l = 0; + break; + } + } + /* + * パターンがあった + */ + if (l) { + penalty += 40L; + } + } + } + } + for (i = 0; i < dim; i++) { + for (j = 0; j < dim - 6; j++) { + if ((j == 0 || !qrIsBlack(qr, j-1, i)) && + qrIsBlack(qr, j+0, i) && + !qrIsBlack(qr, j+1, i) && + qrIsBlack(qr, j+2, i) && + qrIsBlack(qr, j+3, i) && + qrIsBlack(qr, j+4, i) && + !qrIsBlack(qr, j+5, i) && + qrIsBlack(qr, j+6, i) && + (j == dim-7 || !qrIsBlack(qr, j+7, i))) + { + int k, l; + l = 1; + for (k = 0; k < dim - j - 7 && k < 4; k++) { + if (qrIsBlack(qr, j + k + 7, i)) { + l = 0; + break; + } + } + /* + * パターンがあった + */ + if (l) { + penalty += 40L; + } + } + } + } + /* + * 特徴: 全体に対する暗モジュールの占める割合 + * 評価条件: 50±(5×k)%〜50±(5×(k+1))% + * 失点: 10×k + */ + m = 0; + n = 0; + for (i = 0; i < dim; i++) { + for (j = 0; j < dim; j++) { + m++; + if (qrIsBlack(qr, i, j)) { + n++; + } + } + } + penalty += (long)(abs((n * 100 / m) - 50) / 5 * 10); + return penalty; +} + +/* + * シンボルに形式情報と型番情報を配置する + */ +static int +qrFillFormatInfo(QRCode *qr) +{ + int i, j, dim, fmt, modulo, xpos, ypos; + long v; + + dim = qr_vertable[qr->param.version].dimension; + /* + * 形式情報を計算する + * 誤り訂正レベル2ビット(L:01, M:00, Q:11, H:10)と + * マスクパターン参照子3ビットからなる計5ビットに + * Bose-Chaudhuri-Hocquenghem(15,5)符号による + * 誤り訂正ビット10ビットを付加して15ビットとする + * (5ビットをxの次数14〜10の多項式係数とみなして + * 多項式x^10+x^8+x^5+x^4+x^2+x+1(係数10100110111) + * で除算した剰余10ビットを誤り訂正ビットとする) + * さらにすべてのビットがゼロにならないように + * 101010000010010(0x5412)とXORをとる + */ + fmt = ((qr->param.eclevel ^ 1) << 3) | qr->param.masktype; + modulo = fmt << 10; + for (i = 14; i >= 10; i--) { + if ((modulo & (1 << i)) == 0) { + continue; + } + modulo ^= 0x537 << (i - 10); + } + fmt = ((fmt << 10) + modulo) ^ 0x5412; + /* + * 形式情報をシンボルに配置する + */ + for (i = 0; i < 2; i++) { + for (j = 0; j < QR_FIN_MAX; j++) { + if ((fmt & (1 << j)) == 0) { + continue; + } + xpos = (qr_fmtinfopos[i][j].xpos + dim) % dim; + ypos = (qr_fmtinfopos[i][j].ypos + dim) % dim; + qr->symbol[ypos][xpos] |= QR_MM_BLACK; + } + } + xpos = (qr_fmtblackpos.xpos + dim) % dim; + ypos = (qr_fmtblackpos.ypos + dim) % dim; + qr->symbol[ypos][xpos] |= QR_MM_BLACK; + /* + * 型番情報が有効(型番7以上)なら + * 型番情報をシンボルに配置する + */ + v = qr_verinfo[qr->param.version]; + if (v != -1L) { + for (i = 0; i < 2; i++) { + for (j = 0; j < QR_VIN_MAX; j++) { + if ((v & (1L << j)) == 0L) { + continue; + } + xpos = (qr_verinfopos[i][j].xpos + dim) % dim; + ypos = (qr_verinfopos[i][j].ypos + dim) % dim; + qr->symbol[ypos][xpos] |= QR_MM_BLACK; + } + } + } + + return TRUE; +} + +/* + * データコード語の余剰ビットを埋める処理から + * シンボルに形式情報と型番情報を配置する処理までを + * 一括で実行する + */ +QR_API int +qrFinalize(QRCode *qr) +{ + static qr_funcs funcs[] = { + qrFinalizeDataWord, + qrComputeECWord, + qrMakeCodeWord, + qrFillFunctionPattern, + qrFillCodeWord, + qrSelectMaskPattern, + qrFillFormatInfo, + NULL + }; + int i = 0; + int ret = TRUE; + + if (qrIsFinalized(qr)) { + return TRUE; + } + + /* + * 型番自動選択 + */ + if (qr->param.version == -1) { + int maxlen, delta; + int version = 0; + while (++version <= QR_VER_MAX) { + if (version <= VERPOINT1) { + delta = qr->delta1; + } else if (version <= VERPOINT2) { + delta = qr->delta2; + } else { + delta = 0; + } + maxlen = 8 * qr_vertable[version].ecl[qr->param.eclevel].datawords; + if (maxlen >= qr->enclen - delta) { + break; + } + } + if (version > QR_VER_MAX) { + maxlen = 8 * qr_vertable[QR_VER_MAX].ecl[qr->param.eclevel].datawords; + qrSetErrorInfo3(qr, QR_ERR_LARGE_SRC, ", %d total encoded bits" + " (max %d bits on version=%d, ecl=%s)", + qr->enclen, maxlen, QR_VER_MAX, qr_eclname[qr->param.eclevel]); + return FALSE; + } + qr->param.version = version; + } + + /* + * データコード語に入力データを登録する + */ + if (qr->source != NULL) { + qr_byte_t *source; + int mode, size; + + qrInitDataWord(qr); + source = qr->source; + while ((mode = (int)(*source++)) != '\0') { + mode ^= 0x80; + size = ((int)*source++) << 24; + size |= ((int)*source++) << 16; + size |= ((int)*source++) << 8; + size |= (int)*source++; + if (qrEncodeDataWord(qr, source, size, mode) == FALSE) { + return FALSE; + } + source += size; + } + + qrFree(qr->source); + } + + /* + * シンボルを生成する + */ + while (funcs[i] && ret == TRUE) { + ret = funcs[i++](qr); + } + + if (ret == TRUE) { + qrFree(qr->dataword); + qrFree(qr->ecword); + qrFree(qr->codeword); + qr->state = QR_STATE_FINAL; + } + return ret; +} + +/* + * Finalze済か判定する + */ +QR_API int +qrIsFinalized(const QRCode *qr) +{ + if (qr->state == QR_STATE_FINAL) { + return TRUE; + } + return FALSE; +} + +/* + * データをセット済か判定する + */ +QR_API int qrHasData(const QRCode *qr) +{ + if (qr->state == QR_STATE_BEGIN) { + return FALSE; + } + return TRUE; +} + +/* + * 構造的連接の最後のQRコードオブジェクトの + * 仮構造的連接ヘッダを正しい情報で上書きし、Finalizeする + */ +QR_API int +qrsFinalize(QRStructured *st) +{ + int m, n, r; + + if (!qrsHasData(st)) { + qrSetErrorInfo(st->cur, QR_ERR_STATE, _QR_FUNCTION); + return FALSE; + } else if (qrsIsFinalized(st)) { + return TRUE; + } + + m = 0; + n = st->num - 1; + r = TRUE; + while (m <= n && r == TRUE) { + int dwpos, dwbit; + dwpos = st->qrs[m]->dwpos; + dwbit = st->qrs[m]->dwbit; + st->qrs[m]->dwpos = 0; + st->qrs[m]->dwbit = 7; + qrAddDataBits(st->qrs[m], 4, 3); + qrAddDataBits(st->qrs[m], 4, m); + qrAddDataBits(st->qrs[m], 4, n); + qrAddDataBits(st->qrs[m], 8, st->parity); + st->qrs[m]->dwpos = dwpos; + st->qrs[m]->dwbit = dwbit; + r = qrFinalize(st->qrs[m]); + m++; + } + + if (r == TRUE) { + st->state = QR_STATE_FINAL; + } + return r; +} + +/* + * Finalze済か判定する + */ +QR_API int +qrsIsFinalized(const QRStructured *st) +{ + if (st->state == QR_STATE_FINAL) { + return TRUE; + } + return FALSE; +} + +/* + * データをセット済か判定する + */ +QR_API int qrsHasData(const QRStructured *st) +{ + if (st->state == QR_STATE_BEGIN) { + return FALSE; + } + return TRUE; +} + +/* + * 生成されたQRコードシンボルを fmt で指定した形式に変換する + */ +QR_API qr_byte_t * +qrGetSymbol(QRCode *qr, int fmt, int sep, int mag, int *size) +{ + qr_byte_t *buf; + int _size; + + static const QRConverter cnv[QR_FMT_COUNT] = { + qrSymbolToBMP, + qrSymbolToPBM, + qrSymbolToJSON, + qrSymbolToDigit, + qrSymbolToASCII + }; + + if (fmt < 0 || fmt >= QR_FMT_COUNT) { + qrSetErrorInfo(qr, QR_ERR_INVALID_FMT, NULL); + return NULL; + } + + if (cnv[fmt] == NULL) { + qrSetErrorInfo(qr, QR_ERR_UNSUPPORTED_FMT, NULL); + return NULL; + } + + buf = cnv[fmt](qr, sep, mag, &_size); + if (buf == NULL) { + return NULL; + } + + if (size) { + *size = _size; + } + return buf; +} + +/* + * 生成されたQRコードシンボルをストリーム fp に書き込む + */ +QR_API int +qrOutputSymbol(QRCode *qr, FILE *fp, int fmt, int sep, int mag) +{ + qr_byte_t *buf; + int size; + + buf = qrGetSymbol(qr, fmt, sep, mag, &size); + if (buf == NULL) { + return -1; + } + + if (fp == NULL) { + fp = stdout; + } + + if (!fwrite(buf, (size_t)size, 1, fp)) { + qrSetErrorInfo2(qr, QR_ERR_FWRITE, NULL); + free(buf); + return -1; + } + if (ferror(fp)) { + qrSetErrorInfo(qr, QR_ERR_FWRITE, NULL); + free(buf); + return -1; + } + + free(buf); + + return size; +} + +/* + * 生成されたQRコードシンボルをファイル pathname に書き込む + */ +QR_API int +qrOutputSymbol2(QRCode *qr, const char *pathname, int fmt, int sep, int mag) +{ + FILE *fp; + int size; + + if (pathname == NULL || pathname[0] == '\0') { + qrSetErrorInfo(qr, QR_ERR_EMPTY_PARAM, "(empty pathname)"); + return -1; + } + + fp = fopen(pathname, "wb"); + if (fp == NULL) { + qrSetErrorInfo2(qr, QR_ERR_FOPEN, pathname); + return -1; + } + + size = qrOutputSymbol(qr, fp, fmt, sep, mag); + fclose(fp); + + return size; +} + +/* + * 生成されたQRコードシンボルすべてを fmt で指定した形式に変換する + */ +QR_API qr_byte_t * +qrsGetSymbols(QRStructured *st, int fmt, int sep, int mag, int order, int *size) +{ + qr_byte_t *buf; + int _size; + + static QRsConverter cnv[QR_FMT_COUNT] = { + qrsSymbolsToPBM, + qrsSymbolsToJSON, + qrsSymbolsToDigit, + qrsSymbolsToASCII + }; + + if (fmt < 0 || fmt >= QR_FMT_COUNT) { + qrSetErrorInfo(st->cur, QR_ERR_INVALID_FMT, NULL); + return NULL; + } + + buf = cnv[fmt](st, sep, mag, order, &_size); + if (buf == NULL) { + return NULL; + } + + if (size) { + *size = _size; + } + + return buf; +} + +/* + * 生成されたQRコードシンボルすべてをストリーム fp に書き込む + */ +QR_API int +qrsOutputSymbols(QRStructured *st, FILE *fp, int fmt, int sep, int mag, int order) +{ + qr_byte_t *buf; + int size; + + + buf = qrsGetSymbols(st, fmt, sep, mag, order, &size); + if (buf == NULL) { + return -1; + } + + if (fp == NULL) { + fp = stdout; + } + + if (!fwrite(buf, (size_t)size, 1, fp)) { + qrSetErrorInfo2(st->cur, QR_ERR_FWRITE, NULL); + free(buf); + return -1; + } + if (ferror(fp)) { + qrSetErrorInfo(st->cur, QR_ERR_FWRITE, NULL); + free(buf); + return -1; + } + + free(buf); + + return size; +} + +/* + * 生成されたQRコードシンボルすべてをファイル pathname に書き込む + */ +QR_API int +qrsOutputSymbols2(QRStructured *st, const char *pathname, int fmt, int sep, int mag, int order) +{ + FILE *fp; + int size; + + if (pathname == NULL || pathname[0] == '\0') { + qrSetErrorInfo(st->cur, QR_ERR_EMPTY_PARAM, "(empty pathname)"); + return -1; + } + + fp = fopen(pathname, "wb"); + if (fp == NULL) { + qrSetErrorInfo2(st->cur, QR_ERR_FOPEN, pathname); + return -1; + } + + size = qrsOutputSymbols(st, fp, fmt, sep, mag, order); + fclose(fp); + + return size; +} diff --git a/support/sg2002/kvm_system/main/lib/libqr/qr.h b/support/sg2002/kvm_system/main/lib/libqr/qr.h new file mode 100644 index 0000000..657ad09 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/qr.h @@ -0,0 +1,341 @@ +/* + * QR Code Generator Library: Basic Header + * + * Core routines were originally written by Junn Ohta. + * Based on qr.c Version 0.1: 2004/4/3 (Public Domain) + * + * @package libqr + * @author Ryusuke SEKIYAMA + * @copyright 2006-2013 Ryusuke SEKIYAMA + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +#ifndef _QR_H_ +#define _QR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#if defined(WIN32) && !defined(QR_STATIC_BUILD) +#ifdef QR_DLL_BUILD +#define QR_API __declspec(dllexport) +#else +#define QR_API __declspec(dllimport) +#endif +#else +#define QR_API +#endif + +/* + * ライブラリのバージョン + */ +#define LIBQR_VERSION "0.3.1" + +/* + * エラーコード + */ +typedef enum { + /* 汎用エラーコード */ + QR_ERR_NONE = 0, + QR_ERR_USAGE = 0x68, + QR_ERR_NOT_IMPL = 0x69, + QR_ERR_SEE_ERRNO = 0x6e, + QR_ERR_FOPEN = 0x6f, + QR_ERR_FREAD = 0x72, + QR_ERR_STATE = 0x73, + QR_ERR_UNKNOWN = 0x75, + QR_ERR_FWRITE = 0x77, + QR_ERR_MEMORY_EXHAUSTED = 0x78, + + /* パラメータ用エラーコード */ + QR_ERR_INVALID_ARG = 0x01, + QR_ERR_INVALID_VERSION = 0x02, + QR_ERR_INVALID_MODE = 0x03, + QR_ERR_INVALID_ECL = 0x04, + QR_ERR_INVALID_MPT = 0x05, + QR_ERR_INVALID_MAG = 0x06, + QR_ERR_INVALID_SEP = 0x07, + QR_ERR_INVALID_SIZE = 0x08, + QR_ERR_INVALID_FMT = 0x09, + QR_ERR_INVALID_OUT = 0x0a, + QR_ERR_INVALID_MAXNUM = 0x0b, + QR_ERR_UNSUPPORTED_FMT = 0x0c, + QR_ERR_EMPTY_PARAM = 0x0f, + + /* 入力データ用エラーコード */ + QR_ERR_EMPTY_SRC = 0x10, + QR_ERR_LARGE_SRC = 0x11, + QR_ERR_NOT_NUMERIC = 0x12, + QR_ERR_NOT_ALNUM = 0x13, + QR_ERR_NOT_KANJI = 0x14, + + /* 画像処理用エラーコード */ + QR_ERR_IMAGE_TOO_LARGE = 0x30, + QR_ERR_WIDTH_TOO_LARGE = 0x31, + QR_ERR_HEIGHT_TOO_LARGE = 0x32, + QR_ERR_IMAGECREATE = 0x33, + QR_ERR_IMAGEFORMAT = 0x34, + QR_ERR_IMAGEFRAME = 0x35, + + /* zlib用エラーコード */ + QR_ERR_DEFLATE = 0x40 +} qr_err_t; + +/* + * 内部状態 + */ +#define QR_STATE_BEGIN 0 +#define QR_STATE_SET 1 +#define QR_STATE_FINAL 2 + +/* + * 符号化モード + */ +typedef enum { + QR_EM_AUTO = -1, /* 自動選択 */ + QR_EM_NUMERIC = 0, /* 数字 */ + QR_EM_ALNUM = 1, /* 英数字: 0-9 A-Z SP $%*+-./: */ + QR_EM_8BIT = 2, /* 8ビットバイト */ + QR_EM_KANJI = 3 /* 漢字 */ +} qr_em_t; + +/* モード総数 */ +#define QR_EM_COUNT 4 + +/* + * 誤り訂正レベル + */ +typedef enum { + QR_ECL_L = 0, /* レベルL */ + QR_ECL_M = 1, /* レベルM */ + QR_ECL_Q = 2, /* レベルQ */ + QR_ECL_H = 3 /* レベルH */ +} qr_ecl_t; + +/* レベル総数 */ +#define QR_ECL_COUNT 4 + +/* + * 出力形式 + */ +typedef enum { + QR_FMT_PNG = 0, /* PNG */ + QR_FMT_BMP = 1, /* BMP */ + QR_FMT_TIFF = 2, /* TIFF */ + QR_FMT_PBM = 3, /* PBM */ + QR_FMT_SVG = 4, /* SVG */ + QR_FMT_JSON = 5, /* JSON */ + QR_FMT_DIGIT = 6, /* 文字列 */ + QR_FMT_ASCII = 7, /* アスキーアート */ + QR_FMT_UNAVAILABLE = -1 /* 利用不可 */ +} qr_format_t; + +/* 出力形式総数 */ +#define QR_FMT_COUNT 8 + +/* + * モジュール値のマスク + */ +#define QR_MM_DATA 0x01 /* 符号化データの黒モジュール */ +#define QR_MM_BLACK 0x02 /* 印字される黒モジュール */ +#define QR_MM_FUNC 0x04 /* 機能パターン領域(形式/型番情報を含む) */ + +/* + * 機能パターンの定数 + */ +#define QR_DIM_SEP 4 /* 分離パターンの幅 */ +#define QR_DIM_FINDER 7 /* 位置検出パターンの1辺の長さ */ +#define QR_DIM_ALIGN 5 /* 位置合わせパターンの1辺の長さ */ +#define QR_DIM_TIMING 6 /* タイミングパターンのオフセット位置 */ + +/* + * サイズ定数 + */ +#define QR_SRC_MAX 7089 /* 入力データの最大長 */ +#define QR_DIM_MAX 177 /* 1辺のモジュール数の最大値 */ +#define QR_VER_MAX 40 /* 型番の最大値 */ +#define QR_DWD_MAX 2956 /* データコード語の最大長(型番40/レベルL) */ +#define QR_ECW_MAX 2430 /* 誤り訂正コード語の最大長(型番40/レベルH) */ +#define QR_CWD_MAX 3706 /* コード語の最大長(型番40) */ +#define QR_RSD_MAX 123 /* RSブロックデータコード語の最大長 */ +#define QR_RSW_MAX 68 /* RSブロック誤り訂正コード語の最大長 */ +#define QR_RSB_MAX 2 /* RSブロック種別の最大数 */ +#define QR_MPT_MAX 8 /* マスクパターン種別総数 */ +#define QR_APL_MAX 7 /* 位置合わせパターン座標の最大数 */ +#define QR_FIN_MAX 15 /* 形式情報のビット数 */ +#define QR_VIN_MAX 18 /* 型番情報のビット数 */ +#define QR_MAG_MAX 16 /* ピクセル表示倍率の最大値 */ +#define QR_SEP_MAX 16 /* 分離パターン幅の最大値 */ +#define QR_ERR_MAX 1024 /* エラー情報の最大長 */ +#define QR_STA_MAX 16 /* 構造的連接(分割/連結)の最大数 */ +#define QR_STA_LEN 20 /* 構造的連接ヘッダのビット数 */ + +/* + * その他の定数 + */ +#define NAV 0 /* 不使用(not available) */ +#define PADWORD1 0xec /* 埋め草コード語1: 11101100 */ +#define PADWORD2 0x11 /* 埋め草コード語2: 00010001 */ +#define VERPOINT1 9 /* 文字数指示子のビット数が変わる直前の型番1 */ +#define VERPOINT2 26 /* 文字数指示子のビット数が変わる直前の型番2 */ + +/* + * 8bitバイナリデータ型 + */ +typedef unsigned char qr_byte_t; + +/* + * RSブロックごとの情報 + */ +typedef struct qr_rsblock_t { + int rsbnum; /* RSブロック数 */ + int totalwords; /* RSブロック総コード語数 */ + int datawords; /* RSブロックデータコード語数 */ + int ecnum; /* RSブロック誤り訂正数(不使用) */ +} qr_rsblock_t; + +/* + * 誤り訂正レベルごとの情報 + */ +typedef struct qr_eclevel_t { + int datawords; /* データコード語数(全RSブロック) */ + int capacity[QR_EM_COUNT]; /* 符号化モードごとのデータ容量 */ + int nrsb; /* RSブロックの種類(1または2) */ + qr_rsblock_t rsb[QR_RSB_MAX]; /* RSブロックごとの情報 */ +} qr_eclevel_t; + +/* + * 型番ごとの情報 + */ +typedef struct qr_vertable_t { + int version; /* 型番 */ + int dimension; /* 1辺のモジュール数 */ + int totalwords; /* 総コード語数 */ + int remainedbits; /* 剰余ビット数 */ + int nlen[QR_EM_COUNT]; /* 文字数指示子のビット数 */ + qr_eclevel_t ecl[QR_ECL_COUNT]; /* 誤り訂正レベルごとの情報 */ + int aplnum; /* 位置合わせパターン中心座標数 */ + int aploc[QR_APL_MAX]; /* 位置合わせパターン中心座標 */ +} qr_vertable_t; + +/* + * 座標データ型 + */ +typedef struct qr_coord_t { int ypos, xpos; } qr_coord_t; + +/* + * パラメータ構造体 + */ +typedef struct qr_param_t { + int version; /* 型番 */ + int mode; /* 符号化モード */ + int eclevel; /* 誤り訂正レベル */ + int masktype; /* マスクパターン種別 */ +} qr_param_t; + +/* + * QRコードオブジェクト + */ +typedef struct qrcode_t { + qr_byte_t *dataword; /* データコード語領域のアドレス */ + qr_byte_t *ecword; /* 誤り訂正コード語領域のアドレス */ + qr_byte_t *codeword; /* シンボル配置用コード語領域のアドレス */ + qr_byte_t *_symbol; /* シンボルデータ領域のアドレス */ + qr_byte_t **symbol; /* シンボルデータの各行頭のアドレスのポインタ */ + qr_byte_t *source; /* 入力データ領域のアドレス */ + size_t srcmax; /* 入力データ領域の最大容量 */ + size_t srclen; /* 入力データ領域の使用容量 */ + int enclen; /* データコード語の総ビット長 */ + int delta1, delta2; /* 型番自動選択の補助に使われるビット長差分 */ + int dwpos; /* データコード語の追加バイト位置 */ + int dwbit; /* データコード語の追加ビット位置 */ + int xpos, ypos; /* モジュールを配置する座標位置 */ + int xdir, ydir; /* モジュール配置の移動方向 */ + int state; /* 処理の進行状況 */ + int errcode; /* 最後に起こったエラーの番号 */ + char errinfo[QR_ERR_MAX]; /* 最後に起こったエラーの詳細 */ + qr_param_t param; /* 出力パラメータ */ +} QRCode; + +/* + * 構造的連接QRコードオブジェクト + */ +typedef struct qrcode_sa_t { + QRCode *qrs[QR_STA_MAX]; /* QRコードオブジェクトのポインタ配列 */ + QRCode *cur; /* 値を入力する対象のQRコードオブジェクト */ + int num; /* シンボル数 */ + int max; /* 最大シンボル数 */ + int parity; /* パリティ */ + int state; /* 処理の進行状況 */ + qr_param_t param; /* 出力パラメータ */ +} QRStructured; + +/* + * QRコード出力関数型 + */ +typedef qr_byte_t *(*QRConverter)(QRCode *, int, int, int *); +typedef qr_byte_t *(*QRsConverter)(QRStructured *, int, int, int, int *); + +#define qrIsBlacke(qr, i, j) (((qr)->symbol[(i)][(j)] & QR_MM_BLACK) != 0) + +/* + * 基本関数のプロトタイプ + */ +QR_API QRCode *qrInit(int version, int mode, int eclevel, int masktype, int *errcode); +QR_API void qrDestroy(QRCode *qr); +QR_API int qrGetErrorCode(QRCode *qr); +QR_API char *qrGetErrorInfo(QRCode *qr); +QR_API int qrAddData(QRCode *qr, const qr_byte_t *source, int size); +QR_API int qrAddData2(QRCode *qr, const qr_byte_t *source, int size, int mode); +QR_API int qrFinalize(QRCode *qr); +QR_API int qrIsFinalized(const QRCode *qr); +QR_API int qrHasData(const QRCode *qr); +QR_API QRCode *qrClone(const QRCode *qr, int *errcode); + +/* + * 構造的連接操作用関数のプロトタイプ + */ +QR_API QRStructured *qrsInit(int version, int mode, int eclevel, int masktype, int maxnum, int *errcode); +QR_API void qrsDestroy(QRStructured *st); +QR_API int qrsGetErrorCode(QRStructured *st); +QR_API char *qrsGetErrorInfo(QRStructured *st); +QR_API int qrsAddData(QRStructured *st, const qr_byte_t *source, int size); +QR_API int qrsAddData2(QRStructured *st, const qr_byte_t *source, int size, int mode); +QR_API int qrsFinalize(QRStructured *st); +QR_API int qrsIsFinalized(const QRStructured *st); +QR_API int qrsHasData(const QRStructured *st); +QR_API QRStructured *qrsClone(const QRStructured *st, int *errcode); + +/* + * 出力用関数のプロトタイプ + */ +QR_API int qrOutputSymbol(QRCode *qr, FILE *fp, int fmt, int sep, int mag); +QR_API int qrOutputSymbol2(QRCode *qr, const char *pathname, int fmt, int sep, int mag); +QR_API qr_byte_t *qrGetSymbol(QRCode *qr, int fmt, int sep, int mag, int *size); +QR_API qr_byte_t *qrSymbolToDigit(QRCode *qr, int sep, int mag, int *size); +QR_API qr_byte_t *qrSymbolToASCII(QRCode *qr, int sep, int mag, int *size); +QR_API qr_byte_t *qrSymbolToJSON(QRCode *qr, int sep, int mag, int *size); +QR_API qr_byte_t *qrSymbolToPBM(QRCode *qr, int sep, int mag, int *size); +QR_API qr_byte_t *qrSymbolToBMP(QRCode *qr, int sep, int mag, int *size); + +/* + * 構造的連接出力用関数のプロトタイプ + */ +QR_API int qrsOutputSymbols(QRStructured *st, FILE *fp, int fmt, int sep, int mag, int order); +QR_API int qrsOutputSymbols2(QRStructured *st, const char *pathname, int fmt, int sep, int mag, int order); +QR_API qr_byte_t *qrsGetSymbols(QRStructured *st, int fmt, int sep, int mag, int order, int *size); +QR_API qr_byte_t *qrsSymbolsToDigit(QRStructured *st, int sep, int mag, int order, int *size); +QR_API qr_byte_t *qrsSymbolsToASCII(QRStructured *st, int sep, int mag, int order, int *size); +QR_API qr_byte_t *qrsSymbolsToJSON(QRStructured *st, int sep, int mag, int order, int *size); +QR_API qr_byte_t *qrsSymbolsToPBM(QRStructured *st, int sep, int mag, int order, int *size); +QR_API qr_byte_t *qrsSymbolsToBMP(QRStructured *st, int sep, int mag, int order, int *size); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* _QR_H_ */ diff --git a/support/sg2002/kvm_system/main/lib/libqr/qr_dwtable.h b/support/sg2002/kvm_system/main/lib/libqr/qr_dwtable.h new file mode 100644 index 0000000..3289b23 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/qr_dwtable.h @@ -0,0 +1,48 @@ +static const short qr_dwtable_kanji[42][189] = { +{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,-1,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,120,121,122,123,124,125,126,127,-1,-1,-1,-1,-1,-1,-1,-1,136,137,138,139,140,141,142,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,-1,-1,-1,-1,-1,-1,-1,176,177,178,179,180,181,182,183,-1,-1,-1,-1,188}, +{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,207,208,209,210,211,212,213,214,215,216,-1,-1,-1,-1,-1,-1,-1,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,-1,-1,-1,-1,-1,-1,-1,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,-1,-1,-1,-1,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, +{384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,-1,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,-1,-1,-1,-1,-1,-1,-1,-1,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,-1,-1,-1,-1,-1,-1,-1,-1,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, +{576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,-1,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, +{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, +{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, +#ifdef QR_DISABLE_CP932_CHARACTER +{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, +#else +{1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,-1,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,-1,-1,-1,-1,-1,-1,-1,-1,1214,-1,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, +#endif +{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532}, +{1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,-1,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724}, +{1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,-1,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916}, +{1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,-1,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071,2072,2073,2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104,2105,2106,2107,2108}, +{2112,2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125,2126,2127,2128,2129,2130,2131,2132,2133,2134,2135,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145,2146,2147,2148,2149,2150,2151,2152,2153,2154,2155,2156,2157,2158,2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169,2170,2171,2172,2173,2174,-1,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185,2186,2187,2188,2189,2190,2191,2192,2193,2194,2195,2196,2197,2198,2199,2200,2201,2202,2203,2204,2205,2206,2207,2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2220,2221,2222,2223,2224,2225,2226,2227,2228,2229,2230,2231,2232,2233,2234,2235,2236,2237,2238,2239,2240,2241,2242,2243,2244,2245,2246,2247,2248,2249,2250,2251,2252,2253,2254,2255,2256,2257,2258,2259,2260,2261,2262,2263,2264,2265,2266,2267,2268,2269,2270,2271,2272,2273,2274,2275,2276,2277,2278,2279,2280,2281,2282,2283,2284,2285,2286,2287,2288,2289,2290,2291,2292,2293,2294,2295,2296,2297,2298,2299,2300}, +{2304,2305,2306,2307,2308,2309,2310,2311,2312,2313,2314,2315,2316,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2336,2337,2338,2339,2340,2341,2342,2343,2344,2345,2346,2347,2348,2349,2350,2351,2352,2353,2354,2355,2356,2357,2358,2359,2360,2361,2362,2363,2364,2365,2366,-1,2368,2369,2370,2371,2372,2373,2374,2375,2376,2377,2378,2379,2380,2381,2382,2383,2384,2385,2386,2387,2388,2389,2390,2391,2392,2393,2394,2395,2396,2397,2398,2399,2400,2401,2402,2403,2404,2405,2406,2407,2408,2409,2410,2411,2412,2413,2414,2415,2416,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2433,2434,2435,2436,2437,2438,2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453,2454,2455,2456,2457,2458,2459,2460,2461,2462,2463,2464,2465,2466,2467,2468,2469,2470,2471,2472,2473,2474,2475,2476,2477,2478,2479,2480,2481,2482,2483,2484,2485,2486,2487,2488,2489,2490,2491,2492}, +{2496,2497,2498,2499,2500,2501,2502,2503,2504,2505,2506,2507,2508,2509,2510,2511,2512,2513,2514,2515,2516,2517,2518,2519,2520,2521,2522,2523,2524,2525,2526,2527,2528,2529,2530,2531,2532,2533,2534,2535,2536,2537,2538,2539,2540,2541,2542,2543,2544,2545,2546,2547,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,-1,2560,2561,2562,2563,2564,2565,2566,2567,2568,2569,2570,2571,2572,2573,2574,2575,2576,2577,2578,2579,2580,2581,2582,2583,2584,2585,2586,2587,2588,2589,2590,2591,2592,2593,2594,2595,2596,2597,2598,2599,2600,2601,2602,2603,2604,2605,2606,2607,2608,2609,2610,2611,2612,2613,2614,2615,2616,2617,2618,2619,2620,2621,2622,2623,2624,2625,2626,2627,2628,2629,2630,2631,2632,2633,2634,2635,2636,2637,2638,2639,2640,2641,2642,2643,2644,2645,2646,2647,2648,2649,2650,2651,2652,2653,2654,2655,2656,2657,2658,2659,2660,2661,2662,2663,2664,2665,2666,2667,2668,2669,2670,2671,2672,2673,2674,2675,2676,2677,2678,2679,2680,2681,2682,2683,2684}, +{2688,2689,2690,2691,2692,2693,2694,2695,2696,2697,2698,2699,2700,2701,2702,2703,2704,2705,2706,2707,2708,2709,2710,2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722,2723,2724,2725,2726,2727,2728,2729,2730,2731,2732,2733,2734,2735,2736,2737,2738,2739,2740,2741,2742,2743,2744,2745,2746,2747,2748,2749,2750,-1,2752,2753,2754,2755,2756,2757,2758,2759,2760,2761,2762,2763,2764,2765,2766,2767,2768,2769,2770,2771,2772,2773,2774,2775,2776,2777,2778,2779,2780,2781,2782,2783,2784,2785,2786,2787,2788,2789,2790,2791,2792,2793,2794,2795,2796,2797,2798,2799,2800,2801,2802,2803,2804,2805,2806,2807,2808,2809,2810,2811,2812,2813,2814,2815,2816,2817,2818,2819,2820,2821,2822,2823,2824,2825,2826,2827,2828,2829,2830,2831,2832,2833,2834,2835,2836,2837,2838,2839,2840,2841,2842,2843,2844,2845,2846,2847,2848,2849,2850,2851,2852,2853,2854,2855,2856,2857,2858,2859,2860,2861,2862,2863,2864,2865,2866,2867,2868,2869,2870,2871,2872,2873,2874,2875,2876}, +{2880,2881,2882,2883,2884,2885,2886,2887,2888,2889,2890,2891,2892,2893,2894,2895,2896,2897,2898,2899,2900,2901,2902,2903,2904,2905,2906,2907,2908,2909,2910,2911,2912,2913,2914,2915,2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,2931,2932,2933,2934,2935,2936,2937,2938,2939,2940,2941,2942,-1,2944,2945,2946,2947,2948,2949,2950,2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2968,2969,2970,2971,2972,2973,2974,2975,2976,2977,2978,2979,2980,2981,2982,2983,2984,2985,2986,2987,2988,2989,2990,2991,2992,2993,2994,2995,2996,2997,2998,2999,3000,3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068}, +{3072,3073,3074,3075,3076,3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112,3113,3114,3115,3116,3117,3118,3119,3120,3121,3122,3123,3124,3125,3126,3127,3128,3129,3130,3131,3132,3133,3134,-1,3136,3137,3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154,3155,3156,3157,3158,3159,3160,3161,3162,3163,3164,3165,3166,3167,3168,3169,3170,3171,3172,3173,3174,3175,3176,3177,3178,3179,3180,3181,3182,3183,3184,3185,3186,3187,3188,3189,3190,3191,3192,3193,3194,3195,3196,3197,3198,3199,3200,3201,3202,3203,3204,3205,3206,3207,3208,3209,3210,3211,3212,3213,3214,3215,3216,3217,3218,3219,3220,3221,3222,3223,3224,3225,3226,3227,3228,3229,3230,3231,3232,3233,3234,3235,3236,3237,3238,3239,3240,3241,3242,3243,3244,3245,3246,3247,3248,3249,3250,3251,3252,3253,3254,3255,3256,3257,3258,3259,3260}, +{3264,3265,3266,3267,3268,3269,3270,3271,3272,3273,3274,3275,3276,3277,3278,3279,3280,3281,3282,3283,3284,3285,3286,3287,3288,3289,3290,3291,3292,3293,3294,3295,3296,3297,3298,3299,3300,3301,3302,3303,3304,3305,3306,3307,3308,3309,3310,3311,3312,3313,3314,3315,3316,3317,3318,3319,3320,3321,3322,3323,3324,3325,3326,-1,3328,3329,3330,3331,3332,3333,3334,3335,3336,3337,3338,3339,3340,3341,3342,3343,3344,3345,3346,3347,3348,3349,3350,3351,3352,3353,3354,3355,3356,3357,3358,3359,3360,3361,3362,3363,3364,3365,3366,3367,3368,3369,3370,3371,3372,3373,3374,3375,3376,3377,3378,3379,3380,3381,3382,3383,3384,3385,3386,3387,3388,3389,3390,3391,3392,3393,3394,3395,3396,3397,3398,3399,3400,3401,3402,3403,3404,3405,3406,3407,3408,3409,3410,3411,3412,3413,3414,3415,3416,3417,3418,3419,3420,3421,3422,3423,3424,3425,3426,3427,3428,3429,3430,3431,3432,3433,3434,3435,3436,3437,3438,3439,3440,3441,3442,3443,3444,3445,3446,3447,3448,3449,3450,3451,3452}, +{3456,3457,3458,3459,3460,3461,3462,3463,3464,3465,3466,3467,3468,3469,3470,3471,3472,3473,3474,3475,3476,3477,3478,3479,3480,3481,3482,3483,3484,3485,3486,3487,3488,3489,3490,3491,3492,3493,3494,3495,3496,3497,3498,3499,3500,3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,3512,3513,3514,3515,3516,3517,3518,-1,3520,3521,3522,3523,3524,3525,3526,3527,3528,3529,3530,3531,3532,3533,3534,3535,3536,3537,3538,3539,3540,3541,3542,3543,3544,3545,3546,3547,3548,3549,3550,3551,3552,3553,3554,3555,3556,3557,3558,3559,3560,3561,3562,3563,3564,3565,3566,3567,3568,3569,3570,3571,3572,3573,3574,3575,3576,3577,3578,3579,3580,3581,3582,3583,3584,3585,3586,3587,3588,3589,3590,3591,3592,3593,3594,3595,3596,3597,3598,3599,3600,3601,3602,3603,3604,3605,3606,3607,3608,3609,3610,3611,3612,3613,3614,3615,3616,3617,3618,3619,3620,3621,3622,3623,3624,3625,3626,3627,3628,3629,3630,3631,3632,3633,3634,3635,3636,3637,3638,3639,3640,3641,3642,3643,3644}, +{3648,3649,3650,3651,3652,3653,3654,3655,3656,3657,3658,3659,3660,3661,3662,3663,3664,3665,3666,3667,3668,3669,3670,3671,3672,3673,3674,3675,3676,3677,3678,3679,3680,3681,3682,3683,3684,3685,3686,3687,3688,3689,3690,3691,3692,3693,3694,3695,3696,3697,3698,3699,3700,3701,3702,3703,3704,3705,3706,3707,3708,3709,3710,-1,3712,3713,3714,3715,3716,3717,3718,3719,3720,3721,3722,3723,3724,3725,3726,3727,3728,3729,3730,3731,3732,3733,3734,3735,3736,3737,3738,3739,3740,3741,3742,3743,3744,3745,3746,3747,3748,3749,3750,3751,3752,3753,3754,3755,3756,3757,3758,3759,3760,3761,3762,3763,3764,3765,3766,3767,3768,3769,3770,3771,3772,3773,3774,3775,3776,3777,3778,3779,3780,3781,3782,3783,3784,3785,3786,3787,3788,3789,3790,3791,3792,3793,3794,3795,3796,3797,3798,3799,3800,3801,3802,3803,3804,3805,3806,3807,3808,3809,3810,3811,3812,3813,3814,3815,3816,3817,3818,3819,3820,3821,3822,3823,3824,3825,3826,3827,3828,3829,3830,3831,3832,3833,3834,3835,3836}, +{3840,3841,3842,3843,3844,3845,3846,3847,3848,3849,3850,3851,3852,3853,3854,3855,3856,3857,3858,3859,3860,3861,3862,3863,3864,3865,3866,3867,3868,3869,3870,3871,3872,3873,3874,3875,3876,3877,3878,3879,3880,3881,3882,3883,3884,3885,3886,3887,3888,3889,3890,3891,3892,3893,3894,3895,3896,3897,3898,3899,3900,3901,3902,-1,3904,3905,3906,3907,3908,3909,3910,3911,3912,3913,3914,3915,3916,3917,3918,3919,3920,3921,3922,3923,3924,3925,3926,3927,3928,3929,3930,3931,3932,3933,3934,3935,3936,3937,3938,3939,3940,3941,3942,3943,3944,3945,3946,3947,3948,3949,3950,3951,3952,3953,3954,3955,3956,3957,3958,3959,3960,3961,3962,3963,3964,3965,3966,3967,3968,3969,3970,3971,3972,3973,3974,3975,3976,3977,3978,3979,3980,3981,3982,3983,3984,3985,3986,3987,3988,3989,3990,3991,3992,3993,3994,3995,3996,3997,3998,3999,4000,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028}, +{4032,4033,4034,4035,4036,4037,4038,4039,4040,4041,4042,4043,4044,4045,4046,4047,4048,4049,4050,4051,4052,4053,4054,4055,4056,4057,4058,4059,4060,4061,4062,4063,4064,4065,4066,4067,4068,4069,4070,4071,4072,4073,4074,4075,4076,4077,4078,4079,4080,4081,4082,4083,4084,4085,4086,4087,4088,4089,4090,4091,4092,4093,4094,-1,4096,4097,4098,4099,4100,4101,4102,4103,4104,4105,4106,4107,4108,4109,4110,4111,4112,4113,4114,4115,4116,4117,4118,4119,4120,4121,4122,4123,4124,4125,4126,4127,4128,4129,4130,4131,4132,4133,4134,4135,4136,4137,4138,4139,4140,4141,4142,4143,4144,4145,4146,4147,4148,4149,4150,4151,4152,4153,4154,4155,4156,4157,4158,4159,4160,4161,4162,4163,4164,4165,4166,4167,4168,4169,4170,4171,4172,4173,4174,4175,4176,4177,4178,4179,4180,4181,4182,4183,4184,4185,4186,4187,4188,4189,4190,4191,4192,4193,4194,4195,4196,4197,4198,4199,4200,4201,4202,4203,4204,4205,4206,4207,4208,4209,4210,4211,4212,4213,4214,4215,4216,4217,4218,4219,4220}, +{4224,4225,4226,4227,4228,4229,4230,4231,4232,4233,4234,4235,4236,4237,4238,4239,4240,4241,4242,4243,4244,4245,4246,4247,4248,4249,4250,4251,4252,4253,4254,4255,4256,4257,4258,4259,4260,4261,4262,4263,4264,4265,4266,4267,4268,4269,4270,4271,4272,4273,4274,4275,4276,4277,4278,4279,4280,4281,4282,4283,4284,4285,4286,-1,4288,4289,4290,4291,4292,4293,4294,4295,4296,4297,4298,4299,4300,4301,4302,4303,4304,4305,4306,4307,4308,4309,4310,4311,4312,4313,4314,4315,4316,4317,4318,4319,4320,4321,4322,4323,4324,4325,4326,4327,4328,4329,4330,4331,4332,4333,4334,4335,4336,4337,4338,4339,4340,4341,4342,4343,4344,4345,4346,4347,4348,4349,4350,4351,4352,4353,4354,4355,4356,4357,4358,4359,4360,4361,4362,4363,4364,4365,4366,4367,4368,4369,4370,4371,4372,4373,4374,4375,4376,4377,4378,4379,4380,4381,4382,4383,4384,4385,4386,4387,4388,4389,4390,4391,4392,4393,4394,4395,4396,4397,4398,4399,4400,4401,4402,4403,4404,4405,4406,4407,4408,4409,4410,4411,4412}, +{4416,4417,4418,4419,4420,4421,4422,4423,4424,4425,4426,4427,4428,4429,4430,4431,4432,4433,4434,4435,4436,4437,4438,4439,4440,4441,4442,4443,4444,4445,4446,4447,4448,4449,4450,4451,4452,4453,4454,4455,4456,4457,4458,4459,4460,4461,4462,4463,4464,4465,4466,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,4511,4512,4513,4514,4515,4516,4517,4518,4519,4520,4521,4522,4523,4524,4525,4526,4527,4528,4529,4530,4531,4532,4533,4534,4535,4536,4537,4538,4539,4540,4541,4542,4543,4544,4545,4546,4547,4548,4549,4550,4551,4552,4553,4554,4555,4556,4557,4558,4559,4560,4561,4562,4563,4564,4565,4566,4567,4568,4569,4570,4571,4572,4573,4574,4575,4576,4577,4578,4579,4580,4581,4582,4583,4584,4585,4586,4587,4588,4589,4590,4591,4592,4593,4594,4595,4596,4597,4598,4599,4600,4601,4602,4603,4604}, +{4608,4609,4610,4611,4612,4613,4614,4615,4616,4617,4618,4619,4620,4621,4622,4623,4624,4625,4626,4627,4628,4629,4630,4631,4632,4633,4634,4635,4636,4637,4638,4639,4640,4641,4642,4643,4644,4645,4646,4647,4648,4649,4650,4651,4652,4653,4654,4655,4656,4657,4658,4659,4660,4661,4662,4663,4664,4665,4666,4667,4668,4669,4670,-1,4672,4673,4674,4675,4676,4677,4678,4679,4680,4681,4682,4683,4684,4685,4686,4687,4688,4689,4690,4691,4692,4693,4694,4695,4696,4697,4698,4699,4700,4701,4702,4703,4704,4705,4706,4707,4708,4709,4710,4711,4712,4713,4714,4715,4716,4717,4718,4719,4720,4721,4722,4723,4724,4725,4726,4727,4728,4729,4730,4731,4732,4733,4734,4735,4736,4737,4738,4739,4740,4741,4742,4743,4744,4745,4746,4747,4748,4749,4750,4751,4752,4753,4754,4755,4756,4757,4758,4759,4760,4761,4762,4763,4764,4765,4766,4767,4768,4769,4770,4771,4772,4773,4774,4775,4776,4777,4778,4779,4780,4781,4782,4783,4784,4785,4786,4787,4788,4789,4790,4791,4792,4793,4794,4795,4796}, +{4800,4801,4802,4803,4804,4805,4806,4807,4808,4809,4810,4811,4812,4813,4814,4815,4816,4817,4818,4819,4820,4821,4822,4823,4824,4825,4826,4827,4828,4829,4830,4831,4832,4833,4834,4835,4836,4837,4838,4839,4840,4841,4842,4843,4844,4845,4846,4847,4848,4849,4850,4851,4852,4853,4854,4855,4856,4857,4858,4859,4860,4861,4862,-1,4864,4865,4866,4867,4868,4869,4870,4871,4872,4873,4874,4875,4876,4877,4878,4879,4880,4881,4882,4883,4884,4885,4886,4887,4888,4889,4890,4891,4892,4893,4894,4895,4896,4897,4898,4899,4900,4901,4902,4903,4904,4905,4906,4907,4908,4909,4910,4911,4912,4913,4914,4915,4916,4917,4918,4919,4920,4921,4922,4923,4924,4925,4926,4927,4928,4929,4930,4931,4932,4933,4934,4935,4936,4937,4938,4939,4940,4941,4942,4943,4944,4945,4946,4947,4948,4949,4950,4951,4952,4953,4954,4955,4956,4957,4958,4959,4960,4961,4962,4963,4964,4965,4966,4967,4968,4969,4970,4971,4972,4973,4974,4975,4976,4977,4978,4979,4980,4981,4982,4983,4984,4985,4986,4987,4988}, +{4992,4993,4994,4995,4996,4997,4998,4999,5000,5001,5002,5003,5004,5005,5006,5007,5008,5009,5010,5011,5012,5013,5014,5015,5016,5017,5018,5019,5020,5021,5022,5023,5024,5025,5026,5027,5028,5029,5030,5031,5032,5033,5034,5035,5036,5037,5038,5039,5040,5041,5042,5043,5044,5045,5046,5047,5048,5049,5050,5051,5052,5053,5054,-1,5056,5057,5058,5059,5060,5061,5062,5063,5064,5065,5066,5067,5068,5069,5070,5071,5072,5073,5074,5075,5076,5077,5078,5079,5080,5081,5082,5083,5084,5085,5086,5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102,5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,5113,5114,5115,5116,5117,5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133,5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149,5150,5151,5152,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164,5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,5176,5177,5178,5179,5180}, +{5184,5185,5186,5187,5188,5189,5190,5191,5192,5193,5194,5195,5196,5197,5198,5199,5200,5201,5202,5203,5204,5205,5206,5207,5208,5209,5210,5211,5212,5213,5214,5215,5216,5217,5218,5219,5220,5221,5222,5223,5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,5237,5238,5239,5240,5241,5242,5243,5244,5245,5246,-1,5248,5249,5250,5251,5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267,5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283,5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,5296,5297,5298,5299,5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315,5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331,5332,5333,5334,5335,5336,5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347,5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363,5364,5365,5366,5367,5368,5369,5370,5371,5372}, +{5376,5377,5378,5379,5380,5381,5382,5383,5384,5385,5386,5387,5388,5389,5390,5391,5392,5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408,5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424,5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,-1,5440,5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456,5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472,5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488,5489,5490,5491,5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504,5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520,5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536,5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552,5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564}, +{5568,5569,5570,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584,5585,5586,5587,5588,5589,5590,5591,5592,5593,5594,5595,5596,5597,5598,5599,5600,5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616,5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,-1,5632,5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648,5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664,5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680,5681,5682,5683,5684,5685,5686,5687,5688,5689,5690,5691,5692,5693,5694,5695,5696,5697,5698,5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711,5712,5713,5714,5715,5716,5717,5718,5719,5720,5721,5722,5723,5724,5725,5726,5727,5728,5729,5730,5731,5732,5733,5734,5735,5736,5737,5738,5739,5740,5741,5742,5743,5744,5745,5746,5747,5748,5749,5750,5751,5752,5753,5754,5755,5756}, +{5760,5761,5762,5763,5764,5765,5766,5767,5768,5769,5770,5771,5772,5773,5774,5775,5776,5777,5778,5779,5780,5781,5782,5783,5784,5785,5786,5787,5788,5789,5790,5791,5792,5793,5794,5795,5796,5797,5798,5799,5800,5801,5802,5803,5804,5805,5806,5807,5808,5809,5810,5811,5812,5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,-1,5824,5825,5826,5827,5828,5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840,5841,5842,5843,5844,5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856,5857,5858,5859,5860,5861,5862,5863,5864,5865,5866,5867,5868,5869,5870,5871,5872,5873,5874,5875,5876,5877,5878,5879,5880,5881,5882,5883,5884,5885,5886,5887,5888,5889,5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904,5905,5906,5907,5908,5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920,5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936,5937,5938,5939,5940,5941,5942,5943,5944,5945,5946,5947,5948}, +{5952,5953,5954,5955,5956,5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968,5969,5970,5971,5972,5973,5974,5975,5976,5977,5978,5979,5980,5981,5982,5983,5984,5985,5986,5987,5988,5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000,6001,6002,6003,6004,6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,-1,6016,6017,6018,6019,6020,6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032,6033,6034,6035,6036,6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048,6049,6050,6051,6052,6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064,6065,6066,6067,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080,6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096,6097,6098,6099,6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112,6113,6114,6115,6116,6117,6118,6119,6120,6121,6122,6123,6124,6125,6126,6127,6128,6129,6130,6131,6132,6133,6134,6135,6136,6137,6138,6139,6140}, +{6144,6145,6146,6147,6148,6149,6150,6151,6152,6153,6154,6155,6156,6157,6158,6159,6160,6161,6162,6163,6164,6165,6166,6167,6168,6169,6170,6171,6172,6173,6174,6175,6176,6177,6178,6179,6180,6181,6182,6183,6184,6185,6186,6187,6188,6189,6190,6191,6192,6193,6194,6195,6196,6197,6198,6199,6200,6201,6202,6203,6204,6205,6206,-1,6208,6209,6210,6211,6212,6213,6214,6215,6216,6217,6218,6219,6220,6221,6222,6223,6224,6225,6226,6227,6228,6229,6230,6231,6232,6233,6234,6235,6236,6237,6238,6239,6240,6241,6242,6243,6244,6245,6246,6247,6248,6249,6250,6251,6252,6253,6254,6255,6256,6257,6258,6259,6260,6261,6262,6263,6264,6265,6266,6267,6268,6269,6270,6271,6272,6273,6274,6275,6276,6277,6278,6279,6280,6281,6282,6283,6284,6285,6286,6287,6288,6289,6290,6291,6292,6293,6294,6295,6296,6297,6298,6299,6300,6301,6302,6303,6304,6305,6306,6307,6308,6309,6310,6311,6312,6313,6314,6315,6316,6317,6318,6319,6320,6321,6322,6323,6324,6325,6326,6327,6328,6329,6330,6331,6332}, +{6336,6337,6338,6339,6340,6341,6342,6343,6344,6345,6346,6347,6348,6349,6350,6351,6352,6353,6354,6355,6356,6357,6358,6359,6360,6361,6362,6363,6364,6365,6366,6367,6368,6369,6370,6371,6372,6373,6374,6375,6376,6377,6378,6379,6380,6381,6382,6383,6384,6385,6386,6387,6388,6389,6390,6391,6392,6393,6394,6395,6396,6397,6398,-1,6400,6401,6402,6403,6404,6405,6406,6407,6408,6409,6410,6411,6412,6413,6414,6415,6416,6417,6418,6419,6420,6421,6422,6423,6424,6425,6426,6427,6428,6429,6430,6431,6432,6433,6434,6435,6436,6437,6438,6439,6440,6441,6442,6443,6444,6445,6446,6447,6448,6449,6450,6451,6452,6453,6454,6455,6456,6457,6458,6459,6460,6461,6462,6463,6464,6465,6466,6467,6468,6469,6470,6471,6472,6473,6474,6475,6476,6477,6478,6479,6480,6481,6482,6483,6484,6485,6486,6487,6488,6489,6490,6491,6492,6493,6494,6495,6496,6497,6498,6499,6500,6501,6502,6503,6504,6505,6506,6507,6508,6509,6510,6511,6512,6513,6514,6515,6516,6517,6518,6519,6520,6521,6522,6523,6524}, +{6528,6529,6530,6531,6532,6533,6534,6535,6536,6537,6538,6539,6540,6541,6542,6543,6544,6545,6546,6547,6548,6549,6550,6551,6552,6553,6554,6555,6556,6557,6558,6559,6560,6561,6562,6563,6564,6565,6566,6567,6568,6569,6570,6571,6572,6573,6574,6575,6576,6577,6578,6579,6580,6581,6582,6583,6584,6585,6586,6587,6588,6589,6590,-1,6592,6593,6594,6595,6596,6597,6598,6599,6600,6601,6602,6603,6604,6605,6606,6607,6608,6609,6610,6611,6612,6613,6614,6615,6616,6617,6618,6619,6620,6621,6622,6623,6624,6625,6626,6627,6628,6629,6630,6631,6632,6633,6634,6635,6636,6637,6638,6639,6640,6641,6642,6643,6644,6645,6646,6647,6648,6649,6650,6651,6652,6653,6654,6655,6656,6657,6658,6659,6660,6661,6662,6663,6664,6665,6666,6667,6668,6669,6670,6671,6672,6673,6674,6675,6676,6677,6678,6679,6680,6681,6682,6683,6684,6685,6686,6687,6688,6689,6690,6691,6692,6693,6694,6695,6696,6697,6698,6699,6700,6701,6702,6703,6704,6705,6706,6707,6708,6709,6710,6711,6712,6713,6714,6715,6716}, +{6720,6721,6722,6723,6724,6725,6726,6727,6728,6729,6730,6731,6732,6733,6734,6735,6736,6737,6738,6739,6740,6741,6742,6743,6744,6745,6746,6747,6748,6749,6750,6751,6752,6753,6754,6755,6756,6757,6758,6759,6760,6761,6762,6763,6764,6765,6766,6767,6768,6769,6770,6771,6772,6773,6774,6775,6776,6777,6778,6779,6780,6781,6782,-1,6784,6785,6786,6787,6788,6789,6790,6791,6792,6793,6794,6795,6796,6797,6798,6799,6800,6801,6802,6803,6804,6805,6806,6807,6808,6809,6810,6811,6812,6813,6814,6815,6816,6817,6818,6819,6820,6821,6822,6823,6824,6825,6826,6827,6828,6829,6830,6831,6832,6833,6834,6835,6836,6837,6838,6839,6840,6841,6842,6843,6844,6845,6846,6847,6848,6849,6850,6851,6852,6853,6854,6855,6856,6857,6858,6859,6860,6861,6862,6863,6864,6865,6866,6867,6868,6869,6870,6871,6872,6873,6874,6875,6876,6877,6878,6879,6880,6881,6882,6883,6884,6885,6886,6887,6888,6889,6890,6891,6892,6893,6894,6895,6896,6897,6898,6899,6900,6901,6902,6903,6904,6905,6906,6907,6908}, +{6912,6913,6914,6915,6916,6917,6918,6919,6920,6921,6922,6923,6924,6925,6926,6927,6928,6929,6930,6931,6932,6933,6934,6935,6936,6937,6938,6939,6940,6941,6942,6943,6944,6945,6946,6947,6948,6949,6950,6951,6952,6953,6954,6955,6956,6957,6958,6959,6960,6961,6962,6963,6964,6965,6966,6967,6968,6969,6970,6971,6972,6973,6974,-1,6976,6977,6978,6979,6980,6981,6982,6983,6984,6985,6986,6987,6988,6989,6990,6991,6992,6993,6994,6995,6996,6997,6998,6999,7000,7001,7002,7003,7004,7005,7006,7007,7008,7009,7010,7011,7012,7013,7014,7015,7016,7017,7018,7019,7020,7021,7022,7023,7024,7025,7026,7027,7028,7029,7030,7031,7032,7033,7034,7035,7036,7037,7038,7039,7040,7041,7042,7043,7044,7045,7046,7047,7048,7049,7050,7051,7052,7053,7054,7055,7056,7057,7058,7059,7060,7061,7062,7063,7064,7065,7066,7067,7068,7069,7070,7071,7072,7073,7074,7075,7076,7077,7078,7079,7080,7081,7082,7083,7084,7085,7086,7087,7088,7089,7090,7091,7092,7093,7094,7095,7096,7097,7098,7099,7100}, +{7104,7105,7106,7107,7108,7109,7110,7111,7112,7113,7114,7115,7116,7117,7118,7119,7120,7121,7122,7123,7124,7125,7126,7127,7128,7129,7130,7131,7132,7133,7134,7135,7136,7137,7138,7139,7140,7141,7142,7143,7144,7145,7146,7147,7148,7149,7150,7151,7152,7153,7154,7155,7156,7157,7158,7159,7160,7161,7162,7163,7164,7165,7166,-1,7168,7169,7170,7171,7172,7173,7174,7175,7176,7177,7178,7179,7180,7181,7182,7183,7184,7185,7186,7187,7188,7189,7190,7191,7192,7193,7194,7195,7196,7197,7198,7199,7200,7201,7202,7203,7204,7205,7206,7207,7208,7209,7210,7211,7212,7213,7214,7215,7216,7217,7218,7219,7220,7221,7222,7223,7224,7225,7226,7227,7228,7229,7230,7231,7232,7233,7234,7235,7236,7237,7238,7239,7240,7241,7242,7243,7244,7245,7246,7247,7248,7249,7250,7251,7252,7253,7254,7255,7256,7257,7258,7259,7260,7261,7262,7263,7264,7265,7266,7267,7268,7269,7270,7271,7272,7273,7274,7275,7276,7277,7278,7279,7280,7281,7282,7283,7284,7285,7286,7287,7288,7289,7290,7291,7292}, +{7296,7297,7298,7299,7300,7301,7302,7303,7304,7305,7306,7307,7308,7309,7310,7311,7312,7313,7314,7315,7316,7317,7318,7319,7320,7321,7322,7323,7324,7325,7326,7327,7328,7329,7330,7331,7332,7333,7334,7335,7336,7337,7338,7339,7340,7341,7342,7343,7344,7345,7346,7347,7348,7349,7350,7351,7352,7353,7354,7355,7356,7357,7358,-1,7360,7361,7362,7363,7364,7365,7366,7367,7368,7369,7370,7371,7372,7373,7374,7375,7376,7377,7378,7379,7380,7381,7382,7383,7384,7385,7386,7387,7388,7389,7390,7391,7392,7393,7394,7395,7396,7397,7398,7399,7400,7401,7402,7403,7404,7405,7406,7407,7408,7409,7410,7411,7412,7413,7414,7415,7416,7417,7418,7419,7420,7421,7422,7423,7424,7425,7426,7427,7428,7429,7430,7431,7432,7433,7434,7435,7436,7437,7438,7439,7440,7441,7442,7443,7444,7445,7446,7447,7448,7449,7450,7451,7452,7453,7454,7455,7456,7457,7458,7459,7460,7461,7462,7463,7464,7465,7466,7467,7468,7469,7470,7471,7472,7473,7474,7475,7476,7477,7478,7479,7480,7481,7482,7483,7484}, +{7488,7489,7490,7491,7492,7493,7494,7495,7496,7497,7498,7499,7500,7501,7502,7503,7504,7505,7506,7507,7508,7509,7510,7511,7512,7513,7514,7515,7516,7517,7518,7519,7520,7521,7522,7523,7524,7525,7526,7527,7528,7529,7530,7531,7532,7533,7534,7535,7536,7537,7538,7539,7540,7541,7542,7543,7544,7545,7546,7547,7548,7549,7550,-1,7552,7553,7554,7555,7556,7557,7558,7559,7560,7561,7562,7563,7564,7565,7566,7567,7568,7569,7570,7571,7572,7573,7574,7575,7576,7577,7578,7579,7580,7581,7582,7583,7584,7585,7586,7587,7588,7589,7590,7591,7592,7593,7594,7595,7596,7597,7598,7599,7600,7601,7602,7603,7604,7605,7606,7607,7608,7609,7610,7611,7612,7613,7614,7615,7616,7617,7618,7619,7620,7621,7622,7623,7624,7625,7626,7627,7628,7629,7630,7631,7632,7633,7634,7635,7636,7637,7638,7639,7640,7641,7642,7643,7644,7645,7646,7647,7648,7649,7650,7651,7652,7653,7654,7655,7656,7657,7658,7659,7660,7661,7662,7663,7664,7665,7666,7667,7668,7669,7670,7671,7672,7673,7674,7675,7676}, +{7680,7681,7682,7683,7684,7685,7686,7687,7688,7689,7690,7691,7692,7693,7694,7695,7696,7697,7698,7699,7700,7701,7702,7703,7704,7705,7706,7707,7708,7709,7710,7711,7712,7713,7714,7715,7716,7717,7718,7719,7720,7721,7722,7723,7724,7725,7726,7727,7728,7729,7730,7731,7732,7733,7734,7735,7736,7737,7738,7739,7740,7741,7742,-1,7744,7745,7746,7747,7748,7749,7750,7751,7752,7753,7754,7755,7756,7757,7758,7759,7760,7761,7762,7763,7764,7765,7766,7767,7768,7769,7770,7771,7772,7773,7774,7775,7776,7777,7778,7779,7780,7781,7782,7783,7784,7785,7786,7787,7788,7789,7790,7791,7792,7793,7794,7795,7796,7797,7798,7799,7800,7801,7802,7803,7804,7805,7806,7807,7808,7809,7810,7811,7812,7813,7814,7815,7816,7817,7818,7819,7820,7821,7822,7823,7824,7825,7826,7827,7828,7829,7830,7831,7832,7833,7834,7835,7836,7837,7838,7839,7840,7841,7842,7843,7844,7845,7846,7847,7848,7849,7850,7851,7852,7853,7854,7855,7856,7857,7858,7859,7860,7861,7862,7863,7864,7865,7866,7867,7868}, +{7872,7873,7874,7875,7876,7877,7878,7879,7880,7881,7882,7883,7884,7885,7886,7887,7888,7889,7890,7891,7892,7893,7894,7895,7896,7897,7898,7899,7900,7901,7902,7903,7904,7905,7906,7907,7908,7909,7910,7911,7912,7913,7914,7915,7916,7917,7918,7919,7920,7921,7922,7923,7924,7925,7926,7927,7928,7929,7930,7931,7932,7933,7934,-1,7936,7937,7938,7939,7940,7941,7942,7943,7944,7945,7946,7947,7948,7949,7950,7951,7952,7953,7954,7955,7956,7957,7958,7959,7960,7961,7962,7963,7964,7965,7966,7967,7968,7969,7970,7971,7972,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1} +}; diff --git a/support/sg2002/kvm_system/main/lib/libqr/qr_private.h b/support/sg2002/kvm_system/main/lib/libqr/qr_private.h new file mode 100644 index 0000000..ce38775 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/qr_private.h @@ -0,0 +1,512 @@ +/* + * QR Code Generator Library: Private Definitions + * + * Core routines were originally written by Junn Ohta. + * Based on qr.c Version 0.1: 2004/4/3 (Public Domain) + * + * @package libqr + * @author Ryusuke SEKIYAMA + * @copyright 2006-2013 Ryusuke SEKIYAMA + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +#ifndef _QR_PRIVATE_H_ +#define _QR_PRIVATE_H_ + +#include "qr.h" +#include +#include + +/* + * Booblean + */ +#ifdef TRUE +#undef TRUE +#endif +#ifdef FALSE +#undef FALSE +#endif +#define TRUE 1 +#define FALSE 0 + +/* + * 型番データ表 + */ +const qr_vertable_t qr_vertable[QR_VER_MAX+1] = { + { NAV, NAV, NAV, NAV, { 0, 0, 0, 0 }, + {{ NAV, { 0, 0, 0, 0 }, NAV, {{ 0, 0, 0, 0 }}}, + { NAV, { 0, 0, 0, 0 }, NAV, {{ 0, 0, 0, 0 }}}, + { NAV, { 0, 0, 0, 0 }, NAV, {{ 0, 0, 0, 0 }}}, + { NAV, { 0, 0, 0, 0 }, NAV, {{ 0, 0, 0, 0 }}}}, + NAV, { NAV, NAV }}, + { 1, 21, 26, 0, { 10, 9, 8, 8 }, + {{ 19, { 41, 25, 17, 10 }, 1, {{ 1, 26, 19, 2 }}}, + { 16, { 34, 20, 14, 8 }, 1, {{ 1, 26, 16, 4 }}}, + { 13, { 27, 16, 11, 7 }, 1, {{ 1, 26, 13, 6 }}}, + { 9, { 17, 10, 7, 4 }, 1, {{ 1, 26, 9, 8 }}}}, + 0, { NAV, NAV }}, + { 2, 25, 44, 7, { 10, 9, 8, 8 }, + {{ 34, { 77, 47, 32, 20 }, 1, {{ 1, 44, 34, 4 }}}, + { 28, { 63, 38, 26, 16 }, 1, {{ 1, 44, 28, 8 }}}, + { 22, { 48, 29, 20, 12 }, 1, {{ 1, 44, 22, 11 }}}, + { 16, { 34, 20, 14, 8 }, 1, {{ 1, 44, 16, 14 }}}}, + 2, { 6, 18 }}, + { 3, 29, 70, 7, { 10, 9, 8, 8 }, + {{ 55, { 127, 77, 53, 32 }, 1, {{ 1, 70, 55, 7 }}}, + { 44, { 101, 61, 42, 26 }, 1, {{ 1, 70, 44, 13 }}}, + { 34, { 77, 47, 32, 20 }, 1, {{ 2, 35, 17, 9 }}}, + { 26, { 58, 35, 24, 15 }, 1, {{ 2, 35, 13, 11 }}}}, + 2, { 6, 22 }}, + { 4, 33, 100, 7, { 10, 9, 8, 8 }, + {{ 80, { 187, 114, 78, 48 }, 1, {{ 1, 100, 80, 10 }}}, + { 64, { 149, 90, 62, 38 }, 1, {{ 2, 50, 32, 9 }}}, + { 48, { 111, 67, 46, 28 }, 1, {{ 2, 50, 24, 13 }}}, + { 36, { 82, 50, 34, 21 }, 1, {{ 4, 25, 9, 8 }}}}, + 2, { 6, 26 }}, + { 5, 37, 134, 7, { 10, 9, 8, 8 }, + {{ 108, { 255, 154, 106, 65 }, 1, {{ 1, 134, 108, 13 }}}, + { 86, { 202, 122, 84, 52 }, 1, {{ 2, 67, 43, 12 }}}, + { 62, { 144, 87, 60, 37 }, 2, {{ 2, 33, 15, 9 }, { 2, 34, 16, 9 }}}, + { 46, { 106, 64, 44, 27 }, 2, {{ 2, 33, 11, 11 }, { 2, 34, 12, 11 }}}}, + 2, { 6, 30 }}, + { 6, 41, 172, 7, { 10, 9, 8, 8 }, + {{ 136, { 322, 195, 134, 82 }, 1, {{ 2, 86, 68, 9 }}}, + { 108, { 255, 154, 106, 65 }, 1, {{ 4, 43, 27, 8 }}}, + { 76, { 178, 108, 74, 45 }, 1, {{ 4, 43, 19, 12 }}}, + { 60, { 139, 84, 58, 36 }, 1, {{ 4, 43, 15, 14 }}}}, + 2, { 6, 34 }}, + { 7, 45, 196, 0, { 10, 9, 8, 8 }, + {{ 156, { 370, 224, 154, 95 }, 1, {{ 2, 98, 78, 10 }}}, + { 124, { 293, 178, 122, 75 }, 1, {{ 4, 49, 31, 9 }}}, + { 88, { 207, 125, 86, 53 }, 2, {{ 2, 32, 14, 9 }, { 4, 33, 15, 9 }}}, + { 66, { 154, 93, 64, 39 }, 2, {{ 4, 39, 13, 13 }, { 1, 40, 14, 13 }}}}, + 3, { 6, 22, 38 }}, + { 8, 49, 242, 0, { 10, 9, 8, 8 }, + {{ 194, { 461, 279, 192, 118 }, 1, {{ 2, 121, 97, 12 }}}, + { 154, { 365, 221, 152, 93 }, 2, {{ 2, 60, 38, 11 }, { 2, 61, 39, 11 }}}, + { 110, { 259, 157, 108, 66 }, 2, {{ 4, 40, 18, 11 }, { 2, 41, 19, 11 }}}, + { 86, { 202, 122, 84, 52 }, 2, {{ 4, 40, 14, 13 }, { 2, 41, 15, 13 }}}}, + 3, { 6, 24, 42 }}, + { 9, 53, 292, 0, { 10, 9, 8, 8 }, + {{ 232, { 552, 335, 230, 141 }, 1, {{ 2, 146, 116, 15 }}}, + { 182, { 432, 262, 180, 111 }, 2, {{ 3, 58, 36, 11 }, { 2, 59, 37, 11 }}}, + { 132, { 312, 189, 130, 80 }, 2, {{ 4, 36, 16, 10 }, { 4, 37, 17, 10 }}}, + { 100, { 235, 143, 98, 60 }, 2, {{ 4, 36, 12, 12 }, { 4, 37, 13, 12 }}}}, + 3, { 6, 26, 46 }}, + { 10, 57, 346, 0, { 12, 11, 16, 10 }, + {{ 274, { 652, 395, 271, 167 }, 2, {{ 2, 86, 68, 9 }, { 2, 87, 69, 9 }}}, + { 216, { 513, 311, 213, 131 }, 2, {{ 4, 69, 43, 13 }, { 1, 70, 44, 13 }}}, + { 154, { 364, 221, 151, 93 }, 2, {{ 6, 43, 19, 12 }, { 2, 44, 20, 12 }}}, + { 122, { 288, 174, 119, 74 }, 2, {{ 6, 43, 15, 14 }, { 2, 44, 16, 14 }}}}, + 3, { 6, 28, 50 }}, + { 11, 61, 404, 0, { 12, 11, 16, 10 }, + {{ 324, { 772, 468, 321, 198 }, 1, {{ 4, 101, 81, 10 }}}, + { 254, { 604, 366, 251, 155 }, 2, {{ 1, 80, 50, 15 }, { 4, 81, 51, 15 }}}, + { 180, { 427, 259, 177, 109 }, 2, {{ 4, 50, 22, 14 }, { 4, 51, 23, 14 }}}, + { 140, { 331, 200, 137, 85 }, 2, {{ 3, 36, 12, 12 }, { 8, 37, 13, 12 }}}}, + 3, { 6, 30, 54 }}, + { 12, 65, 466, 0, { 12, 11, 16, 10 }, + {{ 370, { 883, 535, 367, 226 }, 2, {{ 2, 116, 92, 12 }, { 2, 117, 93, 12 }}}, + { 290, { 691, 419, 287, 177 }, 2, {{ 6, 58, 36, 11 }, { 2, 59, 37, 11 }}}, + { 206, { 489, 296, 203, 125 }, 2, {{ 4, 46, 20, 13 }, { 6, 47, 21, 13 }}}, + { 158, { 374, 227, 155, 96 }, 2, {{ 7, 42, 14, 14 }, { 4, 43, 15, 14 }}}}, + 3, { 6, 32, 58 }}, + { 13, 69, 532, 0, { 12, 11, 16, 10 }, + {{ 428, { 1022, 619, 425, 262 }, 1, {{ 4, 133, 107, 13 }}}, + { 334, { 796, 483, 331, 204 }, 2, {{ 8, 59, 37, 11 }, { 1, 60, 38, 11 }}}, + { 244, { 580, 352, 241, 149 }, 2, {{ 8, 44, 20, 12 }, { 4, 45, 21, 12 }}}, + { 180, { 427, 259, 177, 109 }, 2, {{ 12, 33, 11, 11 }, { 4, 34, 12, 11 }}}}, + 3, { 6, 34, 62 }}, + { 14, 73, 581, 3, { 12, 11, 16, 10 }, + {{ 461, { 1101, 667, 458, 282 }, 2, {{ 3, 145, 115, 15 }, { 1, 146, 116, 15 }}}, + { 365, { 871, 528, 362, 223 }, 2, {{ 4, 64, 40, 12 }, { 5, 65, 41, 12 }}}, + { 261, { 621, 376, 258, 159 }, 2, {{ 11, 36, 16, 10 }, { 5, 37, 17, 10 }}}, + { 197, { 468, 283, 194, 120 }, 2, {{ 11, 36, 12, 12 }, { 5, 37, 13, 12 }}}}, + 4, { 6, 26, 46, 66 }}, + { 15, 77, 655, 3, { 12, 11, 16, 10 }, + {{ 523, { 1250, 758, 520, 320 }, 2, {{ 5, 109, 87, 11 }, { 1, 110, 88, 11 }}}, + { 415, { 991, 600, 412, 254 }, 2, {{ 5, 65, 41, 12 }, { 5, 66, 42, 12 }}}, + { 295, { 703, 426, 292, 180 }, 2, {{ 5, 54, 24, 15 }, { 7, 55, 25, 15 }}}, + { 223, { 530, 321, 220, 136 }, 2, {{ 11, 36, 12, 12 }, { 7, 37, 13, 12 }}}}, + 4, { 6, 26, 48, 70 }}, + { 16, 81, 733, 3, { 12, 11, 16, 10 }, + {{ 589, { 1408, 854, 586, 361 }, 2, {{ 5, 122, 98, 12 }, { 1, 123, 99, 12 }}}, + { 453, { 1082, 656, 450, 277 }, 2, {{ 7, 73, 45, 14 }, { 3, 74, 46, 14 }}}, + { 325, { 775, 470, 322, 198 }, 2, {{ 15, 43, 19, 12 }, { 2, 44, 20, 12 }}}, + { 253, { 602, 365, 250, 154 }, 2, {{ 3, 45, 15, 15 }, { 13, 46, 16, 15 }}}}, + 4, { 6, 26, 50, 74 }}, + { 17, 85, 815, 3, { 12, 11, 16, 10 }, + {{ 647, { 1548, 938, 644, 397 }, 2, {{ 1, 135, 107, 14 }, { 5, 136, 108, 14 }}}, + { 507, { 1212, 734, 504, 310 }, 2, {{ 10, 74, 46, 14 }, { 1, 75, 47, 14 }}}, + { 367, { 876, 531, 364, 224 }, 2, {{ 1, 50, 22, 14 }, { 15, 51, 23, 14 }}}, + { 283, { 674, 408, 280, 173 }, 2, {{ 2, 42, 14, 14 }, { 17, 43, 15, 14 }}}}, + 4, { 6, 30, 54, 78 }}, + { 18, 89, 901, 3, { 12, 11, 16, 10 }, + {{ 721, { 1725, 1046, 718, 442 }, 2, {{ 5, 150, 120, 15 }, { 1, 151, 121, 15 }}}, + { 563, { 1346, 816, 560, 345 }, 2, {{ 9, 69, 43, 13 }, { 4, 70, 44, 13 }}}, + { 397, { 948, 574, 394, 243 }, 2, {{ 17, 50, 22, 14 }, { 1, 51, 23, 14 }}}, + { 313, { 746, 452, 310, 191 }, 2, {{ 2, 42, 14, 14 }, { 19, 43, 15, 14 }}}}, + 4, { 6, 30, 56, 82 }}, + { 19, 93, 991, 3, { 12, 11, 16, 10 }, + {{ 795, { 1903, 1153, 792, 488 }, 2, {{ 3, 141, 113, 14 }, { 4, 142, 114, 14 }}}, + { 627, { 1500, 909, 624, 384 }, 2, {{ 3, 70, 44, 13 }, { 11, 71, 45, 13 }}}, + { 445, { 1063, 644, 442, 272 }, 2, {{ 17, 47, 21, 13 }, { 4, 48, 22, 13 }}}, + { 341, { 813, 493, 338, 208 }, 2, {{ 9, 39, 13, 13 }, { 16, 40, 14, 13 }}}}, + 4, { 6, 30, 58, 86 }}, + { 20, 97, 1085, 3, { 12, 11, 16, 10 }, + {{ 861, { 2061, 1249, 858, 528 }, 2, {{ 3, 135, 107, 14 }, { 5, 136, 108, 14 }}}, + { 669, { 1600, 970, 666, 410 }, 2, {{ 3, 67, 41, 13 }, { 13, 68, 42, 13 }}}, + { 485, { 1159, 702, 482, 297 }, 2, {{ 15, 54, 24, 15 }, { 5, 55, 25, 15 }}}, + { 385, { 919, 557, 382, 235 }, 2, {{ 15, 43, 15, 14 }, { 10, 44, 16, 14 }}}}, + 4, { 6, 34, 62, 90 }}, + { 21, 101, 1156, 4, { 12, 11, 16, 10 }, + {{ 932, { 2232, 1352, 929, 572 }, 2, {{ 4, 144, 116, 14 }, { 4, 145, 117, 14 }}}, + { 714, { 1708, 1035, 711, 438 }, 1, {{ 17, 68, 42, 13 }}}, + { 512, { 1224, 742, 509, 314 }, 2, {{ 17, 50, 22, 14 }, { 6, 51, 23, 14 }}}, + { 406, { 969, 587, 403, 248 }, 2, {{ 19, 46, 16, 15 }, { 6, 47, 17, 15 }}}}, + 5, { 6, 28, 50, 72, 94 }}, + { 22, 105, 1258, 4, { 12, 11, 16, 10 }, + {{ 1006, { 2409, 1460, 1003, 618 }, 2, {{ 2, 139, 111, 14 }, { 7, 140, 112, 14 }}}, + { 782, { 1872, 1134, 779, 480 }, 1, {{ 17, 74, 46, 14 }}}, + { 568, { 1358, 823, 565, 348 }, 2, {{ 7, 54, 24, 15 }, { 16, 55, 25, 15 }}}, + { 442, { 1056, 640, 439, 270 }, 1, {{ 34, 37, 13, 13 }}}}, + 5, { 6, 26, 50, 74, 98 }}, + { 23, 109, 1364, 4, { 12, 11, 16, 10 }, + {{ 1094, { 2620, 1588, 1091, 672 }, 2, {{ 4, 151, 121, 15 }, { 5, 152, 122, 15 }}}, + { 860, { 2059, 1248, 857, 528 }, 2, {{ 4, 75, 47, 14 }, { 14, 76, 48, 14 }}}, + { 614, { 1468, 890, 611, 376 }, 2, {{ 11, 54, 24, 15 }, { 14, 55, 25, 15 }}}, + { 464, { 1108, 672, 461, 284 }, 2, {{ 16, 45, 15, 15 }, { 14, 46, 16, 15 }}}}, + 5, { 6, 30, 54, 78, 102 }}, + { 24, 113, 1474, 4, { 12, 11, 16, 10 }, + {{ 1174, { 2812, 1704, 1171, 721 }, 2, {{ 6, 147, 117, 15 }, { 4, 148, 118, 15 }}}, + { 914, { 2188, 1326, 911, 561 }, 2, {{ 6, 73, 45, 14 }, { 14, 74, 46, 14 }}}, + { 664, { 1588, 963, 661, 407 }, 2, {{ 11, 54, 24, 15 }, { 16, 55, 25, 15 }}}, + { 514, { 1228, 744, 511, 315 }, 2, {{ 30, 46, 16, 15 }, { 2, 47, 17, 15 }}}}, + 5, { 6, 28, 54, 80, 106 }}, + { 25, 117, 1588, 4, { 12, 11, 16, 10 }, + {{ 1276, { 3057, 1853, 1273, 784 }, 2, {{ 8, 132, 106, 13 }, { 4, 133, 107, 13 }}}, + { 1000, { 2395, 1451, 997, 614 }, 2, {{ 8, 75, 47, 14 }, { 13, 76, 48, 14 }}}, + { 718, { 1718, 1041, 715, 440 }, 2, {{ 7, 54, 24, 15 }, { 22, 55, 25, 15 }}}, + { 538, { 1286, 779, 535, 330 }, 2, {{ 22, 45, 15, 15 }, { 13, 46, 16, 15 }}}}, + 5, { 6, 32, 58, 84, 110 }}, + { 26, 121, 1706, 4, { 12, 11, 16, 10 }, + {{ 1370, { 3283, 1990, 1367, 842 }, 2, {{ 10, 142, 114, 14 }, { 2, 143, 115, 14 }}}, + { 1062, { 2544, 1542, 1059, 652 }, 2, {{ 19, 74, 46, 14 }, { 4, 75, 47, 14 }}}, + { 754, { 1804, 1094, 751, 462 }, 2, {{ 28, 50, 22, 14 }, { 6, 51, 23, 14 }}}, + { 596, { 1425, 864, 593, 365 }, 2, {{ 33, 46, 16, 15 }, { 4, 47, 17, 15 }}}}, + 5, { 6, 30, 58, 86, 114 }}, + { 27, 125, 1828, 4, { 14, 13, 16, 12 }, + {{ 1468, { 3517, 2132, 1465, 902 }, 2, {{ 8, 152, 122, 15 }, { 4, 153, 123, 15 }}}, + { 1128, { 2701, 1637, 1125, 692 }, 2, {{ 22, 73, 45, 14 }, { 3, 74, 46, 14 }}}, + { 808, { 1933, 1172, 805, 496 }, 2, {{ 8, 53, 23, 15 }, { 26, 54, 24, 15 }}}, + { 628, { 1501, 910, 625, 385 }, 2, {{ 12, 45, 15, 15 }, { 28, 46, 16, 15 }}}}, + 5, { 6, 34, 62, 90, 118 }}, + { 28, 129, 1921, 3, { 14, 13, 16, 12 }, + {{ 1531, { 3669, 2223, 1528, 940 }, 2, {{ 3, 147, 117, 15 }, { 10, 148, 118, 15 }}}, + { 1193, { 2857, 1732, 1190, 732 }, 2, {{ 3, 73, 45, 14 }, { 23, 74, 46, 14 }}}, + { 871, { 2085, 1263, 868, 534 }, 2, {{ 4, 54, 24, 15 }, { 31, 55, 25, 15 }}}, + { 661, { 1581, 958, 658, 405 }, 2, {{ 11, 45, 15, 15 }, { 31, 46, 16, 15 }}}}, + 6, { 6, 26, 50, 74, 98, 122 }}, + { 29, 133, 2051, 3, { 14, 13, 16, 12 }, + {{ 1631, { 3909, 2369, 1628, 1002 }, 2, {{ 7, 146, 116, 15 }, { 7, 147, 117, 15 }}}, + { 1267, { 3035, 1839, 1264, 778 }, 2, {{ 21, 73, 45, 14 }, { 7, 74, 46, 14 }}}, + { 911, { 2181, 1322, 908, 559 }, 2, {{ 1, 53, 23, 15 }, { 37, 54, 24, 15 }}}, + { 701, { 1677, 1016, 698, 430 }, 2, {{ 19, 45, 15, 15 }, { 26, 46, 16, 15 }}}}, + 6, { 6, 30, 54, 78, 102, 126 }}, + { 30, 137, 2185, 3, { 14, 13, 16, 12 }, + {{ 1735, { 4158, 2520, 1732, 1066 }, 2, {{ 5, 145, 115, 15 }, { 10, 146, 116, 15 }}}, + { 1373, { 3289, 1994, 1370, 843 }, 2, {{ 19, 75, 47, 14 }, { 10, 76, 48, 14 }}}, + { 985, { 2358, 1429, 982, 604 }, 2, {{ 15, 54, 24, 15 }, { 25, 55, 25, 15 }}}, + { 745, { 1782, 1080, 742, 457 }, 2, {{ 23, 45, 15, 15 }, { 25, 46, 16, 15 }}}}, + 6, { 6, 26, 52, 78, 104, 130 }}, + { 31, 141, 2323, 3, { 14, 13, 16, 12 }, + {{ 1843, { 4417, 2677, 1840, 1132 }, 2, {{ 13, 145, 115, 15 }, { 3, 146, 116, 15 }}}, + { 1455, { 3486, 2113, 1452, 894 }, 2, {{ 2, 74, 46, 14 }, { 29, 75, 47, 14 }}}, + { 1033, { 2473, 1499, 1030, 634 }, 2, {{ 42, 54, 24, 15 }, { 1, 55, 25, 15 }}}, + { 793, { 1897, 1150, 790, 486 }, 2, {{ 23, 45, 15, 15 }, { 28, 46, 16, 15 }}}}, + 6, { 6, 30, 56, 82, 108, 134 }}, + { 32, 145, 2465, 3, { 14, 13, 16, 12 }, + {{ 1955, { 4686, 2840, 1952, 1201 }, 1, {{ 17, 145, 115, 15 }}}, + { 1541, { 3693, 2238, 1538, 947 }, 2, {{ 10, 74, 46, 14 }, { 23, 75, 47, 14 }}}, + { 1115, { 2670, 1618, 1112, 684 }, 2, {{ 10, 54, 24, 15 }, { 35, 55, 25, 15 }}}, + { 845, { 2022, 1226, 842, 518 }, 2, {{ 19, 45, 15, 15 }, { 35, 46, 16, 15 }}}}, + 6, { 6, 34, 60, 86, 112, 138 }}, + { 33, 149, 2611, 3, { 14, 13, 16, 12 }, + {{ 2071, { 4965, 3009, 2068, 1273 }, 2, {{ 17, 145, 115, 15 }, { 1, 146, 116, 15 }}}, + { 1631, { 3909, 2369, 1628, 1002 }, 2, {{ 14, 74, 46, 14 }, { 21, 75, 47, 14 }}}, + { 1171, { 2805, 1700, 1168, 719 }, 2, {{ 29, 54, 24, 15 }, { 19, 55, 25, 15 }}}, + { 901, { 2157, 1307, 898, 553 }, 2, {{ 11, 45, 15, 15 }, { 46, 46, 16, 15 }}}}, + 6, { 6, 30, 58, 86, 114, 142 }}, + { 34, 153, 2761, 3, { 14, 13, 16, 12 }, + {{ 2191, { 5253, 3183, 2188, 1347 }, 2, {{ 13, 145, 115, 15 }, { 6, 146, 116, 15 }}}, + { 1725, { 4134, 2506, 1722, 1060 }, 2, {{ 14, 74, 46, 14 }, { 23, 75, 47, 14 }}}, + { 1231, { 2949, 1787, 1228, 756 }, 2, {{ 44, 54, 24, 15 }, { 7, 55, 25, 15 }}}, + { 961, { 2301, 1394, 958, 590 }, 2, {{ 59, 46, 16, 15 }, { 1, 47, 17, 15 }}}}, + 6, { 6, 34, 62, 90, 118, 146 }}, + { 35, 157, 2876, 0, { 14, 13, 16, 12 }, + {{ 2306, { 5529, 3351, 2303, 1417 }, 2, {{ 12, 151, 121, 15 }, { 7, 152, 122, 15 }}}, + { 1812, { 4343, 2632, 1809, 1113 }, 2, {{ 12, 75, 47, 14 }, { 26, 76, 48, 14 }}}, + { 1286, { 3081, 1867, 1283, 790 }, 2, {{ 39, 54, 24, 15 }, { 14, 55, 25, 15 }}}, + { 986, { 2361, 1431, 983, 605 }, 2, {{ 22, 45, 15, 15 }, { 41, 46, 16, 15 }}}}, + 7, { 6, 30, 54, 78, 102, 126, 150 }}, + { 36, 161, 3034, 0, { 14, 13, 16, 12 }, + {{ 2434, { 5836, 3537, 2431, 1496 }, 2, {{ 6, 151, 121, 15 }, { 14, 152, 122, 15 }}}, + { 1914, { 4588, 2780, 1911, 1176 }, 2, {{ 6, 75, 47, 14 }, { 34, 76, 48, 14 }}}, + { 1354, { 3244, 1966, 1351, 832 }, 2, {{ 46, 54, 24, 15 }, { 10, 55, 25, 15 }}}, + { 1054, { 2524, 1530, 1051, 647 }, 2, {{ 2, 45, 15, 15 }, { 64, 46, 16, 15 }}}}, + 7, { 6, 24, 50, 76, 102, 128, 154 }}, + { 37, 165, 3196, 0, { 14, 13, 16, 12 }, + {{ 2566, { 6153, 3729, 2563, 1577 }, 2, {{ 17, 152, 122, 15 }, { 4, 153, 123, 15 }}}, + { 1992, { 4775, 2894, 1989, 1224 }, 2, {{ 29, 74, 46, 14 }, { 14, 75, 47, 14 }}}, + { 1426, { 3417, 2071, 1423, 876 }, 2, {{ 49, 54, 24, 15 }, { 10, 55, 25, 15 }}}, + { 1096, { 2625, 1591, 1093, 673 }, 2, {{ 24, 45, 15, 15 }, { 46, 46, 16, 15 }}}}, + 7, { 6, 28, 54, 80, 106, 132, 158 }}, + { 38, 169, 3362, 0, { 14, 13, 16, 12 }, + {{ 2702, { 6479, 3927, 2699, 1661 }, 2, {{ 4, 152, 122, 15 }, { 18, 153, 123, 15 }}}, + { 2102, { 5039, 3054, 2099, 1292 }, 2, {{ 13, 74, 46, 14 }, { 32, 75, 47, 14 }}}, + { 1502, { 3599, 2181, 1499, 923 }, 2, {{ 48, 54, 24, 15 }, { 14, 55, 25, 15 }}}, + { 1142, { 2735, 1658, 1139, 701 }, 2, {{ 42, 45, 15, 15 }, { 32, 46, 16, 15 }}}}, + 7, { 6, 32, 58, 84, 110, 136, 162 }}, + { 39, 173, 3532, 0, { 14, 13, 16, 12 }, + {{ 2812, { 6743, 4087, 2809, 1729 }, 2, {{ 20, 147, 117, 15 }, { 4, 148, 118, 15 }}}, + { 2216, { 5313, 3220, 2213, 1362 }, 2, {{ 40, 75, 47, 14 }, { 7, 76, 48, 14 }}}, + { 1582, { 3791, 2298, 1579, 972 }, 2, {{ 43, 54, 24, 15 }, { 22, 55, 25, 15 }}}, + { 1222, { 2927, 1774, 1219, 750 }, 2, {{ 10, 45, 15, 15 }, { 67, 46, 16, 15 }}}}, + 7, { 6, 26, 54, 82, 110, 138, 166 }}, + { 40, 177, 3706, 0, { 14, 13, 16, 12 }, + {{ 2956, { 7089, 4296, 2953, 1817 }, 2, {{ 19, 148, 118, 15 }, { 6, 149, 119, 15 }}}, + { 2334, { 5596, 3391, 2331, 1435 }, 2, {{ 18, 75, 47, 14 }, { 31, 76, 48, 14 }}}, + { 1666, { 3993, 2420, 1663, 1024 }, 2, {{ 34, 54, 24, 15 }, { 34, 55, 25, 15 }}}, + { 1276, { 3057, 1852, 1273, 784 }, 2, {{ 20, 45, 15, 15 }, { 61, 46, 16, 15 }}}}, + 7, { 6, 30, 58, 86, 114, 142, 170 }} +}; + +/* + * αのべき表現→多項式係数の整数表現 + */ +static const unsigned char qr_exp2fac[256] = { + 1, 2, 4, 8, 16, 32, 64,128, 29, 58,116,232,205,135, 19, 38, + 76,152, 45, 90,180,117,234,201,143, 3, 6, 12, 24, 48, 96,192, + 157, 39, 78,156, 37, 74,148, 53,106,212,181,119,238,193,159, 35, + 70,140, 5, 10, 20, 40, 80,160, 93,186,105,210,185,111,222,161, + 95,190, 97,194,153, 47, 94,188,101,202,137, 15, 30, 60,120,240, + 253,231,211,187,107,214,177,127,254,225,223,163, 91,182,113,226, + 217,175, 67,134, 17, 34, 68,136, 13, 26, 52,104,208,189,103,206, + 129, 31, 62,124,248,237,199,147, 59,118,236,197,151, 51,102,204, + 133, 23, 46, 92,184,109,218,169, 79,158, 33, 66,132, 21, 42, 84, + 168, 77,154, 41, 82,164, 85,170, 73,146, 57,114,228,213,183,115, + 230,209,191, 99,198,145, 63,126,252,229,215,179,123,246,241,255, + 227,219,171, 75,150, 49, 98,196,149, 55,110,220,165, 87,174, 65, + 130, 25, 50,100,200,141, 7, 14, 28, 56,112,224,221,167, 83,166, + 81,162, 89,178,121,242,249,239,195,155, 43, 86,172, 69,138, 9, + 18, 36, 72,144, 61,122,244,245,247,243,251,235,203,139, 11, 22, + 44, 88,176,125,250,233,207,131, 27, 54,108,216,173, 71,142, 1 +}; + +/* + * 多項式係数の整数表現→αのべき表現 + */ +static const unsigned char qr_fac2exp[256] = { + NAV, 0, 1, 25, 2, 50, 26,198, 3,223, 51,238, 27,104,199, 75, + 4,100,224, 14, 52,141,239,129, 28,193,105,248,200, 8, 76,113, + 5,138,101, 47,225, 36, 15, 33, 53,147,142,218,240, 18,130, 69, + 29,181,194,125,106, 39,249,185,201,154, 9,120, 77,228,114,166, + 6,191,139, 98,102,221, 48,253,226,152, 37,179, 16,145, 34,136, + 54,208,148,206,143,150,219,189,241,210, 19, 92,131, 56, 70, 64, + 30, 66,182,163,195, 72,126,110,107, 58, 40, 84,250,133,186, 61, + 202, 94,155,159, 10, 21,121, 43, 78,212,229,172,115,243,167, 87, + 7,112,192,247,140,128, 99, 13,103, 74,222,237, 49,197,254, 24, + 227,165,153,119, 38,184,180,124, 17, 68,146,217, 35, 32,137, 46, + 55, 63,209, 91,149,188,207,205,144,135,151,178,220,252,190, 97, + 242, 86,211,171, 20, 42, 93,158,132, 60, 57, 83, 71,109, 65,162, + 31, 45, 67,216,183,123,164,118,196, 23, 73,236,127, 12,111,246, + 108,161, 59, 82, 41,157, 85,170,251, 96,134,177,187,204, 62, 90, + 203, 89, 95,176,156,169,160, 81, 11,245, 22,235,122,117, 44,215, + 79,174,213,233,230,231,173,232,116,214,244,234,168, 80, 88,175 +}; + +/* + * 誤り訂正生成多項式の第2項以降の係数表(べき表現) + */ +static const unsigned char qr_gftable[QR_RSW_MAX+1][QR_RSW_MAX] = { + {0},{0},{0},{0},{0},{0},{0},{87,229,146,149,238,102,21}, + {0},{0},{251,67,46,61,118,70,64,94,32,45}, + {0},{0},{74,152,176,100,86,100,106,104,130,218,206,140,78}, + {0},{8,183,61,91,202,37,51,58,58,237,140,124,5,99,105}, + {120,104,107,109,102,161,76,3,91,191,147,169,182,194,225,120}, + {43,139,206,78,43,239,123,206,214,147,24,99,150,39,243,163,136}, + {215,234,158,94,184,97,118,170,79,187,152,148,252,179,5,98,96,153}, + {0},{17,60,79,50,61,163,26,187,202,180,221,225,83,239,156,164,212,212,188,190}, + {0},{210,171,247,242,93,230,14,109,221,53,200,74,8,172,98,80,219,134,160,105,165,231}, + {0},{229,121,135,48,211,117,251,126,159,180,169,152,192,226,228,218,111,0,117,232,87,96,227,21}, + {0},{173,125,158,2,103,182,118,17,145,201,111,28,165,53,161,21,245,142,13,102,48,227,153,145,218,70}, + {0},{168,223,200,104,224,234,108,180,110,190,195,147,205,27,232,201,21,43,245,87,42,195,212,119,242,37,9,123}, + {0},{41,173,145,152,216,31,179,182,50,48,110,86,239,96,222,125,42,173,226,193,224,130,156,37,251,216,238,40,192,180}, + {0},{10,6,106,190,249,167,4,67,209,138,138,32,242,123,89,27,120,185,80,156,38,69,171,60,28,222,80,52,254,185,220,241}, + {0},{111,77,146,94,26,21,108,19,105,94,113,193,86,140,163,125,58,158,229,239,218,103,56,70,114,61,183,129,167,13,98,62,129,51}, + {0},{200,183,98,16,172,31,246,234,60,152,115,0,167,152,113,248,238,107,18,63,218,37,87,210,105,177,120,74,121,196,117,251,113,233,30,120}, + {0},{159,34,38,228,230,59,243,95,49,218,176,164,20,65,45,111,39,81,49,118,113,222,193,250,242,168,217,41,164,247,177,30,238,18,120,153,60,193}, + {0},{59,116,79,161,252,98,128,205,128,161,247,57,163,56,235,106,53,26,187,174,226,104,170,7,175,35,181,114,88,41,47,163,125,134,72,20,232,53,35,15}, + {0},{250,103,221,230,25,18,137,231,0,3,58,242,221,191,110,84,230,8,188,106,96,147,15,131,139,34,101,223,39,101,213,199,237,254,201,123,171,162,194,117,50,96}, + {0},{190,7,61,121,71,246,69,55,168,188,89,243,191,25,72,123,9,145,14,247,1,238,44,78,143,62,224,126,118,114,68,163,52,194,217,147,204,169,37,130,113,102,73,181}, + {0},{112,94,88,112,253,224,202,115,187,99,89,5,54,113,129,44,58,16,135,216,169,211,36,1,4,96,60,241,73,104,234,8,249,245,119,174,52,25,157,224,43,202,223,19,82,15}, + {0},{228,25,196,130,211,146,60,24,251,90,39,102,240,61,178,63,46,123,115,18,221,111,135,160,182,205,107,206,95,150,120,184,91,21,247,156,140,238,191,11,94,227,84,50,163,39,34,108}, + {0},{232,125,157,161,164,9,118,46,209,99,203,193,35,3,209,111,195,242,203,225,46,13,32,160,126,209,130,160,242,215,242,75,77,42,189,32,113,65,124,69,228,114,235,175,124,170,215,232,133,205}, + {0},{116,50,86,186,50,220,251,89,192,46,86,127,124,19,184,233,151,215,22,14,59,145,37,242,203,134,254,89,190,94,59,65,124,113,100,233,235,121,22,76,86,97,39,242,200,220,101,33,239,254,116,51}, + {0},{183,26,201,87,210,221,113,21,46,65,45,50,238,184,249,225,102,58,209,218,109,165,26,95,184,192,52,245,35,254,238,175,172,79,123,25,122,43,120,108,215,80,128,201,235,8,153,59,101,31,198,76,31,156}, + {0},{106,120,107,157,164,216,112,116,2,91,248,163,36,201,202,229,6,144,254,155,135,208,170,209,12,139,127,142,182,249,177,174,190,28,10,85,239,184,101,124,152,206,96,23,163,61,27,196,247,151,154,202,207,20,61,10}, + {0},{82,116,26,247,66,27,62,107,252,182,200,185,235,55,251,242,210,144,154,237,176,141,192,248,152,249,206,85,253,142,65,165,125,23,24,30,122,240,214,6,129,218,29,145,127,134,206,245,117,29,41,63,159,142,233,125,148,123}, + {0},{107,140,26,12,9,141,243,197,226,197,219,45,211,101,219,120,28,181,127,6,100,247,2,205,198,57,115,219,101,109,160,82,37,38,238,49,160,209,121,86,11,124,30,181,84,25,194,87,65,102,190,220,70,27,209,16,89,7,33,240}, + {0},{65,202,113,98,71,223,248,118,214,94,0,122,37,23,2,228,58,121,7,105,135,78,243,118,70,76,223,89,72,50,70,111,194,17,212,126,181,35,221,117,235,11,229,149,147,123,213,40,115,6,200,100,26,246,182,218,127,215,36,186,110,106}, + {0},{45,51,175,9,7,158,159,49,68,119,92,123,177,204,187,254,200,78,141,149,119,26,127,53,160,93,199,212,29,24,145,156,208,150,218,209,4,216,91,47,184,146,47,140,195,195,125,242,238,63,99,108,140,230,242,31,204,11,178,243,217,156,213,231}, + {0},{5,118,222,180,136,136,162,51,46,117,13,215,81,17,139,247,197,171,95,173,65,137,178,68,111,95,101,41,72,214,169,197,95,7,44,154,77,111,236,40,121,143,63,87,80,253,240,126,217,77,34,232,106,50,168,82,76,146,67,106,171,25,132,93,45,105}, + {0},{247,159,223,33,224,93,77,70,90,160,32,254,43,150,84,101,190,205,133,52,60,202,165,220,203,151,93,84,15,84,253,173,160,89,227,52,199,97,95,231,52,177,41,125,137,241,166,225,118,2,54,32,82,215,175,198,43,238,235,27,101,184,127,3,5,8,163,238} +}; + +#define F0 QR_MM_FUNC +#define F1 (QR_MM_FUNC | QR_MM_BLACK) + +/* + * 位置検出パターンのデータ + */ +static const qr_byte_t qr_finderpattern[QR_DIM_FINDER][QR_DIM_FINDER] = { + { F1, F1, F1, F1, F1, F1, F1 }, + { F1, F0, F0, F0, F0, F0, F1 }, + { F1, F0, F1, F1, F1, F0, F1 }, + { F1, F0, F1, F1, F1, F0, F1 }, + { F1, F0, F1, F1, F1, F0, F1 }, + { F1, F0, F0, F0, F0, F0, F1 }, + { F1, F1, F1, F1, F1, F1, F1 } +}; + +/* + * 位置合わせパターンのデータ + */ +static const qr_byte_t qr_alignpattern[QR_DIM_ALIGN][QR_DIM_ALIGN] = { + { F1, F1, F1, F1, F1 }, + { F1, F0, F0, F0, F1 }, + { F1, F0, F1, F0, F1 }, + { F1, F0, F0, F0, F1 }, + { F1, F1, F1, F1, F1 } +}; + +#undef F0 +#undef F1 + +/* + * モード指示子(英字, 英数字, 8ビットバイト, 漢字) + */ +static const int qr_modeid[QR_EM_COUNT] = { 0x01, 0x02, 0x04, 0x08 }; + +/* + * 符号化モード名 (不使用) + */ +/*const char *qr_modename[QR_EM_COUNT] = { + "Numeric", "Alnum", "8bit-byte", "Kanji" +};*/ + +/* + * エラー訂正レベル名 + */ +const char *qr_eclname[QR_ECL_COUNT] = { "L", "M", "Q", "H" }; + +/* + * 英数字モードの符号化表 + */ +static const signed char qr_alnumtable[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +/* + * 形式情報(2箇所)の座標(下位ビットから) + * (負数は下端/右端からのオフセット) + */ +static const qr_coord_t qr_fmtinfopos[2][QR_FIN_MAX] = { + {{ 0, 8 }, { 1, 8 }, { 2, 8 }, { 3, 8 }, + { 4, 8 }, { 5, 8 }, { 7, 8 }, { 8, 8 }, + { -7, 8 }, { -6, 8 }, { -5, 8 }, { -4, 8 }, + { -3, 8 }, { -2, 8 }, { -1, 8 }}, + {{ 8, -1 }, { 8, -2 }, { 8, -3 }, { 8, -4 }, + { 8, -5 }, { 8, -6 }, { 8, -7 }, { 8, -8 }, + { 8, 7 }, { 8, 5 }, { 8, 4 }, { 8, 3 }, + { 8, 2 }, { 8, 1 }, { 8, 0 }} +}; + +/* + * 形式情報の固定黒モジュール + */ +static const qr_coord_t qr_fmtblackpos = { -8, 8 }; + +/* + * 型番情報(2箇所)の座標(下位ビットから) + * (負数は下端/右端からのオフセット) + */ +static const qr_coord_t qr_verinfopos[2][QR_VIN_MAX] = { + {{ -11, 0 }, { -10, 0 }, { -9, 0 }, + { -11, 1 }, { -10, 1 }, { -9, 1 }, + { -11, 2 }, { -10, 2 }, { -9, 2 }, + { -11, 3 }, { -10, 3 }, { -9, 3 }, + { -11, 4 }, { -10, 4 }, { -9, 4 }, + { -11, 5 }, { -10, 5 }, { -9, 5 }}, + {{ 0, -11 }, { 0, -10 }, { 0, -9 }, + { 1, -11 }, { 1, -10 }, { 1, -9 }, + { 2, -11 }, { 2, -10 }, { 2, -9 }, + { 3, -11 }, { 3, -10 }, { 3, -9 }, + { 4, -11 }, { 4, -10 }, { 4, -9 }, + { 5, -11 }, { 5, -10 }, { 5, -9 }} +}; + +/* + * 型番情報(型番7〜40について有効) + */ +static const long qr_verinfo[QR_VER_MAX+1] = { + -1L, -1L, -1L, -1L, -1L, -1L, + -1L, 0x07c94L, 0x085bcL, 0x09a99L, 0x0a4d3L, 0x0bbf6L, + 0x0c762L, 0x0d847L, 0x0e60dL, 0x0f928L, 0x10b78L, 0x1145dL, + 0x12a17L, 0x13532L, 0x149a6L, 0x15683L, 0x168c9L, 0x177ecL, + 0x18ec4L, 0x191e1L, 0x1afabL, 0x1b08eL, 0x1cc1aL, 0x1d33fL, + 0x1ed75L, 0x1f250L, 0x209d5L, 0x216f0L, 0x228baL, 0x2379fL, + 0x24b0bL, 0x2542eL, 0x26a64L, 0x27541L, 0x28c69L +}; + +/* + * 一連の処理をする関数ポインタ型 + */ +typedef int (*qr_funcs)(QRCode *); + +/* + * 内部処理用関数のプロトタイプ + */ +static void qrAddDataBits(QRCode *qr, int n, int word); +static int qrInitDataWord(QRCode *qr); +static int qrEncodeDataWord(QRCode *qr, const qr_byte_t *source, int size, int mode); +static int qrFinalizeDataWord(QRCode *qr); +static int qrComputeECWord(QRCode *qr); +static int qrMakeCodeWord(QRCode *qr); +static int qrFillFunctionPattern(QRCode *qr); +static int qrFillCodeWord(QRCode *qr); +static void qrInitPosition(QRCode *qr); +static void qrNextPosition(QRCode *qr); +static int qrSelectMaskPattern(QRCode *qr); +static int qrApplyMaskPattern(QRCode *qr); +static int qrApplyMaskPattern2(QRCode *qr, int type); +static long qrEvaluateMaskPattern(QRCode *qr); +static int qrFillFormatInfo(QRCode *qr); + + +#endif /* _QR_PRIVATE_H_ */ diff --git a/support/sg2002/kvm_system/main/lib/libqr/qr_util.h b/support/sg2002/kvm_system/main/lib/libqr/qr_util.h new file mode 100644 index 0000000..51cca39 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/qr_util.h @@ -0,0 +1,87 @@ +/* + * QR Code Generator Library: Header for Utility + * + * Core routines were originally written by Junn Ohta. + * Based on qr.c Version 0.1: 2004/4/3 (Public Domain) + * + * @package libqr + * @author Ryusuke SEKIYAMA + * @copyright 2006-2013 Ryusuke SEKIYAMA + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +#ifndef _QR_UTIL_H_ +#define _QR_UTIL_H_ + +#include "qr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * Determine the module is a dark module or not. + */ +#define qrIsBlack(qr, i, j) (((qr)->symbol[(i)][(j)] & QR_MM_BLACK) != 0) + +/* + * Deallocate and set to NULL. + */ +#define qrFree(ptr) { if ((ptr) != NULL) { free(ptr); (ptr) = NULL; } } + +/* + * Current function name macro. + */ +QR_API extern const char *(*qrGetCurrentFunctionName)(void); +#if defined(__FUNCTION__) +#define _QR_FUNCTION ((qrGetCurrentFunctionName) ? qrGetCurrentFunctionName() : __FUNCTION__) +#elif defined(__func__) +#define _QR_FUNCTION ((qrGetCurrentFunctionName) ? qrGetCurrentFunctionName() : __func__) +#else +#define _QR_FUNCTION ((qrGetCurrentFunctionName) ? qrGetCurrentFunctionName() : "?") +#endif + +/* + * Maximum length of filename extensions. + */ +#define QR_EXT_MAX_LEN 4 + +/* + * Constatns. + */ +extern QR_API const qr_vertable_t qr_vertable[]; +/*extern QR_API const char *qr_modename[]; */ +extern QR_API const char *qr_eclname[]; + +/* + * Functions for utility. + */ +QR_API const char *qrVersion(void); +QR_API const char *qrMimeType(int format); +QR_API const char *qrExtension(int format); +QR_API const char *qrStrError(int errcode); +QR_API void qrSetErrorInfo(QRCode *qr, int errnum, const char *param); +QR_API void qrSetErrorInfo2(QRCode *qr, int errnum, const char *param); +QR_API void qrSetErrorInfo3(QRCode *qr, int errnum, const char *fmt, ...); +QR_API int qrGetEncodedLength(QRCode *qr, int size); +QR_API int qrGetEncodedLength2(QRCode *qr, int size, int mode); +QR_API int qrGetEncodableLength(QRCode *qr, int size); +QR_API int qrGetEncodableLength2(QRCode *qr, int size, int mode); +QR_API int qrRemainedDataBits(QRCode *qr); + +/* + * Functions for checking datatype. + */ +QR_API int qrDetectDataType(const qr_byte_t *source, int size); +QR_API int qrStrPosNotNumeric(const qr_byte_t *source, int size); +QR_API int qrStrPosNotAlnum(const qr_byte_t *source, int size); +QR_API int qrStrPosNotKanji(const qr_byte_t *source, int size); +QR_API int qrStrPosNot8bit(const qr_byte_t *source, int size); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* _QR_UTIL_H_ */ diff --git a/support/sg2002/kvm_system/main/lib/libqr/qrcmd.c b/support/sg2002/kvm_system/main/lib/libqr/qrcmd.c new file mode 100644 index 0000000..c090c11 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/qrcmd.c @@ -0,0 +1,626 @@ +/* + * QR Code Generator Library: Command Line Interface + * + * Core routines were originally written by Junn Ohta. + * Based on qr.c Version 0.1: 2004/4/3 (Public Domain) + * + * @package libqr + * @author Ryusuke SEKIYAMA + * @copyright 2006-2013 Ryusuke SEKIYAMA + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +#include "qrcmd.h" + +#ifdef WIN32 +#define err(code, ...) { \ + printf(__VA_ARGS__); \ + printf(": %s\r\n", strerror(errno)); \ + exit(code); \ +} +#define errx(code, ...) { \ + printf(__VA_ARGS__); \ + printf("\r\n"); \ + exit(code); \ +} +#else +#include +#endif + +#include +#include +#include + +#ifdef WIN32 +#include +#define writeln(msg) printf(msg "\r\n") +#define ewriteln(msg) printf(msg "\r\n") +#define writelnf(fmt, ...) printf(fmt "r\n", __VA_ARGS__) +#define ewritelnf(fmt, ...) printf(fmt "\r\n", __VA_ARGS__) +#else +#define writeln(msg) printf(msg "\n") +#define ewriteln(msg) fprintf(stderr, msg "\n") +#define writelnf(fmt, ...) printf(fmt "\n", __VA_ARGS__) +#define ewritelnf(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__) +#endif + +/* {{{ main() */ + +int +main(int argc, char **argv) +{ + QRCMD_PTR_TYPE *qr; + int mag = 1; + int sep = QR_DIM_SEP; + int fmt = QR_FMT_PBM; + QRCMD_EXTRA_PARAM_A + char output[PATH_MAX] = { '\0' }; + char *ptr = &(output[0]); + int result = 0; + +#ifdef WIN32 + setmode(fileno(stdin), O_BINARY); + setmode(fileno(stdout), O_BINARY); +#endif + + if (argc <= 1) { + writelnf("%s: QR Code Generator", argv[0]); + writelnf("try '%s --help' for more information", argv[0]); + return 1; + } + + /* + * QRコードオブジェクトを生成し、コマンド行引数からパラメータと入力データを設定する + */ + qr = qrGetParameter(argc, argv, &fmt, &sep, &mag, QRCMD_EXTRA_PARAM_C &ptr); + + /* + * データコード語をエンコードし、シンボルを配置する + */ + if (!qrCmdFinalize(qr)) { + ewritelnf("%s: %s", argv[0], qrCmdGetErrorInfo(qr)); + qrCmdDestroy(qr); + return 1; + } + + /* + * シンボルを出力する + */ +#ifdef QRCMD_STRUCTURED_APPEND + if (extra == -1) { + /* + * 連番出力 + */ + int i; + for (i = 0; i < qr->num; i++) { + if (output[0] == '\0') { + result = qrOutputSymbol(qr->qrs[i], stdout, fmt, sep, mag); + } else { + char outputs[PATH_MAX] = { '\0' }; + int written = 0; + written = snprintf(&(outputs[0]), PATH_MAX, "%s%02d.%s", + &(output[0]), i + 1, qrExtension(fmt)); + if (written < 0 || written >= PATH_MAX) { + ewritelnf("%s: output pathname is too long", argv[0]); + qrCmdDestroy(qr); + return 1; + } + result = qrOutputSymbol2(qr->qrs[i], &(outputs[0]), fmt, sep, mag); + } + if (result == -1) { + ewritelnf("%s: QR Code #%d: %s", argv[0], i, qrCmdGetErrorInfo(qr)); + qrCmdDestroy(qr); + return 1; + } + } + } else +#endif + if (output[0] == '\0') { + result = qrCmdOutputSymbol(qr, stdout, fmt, sep, mag QRCMD_EXTRA_PARAM_B); + } else { + result = qrCmdOutputSymbol2(qr, &(output[0]), fmt, sep, mag QRCMD_EXTRA_PARAM_B); + } + if (result == -1) { + ewritelnf("%s: %s", argv[0], qrCmdGetErrorInfo(qr)); + qrCmdDestroy(qr); + return 1; + } + + qrCmdDestroy(qr); + return 0; +} + +/* }}} main() */ +/* {{{ qrShowHelp() */ + +/* + * 使用方法を表示する + */ +void +qrShowHelp(void) +{ + writeln("QR Code Generator"); + writeln(); + writeln("QR Code (R) is registered trademarks of DENSO WAVE INCORPORATED"); + writeln("in JAPAN and other countries."); + writeln(); + writelnf("usage: %s [options ...] [ [ [mode] [text | -i file] ] ...] ", QRCMD_PROG_NAME); + writeln(); + writeln("input:"); + writeln(" The data which is stored in QR Code Symbol (at least 1 character)."); + writeln(" If it is not specified, read from stdin."); + writeln(); + writeln("examples:"); + writelnf(" %s -eM -x6 < input.txt > output.pbm", QRCMD_PROG_NAME); + writelnf(" %s -eM -x6 -o output.pbm -i input.txt", QRCMD_PROG_NAME); + writelnf(" %s -v2 -fBMP -o foobar.bmp -mA 'FOOBAR'", QRCMD_PROG_NAME); + writelnf(" %s -x3 -fSVG -o mixed.svg -mA 'ALNUM' -m8 ' ' -mN '001'", QRCMD_PROG_NAME); + writelnf(" %s -fAA 'Ascii Art'", QRCMD_PROG_NAME); + writeln(); + writeln("options:"); + writeln(" -V show program's version number and exit"); + writeln(" -?, -h, --help show this help message and exit"); +#ifdef QRCMD_STRUCTURED_APPEND + writeln(" -v, --version=NUM symbol version (1-40, default: 1)"); +#else + writeln(" -v, --version=NUM symbol version (1-40, default: auto)"); +#endif + writeln(" -m, --mode=MODE encoding mode (N,A,8,K,S, default: S)"); + writeln(" N: numeric, A: uppercase alphabet and numeric,"); + writeln(" 8: 8-bit byte, K: JIS X 0208 Kanji, S: auto"); + writeln(" -e, --eclevel=LEVEL error correction level (L,M,Q,H, default: M)"); + writeln(" L: 7%%, M: 15%%, Q: 25%%, H: 30%%"); + writeln(" -p, --pattern=NUM mask pattern (0-7, default: auto)"); + writeln(" -x, --magnify=NUM magnifying ratio (1-16, default: 1)"); + writeln(" -s, --separator=NUM separator pattan width (0-16, default: 4)"); + writeln(" '4' is the lower limit of the QR Code specification."); + writeln(" -f, --format=FORMAT output format (default: PBM)"); + writeln(" Available formats are followings."); + writeln(" PNG, BMP, TIFF, PBM, SVG, JSON, DIGIT, ASCII"); + writeln(" These are case-insensitive and some have aliases."); + writeln(" DIGIT -> 01"); + writeln(" ASCII -> asciiart, aa"); + writeln(" JSON -> javascript, js"); + writeln(" TIFF -> tif"); +#ifdef QRCMD_STRUCTURED_APPEND + writelnf(" -a, --maxnum=NUM maximum number of symbols (1-%d, default: %d)", QR_STA_MAX, QR_STA_MAX); + writeln(" -z, --order=NUM ordering method of symbols, in case NUM is ..."); + writeln(" = 0 (default): order to square as possible"); + writeln(" >= 1: order each NUM symbols to horizontal"); + writeln(" <= -1: order each NUM symbols to vertical"); + writeln(" --serial output as serial numbered images"); + writeln(" Number and extension will be added to output pathname."); + writeln(" (default: output as a combined image)"); +#endif + writeln(" -o, --output=PATH output pathname (default: write to stdout)"); + writeln(" -i, --input=PATH input pathname (default: read from stdin)"); + writeln(" To specify multiple files, set this option"); + writeln(" before every filename. And the encoding mode"); + writeln(" can be specified to each files."); + writeln(" (e.g. -mA -i file1 -mK -i file2 ...)"); +} + +/* }}} qrShowHelp() */ +/* {{{ utilities for qrGetParameter() */ + +#define QR_SHORT_OPT(name) (d = 2, !strncmp(ptr, name, d)) +#define QR_LONG_OPT(name) (d = sizeof(name), !strncmp(ptr, name "=", d)) + +#define QR_GETOPT_NEXT() { \ + opt = ptr; \ + ptr += d; \ + if (d == 2 && *ptr == '\0') { \ + i++; \ + if (i == argc) { \ + errx(1, "%s: %s", opt, qrStrError(QR_ERR_EMPTY_PARAM)); \ + } \ + ptr = argv[i]; \ + } \ + if (ptr == NULL || *ptr == '\0') { \ + errx(1, "%s: %s", opt, qrStrError(QR_ERR_EMPTY_PARAM)); \ + } \ +} + +/* }}} utilities for qrGetParameter() */ +/* {{{ qrGetParameter() */ + +/* + * コマンド行引数からパラメータと入力データを設定し、出力ファイル名を取得する + */ +QRCMD_PTR_TYPE * +qrGetParameter(int argc, char **argv, + int *fmt, int *sep, int *mag, QRCMD_EXTRA_PARAM_D char **output) +{ + QRCMD_PTR_TYPE *qr; + char *opt, *ptr; + int i; + size_t d; + int version = QRCMD_DEFAULT_VERSION; + int mode = QR_EM_AUTO; + int eclevel = QR_ECL_M; + int masktype = -1; + QRCMD_MAX_NUM_A + int errcode = QR_ERR_NONE; + int has_data = 0; + + /* + * 引数のパラメータを取得する + */ + for (i = 1; i < argc; i++) { + ptr = argv[i]; + if (!strcmp(ptr, "-?") || !strcasecmp(ptr, "-h") || !strcmp(ptr, "--help")) { + /* + * 使用方法 + */ + qrShowHelp(); + exit(1); + + } else if (!strcmp(ptr, "-V")) { + /* + * バージョン + */ + writelnf("%s: QR Code Generator", QRCMD_PROG_NAME); + writelnf("qrcmd version: %s", QRCMD_PROG_VERSION); + writelnf("libqr version: %s", qrVersion()); + exit(1); + + } else if (QR_SHORT_OPT("-v") || QR_LONG_OPT("--version")) { + /* + * 型番 + */ + QR_GETOPT_NEXT(); + if (*ptr < '0' || *ptr > '9') { + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_INVALID_VERSION)); + } + version = atoi(ptr); + if (version <= 0 || version > QR_VER_MAX) { + errx(1, "%d: %s", version, qrStrError(QR_ERR_INVALID_VERSION)); + } + + } else if (QR_SHORT_OPT("-m") || QR_LONG_OPT("--mode")) { + /* + * 符号化モード + */ + QR_GETOPT_NEXT(); + switch (*ptr) { + case 's': + case 'S': + mode = QR_EM_AUTO; + break; + case 'n': + case 'N': + mode = QR_EM_NUMERIC; + break; + case 'a': + case 'A': + mode = QR_EM_ALNUM; + break; + case '8': + case 'B': + mode = QR_EM_8BIT; + break; + case 'k': + case 'K': + mode = QR_EM_KANJI; + break; + default: + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_INVALID_MODE)); + } + + } else if (QR_SHORT_OPT("-e") || QR_LONG_OPT("--eclevel")) { + /* + * 誤り訂正レベル + */ + QR_GETOPT_NEXT(); + switch (*ptr) { + case 'l': + case 'L': + eclevel = QR_ECL_L; + break; + case 'm': + case 'M': + eclevel = QR_ECL_M; + break; + case 'q': + case 'Q': + eclevel = QR_ECL_Q; + break; + case 'h': + case 'H': + eclevel = QR_ECL_H; + break; + default: + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_INVALID_ECL)); + } + + } else if (QR_SHORT_OPT("-p") || QR_LONG_OPT("--pattern")) { + /* + * マスクパターン種別 + */ + QR_GETOPT_NEXT(); + if (*ptr < '0' || *ptr > '9') { + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_INVALID_MPT)); + } + masktype = atoi(ptr); + if (masktype < 0 || masktype >= QR_MPT_MAX) { + errx(1, "%d: %s", masktype, qrStrError(QR_ERR_INVALID_MPT)); + } + + } else if (QR_SHORT_OPT("-x") || QR_LONG_OPT("--magnify")) { + /* + * ピクセル表示倍率 + */ + QR_GETOPT_NEXT(); + if (*ptr < '0' || *ptr > '9') { + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_INVALID_MAG)); + } + *mag = atoi(ptr); + if (*mag < 0 || *mag > QR_MAG_MAX) { + errx(1, "%d: %s", *mag, qrStrError(QR_ERR_INVALID_MAG)); + } + + } else if (QR_SHORT_OPT("-s") || QR_LONG_OPT("--separator")) { + /* + * 分離パターン幅 + */ + QR_GETOPT_NEXT(); + if (*ptr < '0' || *ptr > '9') { + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_INVALID_SEP)); + } + *sep = atoi(ptr); + if (*sep < 0 || *sep > QR_SEP_MAX) { + errx(1, "%d: %s", *sep, qrStrError(QR_ERR_INVALID_SEP)); + } + + } else if (QR_SHORT_OPT("-f") || QR_LONG_OPT("--format")) { + /* + * 出力形式 + */ + QR_GETOPT_NEXT(); + if (!strcasecmp(ptr, "digit") || !strcasecmp(ptr, "01")) { + *fmt = QR_FMT_DIGIT; + } else if (!strcasecmp(ptr, "asciiart") || + !strcasecmp(ptr, "ascii") || + !strcasecmp(ptr, "aa")) + { + *fmt = QR_FMT_ASCII; + } else if (!strcasecmp(ptr, "javascript") || + !strcasecmp(ptr, "json") || + !strcasecmp(ptr, "js")) + { + *fmt = QR_FMT_JSON; + } else if (!strcasecmp(ptr, "pbm")) { + *fmt = QR_FMT_PBM; + } else if (!strcasecmp(ptr, "bmp")) { + *fmt = QR_FMT_BMP; + } else if (!strcasecmp(ptr, "svg")) { + *fmt = QR_FMT_SVG; + } else if (!strcasecmp(ptr, "tiff") | !strcasecmp(ptr, "tif")) { +#ifdef QR_ENABLE_TIFF + *fmt = QR_FMT_TIFF; +#else + +#endif /* QR_ENABLE_TIFF */ +#ifdef QR_ENABLE_PNG + } else if (!strcasecmp(ptr, "png")) { + *fmt = QR_FMT_PNG; +#endif /* QR_ENABLE_PNG */ + } else { + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_INVALID_FMT)); + } + + } else if (QR_SHORT_OPT("-o") || QR_LONG_OPT("--output")) { + /* + * 出力ファイル名 + */ + QR_GETOPT_NEXT(); + size_t pathlen; + pathlen = strlen(ptr); + if (pathlen >= PATH_MAX) { + errx(1, "argv[%d]: %s", i, strerror(ENAMETOOLONG)); + } + if (*output[0] != '\0') { + errx(1, "%s: %s: Duplicated declaration of output pathname", + *output, ptr); + } + strncpy(*output, ptr, pathlen); + +#ifdef QRCMD_STRUCTURED_APPEND + + } else if (QR_SHORT_OPT("-a") || QR_LONG_OPT("--maxnum")) { + /* + * 最大シンボル数 + */ + QR_GETOPT_NEXT(); + if (*ptr < '0' || *ptr > '9') { + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_INVALID_MAXNUM)); + } + maxnum = atoi(ptr); + if (maxnum < 2 || maxnum > QR_STA_MAX) { + errx(1, "%d: %s", maxnum, qrStrError(QR_ERR_INVALID_MAXNUM)); + } + + } else if (QR_SHORT_OPT("-z") || QR_LONG_OPT("--order")) { + /* + * 並べ方 + */ + QR_GETOPT_NEXT(); + if (*ptr != '-' && (*ptr < '0' || *ptr > '9')) { + /*errx(1, "%s: %s", ptr, qrStrError(QR_ERR_UNKNOWN));*/ + continue; + } + *order = atoi(ptr); + + } else if (!strcmp(ptr, "--serial")) { + /* + * 連番 + */ + if (*extra != 0) { + errx(1, "Serial image output and GIF animation output are exclusive."); + } + *extra = -1; + + } else if (!strcmp(ptr, "--animation") || !strncmp(ptr, "--animation=", 12)) { + /* + * GIFアニメ + */ + if (*extra != 0) { + errx(1, "Serial image output and GIF animation output are exclusive."); + } + if (strlen(ptr) == 11) { + *extra = 100; + } else { + double _f; + ptr += 12; + if (*ptr < '0' || *ptr > '9') { + errx(1, "%s: Invalid animation delay.", ptr); + } + _f = atof(ptr); + *extra = (int)(_f * 100); + if (*extra == 0) { + errx(1, "Zero animation delay."); + } + } + +#endif /* QRCMD_STRUCTURED_APPEND */ + + } else { + break; + } + } + + /* + * QRコードオブジェクトを初期化する + */ + qr = qrCmdInit(version, mode, eclevel, masktype, QRCMD_MAX_NUM_B &errcode); + if (qr == NULL) { + errx(1, "%s", qrStrError(errcode)); + } + + /* + * 入力データを取得する + */ + for (; i < argc; i++) { + ptr = argv[i]; + if (QR_SHORT_OPT("-m") || QR_LONG_OPT("--mode")) { + /* + * 符号化モードを上書き + */ + QR_GETOPT_NEXT(); + switch (*ptr) { + case 'n': + case 'N': + mode = QR_EM_NUMERIC; + break; + case 'a': + case 'A': + mode = QR_EM_ALNUM; + break; + case '8': + case 'B': + mode = QR_EM_8BIT; + break; + case 'k': + case 'K': + mode = QR_EM_KANJI; + break; + default: + qrCmdDestroy(qr); + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_INVALID_MODE)); + } + + } else if (QR_SHORT_OPT("-i") || QR_LONG_OPT("--input")) { + qr_byte_t source[QRCMD_SRC_MAX]; + int srclen = 0; + FILE *fp; + int c; + /* + * 入力データをファイルから読む + */ + QR_GETOPT_NEXT(); + fp = fopen(ptr, "rb"); + if (fp == NULL) { + qrCmdDestroy(qr); + err(1, "%s", ptr); + } + while ((c = fgetc(fp)) != EOF && srclen < QRCMD_SRC_MAX){ + source[srclen++] = (qr_byte_t)c; + } + fclose(fp); + if (srclen == 0) { + qrCmdDestroy(qr); + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_EMPTY_SRC)); + } else if (c != EOF) { + qrCmdDestroy(qr); + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_LARGE_SRC)); + } + if (!qrCmdAddData2(qr, source, srclen, mode)) { + char errinfo[QR_ERR_MAX]; + snprintf(&(errinfo[0]), QR_ERR_MAX, "%s: %s", ptr, qrCmdGetErrorInfo(qr)); + qrCmdDestroy(qr); + errx(1, "%s", errinfo); + } + has_data++; + + } else if (*ptr != '-') { + qr_byte_t source[QRCMD_SRC_MAX]; + int srclen = 0; + /* + * 入力データを引数から得る + */ + srclen = strlen(ptr); + if (srclen == 0) { + qrCmdDestroy(qr); + errx(1, "argv[%d]: %s", i, qrStrError(QR_ERR_EMPTY_SRC)); + } else if (srclen > QRCMD_SRC_MAX) { + qrCmdDestroy(qr); + errx(1, "argv[%d]: %s", i, qrStrError(QR_ERR_LARGE_SRC)); + } + memcpy(&(source[0]), ptr, (size_t)srclen); + if (!qrCmdAddData2(qr, source, srclen, mode)) { + char errinfo[QR_ERR_MAX]; + snprintf(&(errinfo[0]), QR_ERR_MAX, "argv[%d]: %s", i, qrCmdGetErrorInfo(qr)); + qrCmdDestroy(qr); + errx(1, "%s", errinfo); + } + has_data++; + + } else { + /* + * 未定義オプション + */ + qrCmdDestroy(qr); + errx(1, "%s: %s", ptr, qrStrError(QR_ERR_INVALID_ARG)); + } + } + + /* + * 入力データが空なら、標準入力から読む + */ + if (!has_data) { + qr_byte_t source[QRCMD_SRC_MAX]; + int srclen = 0; + int c; + while ((c = getchar()) != EOF && srclen < QRCMD_SRC_MAX){ + source[srclen++] = (qr_byte_t)c; + } + if (srclen == 0) { + qrCmdDestroy(qr); + errx(1, "-stdin: %s", qrStrError(QR_ERR_EMPTY_SRC)); + } + if (c != EOF) { + qrCmdDestroy(qr); + errx(1, "-stdin: %s", qrStrError(QR_ERR_LARGE_SRC)); + } + if (!qrCmdAddData2(qr, source, srclen, mode)) { + char errinfo[QR_ERR_MAX]; + snprintf(&(errinfo[0]), QR_ERR_MAX, "%s", qrCmdGetErrorInfo(qr)); + qrCmdDestroy(qr); + errx(1, "%s", errinfo); + } + } + + return qr; +} + +/* }}} qrGetParameter() */ diff --git a/support/sg2002/kvm_system/main/lib/libqr/qrcmd.h b/support/sg2002/kvm_system/main/lib/libqr/qrcmd.h new file mode 100644 index 0000000..4b4f4f2 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/qrcmd.h @@ -0,0 +1,110 @@ +/* + * QR Code Generator Library: Header for Command Line Interface + * + * Core routines were originally written by Junn Ohta. + * Based on qr.c Version 0.1: 2004/4/3 (Public Domain) + * + * @package libqr + * @author Ryusuke SEKIYAMA + * @copyright 2006-2013 Ryusuke SEKIYAMA + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +#ifndef _QRCMD_H_ +#define _QRCMD_H_ + +/* {{{ include headers */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "qr.h" +#include "qr_util.h" + +/* }}} */ +/* {{{ version information */ + +#define QRCMD_PROG_VERSION "0.2.0" + +/* }}} */ +/* {{{ whether enable structured append or not */ + +#ifdef QRCMD_STRUCTURED_APPEND +/* {{{ enable structured append */ + +/* parameters */ +#define QRCMD_PTR_TYPE QRStructured +#define QRCMD_EXTRA_PARAM_A int order = 0; int extra = 0; +#define QRCMD_EXTRA_PARAM_B , order +#define QRCMD_EXTRA_PARAM_C &order, &extra, +#define QRCMD_EXTRA_PARAM_D int *order, int *extra, +#define QRCMD_MAX_NUM_A int maxnum = QR_STA_MAX; +#define QRCMD_MAX_NUM_B maxnum, +#define QRCMD_DEFAULT_VERSION 1 + +/* functions */ +#define qrCmdInit qrsInit +#define qrCmdDestroy qrsDestroy +#define qrCmdFinalize qrsFinalize +#define qrCmdGetErrorInfo qrsGetErrorInfo +#define qrCmdAddData qrsAddData +#define qrCmdAddData2 qrsAddData2 +#define qrCmdOutputSymbol qrsOutputSymbols +#define qrCmdOutputSymbol2 qrsOutputSymbols2 + +/* others */ +#ifndef QRCMD_PROG_NAME +#define QRCMD_PROG_NAME "qrs" +#endif +#ifndef QR_SA_SRC_MAX +#define QR_SA_SRC_MAX 112233 +#endif +#define QRCMD_SRC_MAX QR_SA_SRC_MAX + +/* }}} */ +#else +/* {{{ disable structured append */ + +/* parameters */ +#define QRCMD_PTR_TYPE QRCode +#define QRCMD_EXTRA_PARAM_A +#define QRCMD_EXTRA_PARAM_B +#define QRCMD_EXTRA_PARAM_C +#define QRCMD_EXTRA_PARAM_D +#define QRCMD_MAX_NUM_A +#define QRCMD_MAX_NUM_B +#define QRCMD_DEFAULT_VERSION -1 + +/* functions */ +#define qrCmdInit qrInit +#define qrCmdDestroy qrDestroy +#define qrCmdFinalize qrFinalize +#define qrCmdGetErrorInfo qrGetErrorInfo +#define qrCmdAddData qrAddData +#define qrCmdAddData2 qrAddData2 +#define qrCmdOutputSymbol qrOutputSymbol +#define qrCmdOutputSymbol2 qrOutputSymbol2 + +/* others */ +#ifndef QRCMD_PROG_NAME +#define QRCMD_PROG_NAME "qr" +#endif +#define QRCMD_SRC_MAX QR_SRC_MAX + +/* }}} */ +#endif /* QRCMD_STRUCTURED_APPEND */ + +/* }}} */ +/* {{{ function prototype declarations */ + +QRCMD_PTR_TYPE * +qrGetParameter(int argc, char **argv, + int *fmt, int *sep, int *mag, QRCMD_EXTRA_PARAM_D char **output); + +void +qrShowHelp(void); + +/* }}} */ + +#endif /* _QRCMD_H_ */ diff --git a/support/sg2002/kvm_system/main/lib/libqr/qrcnv.c b/support/sg2002/kvm_system/main/lib/libqr/qrcnv.c new file mode 100644 index 0000000..519d94d --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/qrcnv.c @@ -0,0 +1,672 @@ +/* + * QR Code Generator Library: Symbol Converters for Basic Output Formats + * + * Core routines were originally written by Junn Ohta. + * Based on qr.c Version 0.1: 2004/4/3 (Public Domain) + * + * @package libqr + * @author Ryusuke SEKIYAMA + * @copyright 2006-2013 Ryusuke SEKIYAMA + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +#include "qrcnv.h" + +/* {{{ utility macro */ + +#define repeat(m, n) for ((m) = 0; (m) < (n); (m)++) + +/* }}} */ +/* {{{ symbol writing macro */ + +#define qrInitRow(filler) { \ + memset(rbuf, (filler), (size_t)rsize); \ + rptr = rbuf; \ +} + +#define qrWriteRow(m, n) { \ + wsize = (int)(rptr - rbuf); \ + for ((m) = 0; (m) < (n); (m)++) { \ + memcpy(sptr, rbuf, (size_t)wsize); \ + sptr += wsize; \ + } \ + if (wsize < rsize) { \ + *size -= (rsize - wsize) * (n); \ + } \ +} + +/* + * シンボルを出力する際の定型作業をまとめたマクロ + * + * このマクロを呼び出す前に以下の4つのマクロをdefineし、 + * 呼び出した後はundefする + * qrWriteBOR() 行頭を書き込む + * qrWriteEOR() 行末を書き込む + * qrWriteBLM(m, n) 明モジュールを書き込む + * qrWriteDKM(m, n) 暗モジュールを書き込む +*/ +#define qrWriteSymbol(qr, filler) { \ + /* 分離パターン (上) */ \ + if (sepdim > 0) { \ + qrInitRow(filler); \ + qrWriteBOR(); \ + qrWriteBLM(j, imgdim); \ + qrWriteEOR(); \ + qrWriteRow(i, sepdim); \ + } \ + for (i = 0; i < dim; i++) { \ + /* 行を初期化 */ \ + qrInitRow(filler); \ + /* 行頭 */ \ + qrWriteBOR(); \ + /* 分離パターン (左) */ \ + qrWriteBLM(j, sepdim); \ + /* シンボル本体 */ \ + for (j = 0; j < dim; j++) { \ + if (qrIsBlack((qr), i, j)) { \ + qrWriteDKM(jx, mag); \ + } else { \ + qrWriteBLM(jx, mag); \ + } \ + } \ + /* 分離パターン (右) */ \ + qrWriteBLM(j, sepdim); \ + /* 行末 */ \ + qrWriteEOR(); \ + /* 行をmag回繰り返し書き込む */ \ + qrWriteRow(ix, mag); \ + } \ + /* 分離パターン (下) */ \ + if (sepdim > 0) { \ + qrInitRow(filler); \ + qrWriteBOR(); \ + qrWriteBLM(j, imgdim); \ + qrWriteEOR(); \ + qrWriteRow(i, sepdim); \ + } \ +} + +/* }}} */ +/* {{{ Structured append symbol writing macro */ + +#define qrsWriteSymbols(st, filler) { \ + for (k = 0; k < rows; k++) { \ + /* 分離パターン (上) */ \ + if (sepdim > 0) { \ + qrInitRow(filler); \ + qrWriteBOR(); \ + qrWriteBLM(j, xdim); \ + qrWriteEOR(); \ + qrWriteRow(i, sepdim); \ + } \ + for (i = 0; i < dim; i++) { \ + /* 行を初期化 */ \ + qrInitRow(filler); \ + /* 行頭 */ \ + qrWriteBOR(); \ + for (kx = 0; kx < cols; kx++) { \ + /* 分離パターン (左) */ \ + qrWriteBLM(j, sepdim); \ + /* シンボル本体 */ \ + if (order < 0) { \ + pos = k + rows * kx; \ + } else { \ + pos = cols * k + kx; \ + } \ + if (pos < (st)->num) { \ + for (j = 0; j < dim; j++) { \ + if (qrIsBlack((st)->qrs[pos], i, j)) { \ + qrWriteDKM(jx, mag); \ + } else { \ + qrWriteBLM(jx, mag); \ + } \ + } \ + } else { \ + qrWriteBLM(j, zdim); \ + } \ + } \ + /* 分離パターン (右) */ \ + qrWriteBLM(j, sepdim); \ + /* 行末 */ \ + qrWriteEOR(); \ + /* 行をmag回繰り返し書き込む */ \ + qrWriteRow(ix, mag); \ + } \ + } \ + /* 分離パターン (下) */ \ + if (sepdim > 0) { \ + qrInitRow(filler); \ + qrWriteBOR(); \ + qrWriteBLM(j, xdim); \ + qrWriteEOR(); \ + qrWriteRow(i, sepdim); \ + } \ +} + +/* }}} */ +/* {{{ Digit formatted symbol writing macro */ + +#define qrWriteBOR_Digit() +#define qrWriteEOR_Digit() { *rptr++ = ' '; } +#define qrWriteBLM_Digit(m, n) { rptr += (n); } +#define qrWriteDKM_Digit(m, n) { repeat(m, n) { *rptr++ = '1'; } } + +/* }}} */ +/* {{{ Ascii art formatted symbol writing macro */ + +#define qrWriteBOR_ASCII() +#ifdef WIN32 +#define qrWriteEOR_ASCII() { *rptr++ = '\r'; *rptr++ = '\n'; } +#else +#define qrWriteEOR_ASCII() { *rptr++ = '\n'; } +#endif +#ifdef _QRCNV_AA_STYLE_U2588 +#define QRCNV_AA_UNIT 3 +#define qrWriteBLM_ASCII(m, n) { rptr += (n) * 2; } +#define qrWriteDKM_ASCII(m, n) { repeat(m, n) { *rptr++ = 0xe2; *rptr++ = 0x96; *rptr++ = 0x88; } } +#else +#define QRCNV_AA_UNIT 2 +#define qrWriteBLM_ASCII(m, n) { rptr += (n) * 2; } +#define qrWriteDKM_ASCII(m, n) { repeat(m, n) { *rptr++ = 'X'; *rptr++ = 'X'; } } +#endif + +/* }}} */ +/* {{{ JSON formatted symbol writing macro */ + +#define qrWriteBOR_JSON() { *rptr++ = '['; } +#define qrWriteEOR_JSON() { rptr--; *rptr++ = ']'; rptr++; } +#ifdef _QR_JSON_STYLE_BOOLELAN +#define QRCNV_JSON_UNIT 6 +#define qrWriteBLM_JSON(m, n) { repeat(m, n) { memcpy(rptr, "false", 5); rptr += 6; } } +#define qrWriteDKM_JSON(m, n) { repeat(m, n) { memcpy(rptr, "true", 4); rptr += 5; } } +#else +#define QRCNV_JSON_UNIT 2 +#define qrWriteBLM_JSON(m, n) { repeat(m, n) { *rptr++ = '0'; rptr++; } } +#define qrWriteDKM_JSON(m, n) { repeat(m, n) { *rptr++ = '1'; rptr++; } } +#endif + +/* }}} */ +/* {{{ PBM formatted symbol writing macro */ + +#define qrWriteBOR_PBM() +#define qrWriteEOR_PBM() { *rptr++ = '\n'; } +#define qrWriteBLM_PBM(m, n) { repeat(m, n) { rptr++; *rptr++ = '0'; } } +#define qrWriteDKM_PBM(m, n) { repeat(m, n) { rptr++; *rptr++ = '1'; } } + +/* }}} */ +/* {{{ qrSymbolToDigit() */ + +/* + * 生成されたQRコードシンボルを0,1と空白で構成される文字列に変換する + */ +QR_API qr_byte_t * +qrSymbolToDigit(QRCode *qr, int sep, int mag, int *size) +{ + qr_byte_t *rbuf, *rptr; + qr_byte_t *sbuf, *sptr; + int rsize, wsize; + int i, j, ix, jx, dim, imgdim, sepdim; + + QRCNV_CHECK_STATE(); + QRCNV_GET_SIZE(); + + /* + * 変換後のサイズを計算し、メモリを確保する + */ + rsize = imgdim + 1; + *size = rsize * imgdim - 1; + QRCNV_MALLOC(rsize, *size + 1); + + sptr = sbuf; + +#define qrWriteBOR qrWriteBOR_Digit +#define qrWriteEOR qrWriteEOR_Digit +#define qrWriteBLM qrWriteBLM_Digit +#define qrWriteDKM qrWriteDKM_Digit + + /* + * シンボルを書き込む + */ + qrWriteSymbol(qr, '0'); + + /* + * 最後の文字(スペース)を終端文字に置換する + */ + *(--sptr) = '\0'; + +#undef qrWriteBOR +#undef qrWriteEOR +#undef qrWriteBLM +#undef qrWriteDKM + + free(rbuf); + + return sbuf; +} + +/* }}} */ +/* {{{ qrSymbolToASCII() */ + +/* + * 生成されたQRコードシンボルを0,1と空白で構成される文字列に変換する + */ +QR_API qr_byte_t * +qrSymbolToASCII(QRCode *qr, int sep, int mag, int *size) +{ + qr_byte_t *rbuf, *rptr; + qr_byte_t *sbuf, *sptr; + int rsize, wsize; + int i, j, ix, jx, dim, imgdim, sepdim; + + QRCNV_CHECK_STATE(); + QRCNV_GET_SIZE(); + + /* + * 変換後のサイズを計算し、メモリを確保する + */ + rsize = imgdim * QRCNV_AA_UNIT + QRCNV_EOL_SIZE; + *size = rsize * imgdim; + QRCNV_MALLOC(rsize, *size + 1); + + sptr = sbuf; + +#define qrWriteBOR qrWriteBOR_ASCII +#define qrWriteEOR qrWriteEOR_ASCII +#define qrWriteBLM qrWriteBLM_ASCII +#define qrWriteDKM qrWriteDKM_ASCII + + /* + * シンボルを書き込む + */ + qrWriteSymbol(qr, ' '); + + /* + * 終端文字を書き込む + */ + *sptr = '\0'; + +#undef qrWriteBOR +#undef qrWriteEOR +#undef qrWriteBLM +#undef qrWriteDKM + + free(rbuf); + + return sbuf; +} + +/* }}} */ +/* {{{ qrSymbolToJSON() */ + +/* + * 生成されたQRコードシンボルをJSON形式の文字列に変換する + * JSONをデコードすると二次元配列が得られる + */ +QR_API qr_byte_t * +qrSymbolToJSON(QRCode *qr, int sep, int mag, int *size) +{ + qr_byte_t *rbuf, *rptr; + qr_byte_t *sbuf, *sptr; + int rsize, wsize; + int i, j, ix, jx, dim, imgdim, sepdim; + + QRCNV_CHECK_STATE(); + QRCNV_GET_SIZE(); + + /* + * 変換後のサイズを計算し、メモリを確保する + */ + rsize = 1 + imgdim * QRCNV_JSON_UNIT + 1; + *size = 1 + rsize * imgdim - 1 + 1; + QRCNV_MALLOC(rsize, *size + 1); + + sptr = sbuf; + +#define qrWriteBOR qrWriteBOR_JSON +#define qrWriteEOR qrWriteEOR_JSON +#define qrWriteBLM qrWriteBLM_JSON +#define qrWriteDKM qrWriteDKM_JSON + + /* + * ヘッダを書き込む + */ + *sptr++ = '['; + + /* + * シンボルを書き込む + */ + qrWriteSymbol(qr, ','); + + /* + * フッタと終端文字を書き込む + */ + sptr--; + *sptr++ = ']'; + *sptr = '\0'; + +#undef qrWriteBOR +#undef qrWriteEOR +#undef qrWriteBLM +#undef qrWriteDKM + + free(rbuf); + + return sbuf; +} + +/* }}} */ +/* {{{ qrSymbolToPBM() */ + +/* + * 生成されたQRコードシンボルをモノクロ2値の + * アスキー形式Portable Bitmap(PBM)に変換する + */ +QR_API qr_byte_t * +qrSymbolToPBM(QRCode *qr, int sep, int mag, int *size) +{ + qr_byte_t *rbuf, *rptr; + qr_byte_t *sbuf, *sptr; + int rsize, wsize; + int i, j, ix, jx, dim, imgdim, sepdim; + char header[64]; + int hsize; + + QRCNV_CHECK_STATE(); + QRCNV_GET_SIZE(); + + /* + * 変換後のサイズを計算し、メモリを確保する + */ + hsize = snprintf(&(header[0]), sizeof(header), "P1\n%d %d\n", imgdim, imgdim); + if (hsize == -1 || header[hsize - 1] != '\n') { + QRCNV_RETURN_FAILURE(QR_ERR_UNKNOWN, _QR_FUNCTION); + } + rsize = imgdim * 2 + 1; + *size = hsize + rsize * imgdim; + QRCNV_MALLOC(rsize, *size + 1); + + sptr = sbuf; + +#define qrWriteBOR qrWriteBOR_PBM +#define qrWriteEOR qrWriteEOR_PBM +#define qrWriteBLM qrWriteBLM_PBM +#define qrWriteDKM qrWriteDKM_PBM + + /* + * ヘッダを書き込む + */ + memcpy(sptr, header, (size_t)hsize); + sptr += hsize; + + /* + * シンボルを書き込む + */ + qrWriteSymbol(qr, ' '); + + /* + * 終端文字を書き込む + */ + *sptr = '\0'; + +#undef qrWriteBOR +#undef qrWriteEOR +#undef qrWriteBLM +#undef qrWriteDKM + + free(rbuf); + + return sbuf; +} + +/* }}} */ +/* {{{ qrsSymbolsToDigit() */ + +/* + * 構造的連接用qrSymbolToDigit() + * order は無視される + */ +QR_API qr_byte_t * +qrsSymbolsToDigit(QRStructured *st, int sep, int mag, int order, int *size) +{ + QRCode *qr = st->cur; + qr_byte_t *rbuf, *rptr; + qr_byte_t *sbuf, *sptr; + int rsize, wsize; + int i, j, k, ix, jx; + int cols, rows, xdim, ydim, zdim; + int dim, imgdim, sepdim; + + QRCNV_SA_CHECK_STATE(); + QRCNV_SA_IF_ONE(qrSymbolToDigit); + QRCNV_SA_GET_SIZE(); + + /* + * 変換後のサイズを計算し、メモリを確保する + */ + rsize = imgdim + 1; + *size = rsize * imgdim * st->num - 1; + QRCNV_MALLOC(rsize, *size + 1); + + sptr = sbuf; + +#define qrWriteBOR qrWriteBOR_Digit +#define qrWriteEOR qrWriteEOR_Digit +#define qrWriteBLM qrWriteBLM_Digit +#define qrWriteDKM qrWriteDKM_Digit + + /* + * シンボルを書き込む + */ + for (k = 0; k < st->num; k++) { + qrWriteSymbol(st->qrs[k], '0'); + *(--sptr) = '\n'; + sptr++; + } + + /* + * 最後の文字(LF)を終端文字に置換する + */ + *(--sptr) = '\0'; + +#undef qrWriteBOR +#undef qrWriteEOR +#undef qrWriteBLM +#undef qrWriteDKM + + free(rbuf); + + return sbuf; +} + +/* }}} */ +/* {{{ qrsSymbolsToASCII() */ + +/* + * 構造的連接用qrSymbolToASCII() + */ +QR_API qr_byte_t * +qrsSymbolsToASCII(QRStructured *st, int sep, int mag, int order, int *size) +{ + QRCode *qr = st->cur; + qr_byte_t *rbuf, *rptr; + qr_byte_t *sbuf, *sptr; + int rsize, wsize; + int i, j, k, ix, jx, kx; + int cols, rows, pos, xdim, ydim, zdim; + int dim, imgdim, sepdim; + + QRCNV_SA_CHECK_STATE(); + QRCNV_SA_IF_ONE(qrSymbolToASCII); + QRCNV_SA_GET_SIZE(); + + /* + * 変換後のサイズを計算し、メモリを確保する + */ + rsize = xdim * QRCNV_AA_UNIT + QRCNV_EOL_SIZE; + *size = rsize * ydim; + QRCNV_MALLOC(rsize, *size + 1); + + sptr = sbuf; + +#define qrWriteBOR qrWriteBOR_ASCII +#define qrWriteEOR qrWriteEOR_ASCII +#define qrWriteBLM qrWriteBLM_ASCII +#define qrWriteDKM qrWriteDKM_ASCII + + /* + * シンボルを書き込む + */ + qrsWriteSymbols(st, ' '); + + /* + * 終端文字を書き込む + */ + *sptr = '\0'; + +#undef qrWriteBOR +#undef qrWriteEOR +#undef qrWriteBLM +#undef qrWriteDKM + + free(rbuf); + + return sbuf; +} + +/* }}} */ +/* {{{ qrsSymbolsToJSON() */ + +/* + * 構造的連接用qrSymbolToJSON() + * order は無視される + */ +QR_API qr_byte_t * +qrsSymbolsToJSON(QRStructured *st, int sep, int mag, int order, int *size) +{ + QRCode *qr = st->cur; + qr_byte_t *rbuf, *rptr; + qr_byte_t *sbuf, *sptr; + int rsize, wsize; + int i, j, k, ix, jx; + int cols, rows, xdim, ydim, zdim; + int dim, imgdim, sepdim; + + QRCNV_SA_CHECK_STATE(); + /*QRCNV_SA_IF_ONE(qrSymbolToJSON);*/ + QRCNV_SA_GET_SIZE(); + + /* + * 変換後のサイズを計算し、メモリを確保する + */ + rsize = 1 + imgdim * QRCNV_JSON_UNIT + 1; + *size = 1 + (1 + rsize * imgdim + 1) * st->num - 1 + 1; + QRCNV_MALLOC(rsize, *size + 1); + + sptr = sbuf; + +#define qrWriteBOR qrWriteBOR_JSON +#define qrWriteEOR qrWriteEOR_JSON +#define qrWriteBLM qrWriteBLM_JSON +#define qrWriteDKM qrWriteDKM_JSON + + /* + * ヘッダを書き込む + */ + *sptr++ = '['; + + /* + * シンボルを書き込む + */ + for (k = 0; k < st->num; k++) { + *sptr++ = '['; + qrWriteSymbol(st->qrs[k], ','); + sptr--; + *sptr++ = ']'; + *sptr++ = ','; + } + + /* + * フッタと終端文字を書き込む + */ + sptr--; + *sptr++ = ']'; + *sptr = '\0'; + +#undef qrWriteBOR +#undef qrWriteEOR +#undef qrWriteBLM +#undef qrWriteDKM + + free(rbuf); + + return sbuf; +} + +/* }}} */ +/* {{{ qrsSymbolsToPBM() */ + +/* + * 構造的連接用qrSymbolToPBM() + */ +QR_API qr_byte_t * +qrsSymbolsToPBM(QRStructured *st, int sep, int mag, int order, int *size) +{ + QRCode *qr = st->cur; + qr_byte_t *rbuf, *rptr; + qr_byte_t *sbuf, *sptr; + int rsize, wsize; + int i, j, k, ix, jx, kx; + int cols, rows, pos, xdim, ydim, zdim; + int dim, imgdim, sepdim; + char header[64]; + int hsize; + + QRCNV_SA_CHECK_STATE(); + QRCNV_SA_IF_ONE(qrSymbolToPBM); + QRCNV_SA_GET_SIZE(); + + /* + * 変換後のサイズを計算し、メモリを確保する + */ + hsize = snprintf(&(header[0]), 64, "P1\n%d %d\n", xdim, ydim); + if (hsize >= 64) { + QRCNV_RETURN_FAILURE(QR_ERR_UNKNOWN, _QR_FUNCTION); + } + rsize = xdim * 2 + 1; + *size = hsize + rsize * ydim; + QRCNV_MALLOC(rsize, *size + 1); + + sptr = sbuf; + +#define qrWriteBOR qrWriteBOR_PBM +#define qrWriteEOR qrWriteEOR_PBM +#define qrWriteBLM qrWriteBLM_PBM +#define qrWriteDKM qrWriteDKM_PBM + + /* + * ヘッダを書き込む + */ + memcpy(sptr, header, (size_t)hsize); + sptr += hsize; + + /* + * シンボルを書き込む + */ + qrsWriteSymbols(st, ' '); + + /* + * 終端文字を付加する + */ + *sptr = '\0'; + +#undef qrWriteBOR +#undef qrWriteEOR +#undef qrWriteBLM +#undef qrWriteDKM + + free(rbuf); + + return sbuf; +} + +/* }}} */ diff --git a/support/sg2002/kvm_system/main/lib/libqr/qrcnv.h b/support/sg2002/kvm_system/main/lib/libqr/qrcnv.h new file mode 100644 index 0000000..8be6d91 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/qrcnv.h @@ -0,0 +1,165 @@ +/* + * QR Code Generator Library: Private Definitions for Symbol Converters + * + * Core routines were originally written by Junn Ohta. + * Based on qr.c Version 0.1: 2004/4/3 (Public Domain) + * + * @package libqr + * @author Ryusuke SEKIYAMA + * @copyright 2006-2013 Ryusuke SEKIYAMA + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +#ifndef _QRCNV_H_ +#define _QRCNV_H_ + +/* {{{ include headers */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "qr.h" +#include "qr_util.h" +#include +#include +#include + +/* }}} */ +/* {{{ determine EOL size (CR+LF or LF) */ + +#ifdef WIN32 +#define QRCNV_EOL_SIZE 2 +#else +#define QRCNV_EOL_SIZE 1 +#endif + +/* }}} */ +/* {{{ error handlers */ + +#define QRCNV_RETURN_FAILURE(e, p) { \ + qrSetErrorInfo(qr, (e), (p)); \ + if (size) { \ + *size = -1; \ + } \ + return NULL; \ +} + +#define QRCNV_RETURN_FAILURE2(e, p) { \ + qrSetErrorInfo2(qr, (e), (p)); \ + if (size) { \ + *size = -1; \ + } \ + return NULL; \ +} + +#define QRCNV_RETURN_FAILURE3(e, p, ...) { \ + qrSetErrorInfo3(qr, (e), (p), __VA_ARGS__); \ + if (size) { \ + *size = -1; \ + } \ + return NULL; \ +} + +/* }}} */ +/* {{{ allocate memory for the working rowl and the symbo */ + +#define QRCNV_MALLOC(rsize, ssize) { \ + rbuf = (qr_byte_t *)malloc((size_t)(rsize)); \ + if (rbuf == NULL) { \ + QRCNV_RETURN_FAILURE2(QR_ERR_MEMORY_EXHAUSTED, _QR_FUNCTION); \ + } \ + sbuf = (qr_byte_t *)malloc((size_t)(ssize)); \ + if (sbuf == NULL) { \ + free(rbuf); \ + QRCNV_RETURN_FAILURE2(QR_ERR_MEMORY_EXHAUSTED, _QR_FUNCTION); \ + } \ +} + +/* }}} */ +/* {{{ check the state and the parameters */ + +#define QRCNV_CHECK_STATE() { \ + if (qr->state < QR_STATE_FINAL) { \ + QRCNV_RETURN_FAILURE(QR_ERR_STATE, _QR_FUNCTION); \ + } \ +} + +#define QRCNV_GET_SIZE() { \ + if (sep != -1 && (sep < 0 || mag > QR_SEP_MAX)) { \ + QRCNV_RETURN_FAILURE3(QR_ERR_INVALID_SEP, ": %d", sep); \ + } \ + if (mag <= 0 || mag > QR_MAG_MAX) { \ + QRCNV_RETURN_FAILURE3(QR_ERR_INVALID_MAG, ": %d", mag); \ + } \ + dim = qr_vertable[qr->param.version].dimension; \ + if (sep == -1) { \ + sepdim = QR_DIM_SEP * mag; \ + } else { \ + sepdim = sep * mag; \ + } \ + imgdim = dim * mag + sepdim * 2; \ +} + +/* }}} */ +/* {{{ check the state and the parameters (structured append) */ + +#define QRCNV_SA_CHECK_STATE() { \ + if (st->state < QR_STATE_FINAL) { \ + QRCNV_RETURN_FAILURE(QR_ERR_STATE, _QR_FUNCTION); \ + } \ +} + +#define QRCNV_SA_GET_SIZE() { \ + if (sep != -1 && (sep < 0 || mag > QR_SEP_MAX)) { \ + QRCNV_RETURN_FAILURE3(QR_ERR_INVALID_SEP, ": %d", sep); \ + } \ + if (mag <= 0 || mag > QR_MAG_MAX) { \ + QRCNV_RETURN_FAILURE3(QR_ERR_INVALID_MAG, ": %d", mag); \ + } \ + dim = qr_vertable[st->param.version].dimension; \ + if (sep == -1) { \ + sepdim = QR_DIM_SEP * mag; \ + } else { \ + sepdim = sep * mag; \ + } \ + zdim = dim * mag; \ + imgdim = zdim + sepdim * 2; \ + if (order > 0) { \ + if (st->num > order) { \ + cols = order; \ + rows = (st->num + order - 1) / cols; \ + } else { \ + cols = st->num; \ + rows = 1; \ + } \ + } else if (order < 0) { \ + int _a; \ + _a = abs(order); \ + if (st->num > _a) { \ + rows = _a; \ + cols = (st->num + _a - 1) / rows; \ + } else { \ + cols = 1; \ + rows = st->num; \ + } \ + } else { \ + double _r; \ + _r = sqrt((double)st->num); \ + cols = (int)ceil(_r); \ + rows = (int)floor(_r); \ + if (cols * rows < st->num) { \ + rows = (int)ceil(_r); \ + } \ + } \ + xdim = (zdim + sepdim) * cols + sepdim; \ + ydim = (zdim + sepdim) * rows + sepdim; \ +} + +#define QRCNV_SA_IF_ONE(func) { \ + if (st->num == 1) { \ + return (func)(st->qrs[0], sep, mag, size); \ + } \ +} + +#endif /* _QRCNV_H_ */ diff --git a/support/sg2002/kvm_system/main/lib/libqr/qrcnv_bmp.c b/support/sg2002/kvm_system/main/lib/libqr/qrcnv_bmp.c new file mode 100644 index 0000000..79a6cc3 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/libqr/qrcnv_bmp.c @@ -0,0 +1,324 @@ +/* + * QR Code Generator Library: Symbol Converters for Windows Bitmap + * + * Core routines were originally written by Junn Ohta. + * Based on qr.c Version 0.1: 2004/4/3 (Public Domain) + * + * @package libqr + * @author Ryusuke SEKIYAMA + * @copyright 2006-2013 Ryusuke SEKIYAMA + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +#include "qrcnv.h" +#if defined(__BIG_ENDIAN__) || defined(__LITTLE_ENDIAN__) +#include +#endif + +/* {{{ size constants */ + +#define QRCNV_BMP_BISIZE 40 /* Windows Bitmap */ +#define QRCNV_BMP_OFFBITS 62 /* 14(bf) + 40(bi) + 4(rgbq) + 4(rgbq) */ +#define QRCNV_BMP_PPM 3780 /* 96 dpi ~= 3780 ppm */ + /* 72 dpi ~= 2835 ppm */ + +/* }}} */ +/* {{{ utility macro */ + +#if defined(__BIG_ENDIAN__) +#define qrBmpWriteShort(ptr, n) { \ + uint16_t us = (uint16_t)((n) & 0xffffU); \ + qr_byte_t *tmp = (qr_byte_t *)&us; \ + *(ptr)++ = tmp[1]; \ + *(ptr)++ = tmp[0]; \ +} +#elif defined(__LITTLE_ENDIAN__) +#define qrBmpWriteShort(ptr, n) { \ + uint16_t us = (uint16_t)((n) & 0xffffU); \ + qr_byte_t *tmp = (qr_byte_t *)&us; \ + *(ptr)++ = tmp[0]; \ + *(ptr)++ = tmp[1]; \ +} +#else +#define qrBmpWriteShort(ptr, n) { \ + *(ptr)++ = (n) & 0xffU; \ + *(ptr)++ = ((n) >> 8) & 0xffU; \ +} +#endif + +#if defined(__BIG_ENDIAN__) +#define qrBmpWriteLong(ptr, n) { \ + uint32_t ul = (uint32_t)(n); \ + qr_byte_t *tmp = (qr_byte_t *)&ul; \ + *(ptr)++ = tmp[3]; \ + *(ptr)++ = tmp[2]; \ + *(ptr)++ = tmp[1]; \ + *(ptr)++ = tmp[0]; \ +} +#elif defined(__LITTLE_ENDIAN__) +#define qrBmpWriteLong(ptr, n) { \ + uint32_t ul = (uint32_t)(n); \ + qr_byte_t *tmp = (qr_byte_t *)&ul; \ + *(ptr)++ = tmp[0]; \ + *(ptr)++ = tmp[1]; \ + *(ptr)++ = tmp[2]; \ + *(ptr)++ = tmp[3]; \ +} +#else +#define qrBmpWriteLong(ptr, n) { \ + *(ptr)++ = (n) & 0xffU; \ + *(ptr)++ = ((n) >> 8) & 0xffU; \ + *(ptr)++ = ((n) >> 16) & 0xffU; \ + *(ptr)++ = ((n) >> 24) & 0xffU; \ +} +#endif + +#define qrBmpNextPixel() { \ + if (pxshift == 0) { \ + rptr++; \ + pxshift = 7; \ + } else { \ + pxshift--; \ + } \ +} + +/* }}} */ +/* {{{ function prototypes */ + +static qr_byte_t * +qrBmpWriteHeader(qr_byte_t *bof, int size, int width, int height, int imagesize); + +/* }}} */ +/* {{{ qrSymbolToBMP() */ + +/* + * 生成されたQRコードシンボルをモノクロ2値の + * Windows Bitmap(BMP)に変換する + */ +QR_API qr_byte_t * +qrSymbolToBMP(QRCode *qr, int sep, int mag, int *size) +{ + qr_byte_t *rbuf, *rptr; + qr_byte_t *sbuf, *sptr; + int rsize, rmod, imgsize; + int sepskips, pxshift; + int i, j, ix, jx, dim, imgdim, sepdim; + + QRCNV_CHECK_STATE(); + QRCNV_GET_SIZE(); + + /* + * 変換後のサイズを計算し、メモリを確保する + */ + rsize = (imgdim + 7) / 8; + if ((rmod = (rsize % 4)) != 0) { + rsize += 4 - rmod; + } + imgsize = rsize * imgdim; + *size = QRCNV_BMP_OFFBITS + imgsize; + QRCNV_MALLOC(rsize, *size); + + /* + * ヘッダを書き込む + */ + sptr = qrBmpWriteHeader(sbuf, *size, imgdim, imgdim, imgsize); + + /* + * シンボルを書き込む + */ + sepskips = rsize * sepdim; + /* 分離パターン (下) */ + if (sepskips) { + memset(sptr, 0, (size_t)sepskips); + sptr += sepskips; + } + for (i = dim - 1; i >= 0; i--) { + memset(rbuf, 0, (size_t)rsize); + pxshift = 7; + rptr = rbuf; + /* 分離パターン (左) */ + for (j = 0; j < sepdim; j++) { + qrBmpNextPixel(); + } + /* シンボル本体 */ + for (j = 0; j < dim; j++) { + if (qrIsBlack(qr, i, j)) { + for (jx = 0; jx < mag; jx++) { + *rptr |= 1 << pxshift; + qrBmpNextPixel(); + } + } else { + for (jx = 0; jx < mag; jx++) { + qrBmpNextPixel(); + } + } + } + /* 行をmag回繰り返し書き込む */ + for (ix = 0; ix < mag; ix++) { + memcpy(sptr, rbuf, (size_t)rsize); + sptr += rsize; + } + } + /* 分離パターン (上) */ + if (sepskips) { + memset(sptr, 0, (size_t)sepskips); + sptr += sepskips; + } + + free(rbuf); + + return sbuf; +} + +/* }}} */ +/* {{{ qrsSymbolsToBMP() */ + +/* + * 構造的連接用qrSymbolToBMP() + */ +QR_API qr_byte_t * +qrsSymbolsToBMP(QRStructured *st, int sep, int mag, int order, int *size) +{ + QRCode *qr = st->cur; + qr_byte_t *rbuf, *rptr; + qr_byte_t *sbuf, *sptr; + int rsize, rmod, imgsize; + int sepskips, pxshift; + int i, j, k, ix, jx, kx; + int cols, rows, pos, xdim, ydim, zdim; + int dim, imgdim, sepdim; + + QRCNV_SA_CHECK_STATE(); + QRCNV_SA_IF_ONE(qrSymbolToBMP); + QRCNV_SA_GET_SIZE(); + + /* + * 変換後のサイズを計算し、メモリを確保する + */ + rsize = (xdim + 7) / 8; + if ((rmod = (rsize % 4)) != 0) { + rsize += 4 - rmod; + } + imgsize = rsize * ydim; + *size = QRCNV_BMP_OFFBITS + imgsize; + QRCNV_MALLOC(rsize, *size); + + /* + * ヘッダを書き込む + */ + sptr = qrBmpWriteHeader(sbuf, *size, xdim, ydim, imgsize); + + /* + * シンボルを書き込む + */ + sepskips = rsize * sepdim; + for (k = rows - 1; k >= 0; k--) { + /* 分離パターン (下) */ + if (sepskips) { + memset(sptr, 0, (size_t)sepskips); + sptr += sepskips; + } + for (i = dim - 1; i >= 0; i--) { + memset(rbuf, 0, (size_t)rsize); + pxshift = 7; + rptr = rbuf; + for (kx = 0; kx < cols; kx++) { + /* 分離パターン (左) */ + for (j = 0; j < sepdim; j++) { + qrBmpNextPixel(); + } + /* シンボル本体 */ + if (order < 0) { + pos = k + rows * kx; + } else { + pos = cols * k + kx; + } + if (pos >= st->num) { + break; + } + for (j = 0; j < dim; j++) { + if (qrIsBlack(st->qrs[pos], i, j)) { + for (jx = 0; jx < mag; jx++) { + *rptr |= 1 << pxshift; + qrBmpNextPixel(); + } + } else { + for (jx = 0; jx < mag; jx++) { + qrBmpNextPixel(); + } + } + } + } + /* 行をmag回繰り返し書き込む */ + for (ix = 0; ix < mag; ix++) { + memcpy(sptr, rbuf, (size_t)rsize); + sptr += rsize; + } + } + } + /* 分離パターン (上) */ + if (sepskips) { + memset(sptr, 0, (size_t)sepskips); + sptr += sepskips; + } + + free(rbuf); + + return sbuf; +} + +/* }}} */ +/* {{{ qrBmpWriteHeader() */ + +static qr_byte_t * +qrBmpWriteHeader(qr_byte_t *bof, int size, int width, int height, int imagesize) +{ + qr_byte_t *ptr; + + ptr = bof; + + /* + * BITMAPFILEHEADER + */ + *ptr++ = 'B'; /* bfType */ + *ptr++ = 'M'; /* bfType */ + qrBmpWriteLong(ptr, size); /* bfSize */ + qrBmpWriteShort(ptr, 0); /* bfReserved1 */ + qrBmpWriteShort(ptr, 0); /* bfReserved2 */ + qrBmpWriteLong(ptr, QRCNV_BMP_OFFBITS); /* bfOffBits */ + + /* + * BMPINFOHEADER + */ + qrBmpWriteLong(ptr, QRCNV_BMP_BISIZE); /* biSize */ + qrBmpWriteLong(ptr, width); /* biWidth */ + qrBmpWriteLong(ptr, height); /* biHeight */ + qrBmpWriteShort(ptr, 1); /* biPlanes */ + qrBmpWriteShort(ptr, 1); /* biBitCount - 1 bit per pixel */ + qrBmpWriteLong(ptr, 0); /* biCopmression - BI_RGB */ + qrBmpWriteLong(ptr, imagesize); /* biSizeImage */ + qrBmpWriteLong(ptr, QRCNV_BMP_PPM); /* biXPixPerMeter */ + qrBmpWriteLong(ptr, QRCNV_BMP_PPM); /* biYPixPerMeter */ + qrBmpWriteLong(ptr, 2); /* biClrUsed */ + qrBmpWriteLong(ptr, 2); /* biCirImportant */ + + /* + * RGBQUAD - white + */ + *ptr++ = 255; /* rgbBlue */ + *ptr++ = 255; /* rgbGreen */ + *ptr++ = 255; /* rgbRed */ + *ptr++ = 0; /* rgbReserved */ + + /* + *RGBQUAD - black + */ + *ptr++ = 0; /* rgbBlue */ + *ptr++ = 0; /* rgbGreen */ + *ptr++ = 0; /* rgbRed */ + *ptr++ = 0; /* rgbReserved */ + + return ptr; +} + +/* }}} */ diff --git a/support/sg2002/kvm_system/main/lib/oled_ctrl/oled_ctrl.cpp b/support/sg2002/kvm_system/main/lib/oled_ctrl/oled_ctrl.cpp new file mode 100644 index 0000000..0543ba5 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/oled_ctrl/oled_ctrl.cpp @@ -0,0 +1,682 @@ +#include "oled_ctrl.h" + +using namespace maix; +using namespace maix::sys; +using namespace maix::peripheral; +i2c::I2C oled_alpha(1, i2c::Mode::MASTER); +i2c::I2C oled_beta(5, i2c::Mode::MASTER); + +uint8_t OLED_state = 0; +uint8_t kvm_hw_ver = 0; + +/* mode = OLED_CMD + * = OLED_DATA */ +void oled_write_register(uint8_t mode, uint8_t data) +{ + if(OLED_state){ + uint8_t buf[2]; + + buf[0] = mode; + buf[1] = data; + if(kvm_hw_ver == 0){ + if(oled_alpha.writeto(OLED_ADDR, buf, 2) == (int)-err::Err::ERR_IO){ + return; + } + } else if(kvm_hw_ver == 1){ + if(oled_beta.writeto(OLED_ADDR, buf, 2) == (int)-err::Err::ERR_IO){ + return; + } + } else if(kvm_hw_ver == 2){ + if(oled_beta.writeto(OLED_PCIe_ADDR, buf, 2) == (int)-err::Err::ERR_IO){ + return; + } + } + return; + } + return; +} + +int oled_exist(void) +{ + // gpio::GPIO oled_rst("GPIOP19", gpio::Mode::OUT, gpio::Pull::PULL_UP); + // oled_rst.high(); + // system("devmem 0x030010D4 32 0x3"); + // system("devmem 0x030010D0 32 0x2"); + // system("devmem 0x030010DC 32 0x2"); + + if(access("/etc/kvm/hw", F_OK) == 0){ + uint8_t RW_Data[2]; + FILE *fp = fopen("/etc/kvm/hw", "r"); + fread(RW_Data, sizeof(char), 2, fp); + fclose(fp); + if(RW_Data[0] == 'b') kvm_hw_ver = 1; + else if(RW_Data[0] == 'p') kvm_hw_ver = 2; + } + + uint8_t buf[2]; + + buf[0] = OLED_CMD; + buf[1] = 0xAE; + if(kvm_hw_ver == 0){ + if(oled_alpha.writeto(OLED_ADDR, buf, 2) == (int)-err::Err::ERR_IO){ + return 0; + } + } else if(kvm_hw_ver == 1){ + printf("beta\r\n"); + if(oled_beta.writeto(OLED_ADDR, buf, 2) == (int)-err::Err::ERR_IO){ + return 0; + } + } else if(kvm_hw_ver == 2){ + printf("PCIe\r\n"); + if(oled_beta.writeto(OLED_PCIe_ADDR, buf, 2) == (int)-err::Err::ERR_IO){ + return 0; + } + } + return 1; +} + +// 坐标设置 +void OLED_Set_Pos(uint8_t x, uint8_t y) +{ + // oled_write_register(OLED_CMD, 0xb0+y); + // oled_write_register(OLED_CMD, ((x&0xf0)>>4)|0x10); + // oled_write_register(OLED_CMD, (x&0x0f)); + if(kvm_hw_ver == 2){ + x += 32; + y += 8; + } + oled_write_register(OLED_CMD, 0xb0+y); + oled_write_register(OLED_CMD, ((x&0xf0)>>4)|0x10); + oled_write_register(OLED_CMD, (x&0x0f)); +} + +//开启OLED显示 +void OLED_Display_On() +{ + oled_write_register(OLED_CMD, 0X8D); + oled_write_register(OLED_CMD, 0X14); + oled_write_register(OLED_CMD, 0XAF); +} + +//关闭OLED显示 +void OLED_Display_Off() +{ + oled_write_register(OLED_CMD, 0X8D); + oled_write_register(OLED_CMD, 0X10); + oled_write_register(OLED_CMD, 0XAE); +} + +//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!! +void OLED_Clear(void) +{ + uint8_t i,n; + for(i=0;i<8;i++) + { + oled_write_register(OLED_CMD, 0xb0+i); + oled_write_register(OLED_CMD, 0x00); + oled_write_register(OLED_CMD, 0x10); + for(n=0;n<128;n++)oled_write_register(OLED_DATA, 0x00); + } //更新显示 +} + +void OLED_Fill(void) +{ + uint8_t i,n; + for(i=0;i<8;i++) + { + oled_write_register(OLED_CMD, 0xb0+i); + oled_write_register(OLED_CMD, 0x00); + oled_write_register(OLED_CMD, 0x10); + for(n=0;n<128;n++)oled_write_register(OLED_DATA, 0xFF); + } //更新显示 +} + +//在指定位置显示一个字符,包括部分字符 +//x:0~127 +//y:0~63 +//sizey:选择字体 6x8 8x16 +void OLED_ShowChar(uint8_t x,uint8_t y,char chr,uint8_t sizey) +{ + uint8_t c=0,sizex=sizey/2; + uint16_t i=0,size1; + if(sizey==8)size1=6; + else if(sizey==4)size1=4; + else size1=(sizey/8+((sizey%8)?1:0))*(sizey/2); + c=chr-' ';//得到偏移后的值 + OLED_Set_Pos(x, y); + for(i=0; i +#include +#include +#include +#include + +#define OLED_DISABLE 0 +#define OLED_ENABLE 1 +#define OLED_ADDR 0x3D +#define OLED_PCIe_ADDR 0x3C +#define OLED_CMD 0x00 +#define OLED_DATA 0x40 + +#define HDMI_STATE 0x01 +#define HID_STATE 0x02 +#define ETH_STATE 0x04 +#define WIFI_STATE 0x08 + +#define KVM_INIT 0x00 +#define KVM_ETH_IP 0x01 +#define KVM_WIFI_IP 0x02 +#define KVM_HDMI_STATE 0x03 +#define KVM_HDMI_RES 0x04 +#define KVM_STEAM_TYPE 0x05 +#define KVM_STEAM_FPS 0x06 +#define KVM_JPG_QLTY 0x07 +#define KVM_CPU_IDLE 0x08 + +#define KVM_RES_none 0x00 +#define KVM_RES_480P 0x01 +#define KVM_RES_600P 0x02 +#define KVM_RES_720P 0x03 +#define KVM_RES_1080P 0x04 + +#define KVM_TYPE_none 0x00 +#define KVM_TYPE_MJPG 0x01 +#define KVM_TYPE_H264 0x02 + +#define AlignRightEND 127 +#define AlignRightEND_P 63 + +extern uint8_t OLED_state; +extern uint8_t kvm_hw_ver; + +/************************************6*8的点阵************************************/ +const uint8_t oled_asc2_0806[][6] ={ +{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},// sp +{0x00, 0x00, 0x00, 0x2f, 0x00, 0x00},// ! +{0x00, 0x00, 0x07, 0x00, 0x07, 0x00},// " +{0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14},// # +{0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12},// $ +{0x00, 0x23, 0x13, 0x08, 0x64, 0x62},// % +{0x00, 0x36, 0x49, 0x55, 0x22, 0x50},// & +{0x00, 0x00, 0x05, 0x03, 0x00, 0x00},// ' +{0x00, 0x00, 0x1c, 0x22, 0x41, 0x00},// ( +{0x00, 0x00, 0x41, 0x22, 0x1c, 0x00},// ) +// {0x00, 0x14, 0x08, 0x3E, 0x08, 0x14},// * +{0x00, 0x00, 0x28, 0x10, 0x28, 0x00},// *->x +{0x00, 0x08, 0x08, 0x3E, 0x08, 0x08},// + +{0x00, 0x00, 0x00, 0xA0, 0x60, 0x00},// , +{0x00, 0x08, 0x08, 0x08, 0x08, 0x08},// - +{0x00, 0x00, 0x60, 0x60, 0x00, 0x00},// . +{0x00, 0x20, 0x10, 0x08, 0x04, 0x02},// / +{0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E},// 0 +{0x00, 0x00, 0x42, 0x7F, 0x40, 0x00},// 1 +{0x00, 0x42, 0x61, 0x51, 0x49, 0x46},// 2 +{0x00, 0x21, 0x41, 0x45, 0x4B, 0x31},// 3 +{0x00, 0x18, 0x14, 0x12, 0x7F, 0x10},// 4 +{0x00, 0x27, 0x45, 0x45, 0x45, 0x39},// 5 +{0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30},// 6 +{0x00, 0x01, 0x71, 0x09, 0x05, 0x03},// 7 +{0x00, 0x36, 0x49, 0x49, 0x49, 0x36},// 8 +{0x00, 0x06, 0x49, 0x49, 0x29, 0x1E},// 9 +{0x00, 0x00, 0x36, 0x36, 0x00, 0x00},// : +{0x00, 0x00, 0x56, 0x36, 0x00, 0x00},// ; +{0x00, 0x08, 0x14, 0x22, 0x41, 0x00},// < +{0x00, 0x14, 0x14, 0x14, 0x14, 0x14},// = +{0x00, 0x00, 0x41, 0x22, 0x14, 0x08},// > +{0x00, 0x02, 0x01, 0x51, 0x09, 0x06},// ? +{0x00, 0x32, 0x49, 0x59, 0x51, 0x3E},// @ +{0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C},// A +{0x00, 0x7F, 0x49, 0x49, 0x49, 0x36},// B +{0x00, 0x3E, 0x41, 0x41, 0x41, 0x22},// C +{0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C},// D +{0x00, 0x7F, 0x49, 0x49, 0x49, 0x41},// E +{0x00, 0x7F, 0x09, 0x09, 0x09, 0x01},// F +{0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A},// G +{0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F},// H +{0x00, 0x00, 0x41, 0x7F, 0x41, 0x00},// I +{0x00, 0x20, 0x40, 0x41, 0x3F, 0x01},// J +{0x00, 0x7F, 0x08, 0x14, 0x22, 0x41},// K +{0x00, 0x7F, 0x40, 0x40, 0x40, 0x40},// L +{0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F},// M +{0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F},// N +{0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E},// O +{0x00, 0x7F, 0x09, 0x09, 0x09, 0x06},// P +{0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E},// Q +{0x00, 0x7F, 0x09, 0x19, 0x29, 0x46},// R +{0x00, 0x46, 0x49, 0x49, 0x49, 0x31},// S +{0x00, 0x01, 0x01, 0x7F, 0x01, 0x01},// T +{0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F},// U +{0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F},// V +{0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F},// W +{0x00, 0x63, 0x14, 0x08, 0x14, 0x63},// X +{0x00, 0x07, 0x08, 0x70, 0x08, 0x07},// Y +{0x00, 0x61, 0x51, 0x49, 0x45, 0x43},// Z +{0x00, 0x00, 0x7F, 0x41, 0x41, 0x00},// [ +{0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55},// 55 +{0x00, 0x00, 0x41, 0x41, 0x7F, 0x00},// ] +{0x00, 0x04, 0x02, 0x01, 0x02, 0x04},// ^ +{0x00, 0x40, 0x40, 0x40, 0x40, 0x40},// _ +{0x00, 0x00, 0x01, 0x02, 0x04, 0x00},// ' +{0x00, 0x20, 0x54, 0x54, 0x54, 0x78},// a +{0x00, 0x7F, 0x48, 0x44, 0x44, 0x38},// b +{0x00, 0x38, 0x44, 0x44, 0x44, 0x20},// c +{0x00, 0x38, 0x44, 0x44, 0x48, 0x7F},// d +{0x00, 0x38, 0x54, 0x54, 0x54, 0x18},// e +{0x00, 0x08, 0x7E, 0x09, 0x01, 0x02},// f +{0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C},// g +{0x00, 0x7F, 0x08, 0x04, 0x04, 0x78},// h +{0x00, 0x00, 0x44, 0x7D, 0x40, 0x00},// i +{0x00, 0x40, 0x80, 0x84, 0x7D, 0x00},// j +{0x00, 0x7F, 0x10, 0x28, 0x44, 0x00},// k +{0x00, 0x00, 0x41, 0x7F, 0x40, 0x00},// l +{0x00, 0x7C, 0x04, 0x18, 0x04, 0x78},// m +{0x00, 0x7C, 0x08, 0x04, 0x04, 0x78},// n +{0x00, 0x38, 0x44, 0x44, 0x44, 0x38},// o +{0x00, 0xFC, 0x24, 0x24, 0x24, 0x18},// p +{0x00, 0x18, 0x24, 0x24, 0x18, 0xFC},// q +{0x00, 0x7C, 0x08, 0x04, 0x04, 0x08},// r +{0x00, 0x48, 0x54, 0x54, 0x54, 0x20},// s +{0x00, 0x04, 0x3F, 0x44, 0x40, 0x20},// t +{0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C},// u +{0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C},// v +{0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C},// w +{0x00, 0x44, 0x28, 0x10, 0x28, 0x44},// x +{0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C},// y +{0x00, 0x44, 0x64, 0x54, 0x4C, 0x44},// z +{0x14, 0x14, 0x14, 0x14, 0x14, 0x14},// horiz lines + +}; + +const uint8_t oled_asc2_0804[][4] ={ +{0x00,0x00,0x00,0x00},// sp +{0x7c,0x52,0x7c,0x00},// ! +{0x00,0x0c,0x00,0x0c},// " +{0x00,0x7c,0x28,0x7c},// # +{0x00,0x5c,0x5e,0x74},// $ +{0x6c,0x30,0x18,0x6c},// % +{0x00,0x68,0x54,0x68},// & +{0x00,0x00,0x0c,0x00},// ' +{0x00,0x38,0x44,0x00},// ( +{0x00,0x44,0x38,0x00},// ) +{0x00,0x00,0x30,0x00},// * +{0x00,0x10,0x38,0x10},// + +{0x00,0x00,0x60,0x00},// , +{0x00,0x10,0x10,0x10},// - +{0x00,0x00,0x40,0x00},// . +{0x00,0x40,0x38,0x04},// / +{0x00,0x7c,0x44,0x7c},// 0 +{0x00,0x44,0x7c,0x40},// 1 +{0x00,0x74,0x54,0x5c},// 2 +{0x00,0x54,0x54,0x7c},// 3 +{0x00,0x1c,0x10,0x7c},// 4 +{0x00,0x5c,0x54,0x74},// 5 +{0x00,0x7c,0x54,0x74},// 6 +{0x00,0x04,0x04,0x7c},// 7 +{0x00,0x7c,0x54,0x7c},// 8 +{0x00,0x5c,0x54,0x7c},// 9 +{0x00,0x00,0x28,0x00},// : +{0x00,0x40,0x28,0x00},// ; +{0x00,0x10,0x28,0x44},// < +{0x00,0x28,0x28,0x28},// = +{0x00,0x44,0x28,0x10},// > +{0x00,0x04,0x54,0x0c},// ? +{0x38,0x64,0x54,0x78},// @ +{0x00,0x78,0x14,0x78},// A +{0x00,0x7c,0x54,0x38},// B +{0x00,0x38,0x44,0x44},// C +{0x00,0x7c,0x44,0x38},// D +{0x00,0x7c,0x54,0x54},// E +{0x00,0x7c,0x14,0x14},// F +{0x00,0x6c,0x54,0x74},// G +{0x00,0x7c,0x10,0x7c},// H +{0x00,0x44,0x7c,0x44},// I +{0x00,0x44,0x7c,0x04},// J +{0x00,0x7c,0x10,0x6c},// K +{0x00,0x7c,0x40,0x40},// L +{0x00,0x7c,0x18,0x7c},// M +{0x00,0x6c,0x18,0x74},// N +{0x00,0x38,0x44,0x38},// O +{0x00,0x7c,0x14,0x1c},// P +{0x00,0x3c,0x24,0x7c},// Q +{0x00,0x7c,0x14,0x6c},// R +{0x00,0x58,0x54,0x64},// S +{0x00,0x04,0x7c,0x04},// T +{0x00,0x3c,0x40,0x7c},// U +{0x00,0x3c,0x60,0x3c},// V +{0x00,0x7c,0x30,0x7c},// W +{0x00,0x6c,0x10,0x6c},// X +{0x00,0x0c,0x70,0x0c},// Y +{0x00,0x64,0x54,0x4c},// Z +{0x00,0x7c,0x44,0x00},// [ +{0x28,0x54,0x28,0x54},// 55 +{0x00,0x44,0x7c,0x00},// ] +}; + +//16*16 ASCII字符集点阵 +const uint8_t oled_asc2_1608[][16]={ +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/ +{0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00},/*"!",1*/ +{0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*""",2*/ +{0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00},/*"#",3*/ +{0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00},/*"$",4*/ +{0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00},/*"%",5*/ +{0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10},/*"&",6*/ +{0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/ +{0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00},/*"(",8*/ +{0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00},/*")",9*/ +{0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00},/*"*",10*/ +{0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00},/*"+",11*/ +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00},/*",",12*/ +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01},/*"-",13*/ +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00},/*".",14*/ +{0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00},/*"/",15*/ +{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00},/*"0",16*/ +{0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"1",17*/ +{0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00},/*"2",18*/ +{0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00},/*"3",19*/ +{0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00},/*"4",20*/ +{0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00},/*"5",21*/ +{0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00},/*"6",22*/ +{0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00},/*"7",23*/ +{0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00},/*"8",24*/ +{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00},/*"9",25*/ +{0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00},/*":",26*/ +{0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00},/*";",27*/ +{0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00},/*"<",28*/ +{0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00},/*"=",29*/ +{0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00},/*">",30*/ +{0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00},/*"?",31*/ +{0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00},/*"@",32*/ +{0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20},/*"A",33*/ +{0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00},/*"B",34*/ +{0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00},/*"C",35*/ +{0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00},/*"D",36*/ +{0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00},/*"E",37*/ +{0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00},/*"F",38*/ +{0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00},/*"G",39*/ +{0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20},/*"H",40*/ +{0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"I",41*/ +{0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00},/*"J",42*/ +{0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00},/*"K",43*/ +{0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00},/*"L",44*/ +{0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00},/*"M",45*/ +{0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00},/*"N",46*/ +{0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00},/*"O",47*/ +{0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00},/*"P",48*/ +{0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00},/*"Q",49*/ +{0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20},/*"R",50*/ +{0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00},/*"S",51*/ +{0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},/*"T",52*/ +{0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},/*"U",53*/ +{0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00},/*"V",54*/ +{0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00},/*"W",55*/ +{0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20},/*"X",56*/ +{0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},/*"Y",57*/ +{0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00},/*"Z",58*/ +{0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00},/*"[",59*/ +{0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00},/*"\",60*/ +{0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00},/*"]",61*/ +{0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"^",62*/ +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},/*"_",63*/ +{0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/ +{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20},/*"a",65*/ +{0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00},/*"b",66*/ +{0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00},/*"c",67*/ +{0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20},/*"d",68*/ +{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00},/*"e",69*/ +{0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"f",70*/ +{0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00},/*"g",71*/ +{0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},/*"h",72*/ +{0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"i",73*/ +{0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00},/*"j",74*/ +{0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00},/*"k",75*/ +{0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"l",76*/ +{0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F},/*"m",77*/ +{0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},/*"n",78*/ +{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},/*"o",79*/ +{0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00},/*"p",80*/ +{0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80},/*"q",81*/ +{0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00},/*"r",82*/ +{0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00},/*"s",83*/ +{0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00},/*"t",84*/ +{0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20},/*"u",85*/ +{0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00},/*"v",86*/ +{0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00},/*"w",87*/ +{0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00},/*"x",88*/ +{0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00},/*"y",89*/ +{0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00},/*"z",90*/ +{0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40},/*"{",91*/ +{0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00},/*"|",92*/ +{0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00},/*"}",93*/ +{0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"~",94*/ +}; + +// [!] +const uint8_t etherror[6] +{0x00,0x3e,0x7f,0x51,0x7f,0x3e}; // [!] + +//6*xx 图标点阵 +const uint8_t oled_kvm_0621[][21]{ +{0x00,0x3e,0x08,0x08,0x3e,0x00,0x3e,0x22,0x22,0x1c,0x00,0x3c,0x02,0x0c,0x02,0x3c,0x00,0x22,0x3e,0x22,0x00},/*"HDMI=0",0*,21*/ +{0x3e,0x41,0x77,0x77,0x41,0x7f,0x41,0x5d,0x5d,0x63,0x7f,0x43,0x7d,0x73,0x7d,0x43,0x7f,0x5d,0x41,0x5d,0x3e},/*"HDMI=1",0*,21*/ +{0x00,0x3e,0x08,0x08,0x3e,0x00,0x22,0x3e,0x20,0x00,0x3e,0x22,0x22,0x1c,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"USB=0",0, 15*/ +{0x3e,0x41,0x77,0x77,0x41,0x7f,0x5d,0x41,0x5f,0x7f,0x41,0x5d,0x5d,0x63,0x3e,0x00,0x00,0x00,0x00,0x00,0x00},/*"USB=1",1, 15*/ +{0x00,0x3e,0x2a,0x2a,0x22,0x00,0x02,0x3e,0x02,0x00,0x3e,0x08,0x08,0x3e,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"ETH=0",1, 15*/ +{0x3e,0x41,0x55,0x55,0x5d,0x7f,0x7d,0x41,0x7d,0x7f,0x41,0x77,0x77,0x41,0x3e,0x00,0x00,0x00,0x00,0x00,0x00},/*"ETH=1",1, 15*/ +{0x00,0x1e,0x20,0x18,0x20,0x1e,0x00,0x3a,0x00,0x3e,0x0a,0x0a,0x00,0x3a,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"WiFi=0",1, 15*/ +{0x3e,0x61,0x5f,0x67,0x5f,0x61,0x7f,0x45,0x7f,0x41,0x75,0x75,0x7f,0x45,0x3e,0x00,0x00,0x00,0x00,0x00,0x00},/*"WiFi=1",1, 15*/ +}; +const uint8_t oled_kvm_logo[116] = +{ + 0xf8,0x1f,0xfc,0x3f,0xf6,0x67,0x06,0x60,0xce,0x6f,0x3e,0x7f,0xfe,0x7c,0xf6,0x73,0x06,0x60,0xe6,0x6f, + 0xfe,0x7f,0xfe,0x71,0xfe,0x6e,0xfe,0x6e,0xfe,0x61,0xfe,0x7f,0xfe,0x60,0xfe,0x7e,0xfe,0x7e,0xfe,0x60, + 0xfe,0x7f,0xfe,0x71,0xfe,0x6e,0xfe,0x6e,0xfe,0x71,0xfe,0x7f,0xf6,0x6f,0x06,0x60,0x76,0x6f,0x3e,0x7e, + 0x8e,0x79,0xe6,0x63,0xfe,0x6f,0xf6,0x7f,0x06,0x7f,0x7e,0x78,0xfe,0x63,0xfe,0x79,0x3e,0x7e,0x86,0x7f, + 0xf6,0x6f,0x7e,0x60,0x86,0x6f,0x86,0x7f,0x7e,0x7c,0x86,0x7f,0x86,0x6f,0x7e,0x60,0xfe,0x6f,0xfc,0x3f, + 0xf8,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0x3f,0xfc,0x3f +}; +const uint8_t oled_sipeed_logo[32] = { +0x00,0x00,0xf8,0x1f,0xfc,0x3f,0xf6,0x7f,0xb2,0x7f,0x1e,0x67,0x0e,0x66,0x66,0x66, +0x66,0x66,0x66,0x66,0x66,0x70,0xfe,0x78,0xfe,0x7d,0xfc,0x3f,0xf8,0x1f,0x00,0x00 +}; +int oled_exist(void); +void OLED_Clear(void); +void OLED_Fill(void); +void OLED_Init(void); +void OLED_Revolve(void); +void OLED_ShowState(uint8_t x,uint8_t y,char chr,uint8_t size); +void OLED_DisplayTurn(uint8_t i); +void OLED_ColorTurn(uint8_t i); +void OLED_ShowError(uint8_t x,uint8_t y,uint8_t _state); +void OLED_ShowCharTurn(uint8_t x,uint8_t y,char chr,uint8_t sizey); +void OLED_ShowNum(uint8_t x, uint8_t y, uint8_t num, uint8_t len, uint8_t sizey); +void OLED_ShowString(uint8_t x, uint8_t y, char *chr, uint8_t sizey); +void OLED_ShowStringTurn(uint8_t x, uint8_t y, char *chr, uint8_t sizey); +void OLED_ShowStringtoend(uint8_t x, uint8_t y, char *chr, uint8_t sizey, uint8_t end); +void OLED_ShowString_AlignRight(uint8_t x_end, uint8_t y, char *chr, uint8_t size); +void OLED_ROW(uint8_t _EN); +void OLED_ShowLogo(); +void OLED_ShowSipeedLogo(); +void OLED_Showline(); +void OLED_Showline_1(); +void OLED_ShowIMG(uint8_t x,uint8_t y,char *chr,uint8_t width,uint8_t height); +void OLED_ShowKVMStreamState(uint8_t kvm_state_s, void* pdata); +void OLED_Show_Res(uint16_t _w, uint16_t _h); +void OLED_ShowKVMState(uint8_t _Interface, int8_t _EN); +void OLED_Show_Network_Error(uint8_t _state); + +#endif // OLED_CTRL_H_ \ No newline at end of file diff --git a/support/sg2002/kvm_system/main/lib/oled_ui/oled_ui.cpp b/support/sg2002/kvm_system/main/lib/oled_ui/oled_ui.cpp new file mode 100644 index 0000000..177af9b --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/oled_ui/oled_ui.cpp @@ -0,0 +1,542 @@ +#include "oled_ui.h" + +using namespace maix; +using namespace maix::sys; + +extern kvm_sys_state_t kvm_sys_state; +extern kvm_oled_state_t kvm_oled_state; + +void kvm_init_cube_ui(void) +{ + uint8_t temp; + char* str_temp = "192.168.1.243"; + + OLED_Clear(); + // OLED_Revolve(); + OLED_ShowLogo(); + OLED_ShowSipeedLogo(); + OLED_ShowKVMState(HDMI_STATE, 0); + OLED_ShowKVMState(HID_STATE, 0); + OLED_ShowKVMState(ETH_STATE, 0); + OLED_ShowKVMState(WIFI_STATE, 0); + OLED_Showline(); + OLED_ShowKVMStreamState(KVM_INIT, &temp); + temp = 0; + OLED_ShowKVMStreamState(KVM_ETH_IP, &temp); + temp = KVM_RES_none; + OLED_ShowKVMStreamState(KVM_HDMI_RES, &temp); + temp = KVM_TYPE_none; + OLED_ShowKVMStreamState(KVM_STEAM_TYPE, &temp); + temp = 0; + OLED_ShowKVMStreamState(KVM_STEAM_FPS, &temp); + temp = 80; + OLED_ShowKVMStreamState(KVM_JPG_QLTY, &temp); +} + +void kvm_init_pcie_ui(void) +{ + OLED_Revolve(); + OLED_Showline_1(); + OLED_ShowSipeedLogo(); + OLED_ShowKVMState(HDMI_STATE, 0); + OLED_ShowKVMState(HID_STATE, 0); + OLED_ShowKVMState(ETH_STATE, 0); + OLED_ShowKVMState(WIFI_STATE, 0); + + // OLED_ShowString(0, 2, "!192.168.222.197", 4); + // OLED_ShowString(1, 2, " 192.168.2.197", 4); + // OLED_ShowString(1, 3, "1920*1080", 4); + // OLED_ShowString(41, 3, " FPS", 4); + + // OLED_ShowString_AlignRight(AlignRightEND_P, 2, "192.168.0.69", 4); + // OLED_ShowString_AlignRight(37, 3, "1920*1080", 4); + OLED_ShowString_AlignRight(AlignRightEND_P, 3, " FPS", 4); + // OLED_ShowString(0, 3, "\"ABCDEFGHIJKLMMN\'", 4); + // OLED_ShowString(5, 2, "1", 4); +} + +void kvm_init_ui(void) +{ + if(kvm_hw_ver != 2){ + kvm_init_cube_ui(); + } else { + kvm_init_pcie_ui(); + } +} + +void qrcode_to_oled(QRCode *qr) +{ + char *p_oled_data; + uint16_t count = 0; + uint8_t bit; + uint8_t begin_x = 2; + uint8_t begin_y = 2; + p_oled_data = (char *)malloc( 132 * sizeof(char)); + if(p_oled_data != NULL){ + // fill + for(int i = 0; i < 132; i++){ + p_oled_data[i] = 0xFF; + } + // i | ; j - + for(int i = 0; i < 29; i++){ + for(int j = 0; j < 29; j++){ + if(qrIsBlacke(qr, i, j)){ + // 敲黑点 + uint16_t data_index = ((i+begin_y)/8)*33+(j+begin_x); + uint8_t mask = ~(0x01 << ((i+begin_y)%8)); + p_oled_data[data_index] &= mask; + } + } + } + OLED_Fill(); + OLED_ShowIMG(29, 0, p_oled_data, 33, 4); + free(p_oled_data); + } +} + +int qrencode(char *string) +{ + // test: qrencode("WIFI:T:WPA2;S:NanoKVM;P:12345678;;"); + int errcode = QR_ERR_NONE; + QRCode* p = qrInit(3, QR_EM_8BIT, 1, 4, &errcode); + if (p == NULL) { + printf("error\n"); + return -1; + } + qrAddData(p, (const qr_byte_t*)string, strlen(string)); + if (!qrFinalize(p)) { + printf("finalize error\n"); + return -1; + } + + qrcode_to_oled(p); + if(string[0] == 'W'){ + OLED_ShowStringTurn(3, 1, "WiFi", 8); + OLED_ShowStringTurn(3, 2, "AP:", 8); + } else if(string[0] == 'h'){ + if(string[7] == '1'){ + OLED_ShowStringTurn(3, 1, "Web:", 8); + } else if(string[8] == 'w'){ + OLED_ShowStringTurn(3, 1, "WiKi", 8); + } + } + int size = 0; + // width = height = qr_vertable[version] * mag + sep * mag * 2 + qr_byte_t * buffer = qrSymbolToBMP(p, 5, 5, &size); + if (buffer == NULL) { + printf("error %s", qrGetErrorInfo(p)); + return -1; + } + // output qrcode to file + // ofstream f("/etc/kvm/wifi_config.bmp"); + // if (f.fail()) { + // return -1; + // } + // f.write((const char *)buffer, size); + // f.close(); + return 0; +} + +ip_addr_t show_which_ip(void) +{ + if(kvm_sys_state.wifi_state == -2) return ETH_IP; + if(kvm_oled_state.eth_state == 3 && kvm_oled_state.wifi_state != 1) return ETH_IP; + if(kvm_oled_state.eth_state != 3 && kvm_oled_state.wifi_state == 1) return WiFi_IP; + static uint8_t run_count = 0; + static ip_addr_t ip_type = ETH_IP; + run_count++; + if(run_count > IP_Change_time/STATE_DELAY){ + run_count = 0; + switch(ip_type){ + case ETH_IP: + ip_type = WiFi_IP; + break; + case WiFi_IP: + ip_type = ETH_IP; + break; + default: + ip_type = ETH_IP; + } + } + // printf("show_which_ip? %d\n", ip_type); + return ip_type; +} + +uint8_t ip_changed(ip_addr_t ip_type) +{ + uint8_t *kvm_sys_ip; + uint8_t *kvm_oled_ip; + uint8_t ret; + if(ip_type == ETH_IP){ + kvm_sys_ip = kvm_sys_state.eth_addr; + kvm_oled_ip = kvm_oled_state.eth_addr; + } else if(ip_type == WiFi_IP){ + kvm_sys_ip = kvm_sys_state.wifi_addr; + kvm_oled_ip = kvm_oled_state.wifi_addr; + } else ret = 0; + for (int i = 0; i < 16; i++){ + if(kvm_sys_ip[i] == 0) ret = 0; + if(kvm_sys_ip[i] != kvm_oled_ip[i]) ret = 1; + } + if(ret == 1){ + memcpy(kvm_oled_ip, kvm_sys_ip, 16); + } + return ret; +} + +uint8_t kvm_state_is_changed() +{ + if( (kvm_sys_state.eth_state != kvm_oled_state.eth_state) || + (kvm_sys_state.wifi_state != kvm_oled_state.wifi_state) || + (kvm_sys_state.usb_state != kvm_oled_state.usb_state) || + (kvm_sys_state.hdmi_state != kvm_oled_state.hdmi_state) || + /* (kvm_sys_state.now_fps != kvm_oled_state.now_fps) || */ + (kvm_sys_state.hdmi_width != kvm_oled_state.hdmi_width) || + (kvm_sys_state.hdmi_height != kvm_oled_state.hdmi_height) || + (kvm_sys_state.type != kvm_oled_state.type) || + (kvm_sys_state.qlty != kvm_oled_state.qlty) ) + return 1; + else + return 0; +} + +void kvm_eth_state_disp(ip_addr_t _ip_type, uint8_t first_disp) +{ + static ip_addr_t _ip_type_old = NULL_IP; + uint8_t temp; + // printf("[kvmd]eth_state = %d\n", kvm_sys_state.eth_state); + if( (kvm_oled_state.eth_state != kvm_sys_state.eth_state) || + (_ip_type_old != _ip_type) || + first_disp || ip_changed(ETH_IP)) + { + kvm_oled_state.eth_state = kvm_sys_state.eth_state; + _ip_type_old = _ip_type; + switch(kvm_oled_state.eth_state){ + case -1: + case 0: + temp = 0; + OLED_ShowKVMState(ETH_STATE, 0); + if(_ip_type == ETH_IP) + OLED_ShowKVMStreamState(KVM_ETH_IP, &temp); + break; + case 1: + OLED_ShowKVMState(ETH_STATE, 1); + if(_ip_type == ETH_IP) + OLED_ShowKVMStreamState(KVM_ETH_IP, &temp); + break; + case 2: + OLED_Show_Network_Error(1); + case 3: + OLED_Show_Network_Error(0); + OLED_ShowKVMState(ETH_STATE, 1); + if(_ip_type == ETH_IP) + OLED_ShowKVMStreamState(KVM_ETH_IP, kvm_sys_state.eth_addr); + break; + } + } +} + +void kvm_wifi_state_disp(ip_addr_t _ip_type, uint8_t first_disp) +{ + static ip_addr_t _ip_type_old = NULL_IP; + uint8_t temp; + // printf("[kvmd]wifi_state = %d\n", kvm_sys_state.wifi_state); + if( (kvm_oled_state.wifi_state != kvm_sys_state.wifi_state) || + (_ip_type_old != _ip_type) || + first_disp || ip_changed(WiFi_IP)) + { + kvm_oled_state.wifi_state = kvm_sys_state.wifi_state; + _ip_type_old = _ip_type; + switch(kvm_oled_state.wifi_state){ + case -2: + OLED_ShowKVMState(WIFI_STATE, -1); + break; + case -1: + case 0: + temp = 0; + OLED_ShowKVMState(WIFI_STATE, 0); + if(_ip_type == WiFi_IP) + OLED_ShowKVMStreamState(KVM_WIFI_IP, &temp); + break; + case 1: + OLED_ShowKVMState(WIFI_STATE, 1); + if(_ip_type == WiFi_IP){ + OLED_ShowKVMStreamState(KVM_WIFI_IP, kvm_sys_state.wifi_addr); + } + break; + } + } +} + +void kvm_usb_state_disp(uint8_t first_disp) +{ + if((kvm_oled_state.usb_state != kvm_sys_state.usb_state) || first_disp){ + kvm_oled_state.usb_state = kvm_sys_state.usb_state; + switch(kvm_oled_state.usb_state){ + case -1: + case 0: + OLED_ShowKVMState(HID_STATE, 0); + break; + case 1: + OLED_ShowKVMState(HID_STATE, 1); + break; + } + } +} + +void kvm_hdmi_state_disp(uint8_t first_disp) +{ + if((kvm_oled_state.hdmi_state != kvm_sys_state.hdmi_state) || first_disp){ + kvm_oled_state.hdmi_state = kvm_sys_state.hdmi_state; + switch(kvm_oled_state.hdmi_state){ + case -1: + case 0: + OLED_ShowKVMState(HDMI_STATE, 0); + break; + case 1: + OLED_ShowKVMState(HDMI_STATE, 1); + break; + } + } +} + +void kvm_fps_disp(uint8_t first_disp) +{ + if((kvm_oled_state.now_fps != kvm_sys_state.now_fps) || first_disp){ + kvm_oled_state.now_fps = kvm_sys_state.now_fps; + OLED_ShowKVMStreamState(KVM_STEAM_FPS, &kvm_oled_state.now_fps); + } +} + +void kvm_res_disp(uint8_t first_disp) +{ + if( (kvm_oled_state.hdmi_width != kvm_sys_state.hdmi_width) || \ + (kvm_oled_state.hdmi_height != kvm_sys_state.hdmi_height) || \ + first_disp ){ + kvm_oled_state.hdmi_width = kvm_sys_state.hdmi_width; + kvm_oled_state.hdmi_height = kvm_sys_state.hdmi_height; + OLED_Show_Res(kvm_sys_state.hdmi_width, kvm_sys_state.hdmi_height); + } +} + +void kvm_type_disp(uint8_t first_disp) +{ + if( (kvm_oled_state.type != kvm_sys_state.type) || first_disp){ + kvm_oled_state.type = kvm_sys_state.type; + OLED_ShowKVMStreamState(KVM_STEAM_TYPE, &kvm_oled_state.type); + } +} + +void kvm_qlty_disp(uint8_t first_disp) +{ + if( (kvm_oled_state.qlty != kvm_sys_state.qlty) || first_disp){ + kvm_oled_state.qlty = kvm_sys_state.qlty; + OLED_ShowKVMStreamState(KVM_JPG_QLTY, &kvm_sys_state.qlty); + } +} + +void kvm_main_disp(uint8_t first_disp) +{ + if(first_disp){ + OLED_Clear(); + kvm_init_ui(); + } +} + +void kvm_oled_clear(uint8_t subpage_changed) +{ + if(subpage_changed){ + OLED_Clear(); + } +} + +void kvm_main_ui_disp(uint8_t first_disp, uint8_t subpage_changed) +{ + ip_addr_t now_ip_type; + // if(kvm_oled_state.sub_page == 0) + // if(kvm_oled_state.oled_sleep_state == 1){ + + // Any operation will update the OLED sleep time + if (kvm_state_is_changed()) + oled_auto_sleep_time_update(); + + if(kvm_oled_state.sub_page == 1){ + // main page (oled sleep) + kvm_oled_clear(first_disp || subpage_changed); + kvm_oled_state.oled_sleep_state = 1; + } else { + // main page + kvm_oled_state.oled_sleep_state = 0; + now_ip_type = show_which_ip(); + kvm_main_disp(first_disp || subpage_changed); + kvm_eth_state_disp(now_ip_type, first_disp || subpage_changed); + kvm_wifi_state_disp(now_ip_type, first_disp || subpage_changed); + kvm_usb_state_disp(first_disp || subpage_changed); + kvm_hdmi_state_disp(first_disp || subpage_changed); + kvm_fps_disp(first_disp || subpage_changed); + kvm_res_disp(first_disp || subpage_changed); + kvm_type_disp(first_disp || subpage_changed); + kvm_qlty_disp(first_disp || subpage_changed); + } +} + +uint8_t show_which_page() +{ + static uint8_t run_count = 0xfe; + static uint8_t show_type = 0; // 0 不动 1 QRcode; 2 text + if(sta_connect_ap()){ + if(ssid_pass_ok()){ + // + } + } + run_count++; + if(run_count > QR_Change_time/STATE_DELAY){ + run_count = 0; + if(show_type == 1) show_type = 2; + else show_type = 1; + return show_type; + } + return 0; +} + +void show_text_wifi_config(char *ap_ssid) +{ + OLED_Clear(); + OLED_ShowString(0, 0, "SSID:", 8); + OLED_ShowString_AlignRight(63, 1, "NanoKVM", 8); + OLED_ShowString(0, 2, "PASS:", 8); + OLED_ShowString_AlignRight(63, 3, ap_ssid, 8); +} + +void show_wifi_config_ip(void) +{ + OLED_Clear(); + get_ip_addr(WiFi_IP); + OLED_ShowString(0, 1, "Config IP:", 8); + OLED_ShowString_AlignRight(63, 2, (char*)kvm_sys_state.wifi_addr, 4); +} + +void show_wifi_config_QR(void) +{ + static char cmd[70]; + OLED_Clear(); + get_ip_addr(WiFi_IP); + printf("http://%s/#/wifi\n", kvm_sys_state.wifi_addr); + sprintf(cmd, "http://%s/#/wifi", kvm_sys_state.wifi_addr); + qrencode(cmd); +} + +void show_wifi_starting(void) +{ + OLED_Clear(); + OLED_ShowString(0, 1, "WiFi AP is", 8); + OLED_ShowString(0, 2, "Starting..", 8); +} + +void show_wifi_connecting(void) +{ + OLED_Clear(); + OLED_ShowString(0, 1, "WiFi", 8); + OLED_ShowString(0, 2, "Connect...", 8); +} + +void kvm_wifi_config_ui_disp(uint8_t first_disp, uint8_t subpage_changed) +{ + static char cmd[70]; + // printf("[kvmd]kvm_wifi_config_ui_disp %d | %d\n", first_disp, subpage_changed); + if(first_disp) kvm_start_wifi_config_process(); // 略有不妥,就这样吧 + if(first_disp || subpage_changed){ + switch(kvm_oled_state.sub_page){ + case 0: // wifi ap is starting + show_wifi_starting(); + break; + case 1: // QRcode + printf("WIFI:T:WPA2;S:NanoKVM;P:%s;;\n", kvm_sys_state.wifi_ap_pass); + sprintf(cmd, "WIFI:T:WPA2;S:NanoKVM;P:%s;;", kvm_sys_state.wifi_ap_pass); + qrencode(cmd); + break; + case 2: // Textcode + show_text_wifi_config(kvm_sys_state.wifi_ap_pass); + break; + case 3: // open IP with QR Code + show_wifi_config_QR(); + break; + case 4: // open IP + show_wifi_config_ip(); + break; + case 5: // Connecting... + show_wifi_connecting(); + break; + } + } + // switch(show_which_page()) +} + +void oled_auto_sleep_time_update(void) +{ + kvm_oled_state.oled_sleep_start = time::time_ms(); +} + +void oled_auto_sleep(void) +{ + uint8_t tmp8; + uint8_t sleep_close_signal = 0; + FILE *fp; + int file_size; + uint8_t RW_Data[10]; + if(access("/etc/kvm/oled_sleep", F_OK) == 0){ + fp = fopen("/etc/kvm/oled_sleep", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + if(file_size != 0){ + tmp8 = atoi((char*)RW_Data); + } else { + tmp8 = OLED_SLEEP_DELAY_DEFAULT; + } + if(tmp8 != kvm_oled_state.oled_sleep_param){ + // printf("/etc/kvm/oled_sleep = %d\n", tmp8); + kvm_oled_state.oled_sleep_param = tmp8; + if(kvm_oled_state.oled_sleep_param < OLED_SLEEP_DELAY_MIN){ + sleep_close_signal = 1; + } else { + // printf("oled_auto_sleep_time_update\n"); + oled_auto_sleep_time_update(); + } + } + } else { + if(kvm_oled_state.oled_sleep_param != 0){ + kvm_oled_state.oled_sleep_param = 0; + sleep_close_signal = 1; + } + } + + if(kvm_oled_state.page == 0){ + if(kvm_oled_state.oled_sleep_param < OLED_SLEEP_DELAY_MIN){ + if(sleep_close_signal == 1){ + kvm_sys_state.sub_page = 0; + } + } else { + if((time::time_ms() - kvm_oled_state.oled_sleep_start)/1000 >= kvm_oled_state.oled_sleep_param){ + // kvm_oled_state.oled_sleep_state = 1; + kvm_sys_state.sub_page = 1; + } else { + kvm_sys_state.sub_page = 0; + } + } + } +} + +void kvm_show_UE(void) +{ + if(kvm_hw_ver != 2){ + OLED_ShowString(0, 0, "HDMI: UE", 16); + } else { + OLED_Revolve(); + OLED_ShowString(0, 0, "HDMI: UE", 16); + // OLED_ShowString_AlignRight(AlignRightEND_P, 3, " FPS", 4); + // kvm_init_pcie_ui(); + } +} \ No newline at end of file diff --git a/support/sg2002/kvm_system/main/lib/oled_ui/oled_ui.h b/support/sg2002/kvm_system/main/lib/oled_ui/oled_ui.h new file mode 100644 index 0000000..5b82104 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/oled_ui/oled_ui.h @@ -0,0 +1,12 @@ +#ifndef OLED_UI_H_ +#define OLED_UI_H_ +#include "config.h" +#include "oled_ctrl.h" + +void kvm_main_ui_disp(uint8_t first_disp, uint8_t subpage_changed); +void kvm_wifi_config_ui_disp(uint8_t first_disp, uint8_t subpage_changed); +void oled_auto_sleep_time_update(void); +void oled_auto_sleep(void); +void kvm_show_UE(void); + +#endif // OLED_UI_H_ \ No newline at end of file diff --git a/support/sg2002/kvm_system/main/lib/system_ctrl/system_ctrl.cpp b/support/sg2002/kvm_system/main/lib/system_ctrl/system_ctrl.cpp new file mode 100644 index 0000000..c367deb --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/system_ctrl/system_ctrl.cpp @@ -0,0 +1,181 @@ +#include "system_ctrl.h" + +using namespace maix; +using namespace maix::sys; + +extern kvm_sys_state_t kvm_sys_state; +extern kvm_oled_state_t kvm_oled_state; + +void gen_hostapd_conf(char* ap_ssid) +{ + char file_data[300] = {0}; + FILE * fp; + sprintf(file_data, "ctrl_interface=/var/run/hostapd\nctrl_interface_group=0\nssid=NanoKVM\nhw_mode=g\nchannel=1\nbeacon_int=100\ndtim_period=2\nmax_num_sta=255\nrts_threshold=-1\nfragm_threshold=-1\nmacaddr_acl=0\nauth_algs=3\nwpa=2\nwpa_passphrase=%s\nieee80211n=1\n", ap_ssid); + fp = fopen("/etc/hostapd.conf", "w"); + fwrite(file_data, sizeof(file_data) , 1, fp ); + fclose(fp); +} + +void gen_udhcpd_conf() +{ + char file_data[300] = {0}; + FILE * fp; + sprintf(file_data, "start 10.10.10.100\nend 10.10.10.200\ninterface wlan0\npidfile /var/run/udhcpd.wlan0.pid\nlease_file /var/lib/misc/udhcpd.wlan0.leases\noption subnet 255.255.255.0\noption lease 864000\n"); + fp = fopen("/etc/udhcpd.wlan0.conf", "w"); + fwrite(file_data, sizeof(file_data) , 1, fp ); + fclose(fp); +} + +void gen_dnsmasq_conf() +{ + char file_data[300] = {0}; + FILE * fp; + sprintf(file_data, "bind-interfaces\ninterface=wlan0\ndhcp-range=10.10.10.2,10.10.10.254\naddress=/#/10.0.0.1\n"); + fp = fopen("/etc/udhcpd.wlan0.conf", "w"); + fwrite(file_data, sizeof(file_data) , 1, fp ); + fclose(fp); +} + +uint8_t sta_connect_ap(void) +{ + uint8_t RW_Data[10]; + FILE *fp; + fp = popen("hostapd_cli all sta | grep aid", "r"); + fgets((char*)RW_Data, 2, fp); + pclose(fp); + if(RW_Data[0] == 'a') return 1; + else return 0; +} + +uint8_t ssid_pass_ok(void) +{ + if(access("/kvmapp/kvm/wifi_try_connect", F_OK) == 0) return 1; + else return 0; +} + +uint8_t wifi_connected(void) +{ + uint8_t RW_Data[20]; + FILE *fp; + fp = popen("wpa_cli -i wlan0 status | grep \"wpa_state\"", "r"); + fgets((char*)RW_Data, 15, fp); + pclose(fp); + if(RW_Data[10] == 'S') return 0; + else if(RW_Data[10] == 'C') return 1; + else return 0; +} + +void kvm_start_wifi_config_process(void) +{ + if(kvm_sys_state.wifi_config_process == -1){ + // printf("[kvmw]开始配置wifi\n"); + kvm_sys_state.wifi_config_process = 0; + kvm_sys_state.sub_page = 0; + } +} + +void kvm_wifi_web_config_process() +{ + if(ssid_pass_ok()){ + system("rm /kvmapp/kvm/wifi_try_connect"); + system("/etc/init.d/S30wifi restart"); + kvm_sys_state.wifi_config_process = 3; + // 尝试连接wifi + if(kvm_sys_state.wifi_config_process == 3){ + // time::sleep_ms(WIFI_CONNECTION_DELAY); + if(wifi_connected()){ + // 连上了 + kvm_sys_state.wifi_config_process = -1; + } else { + // 没连上 + kvm_sys_state.wifi_config_process = 0; + } + } + } +} + +void kvm_wifi_config_process() +{ + int temp; + char ran_num; + static char cmd[70]; + switch(kvm_sys_state.wifi_config_process){ + case -1: + // printf("[kvms]Not in the process of configuring WiFi\n"); + break; + case 0: // 启动wifi + srand((unsigned)time::time_ms()); + for(temp = 0; temp < 4; temp++){ + do{ + ran_num = (char)rand()%10 + '0'; + if(temp == 0) break; + } while (ran_num == kvm_sys_state.wifi_ap_pass[(temp-1)*2]); + kvm_sys_state.wifi_ap_pass[temp*2] = ran_num; + kvm_sys_state.wifi_ap_pass[temp*2+1] = ran_num; + } + kvm_sys_state.wifi_ap_pass[8] = '\0'; + + sprintf(cmd, "echo %s > /kvmapp/kvm/ap.pass", kvm_sys_state.wifi_ap_pass); + system("echo NanoKVM > /kvmapp/kvm/ap.ssid"); + system(cmd); + system("/etc/init.d/S30wifi ap"); + + kvm_sys_state.wifi_config_process = 1; + kvm_sys_state.sub_page = 1; + break; + case 1: // 等待设备连接 + if(sta_connect_ap()){ + kvm_sys_state.wifi_config_process = 2; + kvm_sys_state.sub_page = 3; + } + break; + case 2: // 等待输入帐号密码 + if(ssid_pass_ok()){ + system("rm /kvmapp/kvm/wifi_try_connect"); + system("/etc/init.d/S30wifi restart"); + kvm_sys_state.wifi_config_process = 3; + kvm_sys_state.sub_page = 5; + } + break; + case 3: // 尝试连接wifi + time::sleep_ms(WIFI_CONNECTION_DELAY); + if(wifi_connected()){ + // 连上了 + kvm_sys_state.wifi_config_process = -1; + kvm_sys_state.page = 0; + kvm_sys_state.sub_page = 0; + } else { + // 没连上 + kvm_sys_state.wifi_config_process = 0; + kvm_sys_state.sub_page = 0; + } + break; + } +} + +uint8_t kvm_reset_password(void) +{ + FILE *fp = popen("bash", "w"); + if (fp == NULL) { + perror("popen"); + return 0; + } + + fputs("passwd root\n", fp); + time::sleep_ms(10); + fputs("root\n", fp); + time::sleep_ms(10); + fputs("root\n", fp); + time::sleep_ms(10); + fputs("rm /etc/kvm/pwd\n", fp); + time::sleep_ms(10); + fputs("sync\n", fp); + time::sleep_ms(10); + fputs("exit\n", fp); // 退出 bash + + if (pclose(fp) == -1) { + perror("pclose"); + return 1; + } + return 0; +} \ No newline at end of file diff --git a/support/sg2002/kvm_system/main/lib/system_ctrl/system_ctrl.h b/support/sg2002/kvm_system/main/lib/system_ctrl/system_ctrl.h new file mode 100644 index 0000000..e16f246 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/system_ctrl/system_ctrl.h @@ -0,0 +1,16 @@ +#ifndef SYSTEM_CTRL_H_ +#define SYSTEM_CTRL_H_ +#include "config.h" + +void gen_hostapd_conf(char* ap_ssid); +void gen_udhcpd_conf(); +void gen_dnsmasq_conf(); +uint8_t sta_connect_ap(void); +uint8_t ssid_pass_ok(void); +uint8_t wifi_connected(void); +void kvm_start_wifi_config_process(void); +void kvm_wifi_web_config_process(); +void kvm_wifi_config_process(); +uint8_t kvm_reset_password(void); + +#endif // SYSTEM_CTRL_H_ \ No newline at end of file diff --git a/support/sg2002/kvm_system/main/lib/system_init/system_init.cpp b/support/sg2002/kvm_system/main/lib/system_init/system_init.cpp new file mode 100644 index 0000000..4b05f74 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/system_init/system_init.cpp @@ -0,0 +1,192 @@ +#include "config.h" +#include "system_init.h" + +using namespace maix; +using namespace maix::sys; + +extern kvm_sys_state_t kvm_sys_state; +extern kvm_oled_state_t kvm_oled_state; + +uint8_t get_hdmi_version() +{ + FILE *fp; + uint8_t RW_Data[2]; + system("/kvmapp/system/init.d/S15kvmhwd get_hdmi_version"); + if(access("/etc/kvm/hdmi_version", F_OK) == 0){ + fp = fopen("/etc/kvm/hdmi_version", "r"); + fread(RW_Data, sizeof(char), 2, fp); + fclose(fp); + if(RW_Data[0] == 'u'){ + // 6911uxc + if(RW_Data[1] == 'e'){ + return 2; + } else if(RW_Data[1] == 'x') { + return 1; + } else { + return 1; + } + } else { + // 6911c + return 0; + } + } else { + return 0; + } +} + +void Production_testing_patch(void) +{ + // Product UE version detecte + if(get_hdmi_version() == 2){ + printf("ue_patch_state = 1;\n"); + kvm_oled_state.ue_patch_state = 1; + } else { + printf("ue_patch_state = 0;\n"); + kvm_oled_state.ue_patch_state = 0; + } + + // New products default to disabling mDNS functionality + system("rm -f /etc/init.d/S50ssdpd"); + system("sync"); +} + +void new_app_init(void) +{ + // Update the necessary scripts + system("cp -f /kvmapp/system/update-nanokvm.py /etc/kvm/"); + system("rm -f /etc/init.d/S02udisk"); + system("cp -f /kvmapp/system/init.d/S00kmod /etc/init.d/"); + system("cp -f /kvmapp/system/init.d/S01fs /etc/init.d/"); + system("cp -f /kvmapp/system/init.d/S03usbdev /etc/init.d/"); + system("cp -f /kvmapp/system/init.d/S15kvmhwd /etc/init.d/"); + system("cp -f /kvmapp/system/init.d/S30eth /etc/init.d/"); + system("cp -f /kvmapp/system/init.d/S50sshd /etc/init.d/"); + if(kvm_wifi_exist()) { + system("cp -f /kvmapp/system/init.d/S30wifi /etc/init.d/"); + } else { + system("rm -f /etc/init.d/S30wifi"); + } + + // rmmod soph_saradc + system("rmmod soph_saradc"); + system("rm -f /mnt/system/ko/soph_saradc.ko"); + + // PCIe Patch + // system("cp /kvmapp/system/init.d/S95nanokvm /etc/init.d/"); + if(access("/kvmapp/jpg_stream/dl_lib/libmaixcam_lib.so", F_OK) != 0){ + system("cp -f /kvmapp/system/init.d/S95nanokvm /etc/init.d/"); + } + + // Remove unnecessary components to speed up boot time + system("rm -f /etc/init.d/S04backlight"); + system("rm -f /etc/init.d/S05tp"); + system("rm -f /etc/init.d/S40bluetoothd"); + system("rm -f /etc/init.d/S50ssdpd"); + system("rm -f /etc/init.d/S99*"); + + // Add necessary configuration files for program execution + system("mkdir /kvmapp/kvm"); + system("mkdir /etc/kvm"); + system("echo 0 > /kvmapp/kvm/now_fps"); + system("echo 30 > /kvmapp/kvm/fps"); + system("echo 2000 > /kvmapp/kvm/qlty"); + system("echo 720 > /kvmapp/kvm/res"); + system("echo h264 > /kvmapp/kvm/type"); + system("echo 0 > /kvmapp/kvm/state"); + system("touch /etc/kvm/frame_detact"); + + // rm jpg_stream & kvm_stream + system("rm -rf /kvmapp/jpg_stream"); + system("rm -f /kvmapp/kvm_system/kvm_stream"); // Cannot delete temporarily; the old production test script uses this file to determine if the download is complete + + system("rm -f /kvmapp/kvm_new_app"); + system("sync"); + // system("/etc/init.d/S95nanokvm restart"); + + // update ko + FILE *fp; + uint8_t RW_Data_0[30]; + uint8_t RW_Data_1[30]; + fp = popen("md5sum /mnt/system/ko/soph_mipi_rx.ko | grep 086ed01749188975afaa40fb569374f8 | awk '{print $2}'", "r"); + if ( NULL == fp ) + { + pclose(fp); + // return; + } + fgets((char*)RW_Data_0, 10, fp); + pclose(fp); + fp = popen("md5sum /mnt/system/ko/soph_mipi_rx.ko | grep 69be7eeded3777f750480a5dd5a1aa26 | awk '{print $2}'", "r"); + if ( NULL == fp ) + { + pclose(fp); + // return; + } + fgets((char*)RW_Data_1, 10, fp); + pclose(fp); + + int8_t hdmi_ver = -1; + + if(access("/etc/kvm/hdmi_version", F_OK) == 0){ + uint8_t RW_Data[2]; + FILE *fp = fopen("/etc/kvm/hdmi_version", "r"); + fread(RW_Data, sizeof(char), 2, fp); + fclose(fp); + if(RW_Data[0] == 'c') hdmi_ver = 1; + else if(RW_Data[0] == 'u') hdmi_ver = 2; + } + + // system("/etc/init.d/S03usbdev stop_start"); + create_temp_watchdog(); + + if(hdmi_ver == 2){ + if(RW_Data_1[0] != '/'){ + system("cp /kvmapp/system/ko/soph_mipi_rx.ko /mnt/system/ko/soph_mipi_rx.ko"); + system("sync"); + system("reboot"); + } else { + system("sync"); + system("/etc/init.d/S15kvmhwd start"); + system("/etc/init.d/S95nanokvm restart"); + } + } else { + if((RW_Data_0[0] != '/') && (RW_Data_1[0] != '/')){ + system("cp /kvmapp/system/ko/soph_mipi_rx.ko /mnt/system/ko/soph_mipi_rx.ko"); + system("sync"); + system("reboot"); + } else { + system("sync"); + system("/etc/init.d/S15kvmhwd start"); + system("/etc/init.d/S95nanokvm restart"); + } + } + + if(access("/root/old/kvm_new_img", F_OK) == 0){ + system("rm -r /root/old"); + } + + system("sync"); + system("killall NanoKVM-Server"); + system("rm -r /tmp/server"); + system("cp -r /kvmapp/server /tmp/"); + system("/tmp/server/NanoKVM-Server &"); +} + +void build_complete_resolv(void) +{ + FILE *fp = NULL; + // fp = fopen("/boot/resolv.conf", "w+"); + fp = fopen("/boot/resolv.conf", "w"); + // 阿里: 223.5.5.5 + // 腾讯: 119.29.29.29 + fprintf(fp, "nameserver 192.168.0.1\nnameserver 8.8.4.4\nnameserver 8.8.8.8\nnameserver 114.114.114.114\nnameserver 119.29.29.29\nnameserver 223.5.5.5"); + fclose(fp); + system("rm -rf /etc/resolv.conf"); + system("cp -vf /etc/resolv.conf /etc/resolv.conf.old"); + system("cp -vf /boot/resolv.conf /etc/resolv.conf"); +} + +void new_img_init(void) +{ + build_complete_resolv(); + system("rm /kvmapp/kvm_new_img"); +} \ No newline at end of file diff --git a/support/sg2002/kvm_system/main/lib/system_init/system_init.h b/support/sg2002/kvm_system/main/lib/system_init/system_init.h new file mode 100644 index 0000000..47959bf --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/system_init/system_init.h @@ -0,0 +1,9 @@ +#ifndef SYSTEM_INIT_H_ +#define SYSTEM_INIT_H_ +#include "config.h" + +void new_img_init(void); +void new_app_init(void); +void Production_testing_patch(void); + +#endif // SYSTEM_INIT_H_ diff --git a/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp b/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp new file mode 100644 index 0000000..d98d576 --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp @@ -0,0 +1,522 @@ +#include "config.h" +#include "system_state.h" +#include +#include +#include + +using namespace maix; +using namespace maix::sys; + +extern kvm_sys_state_t kvm_sys_state; +extern kvm_oled_state_t kvm_oled_state; + +int get_nic_state(const char* interface_name) +{ + int sock; + struct ifreq ifr; + int ret = NIC_STATE_NO_EXIST; + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + return ret; + } + strcpy(ifr.ifr_name, interface_name); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { + close(sock); + return ret; + } + if (ifr.ifr_flags & IFF_UP) { + if (ifr.ifr_flags & IFF_RUNNING) { + ret = NIC_STATE_RUNNING; + } else { + ret = NIC_STATE_UP; + } + } else { + ret = NIC_STATE_DOWN; + } + close(sock); + return ret; +} + +int get_ping_allow_state(void) +{ + if(access("/etc/kvm/stop_ping", F_OK) == 0) { + kvm_sys_state.ping_allow = 0; + } else { + kvm_sys_state.ping_allow = 1; + } + return kvm_sys_state.ping_allow; +} + +// net_port +int get_ip_addr(ip_addr_t ip_type) +{ + switch (ip_type){ + case ETH_IP: // eth_addr + if(strcmp(ip_address()["eth0"].c_str(), (char*)kvm_sys_state.eth_addr) != 0){ + if(*(ip_address()["eth0"].c_str()) == 0){ + printf("can`t get ip addr\r\n"); + kvm_sys_state.eth_addr[0] = 0; + return 0; + } + for(int i = 0; i <= 15; i++) + { + kvm_sys_state.eth_addr[i] = *(ip_address()["eth0"].c_str() + i); + printf("%c", kvm_sys_state.eth_addr[i]); + } + printf("\r\n"); + } + return 1; + case WiFi_IP: // wifi_addr + if(strcmp(ip_address()["wlan0"].c_str(), (char*)kvm_sys_state.wifi_addr) != 0){ + if(*(ip_address()["wlan0"].c_str()) == 0){ + printf("can`t get ip addr\r\n"); + kvm_sys_state.wifi_addr[0] = 0; + return 0; + } + for(int i = 0; i <= 15; i++) + { + kvm_sys_state.wifi_addr[i] = *(ip_address()["wlan0"].c_str() + i); + printf("%c", kvm_sys_state.wifi_addr[i]); + } + printf("\r\n"); + } + return 1; + case Tailscale_IP: // tail_addr + if(*(ip_address()["tailscale0"].c_str()) == 0){ + printf("can`t get ip addr\r\n"); + kvm_sys_state.tail_addr[0] = 0; + return 0; + } + for(int i = 0; i <= 15; i++) + { + kvm_sys_state.tail_addr[i] = *(ip_address()["tailscale0"].c_str() + i); + printf("%c", kvm_sys_state.tail_addr[i]); + } + printf("\r\n"); + return 1; + case RNDIS_IP: // rndis_addr + if(*(ip_address()["usb0"].c_str()) == 0){ + printf("can`t get ip addr\r\n"); + kvm_sys_state.rndis_addr[0] = 0; + return 0; + } + for(int i = 0; i <= 15; i++) + { + kvm_sys_state.rndis_addr[i] = *(ip_address()["usb0"].c_str() + i); + printf("%c", kvm_sys_state.rndis_addr[i]); + } + printf("\r\n"); + return 1; + case ETH_ROUTE: // eth_route + if(access("/etc/kvm/gateway", F_OK) != 0){ + // 不存在gateway文件 + memset( kvm_sys_state.eth_route, 0, sizeof( kvm_sys_state.eth_route ) ); + char Cmd[100]={0}; + memset( Cmd, 0, sizeof( Cmd ) ); + sprintf( Cmd,"ip route | grep -i '^default' | grep -i 'eth0' | awk '{print $3}'"); + FILE* fp = popen( Cmd, "r" ); + if ( NULL == fp ) + { + pclose(fp); + return 0; + } + memset( kvm_sys_state.eth_route, 0, sizeof( kvm_sys_state.eth_route ) ); + while ( NULL != fgets( (char*)kvm_sys_state.eth_route,sizeof( kvm_sys_state.eth_route ),fp )) + { + // printf("ip=%s\n",kvm_sys_state.eth_route); + break; + } + if(kvm_sys_state.eth_route[0] == 0){ + // 开机时未插入ETH + pclose(fp); + return 0; + } + for(int i = 0; i < 40; i++){ + if(kvm_sys_state.eth_route[i] == 10){ + kvm_sys_state.eth_route[i] = ' '; + break; + } + } + pclose(fp); + return 1; + } else { + int file_size; + FILE *fp = fopen("/etc/kvm/gateway", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(kvm_sys_state.eth_route, sizeof(char), file_size, fp); + fclose(fp); + return 1; + } + case WiFi_ROUTE: // wifi_route + memset( kvm_sys_state.wifi_route, 0, sizeof( kvm_sys_state.wifi_route ) ); + char Cmd[100]={0}; + memset( Cmd, 0, sizeof( Cmd ) ); + sprintf( Cmd,"ip route | grep -i '^default' | grep -i 'wlan0' | awk '{print $3}'"); + FILE* fp = popen( Cmd, "r" ); + if ( NULL == fp ) + { + pclose(fp); + return 0; + } + memset( kvm_sys_state.wifi_route, 0, sizeof( kvm_sys_state.wifi_route ) ); + while ( NULL != fgets( (char*)kvm_sys_state.wifi_route,sizeof( kvm_sys_state.wifi_route ),fp )) + { + // printf("ip=%s\n",kvm_sys_state.wifi_route); + break; + } + if(kvm_sys_state.wifi_route[0] == 0){ + // 开机时未插入ETH + pclose(fp); + return 0; + } + for(int i = 0; i < 40; i++){ + if(kvm_sys_state.wifi_route[i] == 10){ + kvm_sys_state.wifi_route[i] = ' '; + break; + } + } + pclose(fp); + return 1; + } + return 0; +} + +int chack_net_state(ip_addr_t use_ip_type) +{ + char Cmd[100]={0}; + if (use_ip_type == ETH_ROUTE) sprintf( Cmd,"ping -I eth0 -w 1 %s > /dev/null", kvm_sys_state.eth_route); + else if (use_ip_type == WiFi_ROUTE) sprintf( Cmd,"ping -I wlan0 -w 1 %s > /dev/null", kvm_sys_state.wifi_route); + else return -1; // 不支持的端口 + if(system(Cmd) == 0){ // 256:不通; = 0:通 + return 1; + } + return 0; +} + +void patch_eth_wifi(void) +{ + // system("ip link set eth0 down"); + // system("ip link set eth0 up"); + // system("udhcpc -i eth0 &"); +} + +int kvm_wifi_exist() +{ + if (get_nic_state("wlan0") == NIC_STATE_NO_EXIST) return 0; + else return 1; +} + +void kvm_update_usb_state() +{ + // usb_state, hid_state, rndis_state, udisk_state + FILE *fp; + int file_size; + uint8_t RW_Data[10]; + fp = fopen("/sys/class/udc/4340000.usb/state", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + if(RW_Data[0] == 'n') kvm_sys_state.usb_state = 0; + else if(RW_Data[0] == 'c') kvm_sys_state.usb_state = 1; + else kvm_sys_state.usb_state = -1; + // hid_state & udisk_state (rndis_state单独处理) + if(kvm_sys_state.usb_state == 1){ + if(access("/sys/kernel/config/usb_gadget/g0/configs/c.1/hid.GS*", F_OK) == 0) + kvm_sys_state.hid_state = 1; + if(access("/sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0", F_OK) == 0) + kvm_sys_state.udisk_state = 1; + } else { + kvm_sys_state.hid_state = 0; + kvm_sys_state.udisk_state = 0; + } +} + +void kvm_update_hdmi_state() +{ + static uint8_t check_times = 4; + FILE *fp; + int file_size; + uint8_t RW_Data[10]; + if(++check_times > 5){ + check_times = 0; + fp = popen("cat /proc/cvitek/vi_dbg | grep VIFPS | awk '{print $3}'", "r"); + if (fp == NULL) { + pclose(fp); + return; + } + fgets((char*)RW_Data, 2, fp); + pclose(fp); + // printf("[kvmd]HDMI exist? %c\n", RW_Data[0]); + if (RW_Data[0] != '0'){ + kvm_sys_state.hdmi_state = 1; + } else { + kvm_sys_state.hdmi_state = 0; + } + } +} + +void kvm_update_stream_fps(void) +{ + FILE *fp; + int file_size; + uint8_t RW_Data[10]; + + // FPS + fp = fopen("/kvmapp/kvm/now_fps", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + RW_Data[file_size] = 0; + kvm_sys_state.now_fps = atoi((char*)RW_Data); +} + +void kvm_update_stream_type(void) +{ + FILE *fp; + int file_size; + uint8_t RW_Data[10]; + + // type + fp = fopen("/kvmapp/kvm/type", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + if(RW_Data[0] == 'm') kvm_sys_state.type = KVM_TYPE_MJPG; + else if(RW_Data[0] == 'h') kvm_sys_state.type = KVM_TYPE_H264; + else kvm_sys_state.type = KVM_TYPE_none; +} + +void kvm_update_stream_qlty(void) +{ + FILE *fp; + int file_size; + uint8_t RW_Data[10]; + uint16_t tmp16; + + // QLTY + fp = fopen("/kvmapp/kvm/qlty", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + RW_Data[file_size] = 0; + tmp16 = atoi((char*)RW_Data); + if(kvm_sys_state.type == KVM_TYPE_MJPG){ + if(tmp16 < 60) kvm_sys_state.qlty = 1; + else if(tmp16 >= 60 && tmp16 < 75) kvm_sys_state.qlty = 2; + else if(tmp16 >= 75 && tmp16 < 90) kvm_sys_state.qlty = 3; + else if(tmp16 >= 90 && tmp16 <= 100) kvm_sys_state.qlty = 4; + else kvm_sys_state.qlty = 4; + } else { + if(tmp16 < 1500) kvm_sys_state.qlty = 1; + else if(tmp16 >= 1500 && tmp16 < 2500) kvm_sys_state.qlty = 2; + else if(tmp16 >= 2500 && tmp16 < 3500) kvm_sys_state.qlty = 3; + else if(tmp16 >= 3500 && tmp16 <= 5000) kvm_sys_state.qlty = 4; + else kvm_sys_state.qlty = 4; + } +} + +void kvm_update_hdmi_res(void) +{ + FILE *fp; + int file_size; + uint8_t RW_Data[10]; + // HDMI width + fp = fopen("/kvmapp/kvm/width", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + RW_Data[file_size] = 0; + kvm_sys_state.hdmi_width = atoi((char*)RW_Data); + // HDMI height + fp = fopen("/kvmapp/kvm/height", "r"); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(RW_Data, sizeof(char), file_size, fp); + fclose(fp); + RW_Data[file_size] = 0; + kvm_sys_state.hdmi_height = atoi((char*)RW_Data); +} + +void kvm_update_eth_state(void) +{ + static uint8_t nic_state = 0; + nic_state = get_nic_state("eth0"); + + if(nic_state == NIC_STATE_RUNNING){ + // Get IP + if(strcmp(ip_address()["eth0"].c_str(), (char*)kvm_sys_state.eth_addr) != 0){ + if(get_ip_addr(ETH_IP)){ + kvm_sys_state.eth_state = 2; + } else { + kvm_sys_state.eth_state = 1; + return; + } + } + if(kvm_sys_state.ping_allow){ + // ping route + if(kvm_sys_state.eth_route[0] == 0){ + get_ip_addr(ETH_ROUTE); + } else { + if(chack_net_state(ETH_ROUTE)){ + // Ping successful + kvm_sys_state.eth_state = 3; + } else { + kvm_sys_state.eth_state = 2; + } + } + } else { + // Consider the network to be connected + kvm_sys_state.eth_state = 3; + } + + } else { + kvm_sys_state.eth_state = 0; + patch_eth_wifi(); + } +} + +void kvm_update_wifi_state(void) +{ + // No WiFi module (check for existence?) -> Module exists & not connected (check if connected) -> + if(kvm_sys_state.wifi_state == -2) return; + switch (kvm_sys_state.wifi_state){ + case -1: + // Initial default value. + if (kvm_wifi_exist()) { + kvm_sys_state.wifi_state = 0; + system("touch /etc/kvm/wifi_exist"); + } + else { + kvm_sys_state.wifi_state = -2; // WiFi module does not exist, exiting directly. + system("rm /etc/kvm/wifi_exist"); + return; + } + // break; // Start checking the connection directly. + case 0: + // WiFi is available but not connected. + system("echo 0 > /kvmapp/kvm/wifi_state"); + if (get_ip_addr(WiFi_IP) && get_ip_addr(WiFi_ROUTE)){ + // IP+Route has been acquired + if(kvm_sys_state.ping_allow){ + if (chack_net_state(WiFi_ROUTE)){ + // Ping successful + kvm_sys_state.wifi_state = 1; + } + } else { + // Consider the network to be connected + kvm_sys_state.wifi_state = 1; + } + } + break; + case 1: + // Connected to the network & continuously checking if it can ping successfully. + system("echo 1 > /kvmapp/kvm/wifi_state"); + get_ip_addr(WiFi_IP); + if(kvm_sys_state.ping_allow){ + if (kvm_sys_state.wifi_route[0] != 0){ + if (chack_net_state(WiFi_ROUTE) == 0){ + // Ping successful + kvm_sys_state.wifi_state = 0; + } + } + } + // default: + // kvm_sys_state.wifi_state = -1; + } +} + +void kvm_update_rndis_state(void) +{ + if (get_nic_state("usb0") == NIC_STATE_RUNNING) { + if(kvm_sys_state.rndis_state != 1) { + if (get_ip_addr(RNDIS_IP)) { + kvm_sys_state.rndis_state = 1; + } + } + } + else kvm_sys_state.rndis_state = 0; +} + +void kvm_update_tailscale_state(void) +{ + if (get_nic_state("tailscale0") == NIC_STATE_RUNNING) { + if(kvm_sys_state.tail_state != 1){ + if (get_ip_addr(Tailscale_IP)) { + kvm_sys_state.tail_state = 1; + } + } + } + else kvm_sys_state.tail_state = 0; +} + +//============================================================================ + +uint8_t ion_free_space(void) +{ + //cat /sys/kernel/debug/ion/cvi_carveout_heap_dump/summary | grep "usage rate:" | awk '{print $2}' + + return 0; +} + +int create_temp_watchdog(void) +{ + FILE *file; + + file = fopen(watchdog_temp_path, "w"); + if (file == NULL) { + printf("[kvmv] Temp watchdog create error\n"); + return -1; + } + // fprintf(file, "%s", 'v'); + fclose(file); + return 1; +} + +void rm_temp_watchdog(void) +{ + if(access(watchdog_temp_path, F_OK) == 0) { + remove(watchdog_temp_path); + } +} + +void auto_remove_temp_watchdog(void) +{ + static uint8_t run_times = 0; + static uint8_t temp_watchdog_removed = 0; + if(temp_watchdog_removed) return; + if(run_times++ >= RM_Watchdog_times){ + run_times = 0; + temp_watchdog_removed = 1; + rm_temp_watchdog(); + } +} + +uint8_t watchdog_sf_is_open(void) +{ + if(access(watchdog_mode_path, F_OK) == 0) return 1; + if(access(watchdog_temp_path, F_OK) == 0) return 1; + else return 0; +} + +int check_watchdog() +{ + if(access(watchdog_file, F_OK) == 0) { + if (remove(watchdog_file) == 0) { + return 1; + } else { + return -1; + } + } else return 0; +} \ No newline at end of file diff --git a/support/sg2002/kvm_system/main/lib/system_state/system_state.h b/support/sg2002/kvm_system/main/lib/system_state/system_state.h new file mode 100644 index 0000000..f076f8c --- /dev/null +++ b/support/sg2002/kvm_system/main/lib/system_state/system_state.h @@ -0,0 +1,44 @@ +#ifndef SYSTEM_STATE_H_ +#define SYSTEM_STATE_H_ +#include "config.h" + +enum ip_addr_t +{ + ETH_IP=1, WiFi_IP, Tailscale_IP, RNDIS_IP, ETH_ROUTE, WiFi_ROUTE, NULL_IP +}; + +#define NIC_STATE_UP 1 +#define NIC_STATE_DOWN 0 +#define NIC_STATE_RUNNING 2 +#define NIC_STATE_UNKNOWN -1 +#define NIC_STATE_NO_EXIST -2 + +#define watchdog_mode_path "/etc/kvm/watchdog" +#define watchdog_temp_path "/tmp/watchdog" +#define watchdog_file "/tmp/nanokvm_wd" + +// net_port +int get_ip_addr(ip_addr_t ip_type); +int chack_net_state(ip_addr_t use_ip_type); +int get_ping_allow_state(void); +void patch_eth_wifi(void); +int kvm_wifi_exist(void); +void kvm_update_usb_state(void); +void kvm_update_hdmi_state(void); +void kvm_update_stream_fps(void); +void kvm_update_stream_type(void); +void kvm_update_stream_qlty(void); +void kvm_update_hdmi_res(void); +void kvm_update_eth_state(void); +void kvm_update_wifi_state(void); +void kvm_update_rndis_state(void); +void kvm_update_tailscale_state(void); +uint8_t ion_free_space(void); +int get_nic_state(const char* interface_name); +int create_temp_watchdog(void); +void rm_temp_watchdog(void); +void auto_remove_temp_watchdog(void); +uint8_t watchdog_sf_is_open(void); +int check_watchdog(); + +#endif // SYSTEM_STATE_H_ diff --git a/support/sg2002/kvm_system/main/src/main.cpp b/support/sg2002/kvm_system/main/src/main.cpp new file mode 100644 index 0000000..bb07e8d --- /dev/null +++ b/support/sg2002/kvm_system/main/src/main.cpp @@ -0,0 +1,264 @@ +#include "config.h" + +using namespace maix; +using namespace maix::sys; +using namespace maix::peripheral; + +kvm_sys_state_t kvm_sys_state; +kvm_oled_state_t kvm_oled_state; + +void build_recovery(void) +{ + uint8_t need_recovery = 0; + if (access("/recovery", F_OK) != 0) { + printf("unexist /recovery\n"); + mkdir("/recovery", 0777); + need_recovery = 1; + } else { + printf("exist /recovery\n"); + + } +} + +void* thread_oled_handle(void * arg) +{ + OLED_Init(); + OLED_ColorTurn(0); //0正常显示 1 反色显示 + OLED_DisplayTurn(0); //0正常显示 1 屏幕翻转显示 + OLED_Clear(); + if(kvm_oled_state.ue_patch_state == 1){ + kvm_show_UE(); + while(kvm_sys_state.oled_thread_running){ + time::sleep_ms(100); + } + OLED_Clear(); + } + + while(kvm_sys_state.oled_thread_running) + { + oled_auto_sleep(); + // printf("[kvmd]thread_oled_handle - while\n"); + uint8_t page_changed = (kvm_oled_state.page == kvm_sys_state.page)? 0:1; + uint8_t subpage_changed = (kvm_oled_state.sub_page == kvm_sys_state.sub_page)? 0:1; + // printf("subpage_changed = %d", subpage_changed); + // printf("kvm_oled_state.sub_page = %d", kvm_oled_state.sub_page); + // printf("kvm_sys_state.sub_page = %d\n", kvm_sys_state.sub_page); + kvm_oled_state.page = kvm_sys_state.page; + kvm_oled_state.sub_page = kvm_sys_state.sub_page; + + + switch(kvm_oled_state.page){ + case 0 : // main page + kvm_main_ui_disp(page_changed, subpage_changed); + break; + case 1 : // wifi config page + kvm_wifi_config_ui_disp(page_changed, subpage_changed); + break; + default: + OLED_Clear(); + } + time::sleep_ms(OLED_DELAY); + } + OLED_Clear(); + kvm_sys_state.oled_thread_running = -1; +} + +void* thread_key_handle(void * arg) +{ + uint64_t __attribute__((unused)) press_time; + uint32_t press_cycle; + int fd = open ("/dev/input/event0", O_RDONLY); + if (fd == -1) { + kvm_sys_state.key_thread_running = 0; + } + struct input_event event; + + while(kvm_sys_state.key_thread_running) + { + read (fd, &event, sizeof (event)); + if (event.type == EV_KEY) { + if (event.value == 1){ + // printf ("[kvmk]按键按下\n"); + press_time = time::time_ms(); + } else if (event.value == 0){ + oled_auto_sleep_time_update(); + // printf ("[kvmk]按键抬起\n"); + press_cycle = time::time_ms() - press_time; + if(press_cycle >= KEY_LONGLONG_PRESS){ + kvm_reset_password(); + } else if (press_cycle >= KEY_LONG_PRESS && press_cycle < KEY_LONGLONG_PRESS){ + // long + // printf ("[kvmk]按键长按\n"); + // printf("[kvmk]wifi_state = %d\n", kvm_sys_state.wifi_state); + if(kvm_sys_state.wifi_state == -2){ + kvm_sys_state.page = 0; + kvm_sys_state.sub_page = 0; + continue; + } + switch(kvm_sys_state.page){ + case 0: // main page + kvm_sys_state.page = 1; + kvm_sys_state.sub_page = 0; + break; + case 1: // wifi coonfig page + system("/etc/init.d/S30wifi restart"); + kvm_sys_state.page = 0; + kvm_sys_state.sub_page = 0; + kvm_sys_state.wifi_config_process = -1; + break; + } + } else { + // short + // printf ("[kvmk]按键短按\n"); + // printf ("[kvmk]wifi_config_process = %d\n", kvm_sys_state.wifi_config_process); + // printf ("[kvmk]page = %d\n", kvm_sys_state.page); + // printf ("[kvmk]sub_page = %d\n", kvm_sys_state.sub_page); + switch(kvm_sys_state.page){ + case 0: // main page + if(kvm_sys_state.sub_page == 0) kvm_sys_state.sub_page = 1; + else kvm_sys_state.sub_page = 0; + break; + case 1: // wifi coonfig page + switch(kvm_sys_state.wifi_config_process){ + case 1: // QR1<->TEXT2 + if(kvm_sys_state.sub_page == 1) kvm_sys_state.sub_page = 2; + else kvm_sys_state.sub_page = 1; + // printf("[kvmk]sub_page = %d\n", kvm_sys_state.sub_page); + break; + case 2: + if(kvm_sys_state.sub_page == 1) kvm_sys_state.sub_page = 2; + else if(kvm_sys_state.sub_page == 2) kvm_sys_state.sub_page = 3; + else if(kvm_sys_state.sub_page == 3) kvm_sys_state.sub_page = 4; + else kvm_sys_state.sub_page = 1; + break; + } + break; + } + } + } + } + time::sleep_ms(KEY_DELAY); + } + kvm_sys_state.key_thread_running = 0; +} + +void* thread_sys_handle(void * arg) +{ + get_ping_allow_state(); + while(kvm_sys_state.sys_thread_running) + { + // net + if(kvm_sys_state.page == 0){ + kvm_update_eth_state(); + kvm_update_wifi_state(); + // kvm_update_rndis_state(); + // kvm_update_tailscale_state(); + // sys_state + kvm_update_usb_state(); + kvm_update_hdmi_state(); + kvm_update_stream_fps(); + kvm_update_hdmi_res(); + kvm_update_stream_type(); + kvm_update_stream_qlty(); + kvm_wifi_web_config_process(); + + } else if(kvm_sys_state.page == 1){ + kvm_wifi_config_process(); + } + + time::sleep_ms(STATE_DELAY); + auto_remove_temp_watchdog(); + } + kvm_sys_state.sys_thread_running = 0; +} + +int main(int argc, char* argv[]) +{ + // Catch SIGINT signal(e.g. Ctrl + C), and set exit flag to true. + signal(SIGINT, [](int sig){ + kvm_sys_state.oled_thread_running = 0; + kvm_sys_state.key_thread_running = 0; + kvm_sys_state.sys_thread_running = 0; + app::set_exit_flag(true); + log::info("[kvms]Prepare to exit\n"); + }); + + pthread_t sys_state_thread; + pthread_t display_thread; + pthread_t key_thread; + + // Execute only when it's a new image + if(access("/kvmapp/kvm_new_img", F_OK) == 0){ + new_img_init(); + } + + // Execute only when it's a new app + if(access("/kvmapp/kvm_new_app", F_OK) == 0){ + new_app_init(); + } + + // only for production testing + if (access("/tmp/S49kvmtest", F_OK) == 0){ + printf("Production testing patch\n"); + Production_testing_patch(); + } + + system("sync"); + + OLED_state = oled_exist(); + if(OLED_state){ + printf("oled exist\r\n"); + system("touch /etc/kvm/oled_exist"); + } else { + printf("oled not exist\r\n"); + system("rm /etc/kvm/oled_exist"); + } + + if(kvm_sys_state.sys_thread_running == 0){ + kvm_sys_state.sys_thread_running = 1; + if (0 != pthread_create(&sys_state_thread, NULL, thread_sys_handle, NULL)) { + kvm_sys_state.sys_thread_running = 0; + printf("[kvms]create thread failed!\r\n"); + } + } + if(OLED_state == 1 && kvm_sys_state.oled_thread_running == 0){ + kvm_sys_state.oled_thread_running = 1; + if (0 != pthread_create(&display_thread, NULL, thread_oled_handle, NULL)) { + kvm_sys_state.oled_thread_running = 0; + printf("[kvms]create thread failed!\r\n"); + } + } + if(kvm_sys_state.key_thread_running == 0){ + kvm_sys_state.key_thread_running = 1; + if (0 != pthread_create(&key_thread, NULL, thread_key_handle, NULL)) { + kvm_sys_state.key_thread_running = 0; + printf("[kvms]create thread failed!\r\n"); + } + } + + // while(!app::need_exit()){ + uint8_t kvm_wd_count = 0; + int kvm_wd_state = 0; + while(kvm_sys_state.sys_thread_running){ + time::sleep_ms(1000); + if(watchdog_sf_is_open()){ + kvm_wd_state = check_watchdog(); + if(kvm_wd_state == 1) kvm_wd_count = 0; + else if(kvm_wd_state == 0) { + kvm_wd_count++; + printf("Vision service unresponsive : %d\n", kvm_wd_count); + } + if(kvm_wd_count > KVM_WD_COUNT_MAX){ + printf("Vision service unresponsive, restart now\n"); + system("reboot"); + } + } else { + kvm_wd_count = 0; + } + } + kvm_sys_state.sys_thread_running = 0; + kvm_sys_state.oled_thread_running = 0; + kvm_sys_state.key_thread_running = 0; + while(kvm_sys_state.oled_thread_running != -1) time::sleep_ms(100); +} + diff --git a/support/sg2002/kvm_vision_test/main/CMakeLists.txt b/support/sg2002/kvm_vision_test/main/CMakeLists.txt new file mode 100644 index 0000000..3053d5b --- /dev/null +++ b/support/sg2002/kvm_vision_test/main/CMakeLists.txt @@ -0,0 +1,74 @@ +############### Add include ################### +list(APPEND ADD_INCLUDE "include" + ) +list(APPEND ADD_PRIVATE_INCLUDE "") +############################################### + +############ Add source files ################# +# list(APPEND ADD_SRCS "src/main.c" +# "src/test.c" +# ) +append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS +# list(REMOVE_ITEM COMPONENT_SRCS "src/test2.c") +# FILE(GLOB_RECURSE EXTRA_SRC "src/*.c") +# FILE(GLOB EXTRA_SRC "src/*.c") +# list(APPEND ADD_SRCS ${EXTRA_SRC}) +# aux_source_directory(src ADD_SRCS) # collect all source file in src dir, will set var ADD_SRCS +# append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS +# list(REMOVE_ITEM COMPONENT_SRCS "src/test.c") +# set(ADD_ASM_SRCS "src/asm.S") +# list(APPEND ADD_SRCS ${ADD_ASM_SRCS}) +# SET_PROPERTY(SOURCE ${ADD_ASM_SRCS} PROPERTY LANGUAGE C) # set .S ASM file as C language +# SET_SOURCE_FILES_PROPERTIES(${ADD_ASM_SRCS} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp -D BBBBB") +############################################### + +###### Add required/dependent components ###### +list(APPEND ADD_REQUIREMENTS basic peripheral kvm) +############################################### + +###### Add link search path for requirements/libs ###### +# list(APPEND ADD_LINK_SEARCH_PATH "${CONFIG_TOOLCHAIN_PATH}/lib") +# list(APPEND ADD_REQUIREMENTS pthread m) # add system libs, pthread and math lib for example here +# set (OpenCV_DIR opencv/lib/cmake/opencv4) +# find_package(OpenCV REQUIRED) +############################################### + +############ Add static libs ################## +# list(APPEND ADD_STATIC_LIB "lib/libtest.a") +############################################### + +#### Add compile option for this component #### +#### Just for this component, won't affect other +#### modules, including component that depend +#### on this component +# list(APPEND ADD_DEFINITIONS_PRIVATE -DAAAAA=1) + +#### Add compile option for this component +#### and components denpend on this component +# list(APPEND ADD_DEFINITIONS -DAAAAA222=1 +# -DAAAAA333=1) +############################################### + +############ Add static libs ################## +#### Update parent's variables like CMAKE_C_LINK_FLAGS +# set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group libmaix/libtest.a -ltest2 -Wl,--end-group" PARENT_SCOPE) +############################################### + +######### Add files need to download ######### +# list(APPEND ADD_FILE_DOWNLOADS "{ +# 'url': 'https://*****/abcde.tar.xz', +# 'urls': [], # backup urls, if url failed, will try urls +# 'sites': [], # download site, user can manually download file and put it into dl_path +# 'sha256sum': '', +# 'filename': 'abcde.tar.xz', +# 'path': 'toolchains/xxxxx', +# 'check_files': [] +# }" +# ) +# +# then extracted file in ${DL_EXTRACTED_PATH}/toolchains/xxxxx, +# you can directly use then, for example use it in add_custom_command +############################################## + +# register component, DYNAMIC or SHARED flags will make component compiled to dynamic(shared) lib +register_component() diff --git a/support/sg2002/kvm_vision_test/main/Kconfig b/support/sg2002/kvm_vision_test/main/Kconfig new file mode 100644 index 0000000..e69de29 diff --git a/support/sg2002/kvm_vision_test/main/src/main.cpp b/support/sg2002/kvm_vision_test/main/src/main.cpp new file mode 100644 index 0000000..af2f35b --- /dev/null +++ b/support/sg2002/kvm_vision_test/main/src/main.cpp @@ -0,0 +1,61 @@ +#include "kvm_vision.h" +#include "maix_basic.hpp" + +using namespace maix; +using namespace maix::sys; + +// #define NOT_GET_IMG + +int main(int argc, char* argv[]) +{ + uint64_t __attribute__((unused)) start_time; + // Catch SIGINT signal(e.g. Ctrl + C), and set exit flag to true. + signal(SIGINT, [](int sig){ app::set_exit_flag(true); + log::info("========================\n"); + }); + + kvmv_hdmi_control(0); + kvmv_hdmi_control(1); + + kvmv_init(0); + set_h264_gop(30); + + uint16_t get_fream_count = 0; + + while(!app::need_exit()){ +#ifdef NOT_GET_IMG + printf("NOT_GET_IMG ...\n"); + time::sleep_ms(1000); +#else + uint8_t* p_kvmv_img_data; + uint32_t kvmv_img_data_size; + int ret; + + printf("KVM-Vison Get Fream ...\n"); + + start_time = time::time_ms(); + if(get_fream_count < 1){ + get_fream_count ++; + ret = kvmv_read_img(1920, 1080, 1, 3000, &p_kvmv_img_data, &kvmv_img_data_size); + } else if(get_fream_count >= 1 && get_fream_count < 2){ + get_fream_count ++; + ret = kvmv_read_img(0, 0, 0, 60, &p_kvmv_img_data, &kvmv_img_data_size); + } else { + get_fream_count = 0; + } + + // printf("kvmv_read_img(): %d \r\n", (int)(time::time_ms() - start_time)); + + // ret = kvmv_read_img(1920, 1080, 1, 3000, &p_kvmv_img_data, &kvmv_img_data_size); + // ret = kvmv_read_img(1920, 1080, 0, 60, &p_kvmv_img_data, &kvmv_img_data_size); + + printf("kvmv_read_img ret = %d\n", ret); + + // send... + // if(ret >= 0){ + free_kvmv_data(&p_kvmv_img_data); + // } +#endif + } + kvmv_deinit(); +} \ No newline at end of file diff --git a/tools/nanokvm_update_edid/E21_NanoKVM.bin b/tools/nanokvm_update_edid/E21_NanoKVM.bin new file mode 100644 index 0000000..005cad4 Binary files /dev/null and b/tools/nanokvm_update_edid/E21_NanoKVM.bin differ diff --git a/tools/nanokvm_update_edid/Makefile b/tools/nanokvm_update_edid/Makefile new file mode 100644 index 0000000..9b7b9ca --- /dev/null +++ b/tools/nanokvm_update_edid/Makefile @@ -0,0 +1,22 @@ +# CROSS_COMPILE = aarch64-none-linux-gnu- +# CROSS_COMPILE = riscv64-unknown-linux-gnu- +CROSS_COMPILE = riscv64-unknown-linux-musl- +# CROSS_COMPILE = riscv64-unknown-elf- + +CFLAGS += -I/usr/local/RISC-V-toolchain/riscv64-linux-musl-x86_64/sysroot/usr/include + +CC = $(CROSS_COMPILE)gcc + +SRC = nanokvm_update_edid.c + +TARGET = nanokvm_update_edid + +all: $(TARGET) + +$(TARGET): $(SRC) + $(CC) -march=rv64gc -o $@ $^ + +clean: + rm -f $(TARGET) + +.PHONY: all clean \ No newline at end of file diff --git a/tools/nanokvm_update_edid/nanokvm_update_edid b/tools/nanokvm_update_edid/nanokvm_update_edid new file mode 100644 index 0000000..8b2489e Binary files /dev/null and b/tools/nanokvm_update_edid/nanokvm_update_edid differ diff --git a/tools/nanokvm_update_edid/nanokvm_update_edid.c b/tools/nanokvm_update_edid/nanokvm_update_edid.c new file mode 100644 index 0000000..4a829ee --- /dev/null +++ b/tools/nanokvm_update_edid/nanokvm_update_edid.c @@ -0,0 +1,749 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "nanokvm_update_edid.h" + + +int client; +// old offset set to 0xff first +static uint8_t old_offset = 0xff; + +// ======================================================================================================= + +int get_edid_from_file(const char *filename, uint8_t *edid_data, uint16_t *edid_size) { + FILE *file = fopen(filename, "rb"); + if (!file) { + perror("Failed to open EDID file"); + return -1; + } + + *edid_size = fread(edid_data, 1, EDID_BUFFER_SIZE, file); + fclose(file); + + return 0; +} + +int check_edid(uint8_t *edid_data, uint16_t edid_size) +{ + uint16_t i; + // Check if the EDID data length is valid + if (edid_size != EDID_BUFFER_SIZE) { + fprintf(stderr, "EDID data length is not %d bytes\n", EDID_BUFFER_SIZE); + return -1; // EDID data length is not enough + } + + // Check EDID header + if (edid_data[0] != 0x00 || edid_data[1] != 0xFF || + edid_data[2] != 0xFF || edid_data[3] != 0xFF || + edid_data[4] != 0xFF || edid_data[5] != 0xFF || + edid_data[6] != 0xFF || edid_data[7] != 0x00) { + fprintf(stderr, "EDID header is invalid\n"); + return -1; // EDID header is invalid + } + + // First 128 Bytes checksum + uint8_t checksum1 = 0; + for (i = 0; i < 127; i++) { + checksum1 += edid_data[i]; + } + checksum1 = 0x100 - checksum1; // Reverse + if (checksum1 != edid_data[127]) { + // Checksum for first 128 bytes is incorrect + fprintf(stderr, "Checksum for first 128 bytes is incorrect\n"); + return -1; + } + + // Second 128 Bytes checksum + uint8_t checksum2 = 0; + for (i = 128; i < 255; i++) { + checksum2 += edid_data[i]; + } + checksum2 = 0x100 - checksum2; // Reverse + if (checksum2 != edid_data[255]) { + // Checksum for second 128 bytes is incorrect + fprintf(stderr, "Checksum for second 128 bytes is incorrect\n"); + return -1; + } + + return 0; // EDID is valid +} + +// ======================================================================================================= + +void NanoKVM_PCIe_HDMI_Reset(void) +{ + system("echo 0 > /sys/class/gpio/gpio451/value"); + usleep(100000); + system("echo 1 > /sys/class/gpio/gpio451/value"); + usleep(100000); +} + +// ======================================================================================================= + +// I2C write function +int i2c_write_byte(uint8_t offset, uint8_t reg, uint8_t data) +{ + // reg buffer + uint8_t reg_buf[2] = {0}; + + // if offset is changed, write it first + if (offset != old_offset) { + old_offset = offset; + reg_buf[0] = LT6911_REG_OFFSET; // Set the offset register + reg_buf[1] = offset; // Set the register to read + if (write(client, reg_buf, 2) != 2) { + perror("Failed to write offset to the i2c bus"); + return -1; + } + } + + // write the data to the i2c bus + reg_buf[0] = reg; // Set the register to write + reg_buf[1] = data; // Set the data to write + if (write(client, reg_buf, 2) != 2) { + perror("Failed to write to the i2c bus"); + return -1; + } + return 0; +} + +int i2c_write_bytes(uint8_t offset, uint8_t reg, const uint8_t *data, size_t len) +{ + // check len + if (len == 0) { + fprintf(stderr, "Data length must be greater than 0.\n"); + return -1; + } + + // reg buffer + uint8_t reg_buf[1 + len]; + + // if offset is changed, write it first + if (offset != old_offset) { + old_offset = offset; + reg_buf[0] = LT6911_REG_OFFSET; // Set the offset register + reg_buf[1] = offset; // Set the register to read + if (write(client, reg_buf, 2) != 2) { + perror("Failed to write offset to the i2c bus"); + return -1; + } + } + + // write the data to the i2c bus + reg_buf[0] = reg; + for (size_t i = 0; i < len; i++) { + reg_buf[i + 1] = data[i]; + } + + // write to the I2C bus + if (write(client, reg_buf, 1 + len) != 1 + len) { + perror("Failed to write to the i2c bus"); + return -1; + } + + return 0; +} + +// I2C read function +int i2c_read_byte(uint8_t offset, uint8_t reg, uint8_t *data) +{ + // reg buffer + uint8_t reg_buf[2] = {0}; + + // if offset is changed, write it first + if (offset != old_offset) { + old_offset = offset; + reg_buf[0] = LT6911_REG_OFFSET; // Set the offset register + reg_buf[1] = offset; // Set the register to read + if (write(client, reg_buf, 2) != 2) { + perror("Failed to write offset to the i2c bus"); + return -1; + } + } + + // write the register address to read + reg_buf[0] = reg; // Set the register to read + if (write(client, reg_buf, 1) != 1) { + perror("Failed to write register address to the i2c bus"); + return -1; + } + + // read the data from the i2c bus + if (read(client, data, 1) != 1 ) { + perror("Failed to read from the i2c bus"); + return -1; + } + + return 0; +} + +int i2c_read_bytes(uint8_t offset, uint8_t reg, uint8_t *data, size_t len) +{ + // 检查数据长度 + if (len == 0) { + fprintf(stderr, "Data length must be greater than 0.\n"); + return -1; + } + + uint8_t reg_buf[2]; // 创建寄存器缓冲区 + + // 如果偏移量改变,先写入偏移量 + if (offset != old_offset) { + old_offset = offset; + reg_buf[0] = LT6911_REG_OFFSET; // 设置偏移寄存器 + reg_buf[1] = offset; // 设置偏移值 + if (write(client, reg_buf, 2) != 2) { + perror("Failed to write offset to the i2c bus"); + return -1; + } + } + + // 写入要读取的寄存器地址 + reg_buf[0] = reg; // 设置要读取的寄存器 + if (write(client, reg_buf, 1) != 1) { + perror("Failed to write register address to the i2c bus"); + return -1; + } + + // 从 I2C 总线读取数据 + if (read(client, data, len) != len) { + perror("Failed to read from the i2c bus"); + return -1; + } + + return 0; +} + +// ======================================================================================================= + +int lt6911_enable(void) { + // Enable the LT6911UXC by writing to the appropriate register + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xEE, 0x01) != 0) { + fprintf(stderr, "Failed to enable LT6911UXC\n"); + return -1; + } + return 0; +} + +int lt6911_disable(void) { + // Disable the LT6911UXC by writing to the appropriate register + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xEE, 0x00) != 0) { + fprintf(stderr, "Failed to disable LT6911UXC\n"); + return -1; + } + return 0; +} + +// ======================================================================================================= + +int lt6911uxc_edid_write(uint8_t *edid_data, uint16_t edid_size) +{ + uint8_t i; + int ret; + uint8_t chip_data[16] = {0}; + uint8_t wr_count = edid_size / LT6911UXC_WR_SIZE + 1; + uint8_t version_str[32] = {0}; + + fprintf(stdout, "Writing EDID....\n"); + + // Start + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xDF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x59, 0x51) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x10) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x21) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x84) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x81) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + // Waiting for erasure + usleep(500000); + + if (i2c_read_byte(LT6911_SYS3_OFFSET, 0x08, chip_data) != 0) return -1; + if (chip_data[0] != 0xEE) { + fprintf(stderr, "Unsupported chip version\n"); + return -1; + } + if (i2c_write_byte(LT6911_SYS3_OFFSET, 0x08, 0xAE) != 0) return -1; + if (i2c_write_byte(LT6911_SYS3_OFFSET, 0x08, 0xEE) != 0) return -1; + // Write + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x84) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x84) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + for (i = 0; i < wr_count; i++) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xDF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x20) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x21) != 0) return -1; + if (i != wr_count-1) { + if (i2c_write_bytes(LT6911_SYS_OFFSET, 0x59, edid_data+(LT6911UXC_WR_SIZE*i), LT6911UXC_WR_SIZE) != 0) return -1; + } else { + if (i2c_write_bytes(LT6911_SYS_OFFSET, 0x59, version_str, LT6911UXC_WR_SIZE) != 0) return -1; + } + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i != wr_count-1) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00+(LT6911UXC_WR_SIZE*i)) != 0) return -1; + } else { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x81) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00) != 0) return -1; + } + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xC0) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x90) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i != wr_count-1) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x84) != 0) return -1; + } else { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x88) != 0) return -1; + } + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + } + if (i2c_read_byte(LT6911_SYS3_OFFSET, 0x08, chip_data) != 0) return -1; + if (chip_data[0] != 0xEE) return -1; + if (i2c_write_byte(LT6911_SYS3_OFFSET, 0x08, 0xAE) != 0) return -1; + if (i2c_write_byte(LT6911_SYS3_OFFSET, 0x08, 0xEE) != 0) return -1; + + fprintf(stdout, "EDID write completed\n"); + + return 0; +} + +int lt6911uxc_edid_read(uint8_t *edid_data, uint16_t edid_size) +{ + uint8_t i; + int ret; + uint8_t chip_data[16] = {0}; + uint8_t wr_count = edid_size / LT6911UXC_WR_SIZE; + + // Read EDID data from LT6911UXC + fprintf(stdout, "Reading EDID...\n"); + // Read + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x84) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + for (i = 0; i < wr_count; i++) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0x5F) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0xA0) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00+(LT6911UXC_WR_SIZE*i)) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x90) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x21) != 0) return -1; + if (i2c_read_bytes(LT6911_SYS_OFFSET, 0x5F, edid_data+(LT6911UXC_WR_SIZE*i), LT6911UXC_WR_SIZE) != 0) return -1; + } + + return 0; +} + +int lt6911c_edid_write(uint8_t *edid_data, uint16_t edid_size) +{ + uint8_t i; + int ret; + uint8_t chip_data[16] = {0}; + uint8_t wr_count = edid_size / LT6911C_WR_SIZE; + uint8_t version_str[32] = {0}; + + fprintf(stdout, "Writing EDID....\n"); + + // Start + lt6911_enable(); + if (i2c_read_bytes(LT6911_SYS4_OFFSET, 0x00, chip_data, 2) != 0) return -1; + if (chip_data[0] != 0x16 || chip_data[1] != 0x05) { + fprintf(stderr, "Unsupported chip version\n"); + return -1; + } + lt6911_disable(); + + usleep(100000); + + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_read_bytes(LT6911_SYS4_OFFSET, 0x00, chip_data, 2) != 0) return -1; + if (chip_data[0] != 0x16 || chip_data[1] != 0x05) { + fprintf(stderr, "Unsupported chip version\n"); + return -1; + } + lt6911_disable(); + + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xC0) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x59, 0x51) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x92) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x86) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x83) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + + // waiting to clean + usleep(500000); + if (i2c_read_byte(LT6911_SYS2_OFFSET, 0x02, chip_data) != 0) return -1; + if (chip_data[0] != 0xFF) { + fprintf(stderr, "Clean Error\n"); + return -1; + } + if (i2c_write_byte(LT6911_SYS2_OFFSET, 0x02, 0xDF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS2_OFFSET, 0x02, 0xFF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x86) != 0) return -1; + + for (i = 0; i < wr_count; i++) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x86) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xEF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0xA2) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x01) != 0) return -1; + if (i2c_write_bytes(LT6911_SYS_OFFSET, 0x59, edid_data+(LT6911C_WR_SIZE*i), LT6911C_WR_SIZE) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00+(LT6911C_WR_SIZE*i)) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xE0) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x92) != 0) return -1; + } + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x8A) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_read_byte(LT6911_SYS2_OFFSET, 0x02, chip_data) != 0) return -1; + if (chip_data[0] != 0xFF) return -1; + if (i2c_write_byte(LT6911_SYS2_OFFSET, 0x02, 0xDF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS2_OFFSET, 0x02, 0xFF) != 0) return -1; + + fprintf(stdout, "EDID write completed\n"); + + return 0; +} + +int lt6911c_edid_read(uint8_t *edid_data, uint16_t edid_size) +{ + uint8_t i; + int ret; + uint8_t chip_data[16] = {0}; + uint8_t wr_count = edid_size / LT6911C_WR_SIZE; + + // Read EDID data from LT6911UXC + fprintf(stdout, "Reading EDID...\n"); + // Read + + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x86) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + + for (i = 0; i < wr_count; i++) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0x6F) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0xA2) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00+(LT6911C_WR_SIZE*i)) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x92) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x01) != 0) return -1; + if (i2c_read_bytes(LT6911_SYS_OFFSET, 0x5F, edid_data+(LT6911C_WR_SIZE*i), LT6911C_WR_SIZE) != 0) return -1; + } + + return 0; +} + +// ======================================================================================================= + +int lt6911_edid_config(chip_version_t chip_version, uint8_t *edid_data, uint16_t edid_size) +{ + if (chip_version == CHIP_UNKNOWN) + { + fprintf(stderr, "Unknown chip version\n"); + return -1; + } + + // open i2c device + if ((client = open(I2C_DEVICE, O_RDWR)) < 0) { + perror("Failed to open the i2c bus"); + return -1; + } + + // // set the lt6911uxc address + if (ioctl(client, I2C_SLAVE, I2C_ADDRESS) < 0) { + perror("Failed to acquire bus access and/or talk to slave"); + close(client); + return -1; + } + + // edid write + if (chip_version == CHIP_LT6911UXC) { + if (lt6911uxc_edid_write(edid_data, edid_size) != 0) { + fprintf(stderr, "Failed to write EDID data to LT6911UXC\n"); + close(client); + return -1; + } + } else if (chip_version == CHIP_LT6911C) { + if (lt6911c_edid_write(edid_data, edid_size) != 0) { + fprintf(stderr, "Failed to write EDID data to LT6911C\n"); + close(client); + return -1; + } + } else { + fprintf(stderr, "Unknown chip version\n"); + close(client); + return -1; + } + // sleep 1s + sleep(1); + + // read edid data back + uint8_t edid_read_data[EDID_BUFFER_SIZE] = {0}; + if (chip_version == CHIP_LT6911UXC) { + if (lt6911uxc_edid_read(edid_read_data, EDID_BUFFER_SIZE) != 0) { + fprintf(stderr, "Failed to read EDID data from LT6911UXC\n"); + close(client); + return -1; + } + } else if (chip_version == CHIP_LT6911C) { + if (lt6911c_edid_read(edid_read_data, EDID_BUFFER_SIZE) != 0) { + fprintf(stderr, "Failed to read EDID data from LT6911C\n"); + close(client); + return -1; + } + } else { + fprintf(stderr, "Unknown chip version\n"); + close(client); + return -1; + } + + // check if the read data matches the written data + if (memcmp(edid_data, edid_read_data, EDID_BUFFER_SIZE) != 0) { + fprintf(stderr, "EDID data mismatch after write/read cycle\n"); + // print edid with 16*16 hex format + fprintf(stderr, "Written EDID data:\n"); + for (int i = 0; i < EDID_BUFFER_SIZE; i++) { + fprintf(stderr, "%02X ", edid_data[i]); + if ((i + 1) % 16 == 0) { + fprintf(stderr, "\n"); + } + } + fprintf(stderr, "\nRead EDID data:\n"); + for (int i = 0; i < EDID_BUFFER_SIZE; i++) { + fprintf(stderr, "%02X ", edid_read_data[i]); + if ((i + 1) % 16 == 0) { + fprintf(stderr, "\n"); + } + } + close(client); + return -1; + } else { + fprintf(stdout, "EDID data verified successfully\n"); + } + + // close client + close(client); + return 0; +} + +// ======================================================================================================= + +void print_warning(product_version_t product_version) { + printf("\n=========================================================\n"); + printf("Incorrect EDID may cause issues such as \n"); + printf("inability to display images, please modify with caution\n"); + printf("=========================================================\n\n"); + + if (product_version == PRODUCT_CUBE_A || product_version == PRODUCT_CUBE_B) { + printf("\n==========================================================\n"); + printf("⚠️ WARNING: Hardware version detected as Cube/Lite!\n"); + printf("==========================================================\n"); + printf("After flashing, you MUST manually power cycle the device!\n"); + printf("Please ensure you can physically disconnect its power,\n"); + printf("NOT just remotely reboot it!!\n"); + printf("==========================================================\n\n"); + } +} + +void print_success(product_version_t product_version) { + printf("\n=========================================================\n"); + printf("✅ EDID update successful!\n"); + if (product_version == PRODUCT_CUBE_A || product_version == PRODUCT_CUBE_B) { + printf("Please manually power cycle the device to apply changes.\n"); + } + printf("=========================================================\n\n"); +} + +int get_user_confirmation() { + char input[256]; + printf("Do you want to continue? (Y/N): \n"); + + while (1) { + + if (fgets(input, sizeof(input), stdin) == NULL) { + printf("\nInput error. Exiting.\n"); + return 0; + } + + // 去除换行符 + input[strcspn(input, "\n")] = '\0'; + + if (strlen(input) == 0) { + continue; // 空输入,重新提示 + } + + // 转换为小写方便比较 + char choice = tolower(input[0]); + + if (choice == 'y') { + return 1; + } else if (choice == 'n') { + return 0; + } else { + printf("Invalid input. Please enter Y or N.\n"); + } + } +} + +// ======================================================================================================= + +int main(int argc, char *argv[]) { + + uint8_t counter = 0; + + // Check version + chip_version_t chip_version = CHIP_UNKNOWN; + product_version_t product_version = PRODUCT_UNKNOWN; + char chip_version_str[32] = {0}; + char product_version_str[32] = {0}; + + FILE *file = fopen(VERSION_PATH, "r"); + if (file == NULL) { + fprintf(stderr, "Please upgrade to the latest system\n"); + return 1; + } + + if (fgets(chip_version_str, sizeof(chip_version_str), file) == NULL) { + fprintf(stderr, "Failed to read chip version\n"); + fclose(file); + return 1; + } + fclose(file); + + file = fopen(PRODUCT_PATH, "r"); + if (file == NULL) { + fprintf(stderr, "Please upgrade to the latest system\n"); + return 1; + } + + if (fgets(product_version_str, sizeof(product_version_str), file) == NULL) { + fprintf(stderr, "Failed to read product version\n"); + fclose(file); + return 1; + } + fclose(file); + + chip_version_str[strcspn(chip_version_str, "\n")] = '\0'; + product_version_str[strcspn(product_version_str, "\n")] = '\0'; + + if (strcmp(chip_version_str, "c") == 0) { + fprintf(stdout, "Chip Version: LT6911C\n"); + chip_version = CHIP_LT6911C; + } else if (strcmp(chip_version_str, "ux") == 0) { + fprintf(stdout, "Chip Version: LT6911UXC\n"); + chip_version = CHIP_LT6911UXC; + } else if (strcmp(chip_version_str, "ue") == 0) { + fprintf(stderr, "Chip Version Error: UE version's edid can't be updated\n"); + return 1; + } else { + fprintf(stderr, "Chip Version Error: Unknown version\n"); + return 1; + } + + if (strcmp(product_version_str, "alpha") == 0) { + fprintf(stdout, "Product Version: CUBE_A\n"); + product_version = PRODUCT_CUBE_A; + } else if (strcmp(product_version_str, "beta") == 0) { + fprintf(stdout, "Product Version: CUBE_B\n"); + product_version = PRODUCT_CUBE_B; + } else if (strcmp(product_version_str, "pcie") == 0) { + fprintf(stdout, "Product Version : PCIE_A\n"); + product_version = PRODUCT_PCIE_A; + } else { + fprintf(stderr, "Product Version Error: Unknown version\n"); + return 1; + } + + print_warning(product_version); + if (product_version == PRODUCT_CUBE_A || product_version == PRODUCT_CUBE_B) { + if (get_user_confirmation() == 0) { + return EXIT_FAILURE; + } + } + + // check command line arguments + if (argc != 2) { + fprintf(stderr, "Please enter the location of the EDID file using \"%s /path/to/edid.bin\"\n", argv[0]); + return EXIT_FAILURE; + } + + uint16_t edid_size = 0; + uint8_t edid_data[EDID_BUFFER_SIZE] = {0}; + if (get_edid_from_file(argv[1], edid_data, &edid_size) != 0) { + fprintf(stderr, "Failed to read EDID data from file %s\n", argv[1]); + return EXIT_FAILURE; + } + if (check_edid(edid_data, edid_size) != 0) { + fprintf(stderr, "EDID data is invalid\n"); + return EXIT_FAILURE; + } + fprintf(stdout, "EDID data loaded successfully from %s\n", argv[1]); + + if (product_version == PRODUCT_PCIE_A) NanoKVM_PCIe_HDMI_Reset(); + + // configure lt6911 edid + if (lt6911_edid_config(chip_version, edid_data, edid_size) != 0) { + fprintf(stderr, "Failed to configure LT6911 EDID\n"); + return EXIT_FAILURE; + } + + if (product_version == PRODUCT_PCIE_A) NanoKVM_PCIe_HDMI_Reset(); + + print_success(product_version); + + return EXIT_SUCCESS; +} diff --git a/tools/nanokvm_update_edid/nanokvm_update_edid.h b/tools/nanokvm_update_edid/nanokvm_update_edid.h new file mode 100644 index 0000000..ed743fb --- /dev/null +++ b/tools/nanokvm_update_edid/nanokvm_update_edid.h @@ -0,0 +1,41 @@ +#ifndef NANOKVM_UPDATE_EDID_H +#define NANOKVM_UPDATE_EDID_H + +#define I2C_DEVICE "/dev/i2c-4" // I2C 设备文件 +#define EDID_BUFFER_SIZE 256 // 最大支持的字节数 + +#define I2C_ADDRESS 0x2b // I2C 设备地址(根据实际设备调整) +#define LT6911_REG_OFFSET 0xFF // LT6911UXC 寄存器偏移地址 +#define LT6911_SYS_OFFSET 0x80 // LT6911UXC 寄存器偏移地址 +#define LT6911_SYS2_OFFSET 0x90 // LT6911UXC 寄存器偏移地址 +#define LT6911_SYS3_OFFSET 0x81 // LT6911UXC 寄存器偏移地址 +#define LT6911_SYS4_OFFSET 0xA0 // LT6911UXC 寄存器偏移地址 +#define LT6911_CSI_INFO_OFFSET 0x85 // LT6911UXC CSI接口信息寄存器偏移地址 +#define LT6911_HDMI_INFO_OFFSET 0x86 // LT6911UXC HDMI信息寄存器偏移地址 +#define LT6911_CSI_TOTAL_OFFSET 0xD4 // LT6911UXC CSI总线统计信息 +#define LT6911_AUDIO_INFO_OFFSET 0xB0 // LT6911UXC 音频信息寄存器偏移地址 +#define LT6911C_HDMI_INFO_OFFSET 0xD2 // LT6911C HDMI信息寄存器偏移地址 +#define LT6911C_AUDIO_INFO_OFFSET 0xD1 // LT6911C 音频信息寄存器偏移地址 +#define LT6911C_CSI_INFO_OFFSET 0xC2 // LT6911C CSI信息寄存器偏移地址 + +#define EDID_BUFFER_SIZE 256 // 最大支持的字节数 +#define LT6911UXC_WR_SIZE 32 // LT6911UXC单次读写最大字节数 +#define LT6911C_WR_SIZE 16 // LT6911C单次读写最大字节数 + +#define VERSION_PATH "/etc/kvm/hdmi_version" +#define PRODUCT_PATH "/etc/kvm/hw" + +typedef enum { + CHIP_LT6911UXC = 0, + CHIP_LT6911C, + CHIP_UNKNOWN +} chip_version_t; + +typedef enum { + PRODUCT_CUBE_A = 0, + PRODUCT_CUBE_B, + PRODUCT_PCIE_A, + PRODUCT_UNKNOWN, +} product_version_t; + +#endif // NANOKVM_UPDATE_EDID_H \ No newline at end of file diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 0000000..3dce414 --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true \ No newline at end of file diff --git a/web/.env.development b/web/.env.development new file mode 100644 index 0000000..0755dc5 --- /dev/null +++ b/web/.env.development @@ -0,0 +1,3 @@ +VITE_SERVER_IP=192.168.0.65 +VITE_SERVER_PORT=80 +VITE_WITH_CREDENTIALS=false diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs new file mode 100644 index 0000000..b3c809e --- /dev/null +++ b/web/.eslintrc.cjs @@ -0,0 +1,20 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + 'prettier', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/web/.prettierignore b/web/.prettierignore new file mode 100644 index 0000000..7ec769b --- /dev/null +++ b/web/.prettierignore @@ -0,0 +1 @@ +*.hbs diff --git a/web/.prettierrc.yaml b/web/.prettierrc.yaml new file mode 100644 index 0000000..d560dc1 --- /dev/null +++ b/web/.prettierrc.yaml @@ -0,0 +1,36 @@ +singleQuote: true +trailingComma: none +printWidth: 100 +tabWidth: 2 +bracketSpacing: true +importOrder: + - ^(react/(.*)$)|^(react$) + - ^(next/(.*)$)|^(next$) + - + - '' + - ^types$ + - ^@/assets(.*)$ + - ^@/api/(.*)$ + - ^@/i18n/(.*)$ + - ^@/types$ + - ^@/lib/(.*)$ + - ^@/jotai/(.*)$ + - ^@/hooks/(.*)$ + - ^@/components/(.*)$ + - ^@/pages/(.*)$ + - ^@/styles/(.*)$ + - '' + - '^[./]' +importOrderSeparation: false +importOrderSortSpecifiers: true +importOrderBuiltinModulesToTop: true +importOrderParserPlugins: + - typescript + - jsx + - tsx + - decorators-legacy +importOrderMergeDuplicateImports: true +importOrderCombineTypeAndValueImports: true +plugins: + - '@ianvs/prettier-plugin-sort-imports' + - prettier-plugin-tailwindcss diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..4c385df --- /dev/null +++ b/web/README.md @@ -0,0 +1,63 @@ +# NanoKVM Frontend + +This is NanoKVM web project. For more documentation, please refer to the [Wiki](https://wiki.sipeed.com/nanokvm). + +## Structure + +```shell +src +├── api // backend api +├── assets // static resources +├── components // public components +├── i18n // language resources +├── jotai // Global jotai variables +├── lib // util libs +├── pages // web pages +│ ├── auth // login and password +│ ├── desktop // remote desktop +│ └── terminal // web terminal +├── router.tsx // routers +└── types // types +``` + +## Local Development + +> Development requires SSH. You can enable it in the Web Settings: `Settings > SSH`. + +Due to CORS restrictions, authentication needs to be disabled during local development. + +To develop authentication features, you need to build the project and test in NanoKVM. + +1. Log in to NanoKVM via SSH: `ssh root@your-nanokvm-ip` (default password is root). +2. Open the configuration file `/etc/kvm/server.yaml/` and add `authentication: disable`. ⚠️CAUTION: This option disables all authentication and should NOT be enabled in production environment! +3. Restart the service: `/etc/init.d/S95nanokvm restart`. +4. Edit the `.env.development` file and change `VITE_SERVER_IP` to your NanoKVM IP address. +5. Run `pnpm dev` to start the server and visit http://localhost:3001/ in browser. + + +It's recommended to disable browser caching to avoid access issues during development: + +1. Open the browser Developer Tools; +2. Go to the `Network` tab; +3. Check `Disable cache` option; +4. Refresh the page. + +## Deployment + +Build: + +```shell +cd web +pnpm install +pnpm build +``` + +1. After the compilation is complete, the `dist` folder will be generated. +2. Rename the folder to `web`. +3. Upload `web` to `/kvmapp/server/` in NanoKVM. +4. Restart the service by executing `/etc/init.d/S95nanokvm restart` in NanoKVM. + +Tips: + +1. File uploads requires SSH. You can enable it in the Web Settings: `Settings > SSH`. +2. Browser may have old version cache. If you can't open the page, try a force refresh or clear the cache. diff --git a/web/README_JA.md b/web/README_JA.md new file mode 100644 index 0000000..978c100 --- /dev/null +++ b/web/README_JA.md @@ -0,0 +1,63 @@ +# NanoKVM フロントエンド + +これは NanoKVM のウェブプロジェクトです。詳細なドキュメントについては、[Wiki](https://wiki.sipeed.com/nanokvm) を参照してください。 + +## 構造 + +```shell +src +├── api // バックエンド API +├── assets // 静的リソース +├── components // 公共コンポーネント +├── i18n // 言語リソース +├── jotai // グローバル jotai 変数 +├── lib // ユーティリティライブラリ +├── pages // ウェブページ +│ ├── auth // ログインとパスワード +│ ├── desktop // リモートデスクトップ +│ └── terminal // ウェブターミナル +├── router.tsx // ルーター +└── types // 型定義 +``` + +## ローカル開発 + +> 開発には SSH が必要です。Web 設定で有効にすることができます: `設定 > SSH` + +CORS 制限のため、ローカル開発中は認証を無効にする必要があります。 + +認証機能を開発するには、プロジェクトをビルドして NanoKVM でテストする必要があります。 + +1. SSH を介して NanoKVM にログインします:`ssh root@your-nanokvm-ip`(デフォルトのパスワードは root です)。 +2. 設定ファイル `/etc/kvm/server.yaml/` を開き、`authentication: disable` を追加します。⚠️注意:このオプションはすべての認証を無効にし、本番環境では有効にしないでください! +3. サービスを再起動します:`/etc/init.d/S95nanokvm restart`。 +4. `.env.development` ファイルを編集し、`VITE_SERVER_IP` を NanoKVM の IP アドレスに変更します。 +5. `pnpm dev` を実行してサーバーを起動し、ブラウザで http://localhost:3001/ にアクセスします。 + + +開発中のアクセス問題を避けるため、ブラウザのキャッシュを無効にすることをお勧めします: + +1. ブラウザの開発者ツールを開きます; +2. `Network` タブに移動します; +3. `Disable cache` オプションをチェックします; +4. ページをリフレッシュします。 + +## デプロイ + +ビルド: + +```shell +cd web +pnpm install +pnpm build +``` + +1. コンパイルが完了すると、`dist` フォルダが生成されます。 +2. フォルダの名前を `web` に変更します。 +3. `web` を NanoKVM の `/kvmapp/server/` にアップロードします。 +4. NanoKVM で `/etc/init.d/S95nanokvm restart` を実行してサービスを再起動します。 + +Tips: + +1. ファイルのアップロードには SSH が必要です。Web 設定で有効にすることができます: `設定 > SSH` +2. ブラウザに古いバージョンのキャッシュが残っている可能性があります。ページが開かない場合は、強制リフレッシュまたはキャッシュのクリアを試してください。 diff --git a/web/README_ZH.md b/web/README_ZH.md new file mode 100644 index 0000000..331ef60 --- /dev/null +++ b/web/README_ZH.md @@ -0,0 +1,64 @@ +# NanoKVM 前端页面 + +NanoKVM 前端页面的代码。更多文档请参考 [Wiki](https://wiki.sipeed.com/nanokvm) 。 + +## 目录结构 + +```shell +src +├── api // 后端接口 +├── assets // 资源文件 +├── components // 公共组件 +├── i18n // 多语言 +├── jotai // 全局 jotai 变量 +├── lib // lib +├── pages // 页面 +│ ├── auth // 鉴权页面 +│ ├── desktop // 远程桌面 +│ └── terminal // 终端 +├── router.tsx // 路由 +└── types // 类型定义 +``` + +## 本地开发 + +> 开发需要启用 SSH 功能。请在 Web `设置 - SSH` 中检查 SSH 是否已经启用。 + +由于 CORS 的限制,在本地开发时,需要关闭鉴权功能。 + +如果想要开发鉴权相关的功能,需要编译后在 NanoKVM 中进行测试。 + +1. 通过 SSH 登录到 NanoKVM:`ssh root@your-nanokvm-ip`(默认密码为 root); +2. 修改配置文件 `/etc/kvm/server.yaml`,添加一行 `authentication: disable`。⚠️注意:该选项会禁用所有鉴权功能,生产环境请勿开启该选项! +3. 执行 `/etc/init.d/S95nanokvm restart` 重启服务。 +4. 编辑 `.env.development` 文件,将 `VITE_SERVER_IP` 修改为你的 NanoKVM IP 地址。 +5. 执行 `pnpm dev` 启动服务,然后在浏览器中访问 http://localhost:3001/ 。 + + +建议在浏览器中禁用缓存,防止在开发过程中出现无法访问的情况。 + +1. 打开开发者工具; +2. 点击 `Network` 选项卡; +3. 勾选 `Disable cache` 选项; +4. 刷新页面。 + +## 部署 + +编译: + +```shell +cd web +pnpm install +pnpm build +``` + +1. 编译完成后会生成 `dist` 文件夹; +2. 将该文件夹重命名为 `web`; +3. 将 `web` 文件夹上传到 NanoKVM 的 `/kvmapp/server/` 目录下; +4. 在 NanoKVM 中执行 `/etc/init.d/S95nanokvm restart` 重启服务。 + + +注意: + +1. 上传文件需要启用 SSH 功能。请在 Web `设置 - SSH` 中检查 SSH 是否已经启用。 +2. 更新 web 目录后,浏览器可能会有缓存。如果遇到打不开页面的情况,请强制刷新或清空缓存。 diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..48f696a --- /dev/null +++ b/web/index.html @@ -0,0 +1,13 @@ + + + + + + + BatchuKVM + + +
+ + + diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..ca97c7e --- /dev/null +++ b/web/package.json @@ -0,0 +1,71 @@ +{ + "name": "web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "mocked": "vite --mode mocked", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons": "^5.6.1", + "@xterm/addon-attach": "^0.11.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", + "antd": "^5.29.1", + "axios": "1.12.0", + "clsx": "^2.1.1", + "crypto-js": "^4.2.0", + "i18next": "^23.16.4", + "jotai": "^2.10.1", + "js-cookie": "^3.0.5", + "lucide-react": "^0.469.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-draggable": "^4.4.6", + "react-error-boundary": "^4.1.2", + "react-helmet-async": "^2.0.5", + "react-i18next": "^14.1.3", + "react-responsive": "^10.0.0", + "react-router-dom": "^6.27.0", + "react-simple-keyboard": "^3.8.19", + "semver": "^7.6.3", + "vaul": "^0.9.9", + "websocket": "^1.0.35", + "yocto-queue": "^1.2.1" + }, + "devDependencies": { + "@ianvs/prettier-plugin-sort-imports": "^4.7.0", + "@types/crypto-js": "^4.2.2", + "@types/js-cookie": "^3.0.6", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@types/semver": "^7.5.8", + "@types/websocket": "^1.0.10", + "@typescript-eslint/eslint-plugin": "^8.48.0", + "@typescript-eslint/parser": "^8.48.0", + "@vitejs/plugin-react": "^4.7.0", + "autoprefixer": "^10.4.20", + "eslint": "^9.39.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.16", + "msw": "^2.6.0", + "postcss": "^8.4.47", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.9", + "tailwindcss": "^3.4.18", + "typescript": "^5.6.3", + "vite": "7.1.11", + "vite-tsconfig-paths": "^4.3.2" + }, + "msw": { + "workerDirectory": [ + "public" + ] + } +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml new file mode 100644 index 0000000..56aa94e --- /dev/null +++ b/web/pnpm-lock.yaml @@ -0,0 +1,6202 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ant-design/icons': + specifier: ^5.6.1 + version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@xterm/addon-attach': + specifier: ^0.11.0 + version: 0.11.0(@xterm/xterm@5.5.0) + '@xterm/addon-fit': + specifier: ^0.10.0 + version: 0.10.0(@xterm/xterm@5.5.0) + '@xterm/xterm': + specifier: ^5.5.0 + version: 5.5.0 + antd: + specifier: ^5.29.1 + version: 5.29.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + axios: + specifier: 1.12.0 + version: 1.12.0 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 + i18next: + specifier: ^23.16.4 + version: 23.16.4 + jotai: + specifier: ^2.10.1 + version: 2.10.1(@types/react@18.3.12)(react@18.3.1) + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 + lucide-react: + specifier: ^0.469.0 + version: 0.469.0(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-draggable: + specifier: ^4.4.6 + version: 4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-error-boundary: + specifier: ^4.1.2 + version: 4.1.2(react@18.3.1) + react-helmet-async: + specifier: ^2.0.5 + version: 2.0.5(react@18.3.1) + react-i18next: + specifier: ^14.1.3 + version: 14.1.3(i18next@23.16.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-responsive: + specifier: ^10.0.0 + version: 10.0.0(react@18.3.1) + react-router-dom: + specifier: ^6.27.0 + version: 6.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-simple-keyboard: + specifier: ^3.8.19 + version: 3.8.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + semver: + specifier: ^7.6.3 + version: 7.6.3 + vaul: + specifier: ^0.9.9 + version: 0.9.9(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + websocket: + specifier: ^1.0.35 + version: 1.0.35 + yocto-queue: + specifier: ^1.2.1 + version: 1.2.1 + devDependencies: + '@ianvs/prettier-plugin-sort-imports': + specifier: ^4.7.0 + version: 4.7.0(prettier@3.4.2) + '@types/crypto-js': + specifier: ^4.2.2 + version: 4.2.2 + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 + '@types/react': + specifier: ^18.3.12 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.1 + '@types/semver': + specifier: ^7.5.8 + version: 7.5.8 + '@types/websocket': + specifier: ^1.0.10 + version: 1.0.10 + '@typescript-eslint/eslint-plugin': + specifier: ^8.48.0 + version: 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3) + '@typescript-eslint/parser': + specifier: ^8.48.0 + version: 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3) + '@vitejs/plugin-react': + specifier: ^4.7.0 + version: 4.7.0(vite@7.1.11(@types/node@22.9.0)(jiti@1.21.7)(yaml@2.6.0)) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.4.47) + eslint: + specifier: ^9.39.1 + version: 9.39.1(jiti@1.21.7) + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-react: + specifier: ^7.37.2 + version: 7.37.2(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-react-hooks: + specifier: ^5.1.0 + version: 5.1.0(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-react-refresh: + specifier: ^0.4.16 + version: 0.4.16(eslint@9.39.1(jiti@1.21.7)) + msw: + specifier: ^2.6.0 + version: 2.6.0(@types/node@22.9.0)(typescript@5.6.3) + postcss: + specifier: ^8.4.47 + version: 8.4.47 + prettier: + specifier: ^3.4.2 + version: 3.4.2 + prettier-plugin-tailwindcss: + specifier: ^0.6.9 + version: 0.6.9(@ianvs/prettier-plugin-sort-imports@4.7.0(prettier@3.4.2))(prettier@3.4.2) + tailwindcss: + specifier: ^3.4.18 + version: 3.4.18(yaml@2.6.0) + typescript: + specifier: ^5.6.3 + version: 5.6.3 + vite: + specifier: 7.1.11 + version: 7.1.11(@types/node@22.9.0)(jiti@1.21.7)(yaml@2.6.0) + vite-tsconfig-paths: + specifier: ^4.3.2 + version: 4.3.2(typescript@5.6.3)(vite@7.1.11(@types/node@22.9.0)(jiti@1.21.7)(yaml@2.6.0)) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ant-design/colors@7.1.0': + resolution: {integrity: sha512-MMoDGWn1y9LdQJQSHiCC20x3uZ3CwQnv9QMz6pCmJOrqdgM9YxsoVVY0wtrdXbmfSgnV0KNk6zi09NAhMR2jvg==} + + '@ant-design/colors@7.2.1': + resolution: {integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==} + + '@ant-design/cssinjs-utils@1.1.3': + resolution: {integrity: sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@ant-design/cssinjs@1.24.0': + resolution: {integrity: sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@2.0.6': + resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==} + engines: {node: '>=8.x'} + + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} + + '@ant-design/icons@5.6.1': + resolution: {integrity: sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/react-slick@1.1.2': + resolution: {integrity: sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==} + peerDependencies: + react: '>=16.9.0' + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.2': + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.25.9': + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@bundled-es-modules/cookie@2.0.1': + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@ianvs/prettier-plugin-sort-imports@4.7.0': + resolution: {integrity: sha512-soa2bPUJAFruLL4z/CnMfSEKGznm5ebz29fIa9PxYtu8HHyLKNE1NXAs6dylfw1jn/ilEIfO2oLLN6uAafb7DA==} + peerDependencies: + '@prettier/plugin-oxc': ^0.0.4 + '@vue/compiler-sfc': 2.7.x || 3.x + content-tag: ^4.0.0 + prettier: 2 || 3 || ^4.0.0-0 + prettier-plugin-ember-template-tag: ^2.1.0 + peerDependenciesMeta: + '@prettier/plugin-oxc': + optional: true + '@vue/compiler-sfc': + optional: true + content-tag: + optional: true + prettier-plugin-ember-template-tag: + optional: true + + '@inquirer/confirm@5.0.1': + resolution: {integrity: sha512-6ycMm7k7NUApiMGfVc32yIPp28iPKxhGRMqoNDiUjq2RyTAkbs5Fx0TdzBqhabcKvniDdAAvHCmsRjnNfTsogw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/core@10.0.1': + resolution: {integrity: sha512-KKTgjViBQUi3AAssqjUFMnMO3CM3qwCHvePV9EW+zTKGKafFGFF01sc1yOIYjLJ7QU52G/FbzKc+c01WLzXmVQ==} + engines: {node: '>=18'} + + '@inquirer/figures@1.0.7': + resolution: {integrity: sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.0': + resolution: {integrity: sha512-YYykfbw/lefC7yKj7nanzQXILM7r3suIvyFlCcMskc99axmsSewXWkAfXKwMbgxL76iAFVmRwmYdwNZNc8gjog==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mswjs/interceptors@0.36.9': + resolution: {integrity: sha512-mMRDUBwSNeCgjSMEWfjoh4Rm9fbyZ7xQ9SBq8eGHiiyRn1ieTip3pNEt0wxWVPPxR4i1Rv9bTkeEbkX7M4c15A==} + engines: {node: '>=18'} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@radix-ui/primitive@1.1.0': + resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + + '@radix-ui/react-compose-refs@1.1.0': + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.1': + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.2': + resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.1': + resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.1': + resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.0': + resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.0': + resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-portal@1.1.2': + resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.1': + resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.0.0': + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.1.0': + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.0': + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.0': + resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.0': + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@rc-component/async-validator@5.0.4': + resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==} + engines: {node: '>=14.x'} + + '@rc-component/color-picker@2.0.1': + resolution: {integrity: sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@1.4.0': + resolution: {integrity: sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.0': + resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==} + engines: {node: '>=8.x'} + + '@rc-component/mutate-observer@1.1.0': + resolution: {integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/portal@1.1.2': + resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@1.1.0': + resolution: {integrity: sha512-ABA80Yer0c6I2+moqNY0kF3Y1NxIT6wDP/EINIqbiRbfZKP1HtHpKMh8WuTXLgVGYsoWG2g9/n0PgM8KdnJb4Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tour@1.15.1': + resolution: {integrity: sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/trigger@2.3.0': + resolution: {integrity: sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@remix-run/router@1.20.0': + resolution: {integrity: sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==} + engines: {node: '>=14.0.0'} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + cpu: [x64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/crypto-js@4.2.2': + resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.9.0': + resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} + + '@types/prop-types@15.7.13': + resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + + '@types/react-dom@18.3.1': + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} + + '@types/react@18.3.12': + resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + + '@types/statuses@2.0.5': + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/websocket@1.0.10': + resolution: {integrity: sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==} + + '@typescript-eslint/eslint-plugin@8.48.0': + resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.48.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.48.0': + resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.48.0': + resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.48.0': + resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.48.0': + resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.48.0': + resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.48.0': + resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.48.0': + resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.48.0': + resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.48.0': + resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@xterm/addon-attach@0.11.0': + resolution: {integrity: sha512-JboCN0QAY6ZLY/SSB/Zl2cQ5zW1Eh4X3fH7BnuR1NB7xGRhzbqU2Npmpiw/3zFlxDaU88vtKzok44JKi2L2V2Q==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/addon-fit@0.10.0': + resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/xterm@5.5.0': + resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + antd@5.29.1: + resolution: {integrity: sha512-TTFVbpKbyL6cPfEoKq6Ya3BIjTUr7uDW9+7Z+1oysRv1gpcN7kQ4luH8r/+rXXwz4n6BIz1iBJ1ezKCdsdNW0w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@1.12.0: + resolution: {integrity: sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.8.31: + resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bufferutil@4.0.8: + resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} + engines: {node: '>=6.14.2'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001756: + resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==} + + caniuse-lite@1.0.30001757: + resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + compute-scroll-into-view@3.1.0: + resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + + css-mediaquery@0.1.2: + resolution: {integrity: sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.260: + resolution: {integrity: sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==} + + electron-to-chromium@1.5.51: + resolution: {integrity: sha512-kKeWV57KSS8jH4alKt/jKnvHPmJgBxXzGUSbMd4eQF+iOsVPl7bz2KUmu6eo80eMP8wVioTfTyTzdMgM15WXNg==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.0: + resolution: {integrity: sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-react-hooks@5.1.0: + resolution: {integrity: sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.16: + resolution: {integrity: sha512-slterMlxAhov/DZO8NScf6mEeMBBXodFUolijDvrtTxyezyLoTQaa73FyYus/VbTdftd8wBgBxPMRk3poleXNQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-plugin-react@7.37.2: + resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + graphql@16.9.0: + resolution: {integrity: sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + + hyphenate-style-name@1.1.0: + resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} + + i18next@23.16.4: + resolution: {integrity: sha512-9NIYBVy9cs4wIqzurf7nLXPyf3R78xYbxExVqHLK9od3038rjpyOEzW+XB130kZ1N4PZ9inTtJ471CRJ4Ituyg==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.3: + resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} + engines: {node: '>= 0.4'} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + jotai@2.10.1: + resolution: {integrity: sha512-4FycO+BOTl2auLyF2Chvi6KTDqdsdDDtpaL/WHQMs8f3KS1E3loiUShQzAzFA/sMU5cJ0hz/RT1xum9YbG/zaA==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json2mq@0.2.0: + resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.469.0: + resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + matchmediaquery@0.4.2: + resolution: {integrity: sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msw@2.6.0: + resolution: {integrity: sha512-n3tx2w0MZ3H4pxY0ozrQ4sNPzK/dGtlr2cIIyuEsgq2Bhy4wvcW6ZH2w/gXM9+MEUY6HC1fWhqtcXDxVZr5Jxw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + + node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} + hasBin: true + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + engines: {node: '>= 0.4'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-tailwindcss@0.6.9: + resolution: {integrity: sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==} + engines: {node: '>=14.21.3'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig-melody': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-import-sort: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-style-order: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig-melody': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-import-sort: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-style-order: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.4.2: + resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} + engines: {node: '>=14'} + hasBin: true + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + rc-cascader@3.34.0: + resolution: {integrity: sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-checkbox@3.5.0: + resolution: {integrity: sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-collapse@3.9.0: + resolution: {integrity: sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dialog@9.6.0: + resolution: {integrity: sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-drawer@7.3.0: + resolution: {integrity: sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dropdown@4.2.1: + resolution: {integrity: sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + + rc-field-form@2.7.1: + resolution: {integrity: sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-image@7.12.0: + resolution: {integrity: sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input-number@9.5.0: + resolution: {integrity: sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input@1.8.0: + resolution: {integrity: sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-mentions@2.20.0: + resolution: {integrity: sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-menu@9.16.1: + resolution: {integrity: sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-motion@2.9.5: + resolution: {integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-notification@5.6.4: + resolution: {integrity: sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-overflow@1.5.0: + resolution: {integrity: sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-pagination@5.1.0: + resolution: {integrity: sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-picker@4.11.3: + resolution: {integrity: sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==} + engines: {node: '>=8.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + rc-progress@4.0.0: + resolution: {integrity: sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-rate@2.13.1: + resolution: {integrity: sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-resize-observer@1.4.3: + resolution: {integrity: sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-segmented@2.7.0: + resolution: {integrity: sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-select@14.16.8: + resolution: {integrity: sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-slider@11.1.9: + resolution: {integrity: sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-steps@6.0.1: + resolution: {integrity: sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-switch@4.1.0: + resolution: {integrity: sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-table@7.54.0: + resolution: {integrity: sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tabs@15.7.0: + resolution: {integrity: sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-textarea@1.10.2: + resolution: {integrity: sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tooltip@6.4.0: + resolution: {integrity: sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tree-select@5.27.0: + resolution: {integrity: sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==} + peerDependencies: + react: '*' + react-dom: '*' + + rc-tree@5.13.1: + resolution: {integrity: sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-upload@4.11.0: + resolution: {integrity: sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-util@5.43.0: + resolution: {integrity: sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-util@5.44.4: + resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-virtual-list@3.19.2: + resolution: {integrity: sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-draggable@4.4.6: + resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==} + peerDependencies: + react: '>= 16.3.0' + react-dom: '>= 16.3.0' + + react-error-boundary@4.1.2: + resolution: {integrity: sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==} + peerDependencies: + react: '>=16.13.1' + + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + + react-helmet-async@2.0.5: + resolution: {integrity: sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==} + peerDependencies: + react: ^16.6.0 || ^17.0.0 || ^18.0.0 + + react-i18next@14.1.3: + resolution: {integrity: sha512-wZnpfunU6UIAiJ+bxwOiTmBOAaB14ha97MjOEnLGac2RJ+h/maIYXZuTHlmyqQVX1UVHmU1YDTQ5vxLmwfXTjw==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.6: + resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.6.0: + resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-responsive@10.0.0: + resolution: {integrity: sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==} + engines: {node: '>=14'} + peerDependencies: + react: '>=16.8.0' + + react-router-dom@6.27.0: + resolution: {integrity: sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.27.0: + resolution: {integrity: sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react-simple-keyboard@3.8.19: + resolution: {integrity: sha512-A6CFaTLUC0atK4xEfFOoGqu6UXGaZYujPQZX9TudBDn8zXGYUpMELvqGQ8jDz8LfXPHkpeP0FOj1EzffrYuaSw==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + + react-style-singleton@2.2.1: + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + reflect.getprototypeof@1.0.6: + resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} + engines: {node: '>= 0.4'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexp.prototype.flags@1.5.3: + resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} + engines: {node: '>= 0.4'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} + + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + shallow-equal@3.1.0: + resolution: {integrity: sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==} + + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + string-convert@0.2.1: + resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string.prototype.matchall@4.0.11: + resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.18: + resolution: {integrity: sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsconfck@3.1.4: + resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.26.1: + resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} + engines: {node: '>=16'} + + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} + + typedarray-to-buffer@3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + use-callback-ref@1.3.2: + resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.2: + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + utf-8-validate@5.0.10: + resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} + engines: {node: '>=6.14.2'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vaul@0.9.9: + resolution: {integrity: sha512-7afKg48srluhZwIkaU+lgGtFCUsYBSGOl8vcc8N/M3YQlZFlynHD15AE+pwrYdc826o7nrIND4lL9Y6b9WWZZQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + vite-tsconfig-paths@4.3.2: + resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@7.1.11: + resolution: {integrity: sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + + websocket@1.0.35: + resolution: {integrity: sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==} + engines: {node: '>=4.0.0'} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-builtin-type@1.1.4: + resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaeti@0.0.6: + resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==} + engines: {node: '>=0.10.32'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.6.0: + resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.1: + resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} + engines: {node: '>=12.20'} + + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@ant-design/colors@7.1.0': + dependencies: + '@ctrl/tinycolor': 3.6.1 + + '@ant-design/colors@7.2.1': + dependencies: + '@ant-design/fast-color': 2.0.6 + + '@ant-design/cssinjs-utils@1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/cssinjs': 1.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime': 7.26.0 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@ant-design/cssinjs@1.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + classnames: 2.5.1 + csstype: 3.2.3 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + stylis: 4.3.6 + + '@ant-design/fast-color@2.0.6': + dependencies: + '@babel/runtime': 7.26.0 + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/colors': 7.1.0 + '@ant-design/icons-svg': 4.4.2 + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@ant-design/react-slick@1.1.2(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + json2mq: 0.2.0 + react: 18.3.1 + resize-observer-polyfill: 1.5.1 + throttle-debounce: 5.0.2 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.26.2': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/runtime@7.26.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bundled-es-modules/cookie@2.0.1': + dependencies: + cookie: 0.7.2 + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.1 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + + '@ctrl/tinycolor@3.6.1': {} + + '@emotion/hash@0.8.0': {} + + '@emotion/unitless@0.7.5': {} + + '@esbuild/aix-ppc64@0.25.0': + optional: true + + '@esbuild/android-arm64@0.25.0': + optional: true + + '@esbuild/android-arm@0.25.0': + optional: true + + '@esbuild/android-x64@0.25.0': + optional: true + + '@esbuild/darwin-arm64@0.25.0': + optional: true + + '@esbuild/darwin-x64@0.25.0': + optional: true + + '@esbuild/freebsd-arm64@0.25.0': + optional: true + + '@esbuild/freebsd-x64@0.25.0': + optional: true + + '@esbuild/linux-arm64@0.25.0': + optional: true + + '@esbuild/linux-arm@0.25.0': + optional: true + + '@esbuild/linux-ia32@0.25.0': + optional: true + + '@esbuild/linux-loong64@0.25.0': + optional: true + + '@esbuild/linux-mips64el@0.25.0': + optional: true + + '@esbuild/linux-ppc64@0.25.0': + optional: true + + '@esbuild/linux-riscv64@0.25.0': + optional: true + + '@esbuild/linux-s390x@0.25.0': + optional: true + + '@esbuild/linux-x64@0.25.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.0': + optional: true + + '@esbuild/netbsd-x64@0.25.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.0': + optional: true + + '@esbuild/openbsd-x64@0.25.0': + optional: true + + '@esbuild/sunos-x64@0.25.0': + optional: true + + '@esbuild/win32-arm64@0.25.0': + optional: true + + '@esbuild/win32-ia32@0.25.0': + optional: true + + '@esbuild/win32-x64@0.25.0': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@1.21.7))': + dependencies: + eslint: 9.39.1(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.3.7 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.1': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@ianvs/prettier-plugin-sort-imports@4.7.0(prettier@3.4.2)': + dependencies: + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + prettier: 3.4.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + '@inquirer/confirm@5.0.1(@types/node@22.9.0)': + dependencies: + '@inquirer/core': 10.0.1(@types/node@22.9.0) + '@inquirer/type': 3.0.0(@types/node@22.9.0) + '@types/node': 22.9.0 + + '@inquirer/core@10.0.1(@types/node@22.9.0)': + dependencies: + '@inquirer/figures': 1.0.7 + '@inquirer/type': 3.0.0(@types/node@22.9.0) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + transitivePeerDependencies: + - '@types/node' + + '@inquirer/figures@1.0.7': {} + + '@inquirer/type@3.0.0(@types/node@22.9.0)': + dependencies: + '@types/node': 22.9.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mswjs/interceptors@0.36.9': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@radix-ui/primitive@1.1.0': {} + + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@radix-ui/react-context@1.1.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/react-id@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/react-slot@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@rc-component/async-validator@5.0.4': + dependencies: + '@babel/runtime': 7.26.0 + + '@rc-component/color-picker@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/fast-color': 2.0.6 + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/context@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/mini-decimal@1.1.0': + dependencies: + '@babel/runtime': 7.26.0 + + '@rc-component/mutate-observer@1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/portal@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/qrcode@1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/tour@1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/trigger@2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@remix-run/router@1.20.0': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.53.3': + optional: true + + '@rollup/rollup-android-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-x64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.26.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.0 + + '@types/cookie@0.6.0': {} + + '@types/crypto-js@4.2.2': {} + + '@types/estree@1.0.6': {} + + '@types/estree@1.0.8': {} + + '@types/js-cookie@3.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@22.9.0': + dependencies: + undici-types: 6.19.8 + + '@types/prop-types@15.7.13': {} + + '@types/react-dom@18.3.1': + dependencies: + '@types/react': 18.3.12 + + '@types/react@18.3.12': + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + + '@types/semver@7.5.8': {} + + '@types/statuses@2.0.5': {} + + '@types/tough-cookie@4.0.5': {} + + '@types/websocket@1.0.10': + dependencies: + '@types/node': 22.9.0 + + '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.48.0 + eslint: 9.39.1(jiti@1.21.7) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.48.0 + debug: 4.3.7 + eslint: 9.39.1(jiti@1.21.7) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.48.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.6.3) + '@typescript-eslint/types': 8.48.0 + debug: 4.3.7 + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.48.0': + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 + + '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.6.3)': + dependencies: + typescript: 5.6.3 + + '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.6.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3) + debug: 4.4.3 + eslint: 9.39.1(jiti@1.21.7) + ts-api-utils: 2.1.0(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.48.0': {} + + '@typescript-eslint/typescript-estree@8.48.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/project-service': 8.48.0(typescript@5.6.3) + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.6.3) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 + debug: 4.3.7 + minimatch: 9.0.5 + semver: 7.6.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.6.3) + eslint: 9.39.1(jiti@1.21.7) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.48.0': + dependencies: + '@typescript-eslint/types': 8.48.0 + eslint-visitor-keys: 4.2.1 + + '@vitejs/plugin-react@4.7.0(vite@7.1.11(@types/node@22.9.0)(jiti@1.21.7)(yaml@2.6.0))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 7.1.11(@types/node@22.9.0)(jiti@1.21.7)(yaml@2.6.0) + transitivePeerDependencies: + - supports-color + + '@xterm/addon-attach@0.11.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/xterm@5.5.0': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + antd@5.29.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@ant-design/colors': 7.2.1 + '@ant-design/cssinjs': 1.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/cssinjs-utils': 1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/fast-color': 2.0.6 + '@ant-design/icons': 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/react-slick': 1.1.2(react@18.3.1) + '@babel/runtime': 7.26.0 + '@rc-component/color-picker': 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/mutate-observer': 1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/qrcode': 1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/tour': 1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + copy-to-clipboard: 3.3.3 + dayjs: 1.11.13 + rc-cascader: 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-checkbox: 3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-collapse: 3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-drawer: 7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-field-form: 2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-image: 7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-input-number: 9.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-mentions: 2.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-notification: 5.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-pagination: 5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-picker: 4.11.3(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-progress: 4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-rate: 2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-segmented: 2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-slider: 11.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-steps: 6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-switch: 4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-table: 7.54.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tabs: 15.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-textarea: 1.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tooltip: 6.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree-select: 5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-upload: 4.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-hidden@1.2.4: + dependencies: + tslib: 2.8.1 + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + is-string: 1.0.7 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.flat@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.flatmap@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 + + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + + asynckit@0.4.0: {} + + autoprefixer@10.4.20(postcss@8.4.47): + dependencies: + browserslist: 4.24.2 + caniuse-lite: 1.0.30001756 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 + + axios@1.12.0: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.8.31: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.2: + dependencies: + caniuse-lite: 1.0.30001756 + electron-to-chromium: 1.5.51 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.2) + + browserslist@4.28.0: + dependencies: + baseline-browser-mapping: 2.8.31 + caniuse-lite: 1.0.30001757 + electron-to-chromium: 1.5.260 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) + + bufferutil@4.0.8: + dependencies: + node-gyp-build: 4.8.2 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001756: {} + + caniuse-lite@1.0.30001757: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + classnames@2.5.1: {} + + cli-width@4.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@1.2.1: {} + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@4.1.1: {} + + compute-scroll-into-view@3.1.0: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cookie@0.7.2: {} + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-js@4.2.0: {} + + css-mediaquery@0.1.2: {} + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + csstype@3.2.3: {} + + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + dayjs@1.11.13: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + detect-node-es@1.1.0: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.260: {} + + electron-to-chromium@1.5.51: {} + + emoji-regex@8.0.0: {} + + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 + globalthis: 1.0.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.2 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.3 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-set-tostringtag: 2.0.3 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + iterator.prototype: 1.1.3 + safe-array-concat: 1.1.2 + + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + + esbuild@0.25.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.0(eslint@9.39.1(jiti@1.21.7)): + dependencies: + eslint: 9.39.1(jiti@1.21.7) + + eslint-plugin-react-hooks@5.1.0(eslint@9.39.1(jiti@1.21.7)): + dependencies: + eslint: 9.39.1(jiti@1.21.7) + + eslint-plugin-react-refresh@0.4.16(eslint@9.39.1(jiti@1.21.7)): + dependencies: + eslint: 9.39.1(jiti@1.21.7) + + eslint-plugin-react@7.37.2(eslint@9.39.1(jiti@1.21.7)): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.2 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.0 + eslint: 9.39.1(jiti@1.21.7) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + object.values: 1.2.0 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.11 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.1(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.6 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.3.7 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + + ext@1.7.0: + dependencies: + type: 2.7.3 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + + flatted@3.3.1: {} + + follow-redirects@1.15.9: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fraction.js@4.3.7: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.0.1 + + globrex@0.1.2: {} + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + + gopd@1.2.0: {} + + graphemer@1.4.0: {} + + graphql@16.9.0: {} + + has-bigints@1.0.2: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + headers-polyfill@4.0.3: {} + + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + + hyphenate-style-name@1.1.0: {} + + i18next@23.16.4: + dependencies: + '@babel/runtime': 7.26.0 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 + + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.1.2: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-node-process@1.2.0: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-regex@1.1.4: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-typedarray@1.0.0: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.3: + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + reflect.getprototypeof: 1.0.6 + set-function-name: 2.0.2 + + jiti@1.21.7: {} + + jotai@2.10.1(@types/react@18.3.12)(react@18.3.1): + optionalDependencies: + '@types/react': 18.3.12 + react: 18.3.1 + + js-cookie@3.0.5: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.0.2: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + + json5@2.2.3: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.2 + object.assign: 4.1.5 + object.values: 1.2.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.469.0(react@18.3.1): + dependencies: + react: 18.3.1 + + matchmediaquery@0.4.2: + dependencies: + css-mediaquery: 0.1.2 + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + ms@2.0.0: {} + + ms@2.1.3: {} + + msw@2.6.0(@types/node@22.9.0)(typescript@5.6.3): + dependencies: + '@bundled-es-modules/cookie': 2.0.1 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.0.1(@types/node@22.9.0) + '@mswjs/interceptors': 0.36.9 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 + chalk: 4.1.2 + graphql: 16.9.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + strict-event-emitter: 0.5.1 + type-fest: 4.26.1 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - '@types/node' + + mute-stream@2.0.0: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + nanoid@3.3.8: {} + + natural-compare@1.4.0: {} + + next-tick@1.1.0: {} + + node-gyp-build@4.8.2: {} + + node-releases@2.0.18: {} + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.2: {} + + object-keys@1.1.1: {} + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + object.entries@1.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + object.values@1.2.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + outvariant@1.4.3: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-to-regexp@6.3.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + possible-typed-array-names@1.0.0: {} + + postcss-import@15.1.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.4.47): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.47 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.4.47)(yaml@2.6.0): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.4.47 + yaml: 2.6.0 + + postcss-nested@6.2.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.47: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-plugin-tailwindcss@0.6.9(@ianvs/prettier-plugin-sort-imports@4.7.0(prettier@3.4.2))(prettier@3.4.2): + dependencies: + prettier: 3.4.2 + optionalDependencies: + '@ianvs/prettier-plugin-sort-imports': 4.7.0(prettier@3.4.2) + + prettier@3.4.2: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-from-env@1.1.0: {} + + psl@1.9.0: {} + + punycode@2.3.1: {} + + querystringify@2.2.0: {} + + queue-microtask@1.2.3: {} + + rc-cascader@3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-checkbox@3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-collapse@3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-dialog@9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-drawer@7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-dropdown@4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/async-validator': 5.0.4 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-image@7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-input-number@9.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/mini-decimal': 1.1.0 + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-input@1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-mentions@2.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-textarea: 1.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-menu@9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-motion@2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-notification@5.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-overflow@1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-pagination@5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-picker@4.11.3(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + dayjs: 1.11.13 + + rc-progress@4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-rate@2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-resize-observer@1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + resize-observer-polyfill: 1.5.1 + + rc-segmented@2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-select@14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-slider@11.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-steps@6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-switch@4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-table@7.54.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/context': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tabs@15.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-textarea@1.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tooltip@6.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tree-select@5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tree@5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-upload@4.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-util@5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + + rc-util@5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + + rc-virtual-list@3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-draggable@4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-error-boundary@4.1.2(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + react: 18.3.1 + + react-fast-compare@3.2.2: {} + + react-helmet-async@2.0.5(react@18.3.1): + dependencies: + invariant: 2.2.4 + react: 18.3.1 + react-fast-compare: 3.2.2 + shallowequal: 1.1.0 + + react-i18next@14.1.3(i18next@23.16.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + html-parse-stringify: 3.0.1 + i18next: 23.16.4 + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-refresh@0.17.0: {} + + react-remove-scroll-bar@2.3.6(@types/react@18.3.12)(react@18.3.1): + dependencies: + react: 18.3.1 + react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 + + react-remove-scroll@2.6.0(@types/react@18.3.12)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.6(@types/react@18.3.12)(react@18.3.1) + react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) + tslib: 2.8.1 + use-callback-ref: 1.3.2(@types/react@18.3.12)(react@18.3.1) + use-sidecar: 1.1.2(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + + react-responsive@10.0.0(react@18.3.1): + dependencies: + hyphenate-style-name: 1.1.0 + matchmediaquery: 0.4.2 + prop-types: 15.8.1 + react: 18.3.1 + shallow-equal: 3.1.0 + + react-router-dom@6.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.20.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.27.0(react@18.3.1) + + react-router@6.27.0(react@18.3.1): + dependencies: + '@remix-run/router': 1.20.0 + react: 18.3.1 + + react-simple-keyboard@3.8.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-style-singleton@2.2.1(@types/react@18.3.12)(react@18.3.1): + dependencies: + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + reflect.getprototypeof@1.0.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + which-builtin-type: 1.1.4 + + regenerator-runtime@0.14.1: {} + + regexp.prototype.flags@1.5.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + require-directory@2.1.1: {} + + requires-port@1.0.0: {} + + resize-observer-polyfill@1.5.1: {} + + resolve-from@4.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rollup@4.53.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.2: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 + + safe-regex-test@1.0.3: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.0 + + semver@6.3.1: {} + + semver@7.6.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + shallow-equal@3.1.0: {} + + shallowequal@1.1.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + statuses@2.0.1: {} + + strict-event-emitter@0.5.1: {} + + string-convert@0.2.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string.prototype.matchall@4.0.11: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + regexp.prototype.flags: 1.5.3 + set-function-name: 2.0.2 + side-channel: 1.0.6 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.3 + + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@3.1.1: {} + + stylis@4.3.6: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.18(yaml@2.6.0): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.4.47 + postcss-import: 15.1.0(postcss@8.4.47) + postcss-js: 4.1.0(postcss@8.4.47) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.4.47)(yaml@2.6.0) + postcss-nested: 6.2.0(postcss@8.4.47) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + throttle-debounce@5.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toggle-selection@1.0.6: {} + + tough-cookie@4.1.4: + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + ts-api-utils@2.1.0(typescript@5.6.3): + dependencies: + typescript: 5.6.3 + + ts-interface-checker@0.1.13: {} + + tsconfck@3.1.4(typescript@5.6.3): + optionalDependencies: + typescript: 5.6.3 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.21.3: {} + + type-fest@4.26.1: {} + + type@2.7.3: {} + + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + + typedarray-to-buffer@3.1.5: + dependencies: + is-typedarray: 1.0.0 + + typescript@5.6.3: {} + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + + undici-types@6.19.8: {} + + universalify@0.2.0: {} + + update-browserslist-db@1.1.1(browserslist@4.24.2): + dependencies: + browserslist: 4.24.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + update-browserslist-db@1.1.4(browserslist@4.28.0): + dependencies: + browserslist: 4.28.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + use-callback-ref@1.3.2(@types/react@18.3.12)(react@18.3.1): + dependencies: + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 + + use-sidecar@1.1.2(@types/react@18.3.12)(react@18.3.1): + dependencies: + detect-node-es: 1.1.0 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 + + utf-8-validate@5.0.10: + dependencies: + node-gyp-build: 4.8.2 + + util-deprecate@1.0.2: {} + + vaul@0.9.9(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@7.1.11(@types/node@22.9.0)(jiti@1.21.7)(yaml@2.6.0)): + dependencies: + debug: 4.3.7 + globrex: 0.1.2 + tsconfck: 3.1.4(typescript@5.6.3) + optionalDependencies: + vite: 7.1.11(@types/node@22.9.0)(jiti@1.21.7)(yaml@2.6.0) + transitivePeerDependencies: + - supports-color + - typescript + + vite@7.1.11(@types/node@22.9.0)(jiti@1.21.7)(yaml@2.6.0): + dependencies: + esbuild: 0.25.0 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.9.0 + fsevents: 2.3.3 + jiti: 1.21.7 + yaml: 2.6.0 + + void-elements@3.1.0: {} + + websocket@1.0.35: + dependencies: + bufferutil: 4.0.8 + debug: 2.6.9 + es5-ext: 0.10.64 + typedarray-to-buffer: 3.1.5 + utf-8-validate: 5.0.10 + yaeti: 0.0.6 + transitivePeerDependencies: + - supports-color + + which-boxed-primitive@1.0.2: + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + which-builtin-type@1.1.4: + dependencies: + function.prototype.name: 1.1.6 + has-tostringtag: 1.0.2 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.0.2 + is-generator-function: 1.0.10 + is-regex: 1.1.4 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.15 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.3 + + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + y18n@5.0.8: {} + + yaeti@0.0.6: {} + + yallist@3.1.1: {} + + yaml@2.6.0: + optional: true + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + yocto-queue@1.2.1: {} + + yoctocolors-cjs@2.1.2: {} diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/web/public/mockServiceWorker.js b/web/public/mockServiceWorker.js new file mode 100644 index 0000000..6eb8052 --- /dev/null +++ b/web/public/mockServiceWorker.js @@ -0,0 +1,293 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.6.0' +const INTEGRITY_CHECKSUM = '07a8241b182f8a246a7cd39894799a9e' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + const headers = Object.fromEntries(requestClone.headers.entries()) + + // Remove internal MSW request header so the passthrough request + // complies with any potential CORS preflight checks on the server. + // Some servers forbid unknown request headers. + delete headers['x-msw-intention'] + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/web/public/sipeed.ico b/web/public/sipeed.ico new file mode 100644 index 0000000..4a768a2 Binary files /dev/null and b/web/public/sipeed.ico differ diff --git a/web/src/api/application.ts b/web/src/api/application.ts new file mode 100644 index 0000000..f3192ac --- /dev/null +++ b/web/src/api/application.ts @@ -0,0 +1,29 @@ +import { http } from '@/lib/http.ts'; + +// get application version +export function getVersion() { + return http.get('/api/application/version'); +} + +// update application to latest version +export function update() { + return http.request({ + method: 'post', + url: '/api/application/update', + timeout: 15 * 60 * 1000 + }); +} + +// enable/disable preview updates +export function setPreviewUpdates(enable: boolean) { + const data = { + enable + }; + return http.post('/api/application/preview', data); +} + +// get preview updates state +export function getPreviewUpdates() { + return http.get('/api/application/preview'); +} + diff --git a/web/src/api/auth.ts b/web/src/api/auth.ts new file mode 100644 index 0000000..3657ce4 --- /dev/null +++ b/web/src/api/auth.ts @@ -0,0 +1,29 @@ +import { http } from '@/lib/http'; + +export function login(username: string, password: string) { + const data = { + username, + password + }; + return http.post('/api/auth/login', data); +} + +export function logout() { + return http.post('/api/auth/logout'); +} + +export function getAccount() { + return http.get('/api/auth/account'); +} + +export function changePassword(username: string, password: string) { + const data = { + username, + password + }; + return http.post('/api/auth/password', data); +} + +export function isPasswordUpdated() { + return http.get('/api/auth/password'); +} diff --git a/web/src/api/download.ts b/web/src/api/download.ts new file mode 100644 index 0000000..6ae3cf5 --- /dev/null +++ b/web/src/api/download.ts @@ -0,0 +1,17 @@ +import { http } from '@/lib/http.ts'; + +// Download image +export function downloadImage(file?: string) { + const data = { + file: file ? file : '' + }; + return http.post('/api/download/image', data); +} + +export function statusImage() { + return http.get('/api/download/image/status'); +} + +export function imageEnabled() { + return http.get('/api/download/image/enabled'); +} diff --git a/web/src/api/extensions/tailscale.ts b/web/src/api/extensions/tailscale.ts new file mode 100644 index 0000000..556a0b9 --- /dev/null +++ b/web/src/api/extensions/tailscale.ts @@ -0,0 +1,51 @@ +import { http } from '@/lib/http.ts'; + +// install tailscale +export function install() { + return http.post('/api/extensions/tailscale/install'); +} + +// uninstall tailscale +export function uninstall() { + return http.post('/api/extensions/tailscale/uninstall'); +} + +// get tailscale status +export function getStatus() { + return http.get('/api/extensions/tailscale/status'); +} + +// start tailscale +export function start() { + return http.post('/api/extensions/tailscale/start'); +} + +// restart tailscale +export function restart() { + return http.post('/api/extensions/tailscale/restart'); +} + +// stop tailscale +export function stop() { + return http.post('/api/extensions/tailscale/stop'); +} + +// run tailscale up +export function up() { + return http.post('/api/extensions/tailscale/up'); +} + +// run tailscale down +export function down() { + return http.post('/api/extensions/tailscale/down'); +} + +// login tailscale +export function login() { + return http.post('/api/extensions/tailscale/login'); +} + +// logout tailscale +export function logout() { + return http.post('/api/extensions/tailscale/logout'); +} diff --git a/web/src/api/hid.ts b/web/src/api/hid.ts new file mode 100644 index 0000000..40feaee --- /dev/null +++ b/web/src/api/hid.ts @@ -0,0 +1,24 @@ +import { http } from '@/lib/http.ts'; + +// paste +export function paste(content: string, langue: string) { + return http.post('/api/hid/paste', { content, langue }); +} + +// reset hid +export function reset() { + return http.post('/api/hid/reset'); +} + +// get hid mode +export function getHidMode() { + return http.get('/api/hid/mode'); +} + +// set hid mode +export function setHidMode(mode: string) { + const data = { + mode + }; + return http.post('/api/hid/mode', data); +} diff --git a/web/src/api/network.ts b/web/src/api/network.ts new file mode 100644 index 0000000..f91048c --- /dev/null +++ b/web/src/api/network.ts @@ -0,0 +1,41 @@ +import { http } from '@/lib/http.ts'; + +// wake on lan +export function wol(mac: string) { + const data = { + mac + }; + return http.post('/api/network/wol', data); +} + +// get wake-on-lan macs history +export function getWolMacs() { + return http.get('/api/network/wol/mac'); +} + +export function deleteWolMac(mac: string) { + return http.request({ + method: 'delete', + url: '/api/network/wol/mac', + data: { mac } + }); +} + +// set Mac name +export function setWolMacName(mac: string, name: string) { + return http.post('/api/network/wol/mac/name', { mac, name }); +} + +// get wifi information +export function getWiFi() { + return http.get('/api/network/wifi'); +} + +// connect wifi +export function connectWifi(ssid: string, password: string) { + const data = { + ssid, + password + }; + return http.post('/api/network/wifi', data); +} diff --git a/web/src/api/script.ts b/web/src/api/script.ts new file mode 100644 index 0000000..a441f3c --- /dev/null +++ b/web/src/api/script.ts @@ -0,0 +1,28 @@ +import { http } from '@/lib/http.ts'; + +export function uploadScript(formData: FormData) { + return http.request({ + url: '/api/vm/script/upload', + method: 'post', + headers: { + 'Content-Type': 'multipart/form-data' + }, + data: formData + }); +} + +export function runScript(name: string, type: string) { + return http.post('/api/vm/script/run', { name, type }); +} + +export function getScripts() { + return http.get('/api/vm/script'); +} + +export function deleteScript(name: string) { + return http.request({ + url: '/api/vm/script', + method: 'delete', + data: { name } + }); +} diff --git a/web/src/api/storage.ts b/web/src/api/storage.ts new file mode 100644 index 0000000..250affe --- /dev/null +++ b/web/src/api/storage.ts @@ -0,0 +1,32 @@ +import { http } from '@/lib/http.ts'; + +// get image list +export function getImages() { + return http.get('/api/storage/image'); +} + +// get mounted image +export function getMountedImage() { + return http.get('/api/storage/image/mounted'); +} + +// mount/unmount image +export function mountImage(file?: string, cdrom?: boolean) { + const data = { + file: file ? file : '', + cdrom: cdrom + }; + return http.post('/api/storage/image/mount', data); +} + +// get CD-ROM flag +export function getCdRom() { + return http.get('/api/storage/cdrom'); +} + +export function deleteImage(file: string) { + const data = { + file + }; + return http.post('/api/storage/image/delete', data); +} diff --git a/web/src/api/stream.ts b/web/src/api/stream.ts new file mode 100644 index 0000000..227e55f --- /dev/null +++ b/web/src/api/stream.ts @@ -0,0 +1,17 @@ +import { http } from '@/lib/http.ts'; + +// enable/disable frame detect +export function updateFrameDetect(enabled: boolean) { + const data = { + enabled + }; + return http.post('/api/stream/mjpeg/detect', data); +} + +// pause frame detect for a while (prevent a black screen when opening the page for the first time) +export function stopFrameDetect(duration: number) { + const data = { + duration + }; + return http.post('/api/stream/mjpeg/detect/stop', data); +} diff --git a/web/src/api/virtual-device.ts b/web/src/api/virtual-device.ts new file mode 100644 index 0000000..5d4719a --- /dev/null +++ b/web/src/api/virtual-device.ts @@ -0,0 +1,15 @@ +import { http } from '@/lib/http.ts'; + +// get virtual devices status +export function getVirtualDevice() { + return http.get('/api/vm/device/virtual'); +} + +// mount/unmount virtual device +export function updateVirtualDevice(device: string) { + const data = { + device + }; + + return http.post('/api/vm/device/virtual', data); +} diff --git a/web/src/api/vm.ts b/web/src/api/vm.ts new file mode 100644 index 0000000..7c1f015 --- /dev/null +++ b/web/src/api/vm.ts @@ -0,0 +1,166 @@ +import { http } from '@/lib/http.ts'; + +// get NanoKVM information +export function getInfo() { + return http.get('/api/vm/info'); +} + +// get hardware information +export function getHardware() { + return http.get('/api/vm/hardware'); +} + +// set gpio value +export function setGpio(type: string, duration: number) { + const data = { + type, + duration + }; + return http.post('/api/vm/gpio', data); +} + +// get gpio value +export function getGpio() { + return http.get('/api/vm/gpio'); +} + +// update screen arguments +export function updateScreen(type: string, value: number) { + const data = { + type, + value + }; + return http.post('/api/vm/screen', data); +} + +// get memory limit +export function getMemoryLimit() { + return http.get('/api/vm/memory/limit'); +} + +// set memory limit +export function setMemoryLimit(enabled: boolean, limit: number) { + const data = { + enabled, + limit + }; + return http.post('/api/vm/memory/limit', data); +} + +// get OLED configuration +export function getOLED() { + return http.get('/api/vm/oled'); +} + +// set OLED configuration +export function setOLED(sleep: number) { + return http.post('/api/vm/oled', { sleep }); +} + +// reset HDMI +export function resetHdmi() { + return http.post('/api/vm/hdmi/reset'); +} + +// get HDMI state +export function getHdmiState() { + return http.get('/api/vm/hdmi'); +} + +// enable HDMI +export function enableHdmi() { + return http.post('/api/vm/hdmi/enable'); +} + +// disable HDMI +export function disableHdmi() { + return http.post('/api/vm/hdmi/disable'); +} + +// set HDMI state +export function setHdmiState(enabled: boolean) { + if (enabled) { + return enableHdmi(); + } + return disableHdmi(); +} + +// get SSH state +export function getSSHState() { + return http.get('/api/vm/ssh'); +} + +// enable SSH +export function enableSSH() { + return http.post('/api/vm/ssh/enable'); +} + +// disable SSH +export function disableSSH() { + return http.post('/api/vm/ssh/disable'); +} + +// get swap file size +export function getSwap() { + return http.get('/api/vm/swap'); +} + +// set swap file size +export function setSwap(size: number) { + return http.post('/api/vm/swap', { size }); +} + +// get mouse jiggler +export function getMouseJiggler() { + return http.get('/api/vm/mouse-jiggler'); +} + +// set mouse jiggler +export function setMouseJiggler(enabled: boolean, mode: string) { + return http.post('/api/vm/mouse-jiggler', { enabled, mode }); +} + +// get Hostname +export function getHostname() { + return http.get('/api/vm/hostname'); +} + +// set Hostname +export function setHostname(hostname: string) { + return http.post('/api/vm/hostname', { hostname }); +} + +// get WebTitle +export function getWebTitle() { + return http.get('/api/vm/web-title'); +} + +// set WebTitle +export function setWebTitle(title: string) { + return http.post('/api/vm/web-title', { title }); +} + +// get mDNS state +export function getMdnsState() { + return http.get('/api/vm/mdns'); +} + +// enable mDNS +export function enableMdns() { + return http.post('/api/vm/mdns/enable'); +} + +// disable mDNS +export function disableMdns() { + return http.post('/api/vm/mdns/disable'); +} + +// enable / disable TLS +export function setTLS(enabled: boolean) { + return http.post('/api/vm/tls', { enabled }); +} + +// reboot +export function reboot() { + return http.post('/api/vm/system/reboot'); +} diff --git a/web/src/assets/images/monitor-x.svg b/web/src/assets/images/monitor-x.svg new file mode 100644 index 0000000..c2597f9 --- /dev/null +++ b/web/src/assets/images/monitor-x.svg @@ -0,0 +1 @@ + diff --git a/web/src/assets/images/tailscale.svg b/web/src/assets/images/tailscale.svg new file mode 100644 index 0000000..968323b --- /dev/null +++ b/web/src/assets/images/tailscale.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/web/src/assets/styles/index.css b/web/src/assets/styles/index.css new file mode 100644 index 0000000..ade9c50 --- /dev/null +++ b/web/src/assets/styles/index.css @@ -0,0 +1,36 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, body { + padding: 0; + margin: 0; + background: #000; +} + +::-webkit-scrollbar { + width: 10px; +} +::-webkit-scrollbar-track { + background: #000000; +} +::-webkit-scrollbar-thumb { + background: #555; + border-radius: 5px; +} +::-webkit-scrollbar-thumb:hover { + background: #444; +} + +.spin { +animation: spin 1s linear; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/web/src/assets/styles/keyboard.css b/web/src/assets/styles/keyboard.css new file mode 100644 index 0000000..e8f9060 --- /dev/null +++ b/web/src/assets/styles/keyboard.css @@ -0,0 +1,109 @@ +.keyboardContainer { + display: flex; + background-color: #e5e5e5; + justify-content: center; + margin: 0 auto; + border-radius: 0 5px; +} + +.simple-keyboard.hg-theme-default { + display: inline-block; +} + +.simple-keyboard-main.simple-keyboard { + width: 640px; + min-width: 640px; + background: none; +} + +.simple-keyboard-main.simple-keyboard .hg-button { + box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); +} + +.simple-keyboard-main.simple-keyboard .hg-row:first-child { + margin-bottom: 10px; +} + +.simple-keyboard-arrows.simple-keyboard { + align-self: flex-end; + background: none; +} + +.simple-keyboard .hg-button.selectedButton { + background: rgba(5, 25, 70, 0.53); + color: white; +} + +.simple-keyboard .hg-button.emptySpace { + pointer-events: none; + background: none; + border: none; + box-shadow: none; +} + +.simple-keyboard-arrows .hg-row { + justify-content: center; +} + +.simple-keyboard-arrows .hg-button { + width: 50px; + flex-grow: 0; + justify-content: center; + display: flex; + align-items: center; + box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); +} + +.controlArrows { + display: flex; + align-items: center; + justify-content: space-between; + flex-flow: column; +} + +.simple-keyboard-control.simple-keyboard { + background: none; +} + +.simple-keyboard-control.simple-keyboard .hg-row:first-child { + margin-bottom: 10px; +} + +.simple-keyboard-control .hg-button { + width: 50px; + flex-grow: 0; + justify-content: center; + display: flex; + align-items: center; + font-size: 14px; + box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); +} + +.hg-button.hg-functionBtn.hg-button-space { + width: 250px; +} + +.hg-layout-mac .hg-button.hg-functionBtn.hg-button-space { + width: 350px; +} + +.simple-keyboard .hg-highlight { + background: rgb(37 99 235); + border-bottom: 1px solid #2563eb; + box-shadow: 0 1px 3px 0 rgb(37 99 235 / 0.1), 0 1px 2px -1px rgb(37 99 235 / 0.1); + color: white; +} + +.simple-keyboard .hg-button.hg-double{ + text-align: center; + font-size: 14px; + line-height: 16px; +} + +.keyboard-header { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +.hg-candidate-box { + display: none !important; +} diff --git a/web/src/components/auth.tsx b/web/src/components/auth.tsx new file mode 100644 index 0000000..4b2422e --- /dev/null +++ b/web/src/components/auth.tsx @@ -0,0 +1,14 @@ +import { ReactNode } from 'react'; +import { Navigate } from 'react-router-dom'; + +import { existToken } from '@/lib/cookie.ts'; + +export const ProtectedRoute = ({ children }: { children: ReactNode }) => { + const hasToken = existToken(); + + if (!hasToken) { + return ; + } + + return children; +}; diff --git a/web/src/components/head.tsx b/web/src/components/head.tsx new file mode 100644 index 0000000..1672af0 --- /dev/null +++ b/web/src/components/head.tsx @@ -0,0 +1,38 @@ +import { useEffect } from 'react'; +import { useAtom } from 'jotai'; +import { Helmet, HelmetData } from 'react-helmet-async'; + +import { getWebTitle } from '@/api/vm.ts'; +import { existToken } from '@/lib/cookie.ts'; +import { webTitleAtom } from '@/jotai/settings.ts'; + +type HeadProps = { + title?: string; + description?: string; +}; + +const helmetData = new HelmetData({}); + +export const Head = ({ title = '', description = '' }: HeadProps = {}) => { + const [webTitle, setWebTitle] = useAtom(webTitleAtom); + + useEffect(() => { + if (!existToken()) return; + + getWebTitle().then((rsp) => { + if (rsp.data?.title) { + setWebTitle(rsp.data.title); + } + }); + }, []); + + return ( + + + + ); +}; diff --git a/web/src/components/icons/tailscale.tsx b/web/src/components/icons/tailscale.tsx new file mode 100644 index 0000000..28dcec1 --- /dev/null +++ b/web/src/components/icons/tailscale.tsx @@ -0,0 +1,5 @@ +import icon from '@/assets/images/tailscale.svg'; + +export const Tailscale = () => { + return tailscale; +}; diff --git a/web/src/components/main-error.tsx b/web/src/components/main-error.tsx new file mode 100644 index 0000000..f89a2c1 --- /dev/null +++ b/web/src/components/main-error.tsx @@ -0,0 +1,18 @@ +import { Button } from 'antd'; +import { useTranslation } from 'react-i18next'; + +export const MainError = () => { + const { t } = useTranslation(); + + return ( +
+

{t('error.title')}

+ +
+ ); +}; diff --git a/web/src/components/menu-item.tsx b/web/src/components/menu-item.tsx new file mode 100644 index 0000000..299c7cb --- /dev/null +++ b/web/src/components/menu-item.tsx @@ -0,0 +1,72 @@ +import { ReactNode, useState } from 'react'; +import { Popover, Tooltip } from 'antd'; +import { useMediaQuery } from 'react-responsive'; + +type MenuItemProps = { + title: string; + icon: ReactNode; + content: ReactNode; + className?: string; + fresh?: boolean; + onOpenChange?: (open: boolean) => void; +}; + +export const MenuItem = ({ + title, + icon, + content, + className, + fresh, + onOpenChange +}: MenuItemProps) => { + const isBigScreen = useMediaQuery({ minWidth: 640 }); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [isTooltipOpen, setIsTooltipOpen] = useState(false); + + function togglePopover(open: boolean) { + setIsTooltipOpen(false); + setIsPopoverOpen(open); + + if (onOpenChange) { + onOpenChange(open); + } + } + + function toggleTooltip(open: boolean) { + if (isPopoverOpen) { + return; + } + setIsTooltipOpen(open); + } + + return ( + + +
+ {icon} +
+
+
+ ); +}; diff --git a/web/src/components/root.tsx b/web/src/components/root.tsx new file mode 100644 index 0000000..a51105a --- /dev/null +++ b/web/src/components/root.tsx @@ -0,0 +1,9 @@ +import { Outlet } from 'react-router-dom'; + +export const Root = () => { + return ( +
+ +
+ ); +}; diff --git a/web/src/i18n/README.md b/web/src/i18n/README.md new file mode 100644 index 0000000..765b789 --- /dev/null +++ b/web/src/i18n/README.md @@ -0,0 +1,4 @@ +# How to add a language + +1. Add a language file in i18n/locales folder (for example: en.ts). +2. Add language key and name in i18n/languages.ts (for example: { key: 'en', name: 'English' }). diff --git a/web/src/i18n/index.ts b/web/src/i18n/index.ts new file mode 100644 index 0000000..55d5d25 --- /dev/null +++ b/web/src/i18n/index.ts @@ -0,0 +1,53 @@ +import i18n from 'i18next'; +import type { Resource } from 'i18next'; +import { initReactI18next } from 'react-i18next'; + +import { getLanguage } from '@/lib/localstorage.ts'; + +function getResources(): Resource { + const resources: Resource = {}; + + const modules: Record = import.meta.glob('./locales/*.ts', { eager: true }); + + for (const path in modules) { + const moduleName = path.split('/').pop()?.replace('.ts', ''); + if (moduleName) { + resources[moduleName] = modules[path].default; + } + } + + return resources; +} + +function getCurrentLanguage(): string { + const languages = Object.keys(resources); + + const cookieLng = getLanguage(); + if (cookieLng && languages.includes(cookieLng)) { + return cookieLng; + } + + const navigatorLng = navigator.language.split('-')[0]; + if (languages.includes(navigatorLng)) { + return navigatorLng; + } + + return 'en'; +} + +const resources = getResources(); +const lng = getCurrentLanguage(); + +i18n + .use(initReactI18next) + .init({ + resources, + lng, + fallbackLng: 'en', + interpolation: { + escapeValue: false + } + }) + .then(); + +export default i18n; diff --git a/web/src/i18n/languages.ts b/web/src/i18n/languages.ts new file mode 100644 index 0000000..5dc6434 --- /dev/null +++ b/web/src/i18n/languages.ts @@ -0,0 +1,8 @@ +const languages = [ + { key: 'en', name: 'English' }, + { key: 'ko', name: '한국어' }, +]; + +languages.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })); + +export default languages; diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts new file mode 100644 index 0000000..283fc0b --- /dev/null +++ b/web/src/i18n/locales/en.ts @@ -0,0 +1,372 @@ +const en = { + translation: { + head: { + desktop: 'Remote Desktop', + login: 'Login', + changePassword: 'Change Password', + terminal: 'Terminal', + wifi: 'Wi-Fi' + }, + auth: { + login: 'Login', + placeholderUsername: 'Username', + placeholderPassword: 'Password', + placeholderPassword2: 'Please enter password again', + noEmptyUsername: 'Username required', + noEmptyPassword: 'Password required', + noAccount: 'Failed to get user information, please refresh web page or reset password', + invalidUser: 'Invalid username or password', + error: 'Unexpected error', + changePassword: 'Change Password', + changePasswordDesc: 'For the security of your device, please change the password!', + differentPassword: 'Passwords do not match', + illegalUsername: 'Username contains illegal characters', + illegalPassword: 'Password contains illegal characters', + forgetPassword: 'Forgot Password', + ok: 'Ok', + cancel: 'Cancel', + loginButtonText: 'Login', + tips: { + reset1: + 'To reset the passwords, press and hold the BOOT button on the BatchuKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the BatchuKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for BatchuKVM', + success: 'Please check the network status of BatchuKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' + }, + screen: { + title: 'Screen', + video: 'Video Mode', + videoDirectTips: 'Enable HTTPS in "Settings > Device" to use this mode', + resolution: 'Resolution', + auto: 'Automatic', + autoTips: + "Screen tearing or mouse offset may occur at specific resolutions. Consider adjusting the remote host's resolution or disable automatic mode.", + fps: 'FPS', + customizeFps: 'Customize', + quality: 'Quality', + qualityLossless: 'Lossless', + qualityHigh: 'High', + qualityMedium: 'Medium', + qualityLow: 'Low', + frameDetect: 'Frame Detect', + frameDetectTip: + "Calculate the difference between frames. Stop transmitting video stream when no changes are detected on the remote host's screen.", + resetHdmi: 'Reset HDMI' + }, + keyboard: { + title: 'Keyboard', + paste: 'Paste', + tips: 'Only standard keyboard letters and symbols are supported', + placeholder: 'Please input', + submit: 'Submit', + virtual: 'Keyboard', + ctrlaltdel: 'Ctrl+Alt+Del', + readClipboard: 'Read from Clipboard', + clipboardPermissionDenied: + 'Clipboard permission denied. Please allow clipboard access in your browser.', + clipboardReadError: 'Failed to read clipboard', + dropdownEnglish: 'English', + dropdownGerman: 'German' + }, + mouse: { + title: 'Mouse', + cursor: 'Cursor style', + default: 'Default cursor', + pointer: 'Pointer cursor', + cell: 'Cell cursor', + text: 'Text cursor', + grab: 'Grab cursor', + hide: 'Hide cursor', + mode: 'Mouse mode', + absolute: 'Absolute mode', + relative: 'Relative mode', + speed: 'Wheel speed', + fast: 'Fast', + slow: 'Slow', + requestPointer: 'Using relative mode. Please click desktop to get mouse pointer.', + resetHid: 'Reset HID', + hidOnly: { + title: 'HID-Only mode', + desc: "If your mouse and keyboard stop responding and resetting HID doesn't help, it could be a compatibility issue between the BatchuKVM and the device. Try to enable HID-Only mode for better compatibility.", + tip1: 'Enabling HID-Only mode will unmount the virtual U-disk and virtual network', + tip2: 'In HID-Only mode, image mounting is disabled', + tip3: 'BatchuKVM will automatically reboot after switching modes', + enable: 'Enable HID-Only mode', + disable: 'Disable HID-Only mode' + } + }, + image: { + title: 'Images', + loading: 'Loading...', + empty: 'Nothing Found', + mountMode: 'Mount mode', + mountFailed: 'Mount failed', + mountDesc: + 'On some systems, you need to eject the virtual disk from the remote host before mounting the image.', + unmountFailed: 'Unmount failed', + unmountDesc: + 'On some systems, you need to manually eject from the remote host before unmounting the image.', + refresh: 'Refresh the image list', + attention: 'Attention', + deleteConfirm: 'Are you sure you want to delete this image?', + okBtn: 'Yes', + cancelBtn: 'No', + tips: { + title: 'How to upload', + usb1: 'Connect the BatchuKVM to your computer via USB.', + usb2: 'Ensure that the virtual disk is mounted (Settings - Virtual Disk).', + usb3: 'Open the virtual disk on your computer and copy the image file to the root directory of the virtual disk.', + scp1: 'Make sure the BatchuKVM and your computer are on the same local network.', + scp2: 'Open a terminal on your computer and use the SCP command to upload the image file to the /data directory on the BatchuKVM.', + scp3: 'Example: scp your-image-path root@your-BatchuKVM-ip:/data', + tfCard: 'TF Card', + tf1: 'This method is supported on Linux system', + tf2: 'Get TF card from the BatchuKVM (for the FULL version, disassemble the case first).', + tf3: 'Insert the TF card into a card reader and connect it to your computer.', + tf4: 'Copy the image file to the /data directory on the TF card.', + tf5: 'Insert the TF card into the BatchuKVM.' + } + }, + script: { + title: 'Scripts', + upload: 'Upload', + run: 'Run', + runBackground: 'Run Background', + runFailed: 'Run failed', + attention: 'Attention', + delDesc: 'Are you sure you want to delete this file?', + confirm: 'Yes', + cancel: 'No', + delete: 'Delete', + close: 'Close' + }, + terminal: { + title: 'Terminal', + BatchuKVM: 'BatchuKVM Terminal', + serial: 'Serial Port Terminal', + serialPort: 'Serial Port', + serialPortPlaceholder: 'Please enter the serial port', + baudrate: 'Baud rate', + parity: 'Parity', + parityNone: 'None', + parityEven: 'Even', + parityOdd: 'Odd', + flowControl: 'Flow control', + flowControlNone: 'None', + flowControlSoft: 'Soft', + flowControlHard: 'Hard', + dataBits: 'Data bits', + stopBits: 'Stop bits', + confirm: 'Ok' + }, + wol: { + title: 'Wake-on-LAN', + sending: 'Sending command...', + sent: 'Command sent', + input: 'Please enter the MAC', + ok: 'Ok' + }, + download: { + title: 'Image Downloader', + input: 'Please enter a remote image URL', + ok: 'Ok', + disabled: '/data partition is RO, so we cannot download the image' + }, + power: { + title: 'Power', + showConfirm: 'Confirmation', + showConfirmTip: 'Power operations require an extra confirmation', + reset: 'Reset', + power: 'Power', + powerShort: 'Power (short click)', + powerLong: 'Power (long click)', + resetConfirm: 'Proceed reset operation?', + powerConfirm: 'Proceed power operation?', + okBtn: 'Yes', + cancelBtn: 'No' + }, + settings: { + title: 'Settings', + about: { + title: 'About BatchuKVM', + information: 'Information', + ip: 'IP', + mdns: 'mDNS', + application: 'Application Version', + applicationTip: 'BatchuKVM web application version', + image: 'Image Version', + imageTip: 'BatchuKVM system image version', + deviceKey: 'Device Key', + community: 'Community', + hostname: 'Hostname', + hostnameUpdated: 'Hostname updated. Reboot to apply.', + ipType: { + Wired: 'Wired', + Wireless: 'Wireless', + Other: 'Other' + } + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar', + webTitle: 'Web Title', + webTitleDesc: 'Customize the web page title' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'Turn off OLED screen after', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + ssh: { + description: 'Enable SSH remote access', + tip: 'Set a strong password before enabling (Account - Change Password)' + }, + tls: { + description: 'Enable HTTPS protocol', + tip: 'Be aware: Using HTTPS can increase latency, especially with MJPEG video mode.' + }, + advanced: 'Advanced Settings', + swap: { + title: 'Swap', + disable: 'Disable', + description: 'Set the swap file size', + tip: "Enabling this feature could shorten your SD card's usable life!" + }, + mouseJiggler: { + title: 'Mouse Jiggler', + description: 'Prevent the remote host from sleeping', + disable: 'Disable', + absolute: 'Absolute Mode', + relative: 'Relative Mode' + }, + mdns: { + description: 'Enable mDNS discovery service', + tip: "Turning it off if it's not needed" + }, + hdmi: { + description: 'Enable HDMI/monitor output' + }, + hidOnly: 'HID-Only Mode', + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + reboot: 'Reboot', + rebootDesc: 'Are you sure you want to reboot BatchuKVM?', + okBtn: 'Yes', + cancelBtn: 'No' + }, + tailscale: { + title: 'Tailscale', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory. A Tailscale restart is required for the change to take effect.' + }, + swap: { + title: 'Swap memory', + tip: 'If issues persist after enabling memory optimization, try enabling swap memory. This sets the swap file size to 256MB by default, which can be adjusted in "Settings > Device".' + }, + restart: 'Restart Tailscale?', + stop: 'Stop Tailscale?', + stopDesc: 'Log out Tailscale and disable automatic startup on boot.', + loading: 'Loading...', + notInstall: 'Tailscale not found! Please install.', + install: 'Install', + installing: 'Installing', + failed: 'Install failed', + retry: 'Please refresh and try again. Or try to install manually', + download: 'Download the', + package: 'installation package', + unzip: 'and unzip it', + upTailscale: 'Upload tailscale to BatchuKVM directory /usr/bin/', + upTailscaled: 'Upload tailscaled to BatchuKVM directory /usr/sbin/', + refresh: 'Refresh current page', + notRunning: 'Tailscale is not running. Please start it to continue.', + run: 'Start', + notLogin: + 'The device has not been bound yet. Please login and bind this device to your account.', + urlPeriod: 'This url is valid for 10 minutes', + login: 'Login', + loginSuccess: 'Login Success', + enable: 'Enable Tailscale', + deviceName: 'Device Name', + deviceIP: 'Device IP', + account: 'Account', + logout: 'Logout', + logoutDesc: 'Are you sure you want to logout?', + uninstall: 'Uninstall Tailscale', + uninstallDesc: 'Are you sure you want to uninstall Tailscale?', + okBtn: 'Yes', + cancelBtn: 'No' + }, + update: { + title: 'Check for Updates', + queryFailed: 'Get version failed', + updateFailed: 'Update failed. Please retry.', + isLatest: 'You already have the latest version.', + available: 'An update is available. Are you sure you want to update now?', + updating: 'Update started. Please wait...', + confirm: 'Confirm', + cancel: 'Cancel', + preview: 'Preview Updates', + previewDesc: 'Get early access to new features and improvements', + previewTip: + 'Please be aware that preview releases may contain bugs or incomplete functionality!' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Change', + logoutBtn: 'Logout', + logoutDesc: 'Are you sure you want to logout?', + okBtn: 'Yes', + cancelBtn: 'No' + } + }, + error: { + title: "We've ran into an issue", + refresh: 'Refresh' + }, + fullscreen: { + toggle: 'Toggle Fullscreen' + }, + menu: { + collapse: 'Collapse Menu', + expand: 'Expand Menu' + } + } +}; + +export default en; diff --git a/web/src/i18n/locales/ko.ts b/web/src/i18n/locales/ko.ts new file mode 100644 index 0000000..75b5642 --- /dev/null +++ b/web/src/i18n/locales/ko.ts @@ -0,0 +1,353 @@ +const ko = { + translation: { + head: { + desktop: '원격 데스크톱', + login: '로그인', + changePassword: '비밀번호 변경', + terminal: '터미널', + wifi: 'Wi-Fi' + }, + auth: { + login: '로그인', + placeholderUsername: '사용자 이름을 입력하세요.', + placeholderPassword: '비밀번호를 입력하세요.', + placeholderPassword2: '비밀번호를 다시 입력하세요.', + noEmptyUsername: '사용자 이름은 비어있을 수 없습니다.', + noEmptyPassword: '비밀번호는 비어있을 수 없습니다.', + noAccount: '사용자 정보를 불러오는 데 실패했습니다. 페이지를 새로고침하거나 비밀번호를 초기화하세요.', + invalidUser: '사용자 이름이나 비밀번호가 틀렸습니다.', + error: '알 수 없는 오류', + changePassword: '비밀번호 변경', + changePasswordDesc: '보안을 위해 웹 로그인 비밀번호를 변경하세요.', + differentPassword: '비밀번호가 서로 일치하지 않습니다.', + illegalUsername: '사용자 이름에 사용할 수 없는 문자가 있습니다.', + illegalPassword: '비밀번호에 사용할 수 없는 문자가 있습니다.', + forgetPassword: '비밀번호 분실', + ok: '확인', + cancel: '취소', + loginButtonText: '로그인', + tips: { + reset1: + '비밀번호를 재설정하려면 BatchuKVM의 BOOT 버튼을 10초 동안 누르고 계세요.', + reset2: '자세한 절차는 이 문서를 참조하세요:', + reset3: '웹 기본 계정:', + reset4: 'SSH 기본 계정:', + change1: '이 작업을 수행하면 다음 비밀번호가 변경됩니다:', + change2: '웹 로그인 비밀번호', + change3: '시스템 루트 비밀번호 (SSH 로그인 비밀번호)', + change4: '비밀번호를 재설정하려면 BatchuKVM의 BOOT 버튼을 길게 누르세요.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'BatchuKVM Wi-Fi 설정', + success: 'BatchuKVM의 네트워크 상태를 확인하고 새 IP 주소로 접속하세요.', + failed: '작업에 실패했습니다. 다시 시도하세요.', + confirmBtn: '확인', + finishBtn: '완료' + }, + screen: { + title: '화면', + video: '비디오 모드', + videoDirectTips: '이 모드를 사용하려면 "설정 > 장치"에서 HTTPS를 활성화하세요', + resolution: '해상도', + auto: '자동 설정', + autoTips: + '일부 해상도에서는 화면이 왜곡되거나 마우스 동작이 비정상적으로 나타날 수 있습니다. 원격 컴퓨터의 해상도를 변경하거나 자동 설정 대신 수동 설정을 사용해 보세요.', + fps: 'FPS', + customizeFps: '사용자 지정', + quality: '품질', + qualityLossless: '무손실', + qualityHigh: '높음', + qualityMedium: '중간', + qualityLow: '낮음', + frameDetect: '프레임 탐지', + frameDetectTip: + '프레임 간의 차이를 계산합니다. 원격 호스트 화면에 변경 사항이 감지되지 않으면 비디오 스트림 전송을 중지합니다.', + resetHdmi: 'HDMI 초기화' + }, + keyboard: { + title: '키보드', + paste: '붙여넣기', + tips: '표준 키보드 문자 및 기호만 지원됩니다', + placeholder: '입력하세요', + submit: '전송', + virtual: '키보드', + ctrlaltdel: 'Ctrl+Alt+Del' + }, + mouse: { + title: '마우스', + cursor: '커서 스타일', + default: '기본 커서', + pointer: '포인터 커서', + cell: '셀 커서', + text: '텍스트 커서', + grab: '잡기 커서', + hide: '커서 숨기기', + mode: '마우스 모드', + absolute: '절대값 모드', + relative: '상대값 모드', + speed: '휠 속도', + fast: '빠름', + slow: '느림', + requestPointer: '상대값 모드를 사용 중입니다. 커서를 찾으려면 데스크톱을 클릭하세요.', + resetHid: 'HID 초기화', + hidOnly: { + title: 'HID 전용 모드', + desc: '마우스와 키보드가 응답하지 않고 HID 초기화도 도움이 되지 않는다면, BatchuKVM과 장치 간의 호환성 문제일 수 있습니다. 더 나은 호환성을 위해 HID 전용 모드를 활성화해 보세요.', + tip1: 'HID 전용 모드를 활성화하면 가상 USB와 가상 네트워크가 언마운트됩니다', + tip2: 'HID 전용 모드에서는 이미지 마운트가 비활성화됩니다', + tip3: '모드 전환 후 BatchuKVM이 자동으로 재부팅됩니다', + enable: 'HID 전용 모드 활성화', + disable: 'HID 전용 모드 비활성화' + } + }, + image: { + title: '이미지', + loading: '불러오는 중...', + empty: '아무것도 없습니다.', + cdrom: 'CD-ROM 모드로 이미지 마운트', + mountFailed: '이미지 마운트 실패', + mountDesc: + '일부 시스템에서는 이미지를 마운트하기 전에 원격 호스트에서 가상 디스크를 제거해야 합니다.', + refresh: '이미지 목록 새로고침', + tips: { + title: '업로드 방법', + usb1: 'USB를 통해 BatchuKVM을 컴퓨터에 연결하세요.', + usb2: '가상 디스크가 마운트되었는지 확인하세요. (설정 - 가상 디스크).', + usb3: '컴퓨터에서 가상 디스크를 열고 이미지 파일을 가상 디스크의 루트 디렉토리로 복사하세요.', + scp1: 'BatchuKVM과 컴퓨터가 동일한 로컬 네트워크에 있는지 확인하세요.', + scp2: '컴퓨터에서 터미널을 열고 SCP 명령을 사용하여 이미지 파일을 BatchuKVM의 /data 디렉터리에 업로드하세요.', + scp3: '예시: scp [이미지 파일 경로] root@[BatchuKVM IP 주소]:/data', + tfCard: 'TF 카드', + tf1: '이 방법은 Linux 시스템에서 지원됩니다', + tf2: 'BatchuKVM에서 TF 카드를 가져옵니다(전체 버전의 경우 먼저 케이스를 분해하세요).', + tf3: 'TF 카드를 카드 리더기에 삽입하고 컴퓨터에 연결하세요.', + tf4: '이미지 파일을 TF 카드의 /data 디렉터리에 복사하세요.', + tf5: 'TF 카드를 BatchuKVM에 삽입하세요.' + } + }, + script: { + title: '스크립트', + upload: '업로드', + run: '실행', + runBackground: '백그라운드에서 실행', + runFailed: '실행 실패', + attention: '주의', + delDesc: '이 파일을 정말로 삭제합니까?', + confirm: '네', + cancel: '아니오', + delete: '삭제', + close: '닫기' + }, + terminal: { + title: '터미널', + BatchuKVM: 'BatchuKVM 터미널', + serial: '시리얼 포트 터미널', + serialPort: '시리얼 포트', + serialPortPlaceholder: '시리얼 포트를 입력하세요', + baudrate: '전송 속도', + parity: '패리티', + parityNone: '없음', + parityEven: '짝수', + parityOdd: '홀수', + flowControl: '흐름 제어', + flowControlNone: '없음', + flowControlSoft: '소프트웨어', + flowControlHard: '하드웨어', + dataBits: '데이터 비트', + stopBits: '정지 비트', + confirm: '확인' + }, + wol: { + title: 'Wake-on-LAN', + sending: '패킷 전송 중...', + sent: '패킷 전송 완료', + input: 'MAC주소를 입력하세요.', + ok: '확인' + }, + download: { + title: '이미지 다운로드', + input: '원격 이미지 URL을 입력하세요.', + ok: '확인', + disabled: '/data 파티션이 읽기 전용(RO) 상태이므로 이미지를 다운로드할 수 없습니다.' + }, + power: { + title: '전원', + showConfirm: '확인', + showConfirmTip: '전원 작업에는 추가 확인이 필요합니다', + reset: '리셋', + power: '전원', + powerShort: '전원 (짧게 누르기)', + powerLong: '전원 (길게 누르기)', + resetConfirm: '리셋 작업을 진행하시겠습니까?', + powerConfirm: '전원 작업을 진행하시겠습니까?', + okBtn: '네', + cancelBtn: '아니오' + }, + settings: { + title: '설정', + about: { + title: 'BatchuKVM 정보', + information: '정보', + ip: 'IP', + mdns: 'mDNS', + application: '펌웨어 버전', + applicationTip: 'BatchuKVM 웹 애플리케이션 버전', + image: '이미지 버전', + imageTip: 'BatchuKVM 시스템 이미지 버전', + deviceKey: '장치 키', + community: '커뮤니티', + hostname: '호스트 이름', + hostnameUpdated: '호스트 이름이 업데이트되었습니다. 적용하려면 재부팅하세요.', + ipType: { + Wired: '유선', + Wireless: '무선', + Other: '기타' + } + }, + appearance: { + title: '디자인', + display: '표시', + language: '언어', + menuBar: '메뉴 바', + menuBarDesc: '메뉴 바에 아이콘을 표시', + webTitle: '웹 제목', + webTitleDesc: '웹 페이지 제목 사용자 지정' + }, + device: { + title: '장치', + oled: { + title: 'OLED', + description: 'OLED 화면 자동 절전', + 0: '사용 안 함', + 15: '15초', + 30: '30초', + 60: '1분', + 180: '3분', + 300: '5분', + 600: '10분', + 1800: '30분', + 3600: '1시간' + }, + wifi: { + title: 'Wi-Fi', + description: 'Wi-Fi 설정', + setBtn: '설정' + }, + ssh: { + description: 'SSH 원격 접속 활성화', + tip: '활성화하기 전에 강력한 비밀번호를 설정하세요. (계정 - 비밀번호 변경)' + }, + tls: { + description: 'HTTPS 프로토콜 활성화', + tip: '주의: HTTPS 사용 시 특히 MJPEG 비디오 모드에서 지연 시간이 증가할 수 있습니다.' + }, + advanced: '고급 설정', + swap: { + title: '스왑', + disable: '비활성화', + description: '스왑 파일 크기 설정', + tip: '이 기능을 활성화하면 SD 카드의 수명이 단축될 수 있습니다!' + }, + mouseJiggler: { + title: '마우스 흔들기', + description: '원격 호스트가 절전 모드로 진입하는 것을 방지', + disable: '비활성화', + absolute: '절대값 모드', + relative: '상대값 모드' + }, + mdns: { + description: 'mDNS 검색 서비스 활성화', + tip: '사용하지 않는 경우 끄는 것이 좋습니다' + }, + hdmi: { + description: 'HDMI/모니터 출력 활성화' + }, + hidOnly: 'HID 전용 모드', + disk: '가상 디스크', + diskDesc: '원격 호스트에서 가상 USB를 마운트합니다.', + network: '가상 네트워크', + networkDesc: '원격 호스트에서 가상 네트워크 카드를 마운트합니다.', + reboot: '재부팅', + rebootDesc: 'BatchuKVM을 재부팅하시겠습니까?', + okBtn: '네', + cancelBtn: '아니오' + }, + tailscale: { + title: 'Tailscale', + memory: { + title: '메모리 최적화', + tip: '메모리 사용량이 제한을 초과하면 가비지 컬렉션이 더 적극적으로 실행되어 메모리를 확보하려고 시도합니다. Tailscale을 사용할 경우 50MB로 설정하는 것이 좋습니다. 변경 사항을 적용하려면 Tailscale을 다시 시작해야 합니다.', + disable: '비활성화' + }, + restart: '정말로 Tailscale을 다시 시작하시겠습니까?', + stop: '정말로 Tailscale을 중지하시겠습니까?', + stopDesc: 'Tailscale에서 로그아웃하고 자동 시작을 비활성화합니다.', + loading: '불러오는 중...', + notInstall: 'Tailscale이 없습니다. 설치해주세요.', + install: '설치', + installing: '설치중', + failed: '설치 실패', + retry: '새로고침하고 다시 시도하거나, 수동으로 설치하세요', + download: '다운로드 중 :', + package: '패키지 설치', + unzip: '압축 해제', + upTailscale: 'tailscale을 BatchuKVM 의 다음 경로에 업로드 했습니다. : /usr/bin/', + upTailscaled: 'tailscaled을 BatchuKVM 의 다음 경로에 업로드 했습니다. : /usr/sbin/', + refresh: '현재 페이지 새로고침', + notLogin: + '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.', + urlPeriod: '이 주소는 10분간 유효합니다.', + login: '로그인', + loginSuccess: '로그인 성공', + enable: 'Tailscale 활성화', + deviceName: '장치 이름', + deviceIP: '장치 IP', + account: '계정', + logout: '로그아웃', + logoutDesc: '정말로 로그아웃 하시겠습니까?', + logout2: '정말로 로그아웃 합니까?', + uninstall: 'Tailscale 제거', + okBtn: '네', + cancelBtn: '아니오' + }, + update: { + title: '업데이트 확인', + queryFailed: '버전 확인 실패', + updateFailed: '업데이트 실패, 재시도하세요.', + isLatest: '이미 최신 버전입니다.', + available: '업데이트가 가능합니다. 정말로 업데이트 할까요?', + updating: '업데이트 시작. 잠시 기다려주세요...', + confirm: '확인', + cancel: '취소', + preview: '미리보기 업데이트', + previewDesc: '새로운 기능과 개선 사항에 미리 접근하세요', + previewTip: '미리보기 버전에는 버그나 완성되지 않은 기능이 포함될 수 있으니 주의하세요!' + }, + account: { + title: '계정', + webAccount: '웹 계정', + password: '비밀번호', + updateBtn: '업데이트', + logoutBtn: '로그아웃', + logoutDesc: '정말로 로그아웃 하시겠습니까?', + okBtn: '네', + cancelBtn: '아니오' + } + }, + error: { + title: '문제가 발생했습니다.', + refresh: '새로고침' + }, + fullscreen: { + toggle: '전체 화면 전환' + }, + menu: { + collapse: '메뉴 접기', + expand: '메뉴 펼치기' + } + } +}; + +export default ko; diff --git a/web/src/jotai/keyboard.ts b/web/src/jotai/keyboard.ts new file mode 100644 index 0000000..a67585f --- /dev/null +++ b/web/src/jotai/keyboard.ts @@ -0,0 +1,7 @@ +import { atom } from 'jotai'; + +// is the keyboard enabled (Disable keyboard events when input is required) +export const isKeyboardEnableAtom = atom(true); + +// is the virtual keyboard opened +export const isKeyboardOpenAtom = atom(false); diff --git a/web/src/jotai/mouse.ts b/web/src/jotai/mouse.ts new file mode 100644 index 0000000..691f03e --- /dev/null +++ b/web/src/jotai/mouse.ts @@ -0,0 +1,13 @@ +import { atom } from 'jotai'; + +// mouse cursor style +export const mouseStyleAtom = atom('cursor-default'); + +// mouse mode: absolute or relative +export const mouseModeAtom = atom('absolute'); + +// mouse scroll interval (unit: ms) +export const scrollIntervalAtom = atom(0); + +// hid mode: normal or hid-only +export const hidModeAtom = atom<'normal' | 'hid-only'>('normal'); diff --git a/web/src/jotai/screen.ts b/web/src/jotai/screen.ts new file mode 100644 index 0000000..16f75a2 --- /dev/null +++ b/web/src/jotai/screen.ts @@ -0,0 +1,14 @@ +import { atom } from 'jotai'; + +import { Resolution } from '@/types'; + +export const isHdmiEnabledAtom = atom(true); + +// video mode +// direct: stream H.264 over HTTP +// h264: stream H.264 over WebRTC +// mjpeg: stream JPEG over HTTP +export const videoModeAtom = atom(''); + +// browser screen resolution +export const resolutionAtom = atom(null); diff --git a/web/src/jotai/settings.ts b/web/src/jotai/settings.ts new file mode 100644 index 0000000..82f9e49 --- /dev/null +++ b/web/src/jotai/settings.ts @@ -0,0 +1,7 @@ +import { atom } from 'jotai'; + +// menu bar disabled items +export const menuDisabledItemsAtom = atom([]); + +// web title +export const webTitleAtom = atom(''); diff --git a/web/src/lib/cookie.ts b/web/src/lib/cookie.ts new file mode 100644 index 0000000..08fe719 --- /dev/null +++ b/web/src/lib/cookie.ts @@ -0,0 +1,23 @@ +import Cookies from 'js-cookie'; + +const COOKIE_TOKEN_KEY = 'nano-kvm-token'; + +export function existToken() { + const token = Cookies.get(COOKIE_TOKEN_KEY); + return !!token; +} + +export function getToken() { + const token = Cookies.get(COOKIE_TOKEN_KEY); + if (!token) return null; + + return token; +} + +export function setToken(token: string) { + Cookies.set(COOKIE_TOKEN_KEY, token, { expires: 30 }); +} + +export function removeToken() { + Cookies.remove(COOKIE_TOKEN_KEY); +} diff --git a/web/src/lib/encrypt.ts b/web/src/lib/encrypt.ts new file mode 100644 index 0000000..54e9d5b --- /dev/null +++ b/web/src/lib/encrypt.ts @@ -0,0 +1,9 @@ +import CryptoJS from 'crypto-js'; + +// This key is only used to prevent the data from being transmitted in plaintext. +const SECRET_KEY = 'NanoKVM-KOREA-TestKey-2512092155'; + +export function encrypt(data: string) { + const dataEncrypt = CryptoJS.AES.encrypt(data, SECRET_KEY).toString(); + return encodeURIComponent(dataEncrypt); +} diff --git a/web/src/lib/http.ts b/web/src/lib/http.ts new file mode 100644 index 0000000..0653ac2 --- /dev/null +++ b/web/src/lib/http.ts @@ -0,0 +1,74 @@ +import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; + +import { removeToken } from '@/lib/cookie.ts'; +import { getBaseUrl } from '@/lib/service.ts'; + +type Response = { + code: number; + msg: string; + data: any; +}; + +class Http { + private instance: AxiosInstance; + + constructor() { + const baseURL = getBaseUrl('http'); + const withCredentials = (import.meta.env.VITE_WITH_CREDENTIALS as string) !== 'false'; + + this.instance = axios.create({ + baseURL, + withCredentials, + timeout: 60 * 1000 + }); + + this.setInterceptors(); + } + + private setInterceptors() { + this.instance.interceptors.request.use((config) => { + if (config.headers) { + config.headers.Accept = 'application/json'; + } + + return config; + }); + + this.instance.interceptors.response.use( + (response) => { + return response.data; + }, + (error) => { + console.log(error); + const code = error.response?.status; + if (code === 401) { + removeToken(); + window.location.reload(); + } + return Promise.reject(error); + } + ); + } + + public get(url: string, params?: any): Promise { + return this.instance.request({ + method: 'get', + url, + params + }); + } + + public post(url: string, data?: any): Promise { + return this.instance.request({ + method: 'post', + url, + data + }); + } + + public request(config: AxiosRequestConfig): Promise { + return this.instance.request(config); + } +} + +export const http = new Http(); diff --git a/web/src/lib/localstorage.ts b/web/src/lib/localstorage.ts new file mode 100644 index 0000000..b6cfef0 --- /dev/null +++ b/web/src/lib/localstorage.ts @@ -0,0 +1,196 @@ +import { Resolution } from '@/types'; + +const LANGUAGE_KEY = 'nano-kvm-language'; +const VIDEO_MODE_KEY = 'nano-kvm-vide-mode'; +const WEB_RESOLUTION_KEY = 'nano-kvm-web-resolution'; +const FPS_KEY = 'nano-kvm-fps'; +const QUALITY_KEY = 'nano-kvm-quality'; +const GOP_KEY = 'nano-kvm-gop'; +const FRAME_DETECT_KEY = 'nano-kvm-frame-detect'; +const MOUSE_STYLE_KEY = 'nano-kvm-mouse-style'; +const MOUSE_MODE_KEY = 'nano-kvm-mouse-mode'; +const MOUSE_SCROLL_INTERVAL_KEY = 'nanokvm-kvm-mouse-scroll-interval'; +const SKIP_UPDATE_KEY = 'nano-kvm-check-update'; +const KEYBOARD_SYSTEM_KEY = 'nano-kvm-keyboard-system'; +const KEYBOARD_LANGUAGE_KEY = 'nano-kvm-keyboard-language'; +const SKIP_MODIFY_PASSWORD_KEY = 'nano-kvm-skip-modify-password'; +const MENU_DISABLED_ITEMS_KEY = 'nano-kvm-menu-disabled-items'; +const POWER_CONFIRM_KEY = 'nano-kvm-power-confirm'; + +type ItemWithExpiry = { + value: string; + expiry: number; +}; + +// set the value with expiration time (unit: milliseconds) +function setWithExpiry(key: string, value: string, ttl: number) { + const now = new Date(); + + const item: ItemWithExpiry = { + value: value, + expiry: now.getTime() + ttl + }; + + localStorage.setItem(key, JSON.stringify(item)); +} + +// get the value with expiration time +function getWithExpiry(key: string) { + const itemStr = localStorage.getItem(key); + if (!itemStr) return null; + + const item: ItemWithExpiry = JSON.parse(itemStr); + const now = new Date(); + if (now.getTime() > item.expiry) { + localStorage.removeItem(key); + return null; + } + + return item.value; +} + +export function getLanguage() { + return localStorage.getItem(LANGUAGE_KEY); +} + +export function setLanguage(language: string) { + localStorage.setItem(LANGUAGE_KEY, language); +} + +export function getVideoMode() { + return localStorage.getItem(VIDEO_MODE_KEY); +} + +export function setVideoMode(mode: string) { + localStorage.setItem(VIDEO_MODE_KEY, mode); +} + +export function getResolution(): Resolution | null { + const resolution = localStorage.getItem(WEB_RESOLUTION_KEY); + if (resolution) { + const obj = JSON.parse(window.atob(resolution)); + return obj as Resolution; + } + + return null; +} + +export function setResolution(resolution: Resolution) { + localStorage.setItem(WEB_RESOLUTION_KEY, window.btoa(JSON.stringify(resolution))); +} + +export function getFps() { + const fps = localStorage.getItem(FPS_KEY); + return fps ? Number(fps) : null; +} + +export function setFps(fps: number) { + localStorage.setItem(FPS_KEY, String(fps)); +} + +export function getQuality() { + const quality = localStorage.getItem(QUALITY_KEY); + return quality ? Number(quality) : null; +} + +export function setQuality(quality: number) { + localStorage.setItem(QUALITY_KEY, String(quality)); +} + +export function getGop() { + const gop = localStorage.getItem(GOP_KEY); + return gop ? Number(gop) : null; +} + +export function setGop(gop: number) { + localStorage.setItem(GOP_KEY, String(gop)); +} + +export function getFrameDetect(): boolean { + const enabled = localStorage.getItem(FRAME_DETECT_KEY); + return enabled === 'true'; +} + +export function setFrameDetect(enabled: boolean) { + localStorage.setItem(FRAME_DETECT_KEY, String(enabled)); +} + +export function getMouseStyle() { + return localStorage.getItem(MOUSE_STYLE_KEY); +} + +export function setMouseStyle(mouse: string) { + localStorage.setItem(MOUSE_STYLE_KEY, mouse); +} + +export function getMouseMode() { + return localStorage.getItem(MOUSE_MODE_KEY); +} + +export function setMouseMode(mouse: string) { + localStorage.setItem(MOUSE_MODE_KEY, mouse); +} + +export function getMouseScrollInterval() { + const interval = localStorage.getItem(MOUSE_SCROLL_INTERVAL_KEY); + return interval ? Number(interval) : null; +} + +export function setMouseScrollInterval(interval: number): void { + localStorage.setItem(MOUSE_SCROLL_INTERVAL_KEY, String(interval)); +} + +export function getSkipUpdate() { + const skip = getWithExpiry(SKIP_UPDATE_KEY); + return skip === 'true'; +} + +export function setSkipUpdate(skip: boolean) { + const expiry = 3 * 24 * 60 * 60 * 1000; // 3 days + setWithExpiry(SKIP_UPDATE_KEY, String(skip), expiry); +} + +export function setKeyboardSystem(system: string) { + localStorage.setItem(KEYBOARD_SYSTEM_KEY, system); +} + +export function getKeyboardSystem() { + return localStorage.getItem(KEYBOARD_SYSTEM_KEY); +} + +export function setKeyboardLanguage(language: string) { + localStorage.setItem(KEYBOARD_LANGUAGE_KEY, language); +} + +export function getKeyboardLanguage() { + return localStorage.getItem(KEYBOARD_LANGUAGE_KEY); +} + +export function setSkipModifyPassword(skip: boolean) { + const expiry = 3 * 24 * 60 * 60 * 1000; // 3 days + setWithExpiry(SKIP_MODIFY_PASSWORD_KEY, String(skip), expiry); +} + +export function getSkipModifyPassword() { + const skip = getWithExpiry(SKIP_MODIFY_PASSWORD_KEY); + return skip === 'true'; +} + +export function setMenuDisabledItems(items: string[]) { + const value = JSON.stringify(items); + localStorage.setItem(MENU_DISABLED_ITEMS_KEY, value); +} + +export function getMenuDisabledItems(): string[] { + const value = localStorage.getItem(MENU_DISABLED_ITEMS_KEY); + return value ? JSON.parse(value) : []; +} + +export function getPowerConfirm() { + const enabled = localStorage.getItem(POWER_CONFIRM_KEY); + return enabled === 'true'; +} + +export function setPowerConfirm(enabled: boolean) { + localStorage.setItem(POWER_CONFIRM_KEY, String(enabled)); +} diff --git a/web/src/lib/service.ts b/web/src/lib/service.ts new file mode 100644 index 0000000..31dcfb7 --- /dev/null +++ b/web/src/lib/service.ts @@ -0,0 +1,21 @@ +export function getHostname(): string { + const ip = import.meta.env.VITE_SERVER_IP as string; + return ip ? ip : window.location.hostname; +} + +export function getPort(): string { + const port = import.meta.env.VITE_SERVER_PORT as string; + return port ? port : window.location.port; +} + +export function getBaseUrl(type: 'http' | 'ws'): string { + let protocol = window.location.protocol; + if (type === 'ws') { + protocol = protocol === 'https:' ? 'wss:' : 'ws:'; + } + + const hostname = getHostname(); + const port = getPort(); + + return `${protocol}//${hostname}:${port}`; +} diff --git a/web/src/lib/websocket.ts b/web/src/lib/websocket.ts new file mode 100644 index 0000000..e056efd --- /dev/null +++ b/web/src/lib/websocket.ts @@ -0,0 +1,66 @@ +import { IMessageEvent, w3cwebsocket as W3cWebSocket } from 'websocket'; + +import { getBaseUrl } from '@/lib/service.ts'; + +type Event = (message: IMessageEvent) => void; + +const eventMap: Map = new Map(); + +class WsClient { + private readonly url: string; + private instance: W3cWebSocket; + + constructor() { + this.url = `${getBaseUrl('ws')}/api/ws`; + this.instance = new W3cWebSocket(this.url); + this.setEvents(); + } + + public connect() { + this.close(); + + this.instance = new W3cWebSocket(this.url); + this.setEvents(); + } + + public send(data: number[]) { + if (this.instance.readyState !== W3cWebSocket.OPEN) { + return; + } + + const message = JSON.stringify(data); + this.instance.send(message); + } + + public close() { + if (this.instance.readyState === W3cWebSocket.OPEN) { + this.instance.close(); + } + } + + public register(type: string, fn: (message: IMessageEvent) => void) { + eventMap.set(type, fn); + + this.setEvents(); + } + + public unregister(type: string) { + eventMap.delete(type); + + this.setEvents(); + } + + private setEvents() { + this.instance.onmessage = (message) => { + const data = JSON.parse(message.data as string); + if (!data) return; + + const fn = eventMap.get(data.type); + if (!fn) return; + + fn(message); + }; + } +} + +export const client = new WsClient(); diff --git a/web/src/main.tsx b/web/src/main.tsx new file mode 100644 index 0000000..50cbc44 --- /dev/null +++ b/web/src/main.tsx @@ -0,0 +1,53 @@ +import React, { Suspense } from 'react'; +import { ConfigProvider, Spin, theme } from 'antd'; +import ReactDOM from 'react-dom/client'; +import { ErrorBoundary } from 'react-error-boundary'; +import { HelmetProvider } from 'react-helmet-async'; +import { RouterProvider } from 'react-router-dom'; + +import { MainError } from './components/main-error.tsx'; +import { router } from './router'; + +import './i18n'; +import './assets/styles/index.css'; + +const renderApp = () => { + const themeConfig = { + algorithm: theme.darkAlgorithm, + components: { + Collapse: { + headerPadding: 0, + contentPadding: 0 + } + } + }; + + return ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + } + > + + + + + + + + + + ); +}; + +if (import.meta.env.MODE === 'mocked') { + const { worker } = await import('./mocks/browser'); + worker.start().then(() => { + return renderApp(); + }); +} + +renderApp(); diff --git a/web/src/mocks/browser.ts b/web/src/mocks/browser.ts new file mode 100644 index 0000000..3cb0487 --- /dev/null +++ b/web/src/mocks/browser.ts @@ -0,0 +1,14 @@ +import { setupWorker } from 'msw/browser' +import { http, HttpResponse } from 'msw' + +export const handlers = [ + http.post('/api/auth/login', () => { + return HttpResponse.json({ + code: 0, + data: { + token: 'mocked_token', + }, + }) + }), +] +export const worker = setupWorker(...handlers) diff --git a/web/src/pages/auth/login/index.tsx b/web/src/pages/auth/login/index.tsx new file mode 100644 index 0000000..a94a881 --- /dev/null +++ b/web/src/pages/auth/login/index.tsx @@ -0,0 +1,118 @@ +import { useEffect, useState, ReactElement } from 'react'; +import { LockOutlined, UserOutlined } from '@ant-design/icons'; +import { Button, Form, Input } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; + +import * as api from '@/api/auth.ts'; +import { existToken, setToken } from '@/lib/cookie.ts'; +import { encrypt } from '@/lib/encrypt.ts'; +import { Head } from '@/components/head.tsx'; + +import { Tips } from './tips.tsx'; + +export const Login = (): ReactElement => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + const [isLoading, setIsloading] = useState(false); + const [msg, setMsg] = useState(''); + + useEffect(() => { + if (existToken()) { + navigate('/', { replace: true }); + } + }, []); + + useEffect(() => { + if (msg) { + setTimeout(() => setMsg(''), 3000); + } + }, [msg]); + + function login(values: any) { + if (isLoading) return; + setIsloading(true); + + const username = values.username; + const password = encrypt(values.password); + + api + .login(username, password) + .then((rsp: any) => { + if (rsp.code !== 0) { + setMsg(rsp.code === -2 ? t('auth.invalidUser') : t('auth.error')); + return; + } + + setMsg(''); + setToken(rsp.data.token); + + navigate('/', { replace: true }); + window.location.reload(); + }) + .catch(() => { + setMsg(t('auth.error')); + }) + .finally(() => { + setIsloading(false); + }); + } + + return ( + <> + + +
+
+
+ { + evt.preventDefault(); + (evt.target as HTMLImageElement).classList.add('animate-spin'); + setTimeout(() => { + (evt.target as HTMLImageElement).classList.remove('animate-spin'); + }, 1000); + }} /> +
+ + } placeholder={t('auth.placeholderUsername')} /> + + + + } + type="password" + placeholder={t('auth.placeholderPassword')} + /> + + +
{msg}
+ + + + + +
+ +
+
+
+ + ); +}; diff --git a/web/src/pages/auth/login/tips.tsx b/web/src/pages/auth/login/tips.tsx new file mode 100644 index 0000000..6a04491 --- /dev/null +++ b/web/src/pages/auth/login/tips.tsx @@ -0,0 +1,68 @@ +import { useState } from 'react'; +import { Button, Card, Modal, Typography } from 'antd'; +import { useTranslation } from 'react-i18next'; + +const { Text } = Typography; + +export const Tips = () => { + const { t } = useTranslation(); + const [isModalOpen, setIsModalOpen] = useState(false); + + const showModal = () => { + setIsModalOpen(true); + }; + + const hideModal = () => { + setIsModalOpen(false); + }; + + return ( + <> + + {t('auth.forgetPassword')} + + + + +
+
{t('auth.tips.reset1')}
+ +
+ {t('auth.tips.reset2')} + + wiki + +
+ +
    +
  • + {t('auth.tips.reset3')} + admin/admin +
  • +
  • + {t('auth.tips.reset4')} + root/root +
  • +
+
+
+ +
+ +
+
+ + ); +}; diff --git a/web/src/pages/auth/password/index.tsx b/web/src/pages/auth/password/index.tsx new file mode 100644 index 0000000..a75457a --- /dev/null +++ b/web/src/pages/auth/password/index.tsx @@ -0,0 +1,132 @@ +import { useEffect, useState } from 'react'; +import { LockOutlined, UserOutlined } from '@ant-design/icons'; +import { Button, Card, Form, Input } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; + +import * as api from '@/api/auth.ts'; +import { removeToken } from '@/lib/cookie.ts'; +import { encrypt } from '@/lib/encrypt.ts'; +import { Head } from '@/components/head.tsx'; + +export const Password = () => { + const { t } = useTranslation(); + const [msg, setMsg] = useState(''); + const navigate = useNavigate(); + + useEffect(() => { + if (msg) { + setTimeout(() => setMsg(''), 3000); + } + }, [msg]); + + function changePassword(values: any) { + if (values.password !== values.password2) { + setMsg(t('auth.differentPassword')); + return; + } + if (!validateString(values.username)) { + setMsg(t('auth.illegalUsername')); + return; + } + if (!validateString(values.password)) { + setMsg('auth.illegalPassword'); + return; + } + + const username = values.username; + const password = encrypt(values.password); + + api + .changePassword(username, password) + .then((rsp: any) => { + if (rsp.code !== 0) { + setMsg(t('auth.error')); + return; + } + + removeToken(); + navigate('/auth/login', { replace: true }); + }) + .catch(() => { + setMsg(t('auth.error')); + }); + } + + function validateString(str: string) { + const regex = /['"\\/]/; + return !regex.test(str); + } + + function cancel() { + window.location.replace('/'); + } + + return ( + <> + + +
+

{t('auth.changePassword')}

+ +
+ + } placeholder={t('auth.placeholderUsername')} /> + + + + } + type="password" + placeholder={t('auth.placeholderPassword')} + /> + + + + } + type="password" + placeholder={t('auth.placeholderPassword2')} + /> + + + {msg} + +
+ + +
+
+
+ + +
+
{t('auth.tips.change1')}
+
    +
  • {t('auth.tips.change2')}
  • +
  • {t('auth.tips.change3')}
  • +
+
{t('auth.tips.change4')}
+
+
+
+ + ); +}; diff --git a/web/src/pages/desktop/index.tsx b/web/src/pages/desktop/index.tsx new file mode 100644 index 0000000..f3cad28 --- /dev/null +++ b/web/src/pages/desktop/index.tsx @@ -0,0 +1,77 @@ +import { useEffect } from 'react'; +import { useAtom, useAtomValue } from 'jotai'; +import { useTranslation } from 'react-i18next'; +import { useMediaQuery } from 'react-responsive'; + +import * as storage from '@/lib/localstorage.ts'; +import { client } from '@/lib/websocket.ts'; +import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts'; +import { resolutionAtom, videoModeAtom } from '@/jotai/screen.ts'; +import { Head } from '@/components/head.tsx'; + +import { Keyboard } from './keyboard'; +import { VirtualKeyboard } from './keyboard/virtual-keyboard'; +import { Menu } from './menu'; +import { Mouse } from './mouse'; +import { Notification } from './notification.tsx'; +import { Screen } from './screen'; + +export const Desktop = () => { + const { t } = useTranslation(); + const isBigScreen = useMediaQuery({ minWidth: 850 }); + + const [videoMode, setVideoMode] = useAtom(videoModeAtom); + const [resolution, setResolution] = useAtom(resolutionAtom); + const isKeyboardEnable = useAtomValue(isKeyboardEnableAtom); + + useEffect(() => { + const mode = getVideoMode(); + setVideoMode(mode); + + const res = storage.getResolution() || { width: 0, height: 0 }; + setResolution(res); + + const timer = setInterval(() => { + client.send([0]); + }, 60 * 1000); + + return () => { + clearInterval(timer); + client.unregister('stream'); + client.close(); + }; + }, []); + + function getVideoMode() { + const defaultVideoMode = window.RTCPeerConnection ? 'h264' : 'mjpeg'; + + const cookieVideoMode = storage.getVideoMode(); + if (cookieVideoMode) { + if (cookieVideoMode === 'direct' && !window.VideoDecoder) { + return defaultVideoMode; + } + return cookieVideoMode; + } + + return defaultVideoMode; + } + + return ( + <> + + + {isBigScreen && } + + {videoMode && resolution && ( + <> + + + + {isKeyboardEnable && } + + )} + + + + ); +}; diff --git a/web/src/pages/desktop/keyboard/index.tsx b/web/src/pages/desktop/keyboard/index.tsx new file mode 100644 index 0000000..a963f7f --- /dev/null +++ b/web/src/pages/desktop/keyboard/index.tsx @@ -0,0 +1,103 @@ +import { useEffect, useRef } from 'react'; + +import { client } from '@/lib/websocket.ts'; + +import { KeyboardCodes } from './mappings.ts'; + +export const Keyboard = () => { + const pressedKeys = useRef>(new Set()); + + // listen keyboard events + useEffect(() => { + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + window.addEventListener('blur', releaseAllKeys); + document.addEventListener('visibilitychange', handleVisibilityChange); + + // press button + function handleKeyDown(event: KeyboardEvent) { + disableEvent(event); + + if (!pressedKeys.current.has(event.code)) { + pressedKeys.current.add(event.code); + } + + sendKeyDown(event); + } + + // release button + function handleKeyUp(event: KeyboardEvent) { + disableEvent(event); + + if (pressedKeys.current.has(event.code)) { + pressedKeys.current.delete(event.code); + } + + sendKeyUp(); + } + + function releaseAllKeys() { + if (pressedKeys.current.size === 0) { + return; + } + + sendKeyUp(); + pressedKeys.current.clear(); + } + + function handleVisibilityChange() { + if (document.hidden) { + releaseAllKeys(); + } + } + + return () => { + window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('keyup', handleKeyUp); + window.removeEventListener('blur', releaseAllKeys); + document.removeEventListener('visibilitychange', handleVisibilityChange); + }; + }, []); + + function sendKeyDown(event: KeyboardEvent) { + const code = KeyboardCodes.get(event.code); + if (!code) { + console.log('unknown code: ', event.code); + return; + } + + let ctrl = 0; + if (event.ctrlKey) { + if (pressedKeys.current.has('ControlLeft')) { + ctrl = 1; + } else if (pressedKeys.current.has('ControlRight')) { + ctrl = 16; + } else if (pressedKeys.current.has('AltRight')) { + ctrl = 0; + } else { + ctrl = 1; + } + } + + const modifiers = [ + ctrl, + event.shiftKey ? (pressedKeys.current.has('ShiftRight') ? 32 : 2) : 0, + event.altKey ? (pressedKeys.current.has('AltRight') ? 64 : 4) : 0, + event.metaKey ? (pressedKeys.current.has('MetaRight') ? 128 : 8) : 0 + ]; + + client.send([1, code, ...modifiers]); + } + + function sendKeyUp() { + client.send([1, 0, 0, 0, 0, 0]); + } + + // disable the default keyboard events + function disableEvent(event: KeyboardEvent) { + event.preventDefault(); + event.stopPropagation(); + } + + return <>; +}; diff --git a/web/src/pages/desktop/keyboard/mappings.ts b/web/src/pages/desktop/keyboard/mappings.ts new file mode 100644 index 0000000..6ca3ba9 --- /dev/null +++ b/web/src/pages/desktop/keyboard/mappings.ts @@ -0,0 +1,168 @@ +export const KeyboardCodes: Map = new Map([ + ['KeyA', 4], + ['KeyB', 5], + ['KeyC', 6], + ['KeyD', 7], + ['KeyE', 8], + ['KeyF', 9], + ['KeyG', 10], + ['KeyH', 11], + ['KeyI', 12], + ['KeyJ', 13], + ['KeyK', 14], + ['KeyL', 15], + ['KeyM', 16], + ['KeyN', 17], + ['KeyO', 18], + ['KeyP', 19], + ['KeyQ', 20], + ['KeyR', 21], + ['KeyS', 22], + ['KeyT', 23], + ['KeyU', 24], + ['KeyV', 25], + ['KeyW', 26], + ['KeyX', 27], + ['KeyY', 28], + ['KeyZ', 29], + + ['RusA', 4], + ['RusB', 5], + ['RusC', 6], + ['RusD', 7], + ['RusE', 8], + ['RusF', 9], + ['RusG', 10], + ['RusH', 11], + ['RusI', 12], + ['RusJ', 13], + ['RusK', 14], + ['RusL', 15], + ['RusM', 16], + ['RusN', 17], + ['RusO', 18], + ['RusP', 19], + ['RusQ', 20], + ['RusR', 21], + ['RusS', 22], + ['RusT', 23], + ['RusU', 24], + ['RusV', 25], + ['RusW', 26], + ['RusX', 27], + ['RusY', 28], + ['RusZ', 29], + + ['RusBracketLeft', 47], + ['RusBracketRight', 48], + ['RusBackslash', 49], + ['RusSemicolon', 51], + ['RusQuote', 52], + ['RusComma', 54], + ['RusPeriod', 55], + ['RusSlash', 56], + + ['Digit1', 30], + ['Digit2', 31], + ['Digit3', 32], + ['Digit4', 33], + ['Digit5', 34], + ['Digit6', 35], + ['Digit7', 36], + ['Digit8', 37], + ['Digit9', 38], + ['Digit0', 39], + + ['Enter', 40], + ['Escape', 41], + ['Backspace', 42], + ['Tab', 43], + ['Space', 44], + ['Minus', 45], + ['Equal', 46], + ['BracketLeft', 47], + ['BracketRight', 48], + ['Backslash', 49], + ['IntlBackslash', 49], + ['Backquote_azerty', 100], + ['IntlBackslash_qwertz', 100], + + ['Semicolon', 51], + ['Quote', 52], + ['Backquote', 53], + ['KeyTilde', 53], + ['Comma', 54], + ['Period', 55], + ['KeyDot', 55], + ['Slash', 56], + ['CapsLock', 57], + + ['F1', 58], + ['F2', 59], + ['F3', 60], + ['F4', 61], + ['F5', 62], + ['F6', 63], + ['F7', 64], + ['F8', 65], + ['F9', 66], + ['F10', 67], + ['F11', 68], + ['F12', 69], + ['F13', 70], + + ['PrintScreen', 70], + ['ScrollLock', 71], + ['Pause', 72], + ['Insert', 73], + ['Home', 74], + ['PageUp', 75], + ['Delete', 76], + ['End', 77], + ['PageDown', 78], + ['ArrowRight', 79], + ['ArrowLeft', 80], + ['ArrowDown', 81], + ['ArrowUp', 82], + + ['NumLock', 83], + ['NumpadDivide', 84], + ['NumpadMultiply', 85], + ['NumpadSubtract', 86], + ['NumpadAdd', 87], + ['NumpadEnter', 88], + ['Numpad1', 89], + ['Numpad2', 90], + ['Numpad3', 91], + ['Numpad4', 92], + ['Numpad5', 93], + ['Numpad6', 94], + ['Numpad7', 95], + ['Numpad8', 96], + ['Numpad9', 97], + ['Numpad0', 98], + ['NumpadDecimal', 99], + ['KeyKpDot', 99], + + ['Menu', 118], + + ['ControlLeft', 224], + ['ShiftLeft', 225], + ['AltLeft', 226], + ['MetaLeft', 227], + ['ControlRight', 228], + ['ShiftRight', 229], + ['AltRight', 230], + ['MetaRight', 231] +]); + +export const ModifierCodes: Map = new Map([ + ['ControlLeft', 1], + ['ShiftLeft', 2], + ['AltLeft', 4], + ['MetaLeft', 8], + ['ControlRight', 16], + ['ShiftRight', 32], + ['AltRight', 64], + ['MetaRight', 128] +]); diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx new file mode 100644 index 0000000..76838d8 --- /dev/null +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -0,0 +1,296 @@ +import { useEffect, useRef, useState } from 'react'; +import { AppleOutlined, WindowsOutlined } from '@ant-design/icons'; +import clsx from 'clsx'; +import { useAtom } from 'jotai'; +import { XIcon } from 'lucide-react'; +import Keyboard, { KeyboardButtonTheme } from 'react-simple-keyboard'; +import { Drawer } from 'vaul'; + +import 'react-simple-keyboard/build/css/index.css'; +import '@/assets/styles/keyboard.css'; + +import { ConfigProvider, Segmented, Select, theme } from 'antd'; +import { useMediaQuery } from 'react-responsive'; + +import * as storage from '@/lib/localstorage.ts'; +import { client } from '@/lib/websocket.ts'; +import { isKeyboardOpenAtom } from '@/jotai/keyboard.ts'; + +import { KeyboardCodes, ModifierCodes } from './mappings.ts'; +import { + doubleKeys, + keyboardArrowsOptions, + keyboardControlPadOptions, + keyboardOptions, + modifierKeys, + specialKeyMap +} from './virtual-keys.ts'; + +export const VirtualKeyboard = () => { + const isBigScreen = useMediaQuery({ minWidth: 850 }); + + const [isKeyboardOpen, setIsKeyboardOpen] = useAtom(isKeyboardOpenAtom); + + const [keyboardLayout, setKeyboardLayout] = useState('default'); + const [keyboardSystem, setKeyboardSystem] = useState('win'); + const [keyboardLanguage, setKeyboardLanguage] = useState('en'); + const [activeModifierKeys, setActiveModifierKeys] = useState([]); + + const keyboardRef = useRef(null); + + const systems = [ + { value: 'win', icon: }, + { value: 'mac', icon: } + ]; + + const languages = [ + { value: 'en', label: 'English' }, + { value: 'fr', label: 'French' }, + { value: 'de', label: 'Deutsch' }, + { value: 'ru', label: 'Russian' } + ]; + + useEffect(() => { + const system = storage.getKeyboardSystem(); + if (system && ['win', 'mac'].includes(system)) { + setKeyboardSystem(system); + } + + const language = storage.getKeyboardLanguage(); + if (language && languages.some((lng) => lng.value === language)) { + setKeyboardLanguage(language); + } + }, []); + + useEffect(() => { + const layoutMap = new Map([ + ['en', 'default'], + ['ru', 'rus'], + ['de', 'qwertz'], + ['fr', 'azerty'] + ]); + + if (keyboardLanguage === 'en' && keyboardSystem === 'mac') { + setKeyboardLayout('mac'); + return; + } + + if (layoutMap.has(keyboardLanguage)) { + setKeyboardLayout(layoutMap.get(keyboardLanguage)!); + return; + } + + setKeyboardLayout('default'); + }, [keyboardSystem, keyboardLanguage]); + + function onKeyPress(key: string) { + if (modifierKeys.includes(key)) { + if (activeModifierKeys.includes(key)) { + sendModifierKeyDown(); + sendModifierKeyUp(); + } else { + setActiveModifierKeys([...activeModifierKeys, key]); + } + return; + } + + sendKeydown(key); + } + + function onKeyReleased(key: string) { + if (modifierKeys.includes(key)) { + return; + } + + sendKeyup(); + } + + function sendKeydown(key: string) { + const code = getKeyCode(key); + if (!code) { + console.log('unknown code: ', key); + return; + } + + const modifiers = sendModifierKeyDown(); + + client.send([1, code, ...modifiers]); + } + + function getKeyCode(key: string) { + // AZERTY: swap A↔Q and Z↔W on French physical positions + if (keyboardLanguage === 'fr' && key.endsWith('_azerty')) { + const base = key.replace('_azerty', ''); + if (base === 'KeyA') return KeyboardCodes.get('KeyQ'); + if (base === 'KeyQ') return KeyboardCodes.get('KeyA'); + if (base === 'KeyZ') return KeyboardCodes.get('KeyW'); + if (base === 'KeyW') return KeyboardCodes.get('KeyZ'); + // all other labels use their own code + return KeyboardCodes.get(base); + } + if (keyboardLanguage === 'de' && key.endsWith('_qwertz')) { + const base = key.replace('_qwertz', ''); + // Tausch + if (base === 'KeyZ') return KeyboardCodes.get('KeyY'); + if (base === 'KeyY') return KeyboardCodes.get('KeyZ'); + + if (base === 'IntlBackslash') return KeyboardCodes.get('IntlBackslash_qwertz'); + + // all other labels use their own code + return KeyboardCodes.get(base); + } + + const specialKey = specialKeyMap.get(key); + if (specialKey) { + return KeyboardCodes.get(specialKey); + } + + return KeyboardCodes.get(key); + } + + function sendKeyup() { + sendModifierKeyUp(); + client.send([1, 0, 0, 0, 0, 0]); + } + + function sendModifierKeyDown() { + let ctrl = 0; + let shift = 0; + let alt = 0; + let meta = 0; + + activeModifierKeys.forEach((modifierKey) => { + const key = specialKeyMap.get(modifierKey)!; + + const code = KeyboardCodes.get(key)!; + const modifier = ModifierCodes.get(key)!; + + if ([1, 16].includes(modifier)) { + ctrl = modifier; + } else if ([2, 32].includes(modifier)) { + shift = modifier; + } else if ([4, 64].includes(modifier)) { + alt = modifier; + } else if ([8, 128].includes(modifier)) { + meta = modifier; + } + + client.send([1, code, ctrl, shift, alt, meta]); + }); + + return [ctrl, shift, alt, meta]; + } + + function sendModifierKeyUp() { + if (activeModifierKeys.length === 0) return; + + activeModifierKeys.forEach(() => { + client.send([1, 0, 0, 0, 0, 0]); + }); + + setActiveModifierKeys([]); + } + + function selectSystem(system: string) { + setKeyboardSystem(system); + storage.setKeyboardSystem(system); + } + + function selectLanguage(language: string) { + setKeyboardLanguage(language); + storage.setKeyboardLanguage(language); + } + + function getButtonTheme(): KeyboardButtonTheme[] { + const theme = [{ class: 'hg-double', buttons: doubleKeys.join(' ') }]; + + if (activeModifierKeys.length > 0) { + const buttons = activeModifierKeys.join(' '); + theme.push({ class: 'hg-highlight', buttons }); + } + + return theme; + } + + return ( + + + + {/* header */} +
+ +
+ + +
+ + )} +
+ {status && ( +
+ {log} +
+ )} +
+
+ ); + + return ( + } + content={content} + onOpenChange={handleOpenChange} + /> + ); +}; diff --git a/web/src/pages/desktop/menu/fullscreen/index.tsx b/web/src/pages/desktop/menu/fullscreen/index.tsx new file mode 100644 index 0000000..7744240 --- /dev/null +++ b/web/src/pages/desktop/menu/fullscreen/index.tsx @@ -0,0 +1,42 @@ +import { useEffect, useState } from 'react'; +import { Tooltip } from 'antd'; +import { MaximizeIcon, MinimizeIcon } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; + +export const Fullscreen = () => { + const { t } = useTranslation(); + const [isFullscreen, setIsFullscreen] = useState(false); + + useEffect(() => { + function onFullscreenChange() { + setIsFullscreen(!!document.fullscreenElement); + } + onFullscreenChange(); + + document.addEventListener('fullscreenchange', onFullscreenChange); + + return () => { + document.removeEventListener('fullscreenchange', onFullscreenChange); + }; + }, []); + + function handleFullscreen() { + if (!document.fullscreenElement) { + const element = document.documentElement; + element.requestFullscreen(); + } else { + document.exitFullscreen(); + } + } + + return ( + +
+ {isFullscreen ? : } +
+
+ ); +}; diff --git a/web/src/pages/desktop/menu/image/images.tsx b/web/src/pages/desktop/menu/image/images.tsx new file mode 100644 index 0000000..1ad9fb0 --- /dev/null +++ b/web/src/pages/desktop/menu/image/images.tsx @@ -0,0 +1,249 @@ +import { useEffect, useState } from 'react'; +import { Button, Modal, notification, Typography } from 'antd'; +import clsx from 'clsx'; +import { + ArrowBigDownDashIcon, + ArrowBigUpDashIcon, + LoaderCircleIcon, + PackageIcon, + PackageSearchIcon, + Trash2Icon +} from 'lucide-react'; +import { useTranslation } from 'react-i18next'; + +import * as api from '@/api/storage.ts'; +import { client } from '@/lib/websocket.ts'; + +type ImagesProps = { + isOpen: boolean; + cdrom: boolean; + setIsMounted: (isMounted: boolean) => void; +}; + +export const Images = ({ isOpen, cdrom, setIsMounted }: ImagesProps) => { + const { t } = useTranslation(); + const [notify, contextHolder] = notification.useNotification(); + + const [isLoading, setIsLoading] = useState(false); + const [images, setImages] = useState([]); + const [mountingImage, setMountingImage] = useState(''); + const [mountedImage, setMountedImage] = useState(''); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedImage, setSelectedImage] = useState(''); + const [deletingImage, setDeletingImage] = useState(''); + + useEffect(() => { + if (isOpen) { + getImages(); + } + }, [isOpen]); + + // get image list + function getImages() { + if (isLoading) return; + setIsLoading(true); + + api + .getImages() + .then((rsp) => { + if (rsp.code !== 0) { + return; + } + + const files = rsp.data?.files; + + if (files?.length > 0) { + setImages(files); + getMountedImage(); + } else { + setImages([]); + } + }) + .finally(() => { + setIsLoading(false); + }); + } + + // get mounted image + function getMountedImage() { + api.getMountedImage().then((rsp) => { + if (rsp.code !== 0) return; + + const file = rsp.data?.file; + setMountedImage(file); + setIsMounted(!!file); + }); + } + + // mount/unmount image + function mountImage(image: string) { + if (mountingImage) return; + setMountingImage(image); + + client.close(); + + const isMounted = mountedImage === image; + const filename = isMounted ? '' : image; + + api + .mountImage(filename, cdrom) + .then((rsp) => { + if (rsp.code !== 0) { + console.log(rsp.msg); + openNotification(isMounted); + return; + } + + setMountedImage(filename); + setIsMounted(!!filename); + }) + .finally(() => { + setMountingImage(''); + client.connect(); + }); + } + + // show delete image modal + function showDeleteModal(e: any, image: string) { + e.stopPropagation(); + + const isMounted = mountedImage === image; + const isDeleting = deletingImage !== ''; + + if (isMounted || isDeleting) { + return; + } + + setSelectedImage(image); + setIsModalOpen(true); + } + + // delete image + function deleteImage() { + if (!selectedImage || !!deletingImage) return; + setDeletingImage(selectedImage); + + setIsModalOpen(false); + + api + .deleteImage(selectedImage) + .then((rsp) => { + if (rsp.code !== 0) { + console.log(rsp.msg); + return; + } + + getImages(); + + setSelectedImage(''); + }) + .finally(() => { + setDeletingImage(''); + }); + } + + // show mount/unmount failed notification + function openNotification(isMounted: boolean) { + const message = isMounted ? 'image.unmountFailed' : 'image.mountFailed'; + const description = isMounted ? 'image.unmountDesc' : 'image.mountDesc'; + + notify.open({ + message: t(message), + description: t(description), + duration: 10 + }); + } + + // loading + if (isLoading) { + return ( +
+ + {t('image.loading')} +
+ ); + } + + // empty image + if (images.length === 0) { + return ( +
+ + {t('image.empty')} +
+ ); + } + + return ( + <> +
+ {images.map((image) => ( +
mountImage(image)} + > +
+ {mountingImage === image ? ( + + ) : ( + + )} +
+ +
{image.replace(/^.*[\\/]/, '')}
+ +
+ {mountedImage === image ? ( + + ) : ( + + )} +
+ +
showDeleteModal(e, image)} + > + {deletingImage === image ? ( + + ) : ( + + )} +
+
+ ))} +
+ + setIsModalOpen(false)} + > +
+

{t('image.deleteConfirm')}

+ {selectedImage} +
+ +
+ + +
+
+ + {contextHolder} + + ); +}; diff --git a/web/src/pages/desktop/menu/image/index.tsx b/web/src/pages/desktop/menu/image/index.tsx new file mode 100644 index 0000000..80e0c08 --- /dev/null +++ b/web/src/pages/desktop/menu/image/index.tsx @@ -0,0 +1,87 @@ +import { useEffect, useState } from 'react'; +import { Divider, Modal, Segmented } from 'antd'; +import clsx from 'clsx'; +import { DiscIcon, HardDriveIcon } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; + +import * as api from '@/api/storage.ts'; + +import { Images } from './images.tsx'; +import { Tips } from './tips.tsx'; + +export const Image = () => { + const { t } = useTranslation(); + + const [isModalOpen, setIsModalOpen] = useState(false); + const [isMounted, setIsMounted] = useState(false); + const [mode, setMode] = useState('mass-storage'); + + const modes = [ + { + value: 'mass-storage', + label: ( +
+ + Mass Storage +
+ ) + }, + { + value: 'cd-rom', + label: ( +
+ + CD ROM +
+ ) + } + ]; + + useEffect(() => { + api.getMountedImage().then((rsp) => { + if (rsp.code === 0) { + setIsMounted(!!rsp.data?.file); + } + }); + + api.getCdRom().then((rsp) => { + if (rsp.code === 0) { + setMode(rsp.data?.cdrom === 1 ? 'cd-rom' : 'mass-storage'); + } + }); + }, []); + + return ( + <> +
setIsModalOpen(true)} + > + +
+ + setIsModalOpen(false)}> +
+ {t('image.title')} + +
+ + + +
+
+ {t('image.mountMode')} + +
+ + + + +
+
+ + ); +}; diff --git a/web/src/pages/desktop/menu/image/tips.tsx b/web/src/pages/desktop/menu/image/tips.tsx new file mode 100644 index 0000000..e54644f --- /dev/null +++ b/web/src/pages/desktop/menu/image/tips.tsx @@ -0,0 +1,78 @@ +import { useState } from 'react'; +import type { CollapseProps } from 'antd'; +import { Collapse, Modal } from 'antd'; +import { CircleHelpIcon } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; + +export const Tips = () => { + const { t } = useTranslation(); + const [isModalOpen, setIsModalOpen] = useState(false); + + const items: CollapseProps['items'] = [ + { + key: '1', + label: 'USB', + children: ( +
    +
  • {t('image.tips.usb1')}
  • +
  • {t('image.tips.usb2')}
  • +
  • {t('image.tips.usb3')}
  • +
+ ) + }, + { + key: '2', + label: 'SCP', + children: ( +
    +
  • {t('image.tips.scp1')}
  • +
  • {t('image.tips.scp2')}
  • +
  • {t('image.tips.scp3')}
  • +
+ ) + }, + { + key: '3', + label: t('image.tips.tfCard'), + children: ( + <> +

{t('image.tips.tf1')}

+
    +
  • {t('image.tips.tf2')}
  • +
  • {t('image.tips.tf3')}
  • +
  • {t('image.tips.tf4')}
  • +
  • {t('image.tips.tf5')}
  • +
+ + ) + } + ]; + + return ( + <> +
setIsModalOpen(true)} + > + +
+ + setIsModalOpen(false)} + > + + + + ); +}; diff --git a/web/src/pages/desktop/menu/index.tsx b/web/src/pages/desktop/menu/index.tsx new file mode 100644 index 0000000..97cc63c --- /dev/null +++ b/web/src/pages/desktop/menu/index.tsx @@ -0,0 +1,131 @@ +import { useEffect, useRef, useState } from 'react'; +import { Divider, Tooltip } from 'antd'; +import clsx from 'clsx'; +import { useAtom } from 'jotai'; +import { MenuIcon, XIcon } from 'lucide-react'; +import Draggable from 'react-draggable'; +import { useTranslation } from 'react-i18next'; + +import { getMenuDisabledItems } from '@/lib/localstorage.ts'; +import { menuDisabledItemsAtom } from '@/jotai/settings.ts'; + +import { DownloadImage } from './download.tsx'; +import { Fullscreen } from './fullscreen'; +import { Image } from './image'; +import { Keyboard } from './keyboard'; +import { Mouse } from './mouse'; +import { Power } from './power'; +import { Screen } from './screen'; +import { Script } from './script'; +import { Settings } from './settings'; +import { Terminal } from './terminal'; +import { Wol } from './wol'; + +export const Menu = () => { + const { t } = useTranslation(); + const [menuDisabledItems, setMenuDisabledItems] = useAtom(menuDisabledItemsAtom); + + const [isMenuOpen, setIsMenuOpen] = useState(true); + const [bounds, setBounds] = useState({ left: 0, right: 0, top: 0, bottom: 0 }); + + const nodeRef = useRef(null); + + useEffect(() => { + // disabled menu items + const items = getMenuDisabledItems(); + setMenuDisabledItems(items); + + // react-draggable bounds + const handleResize = () => { + if (!nodeRef.current) return; + + const elementRect = nodeRef.current.getBoundingClientRect(); + const width = (window.innerWidth - elementRect.width) / 2; + + setBounds({ + left: -width, + top: -10, + right: width, + bottom: window.innerHeight - elementRect.height - 10 + }); + }; + + handleResize(); + + window.addEventListener('resize', handleResize); + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + return ( + +
+
+
+ +
+ sipeed +
+
+ + + + + + + {!menuDisabledItems.includes('image') && } + {!menuDisabledItems.includes('download') && } + {!menuDisabledItems.includes('script') &&