commit 48661005be616d7c52e8985489422eb8e1097a2d Author: Sagi Dayan Date: Tue Jan 16 17:21:35 2024 +0200 initial commit Signed-off-by: Sagi Dayan diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..8b44f70 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,76 @@ +# This file contains all available configuration options +# with their default values. + +# options for analysis running +run: + # default concurrency is a available CPU number + concurrency: 4 + + # timeout for analysis, e.g. 30s, 5m, default is 1m + timeout: 10m + + # exit code when at least one issue was found, default is 1 + issues-exit-code: 1 + + # include test files or not, default is true + tests: true + + # which dirs to skip: issues from them won't be reported; + # can use regexp here: generated.*, regexp is applied on full path; + # default value is empty list, but default dirs are skipped independently + # from this option's value (see skip-dirs-use-default). + skip-dirs: + - bin + - deploy + - docs + - examples + - hack + - packaging + - reports + +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" + format: colored-line-number + + # print lines of code with issue, default is true + print-issued-lines: true + + # print linter name in the end of issue text, default is true + print-linter-name: true + + # make issues output unique by line, default is true + uniq-by-line: true + +issues: + # List of regexps of issue texts to exclude, empty list by default. + # But independently from this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. To list all + # excluded by default patterns execute `golangci-lint run --help` + exclude: + - 'declaration of "err" shadows declaration at' + +linters: + enable: + - megacheck + - govet + - gocyclo + - gofmt + - gosec + - megacheck + - unconvert + - gci + - goimports + - exportloopref + +linters-settings: + govet: + check-shadowing: true + + settings: + printf: + funcs: + - Infof + - Warnf + - Errorf + - Fatalf 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/Makefile b/Makefile new file mode 100644 index 0000000..9a411f3 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +BINARY_NAME=subsonic-tui +BUILD_FOLDER=build + + +.PHONY: build +build: + GOARCH=amd64 GOOS=darwin go build -o ${BUILD_FOLDER}/${BINARY_NAME}-darwin main.go + GOARCH=amd64 GOOS=linux go build -o ${BUILD_FOLDER}/${BINARY_NAME}-linux main.go + GOARCH=amd64 GOOS=windows go build -o ${BUILD_FOLDER}/${BINARY_NAME}-windows main.go + +run: build + ./${BINARY_NAME} + +clean: + go clean + rm ${BINARY_NAME}-darwin + rm ${BINARY_NAME}-linux + rm ${BINARY_NAME}-windows + +test: + go test ./... + +test_coverage: + go test ./... -coverprofile=coverage.out + +dep: + go mod download + +vet: + go vet + +lint: + golangci-lint run --enable-all diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a919a7 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# Subsonic TUI + +**NOTE ⚠ !!: This is under heavy development! Do not fork just yet. Im still force pushing to main!** + +A Subsonic client and player written in go. + +subsonictui (Name in progress) is a simple and easy to use player for Linux, Mac and (yes...) Windows. + +

+ Screenshot + Screenshot mini player +

+ +> Note: Screenshots blurred for (I dont know) copyright issues? + +## Features + + - [x] Browse Artists/Albums/Playlists + - [x] Artist view + - [x] Playlist view + - [x] Miniplayer on small screen + - [x] Album view + - [x] Search + - [x] Playback + - [x] Scrobble (configurable) + - [x] Play album + - [x] Shuffle album + - [x] Add album to queue + - [x] Add song to queue + - [x] Play Next/Prev + - [x] Stop/Pause + - [x] Generate artist radio + - [x] Generate song radio + - [-] Desktop integration + - [x] Linux (MPRIS) + - [ ] Windows + - [ ] MacOS + - [ ] Playlist management + - [ ] Add song to playlist + - [ ] Create playlist from queue + + +## Keybindings +| Key(s) | Action | +|------------------------------------------------------------------------------|-------------------------------------------------| +| ` | Focus on Main pane | +| 1 | Focus on Atrists pane | +| 2 | Focus on Albums pane | +| 3 | Focus on Playlists pane | +| 4 | Focus on Queue pane | +| Arrow keys or h j k l | Navigation in a pane. Shift for switching panes | +| g | Jump to first item in a list of a pane | +| G | Jump to last item in a list of a pane | +| n | Next song | +| N | Prev song | +| q | Exit | +| r | While on a song - Start Song radio | +| s | Playback - Stop | +| p | Playback - Toggle Play/Pause | +| c | Stop, clear queue | +| / | Search | +| ? | Help | + +## Config +`subsonictui` stores a config file at: +- Linux: `$HOME/.config/subsonictui/config.yaml` +- macOS: `$HOME/Library/Application Support/subsonictui/config.yaml` +- Windows: `C:\\Users\%USER%\AppData\Roaming\subsonictui\config.yaml` + + +## Development +### Build Dependencies +subsonictui uses [Beep](https://github.com/faiface/beep), that uses [OTO](https://github.com/hajimehoshi/oto) under the hood, so you will need OTO dependencies. +Mainly on Linux you will need `alsa-devel`. + +To Build the project: +``` +$ make build +``` + +## Special Thanks +subsonictui is built on top of a few projects. I would like to thank them here + - [go-subsonic](https://github.com/delucks/go-subsonic) + - [OTO](https://github.com/hajimehoshi/oto) + - [Beep](https://github.com/faiface/beep) + - [tview](https://github.com/rivo/tview) + - [tcell](https://github.com/gdamore/tcell) + - [MPRIS server](https://github.com/quarckster/go-mpris-server) + +Thank you for creating wondeful software that everyone can use. Including myself. + diff --git a/assets/screenshot-miniplayer.png b/assets/screenshot-miniplayer.png new file mode 100644 index 0000000..e81e645 Binary files /dev/null and b/assets/screenshot-miniplayer.png differ diff --git a/assets/screenshot.png b/assets/screenshot.png new file mode 100644 index 0000000..ed18319 Binary files /dev/null and b/assets/screenshot.png differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8cedf7f --- /dev/null +++ b/go.mod @@ -0,0 +1,28 @@ +module git.dayanhub.com/sagi/subsonic-tui + +go 1.21.5 + +require ( + github.com/creasty/defaults v1.7.0 + github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5 + github.com/gdamore/tcell/v2 v2.7.4 + github.com/godbus/dbus/v5 v5.1.0 + github.com/gopxl/beep v1.4.0 + github.com/quarckster/go-mpris-server v1.0.3 + github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/ebitengine/oto/v3 v3.1.1 // indirect + github.com/ebitengine/purego v0.6.1 // indirect + github.com/gdamore/encoding v1.0.1 // indirect + github.com/hajimehoshi/go-mp3 v0.3.4 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1f053de --- /dev/null +++ b/go.sum @@ -0,0 +1,83 @@ +github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA= +github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5 h1:RuuxidatioSKGOiBzL1mTY4X22DQD8weEbS3iRLHnAg= +github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo= +github.com/ebitengine/oto/v3 v3.1.1 h1:utFNkSF4yXqA7VhMg7oHp3OSdz3vuzJQ42rCDnd8pc8= +github.com/ebitengine/oto/v3 v3.1.1/go.mod h1:bQM4zk9glIVjTynn8X0Lp1zngTlZltFFfzJvx543vdA= +github.com/ebitengine/purego v0.6.1 h1:sjN8rfzbhXQ59/pE+wInswbU9aMDHiwlup4p/a07Mkg= +github.com/ebitengine/purego v0.6.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= +github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= +github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= +github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gopxl/beep v1.4.0 h1:pJERVDZMJkf49R1g/tV9DhVct4xNRuTlyMnMa53gGsc= +github.com/gopxl/beep v1.4.0/go.mod h1:gGVz7MJKlfHrmkzr0wSLGNyY7oisM6rFWJnaLjNxEwA= +github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68= +github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo= +github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quarckster/go-mpris-server v1.0.3 h1:ef6d3DpxlORtdEBHnhQ/j3gS0Z3+YUfXeJhC9L9DZvA= +github.com/quarckster/go-mpris-server v1.0.3/go.mod h1:2b4IdrpnEoEfU+6fQKjYhAgdvsiz4JxmTpDAUrMJVO4= +github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 h1:bWLHTRekAy497pE7+nXSuzXwwFHI0XauRzz6roUvY+s= +github.com/rivo/tview v0.0.0-20240307173318-e804876934a1/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/client/artwork_cache.go b/internal/client/artwork_cache.go new file mode 100644 index 0000000..c3049e6 --- /dev/null +++ b/internal/client/artwork_cache.go @@ -0,0 +1,97 @@ +package client + +import ( + "fmt" + "image" + "image/jpeg" + "os" + "path" +) + +type artcache struct { + artPaths map[string]string + cacheDir string +} + +var ArtCache *artcache + +func (c *artcache) saveArt(id string, img image.Image) *string { + path := c.GetPath(id) + if path != nil { + return path + } + + path = c.saveImage(id, img) + if path != nil { + c.artPaths[id] = *path + } + return path +} + +func (c *artcache) saveImage(id string, img image.Image) *string { + filePath := c.filepath(id) + f, err := os.Create(filePath) + + defer func() { + _ = f.Close() + }() + + if err != nil { + return nil + } + err = jpeg.Encode(f, img, nil) + if err != nil { + return nil + } + return &filePath +} + +func (c *artcache) GetPath(id string) *string { + if path, ok := c.artPaths[id]; ok { + return &path + } + return nil +} + +func (c *artcache) GetImage(id string) *image.Image { + path := c.GetPath(id) + if path == nil { + return nil + } + + f, err := os.Open(*path) + if err != nil { + return nil + } + + defer func() { + _ = f.Close() + }() + + img, err := jpeg.Decode(f) + if err != nil { + return nil + } + return &img +} + +func (c *artcache) filepath(id string) string { + return path.Join(c.cacheDir, fmt.Sprintf("%s.jpg", id)) +} + +func (c *artcache) Destroy() { + os.RemoveAll(c.cacheDir) +} + +func init() { + tmpDir := os.TempDir() + cacheDir := path.Join(tmpDir, fmt.Sprintf("subsonic-tui-%d", os.Getpid())) + err := os.Mkdir(cacheDir, 0777) + if err != nil { + panic("Failed to create cacheDir") + } + ArtCache = &artcache{ + cacheDir: cacheDir, + artPaths: make(map[string]string), + } +} diff --git a/internal/client/client.go b/internal/client/client.go new file mode 100644 index 0000000..2115e82 --- /dev/null +++ b/internal/client/client.go @@ -0,0 +1,163 @@ +package client + +import ( + "fmt" + "image" + "io" + "net/http" + "sync" + + "git.dayanhub.com/sagi/subsonic-tui/internal/common" + "github.com/delucks/go-subsonic" +) + +type Client struct { + client subsonic.Client +} + +func NewClient(baseURL string) *Client { + var client subsonic.Client = subsonic.Client{ + Client: &http.Client{}, + ClientName: "subsonic-tui", + BaseUrl: baseURL, + PasswordAuth: true, + } + + return &Client{ + client: client, + } +} + +func (c *Client) Authenticate(username, password string) error { + c.client.User = username + return c.client.Authenticate(password) +} + +func (c *Client) GetUser() (*subsonic.User, error) { + return c.client.GetUser(c.client.User) +} + +func (c *Client) GetPlaylists() ([]*subsonic.Playlist, error) { + return c.client.GetPlaylists(map[string]string{}) +} + +func (c *Client) GetPlaylist(ID string) (*subsonic.Playlist, error) { + return c.client.GetPlaylist(ID) +} + +func (c *Client) GetArtists() ([]*subsonic.ArtistID3, error) { + indexes, err := c.client.GetArtists(map[string]string{}) + if err != nil { + return nil, err + } + artists := []*subsonic.ArtistID3{} + for _, i := range indexes.Index { + artists = append(artists, i.Artist...) + } + + return artists, nil +} + +func (c *Client) GetAlbums() ([]*subsonic.AlbumID3, error) { + return c.client.GetAlbumList2("alphabeticalByName", map[string]string{ + "size": "500", + }) +} + +func (c *Client) GetArtist(ID string) (*subsonic.ArtistID3, error) { + return c.client.GetArtist(ID) +} + +func (c *Client) GetArtistInfo(ID string) (*subsonic.ArtistInfo2, error) { + return c.client.GetArtistInfo2(ID, map[string]string{ + "count": "20", + }) +} + +func (c *Client) GetAlbum(ID string) (*subsonic.AlbumID3, error) { + return c.client.GetAlbum(ID) +} + +func (c *Client) GetCoverArt(ID string) (image.Image, error) { + if img := ArtCache.GetImage(ID); img != nil { + return *img, nil + } + img, err := c.client.GetCoverArt(ID, map[string]string{ + //"size": "64", + }) + if err != nil { + return nil, err + } + ArtCache.saveArt(ID, img) + return img, err +} + +func (c *Client) GetSimilarSongs(artistID string, maxSongs int) ([]*subsonic.Child, error) { + max := fmt.Sprintf("%d", maxSongs) + return c.client.GetSimilarSongs2(artistID, map[string]string{ + "count": max, + }) +} + +func (c *Client) Stream(ID string) (io.Reader, error) { + return c.client.Stream(ID, map[string]string{ + "format": "mp3", + }) +} + +func (c *Client) Scrobble(ID string) error { + return c.client.Scrobble(ID, map[string]string{}) +} + +func (c *Client) GetTopSongs(name string, max int) ([]*subsonic.Child, error) { + count := fmt.Sprintf("%d", max) + return c.client.GetTopSongs(name, map[string]string{ + "count": count, + }) +} + +func (c *Client) Search(query string) (*subsonic.SearchResult3, error) { + return c.client.Search3(query, map[string]string{ + "artistCount": "20", + "songCount": "20", + "albumCount": "20", + }) +} + +func (c *Client) GetExperimentalArtistRadio(artistId3 *subsonic.ArtistID3, info *subsonic.ArtistInfo2, max int) ([]*subsonic.Child, error) { + var wg sync.WaitGroup + ID := artistId3.ID + similarArtists := info.SimilarArtist + songs := []*subsonic.Child{} + similarArtistsSongs := 10 + thisArtistFactor := 3 + portion := len(info.SimilarArtist) * similarArtistsSongs * thisArtistFactor + wg.Add(2) + go func() { + s, _ := c.GetSimilarSongs(ID, portion) + songs = append(songs, s...) + wg.Done() + }() + go func() { + s, _ := c.GetTopSongs(artistId3.Name, similarArtistsSongs) + songs = append(songs, s...) + wg.Done() + }() + common.ShuffleSlice(similarArtists) + for _, a := range similarArtists { + wg.Add(1) + artist := a + go func() { + s, _ := c.GetSimilarSongs(artist.ID, similarArtistsSongs) + songs = append(songs, s...) + wg.Done() + }() + } + wg.Wait() + if max > len(songs) { + max = len(songs) + } + songs = songs[:max] + common.ShuffleSlice(songs) + return songs, nil +} diff --git a/internal/common/shuffle.go b/internal/common/shuffle.go new file mode 100644 index 0000000..82d420f --- /dev/null +++ b/internal/common/shuffle.go @@ -0,0 +1,21 @@ +package common + +import ( + "crypto/rand" + "math/big" + "reflect" +) + +func ShuffleSlice(slice interface{}) { + rv := reflect.ValueOf(slice) + swap := reflect.Swapper(slice) + + length := rv.Len() + for i := length - 1; i > 0; i-- { + j, err := rand.Int(rand.Reader, big.NewInt(int64(i+1))) + if err != nil { + panic("Shuffle error") + } + swap(i, int(j.Int64())) + } +} diff --git a/internal/config/colors.go b/internal/config/colors.go new file mode 100644 index 0000000..5442938 --- /dev/null +++ b/internal/config/colors.go @@ -0,0 +1,21 @@ +package config + +import ( + "github.com/gdamore/tcell/v2" +) + +const ( + ColorBackground = tcell.ColorDefault + ColorSelectedBoarder = tcell.ColorRed + ColorBluredBoarder = tcell.ColorWhite + ColorText = tcell.ColorWhite + ColorTextAccent = tcell.ColorYellow + ColorPlaybackProgressElapsed = tcell.ColorLightCyan + ColorPlaybackProgressRemaining = tcell.ColorBlack + ColorQueuePlayedBg = tcell.ColorBlack + ColorQueuePlayingBg = tcell.ColorDarkRed + ColorButtonBg = tcell.ColorBlue + ColorButtonTxt = tcell.ColorBlack + ColorButtonSelectedBg = tcell.ColorYellow + ColorButtonSelectedTxt = tcell.ColorDarkRed +) diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..7d10602 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,123 @@ +package config + +import ( + "encoding/base64" + "fmt" + "os" + "path" + "strings" + + "github.com/creasty/defaults" + "gopkg.in/yaml.v3" +) + +var configPath string + +type _config struct { + Username string `yaml:"username"` + Password string `yaml:"password"` + URL string `yaml:"url"` + EnableScrobble bool `yaml:"enable_scrobble" default:"false"` + MaxRadioSongs int `yaml:"max_radio_songs" default:"50"` + ExperimentalRadioAlgo bool `yaml:"experimental_radio_algo" default:"false"` +} + +var configStruct *_config + +func init() { + userConfigDir, err := os.UserConfigDir() + configDir := path.Join(userConfigDir, "subsonictui") + configPath = path.Join(configDir, "config.yaml") + + if err != nil { + fmt.Printf("[ERROR] Failed to fetch user config directory. %e\n", err) + os.Exit(1) + } + if _, err := os.Stat(configDir); os.IsNotExist(err) { + err := os.MkdirAll(configDir, 0700) + if err != nil { + panic(err) + } + } + var configFile *os.File + if _, err := os.Stat(configPath); os.IsNotExist(err) { + configFile, err = os.Create(configPath) + defer func() { + err := configFile.Close() + if err != nil { + panic(err) + } + }() + if err != nil { + fmt.Printf("[ERROR] Failed to create config file @ %s. %e\n", configPath, err) + os.Exit(1) + } + } + configStruct, err = loadConfig() + if err != nil { + fmt.Printf("[ERROR] Failed to load config file @ %s. %e\n", configPath, err) + os.Exit(1) + } + fmt.Printf("Init Config %s\n", configPath) +} + +func URL() string { + return configStruct.URL +} +func Username() string { + return configStruct.Username +} +func Password() string { + p, _ := base64.StdEncoding.DecodeString(configStruct.Password) + return strings.TrimSpace(string(p)) +} +func ScrobbleEnabled() bool { + return configStruct.EnableScrobble +} +func MaxRadioSongs() int { + return configStruct.MaxRadioSongs +} +func ExperimentalRadioAlgo() bool { + return configStruct.ExperimentalRadioAlgo +} + +func SetPassword(p string) { + configStruct.Password = base64.StdEncoding.EncodeToString([]byte(p)) +} + +func SetUsername(u string) { + configStruct.Username = u +} + +func SetURL(u string) { + configStruct.URL = u +} + +func loadConfig() (*_config, error) { + c := &_config{} + err := defaults.Set(c) + if err != nil { + panic(err) + } + file, err := os.ReadFile(configPath) + if err != nil { + return nil, err + } + if err = yaml.Unmarshal(file, c); err != nil { + return nil, err + } + return c, nil +} + +func SaveConfig() { + yml, err := yaml.Marshal(configStruct) + if err != nil { + fmt.Printf("[ERROR] Failed to convert config to yaml. %e\n", err) + os.Exit(1) + } + err = os.WriteFile(configPath, yml, 0600) + if err != nil { + fmt.Printf("[ERROR] Failed to save config file @ %s. %e\n", configPath, err) + os.Exit(1) + } +} diff --git a/internal/playback/controller.go b/internal/playback/controller.go new file mode 100644 index 0000000..69cb8b8 --- /dev/null +++ b/internal/playback/controller.go @@ -0,0 +1,223 @@ +package playback + +import ( + "io" + "time" + + "git.dayanhub.com/sagi/subsonic-tui/internal/client" + "github.com/delucks/go-subsonic" + "github.com/gopxl/beep" + "github.com/gopxl/beep/mp3" + "github.com/gopxl/beep/speaker" +) + +type PlaybackState int + +const ( + PlaybackStateStopped = iota + PlaybackStatePaused + PlaybackStatePlaying +) + +type Controller struct { + client *client.Client + stream beep.StreamSeekCloser + song *subsonic.Child + songElapsedFunc func(song *subsonic.Child, elapsed time.Duration) + initialFormat *beep.Format + currentFormat *beep.Format + position float64 + closeChan chan bool + songEndedChan chan bool + songEndedFunc func(song *subsonic.Child) + ctrl *beep.Ctrl + desktopPlayback DesktopPlayback + queue *queue + playbackState PlaybackState +} + +func NewController(client *client.Client) *Controller { + controller := &Controller{ + client: client, + closeChan: make(chan bool), + songEndedChan: make(chan bool), + playbackState: PlaybackStateStopped, + queue: newQueue(), + } + controller.desktopPlayback = desktopPlayer(controller) + controller.desktopPlayback.Start() + go controller.playbackTicker() + return controller +} + +func (c *Controller) State() PlaybackState { + return c.playbackState +} + +func (c *Controller) Play(song *subsonic.Child) { + if song == nil { + return + } + r, err := c.client.Stream(song.ID) + if err != nil { + //TODO: Log error + c.Stop() + return + } + c.Stream(r, song) +} +func (c *Controller) Next() { + song := c.queue.Next() + if song != nil { + c.Play(song) + } +} + +func (c *Controller) AddToQueue(songs []*subsonic.Child) { + shouldPlay := c.queue.Add(songs...) + if shouldPlay { + c.Play(c.queue.GetCurrentSong()) + } + c.desktopPlayback.OnPlaylistChanged() +} + +func (c *Controller) GetQueuePosition() int { + return c.queue.GetPosition() +} + +func (c *Controller) GetCurrentSong() *subsonic.Child { + return c.queue.GetCurrentSong() +} + +func (c *Controller) SetQueue(songs []*subsonic.Child) { + c.queue.Clear() + c.Stop() + c.queue.Set(songs) + c.Play(c.queue.GetCurrentSong()) + c.desktopPlayback.OnPlaylistChanged() +} + +func (c *Controller) SetQueuePosition(position int) { + s := c.queue.SetPosition(position) + c.Play(s) +} + +func (c *Controller) Prev() { + song := c.queue.Prev() + if song != nil { + c.Play(song) + } +} +func (c *Controller) GetQueue() []*subsonic.Child { + return c.queue.Get() +} + +func (c *Controller) ClearQueue() { + c.Stop() + c.queue.Clear() + c.desktopPlayback.OnPlayPause() + c.desktopPlayback.OnSongChanged() + c.desktopPlayback.OnPlaylistChanged() +} + +func (c *Controller) SetSongElapsedFunc(f func(sing *subsonic.Child, elapsed time.Duration)) { + c.songElapsedFunc = f +} + +func (c *Controller) SetSongEndedFunc(f func(song *subsonic.Child)) { + c.songEndedFunc = f +} + +func (c *Controller) Close() error { + c.Stop() + c.closeChan <- true + return nil +} + +func (c *Controller) TogglePlayPause() { + if c.playbackState != PlaybackStateStopped { + c.ctrl.Paused = !c.ctrl.Paused + if c.ctrl.Paused { + c.playbackState = PlaybackStatePaused + } else { + c.playbackState = PlaybackStatePlaying + } + } + c.desktopPlayback.OnPlayPause() +} + +func (c *Controller) Stop() { + if c.ctrl == nil { + return + } + speaker.Clear() + c.ctrl.Paused = true + c.playbackState = PlaybackStateStopped + c.ctrl = nil + c.stream = nil + c.songElapsedFunc(c.song, time.Duration(0)) + c.song = nil + c.position = 0 + c.desktopPlayback.OnPlayPause() + c.desktopPlayback.OnPlaylistChanged() +} + +func (c *Controller) playbackTicker() { + for { + select { + case <-c.closeChan: + return + case <-c.songEndedChan: + c.Stop() + c.Next() + default: + if c.playbackState == PlaybackStatePlaying && c.song != nil { + if c.stream != nil { + pos := c.stream.Position() + elapsed := c.currentFormat.SampleRate.D(pos).Round(time.Second) + c.position = elapsed.Seconds() + c.songElapsedFunc(c.song, elapsed) + c.desktopPlayback.OnPositionChanged(int(c.position)) + } + } + } + time.Sleep(time.Second) + } +} + +func (c *Controller) Stream(reader io.Reader, song *subsonic.Child) { + c.Stop() + // Ensure artwork cache... + _, _ = c.client.GetCoverArt(song.CoverArt) + + readerCloser := io.NopCloser(reader) + decodedMp3, format, err := mp3.Decode(readerCloser) + decodedMp3.Position() + if err != nil { + panic("mp3.NewDecoder failed: " + err.Error()) + } + + if c.initialFormat == nil { + c.initialFormat = &format + } + + c.currentFormat = &format + + var stream beep.Streamer = decodedMp3 + if err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)); err != nil { + stream = beep.Resample(3, format.SampleRate, c.initialFormat.SampleRate, decodedMp3) + } + + c.stream = decodedMp3 + c.song = song + + ctrl := &beep.Ctrl{Streamer: stream} + c.ctrl = ctrl + c.playbackState = PlaybackStatePlaying + speaker.Play(beep.Seq(ctrl, beep.Callback(func() { + c.songEndedFunc(song) + c.songEndedChan <- true + }))) + c.desktopPlayback.OnSongChanged() + c.desktopPlayback.OnPlayPause() +} diff --git a/internal/playback/desktop.go b/internal/playback/desktop.go new file mode 100644 index 0000000..f5f7d6f --- /dev/null +++ b/internal/playback/desktop.go @@ -0,0 +1,10 @@ +package playback + +type DesktopPlayback interface { + Start() + Stop() error + OnPlayPause() + OnPlaylistChanged() + OnSongChanged() + OnPositionChanged(sec int) +} diff --git a/internal/playback/mpris.go b/internal/playback/mpris.go new file mode 100644 index 0000000..dd7e83c --- /dev/null +++ b/internal/playback/mpris.go @@ -0,0 +1,249 @@ +package playback + +import ( + "encoding/base32" + "fmt" + + "git.dayanhub.com/sagi/subsonic-tui/internal/client" + "github.com/delucks/go-subsonic" + "github.com/godbus/dbus/v5" + "github.com/quarckster/go-mpris-server/pkg/events" + "github.com/quarckster/go-mpris-server/pkg/server" + . "github.com/quarckster/go-mpris-server/pkg/types" +) + +const ( + mprisPlayerNmae = "MehSonic" + mprisNoTrack = "/org/mpris/MediaPlayer2/TrackList/NoTrack" +) + +type mprisRoot struct{} + +func (r mprisRoot) Raise() error { + return nil +} +func (r mprisRoot) Quit() error { + return nil +} +func (r mprisRoot) CanQuit() (bool, error) { + return true, nil +} +func (r mprisRoot) CanRaise() (bool, error) { + return false, nil +} +func (r mprisRoot) HasTrackList() (bool, error) { + return false, nil +} +func (r mprisRoot) Identity() (string, error) { + return mprisPlayerNmae, nil +} +func (r mprisRoot) SupportedUriSchemes() ([]string, error) { + return []string{}, nil +} +func (r mprisRoot) SupportedMimeTypes() ([]string, error) { + return []string{}, nil +} + +type mprisPlayer struct { + ctrl *Controller +} + +// Implement other methods of `pkg.types.OrgMprisMediaPlayer2PlayerAdapter` +func (p mprisPlayer) Next() error { + p.ctrl.Next() + return nil +} +func (p mprisPlayer) Previous() error { + p.ctrl.Prev() + return nil +} +func (p mprisPlayer) Pause() error { + if p.ctrl.State() == PlaybackStatePlaying { + p.ctrl.TogglePlayPause() + } + return nil +} +func (p mprisPlayer) PlayPause() error { + switch p.ctrl.State() { + case PlaybackStatePaused, PlaybackStatePlaying: + p.ctrl.TogglePlayPause() + case PlaybackStateStopped: + p.ctrl.Play(p.ctrl.GetCurrentSong()) + } + return nil +} +func (p mprisPlayer) Stop() error { + p.ctrl.Stop() + return nil +} +func (p mprisPlayer) Play() error { + switch p.ctrl.State() { + case PlaybackStatePaused: + p.ctrl.TogglePlayPause() + case PlaybackStateStopped: + p.ctrl.Play(p.ctrl.GetCurrentSong()) + } + return nil +} +func (p mprisPlayer) Seek(offset Microseconds) error { + return nil +} +func (p mprisPlayer) SetPosition(trackId string, position Microseconds) error { + return nil +} +func (p mprisPlayer) OpenUri(uri string) error { + return nil +} +func (p mprisPlayer) PlaybackStatus() (PlaybackStatus, error) { + switch p.ctrl.State() { + case PlaybackStatePlaying: + return PlaybackStatusPlaying, nil + case PlaybackStatePaused: + return PlaybackStatusPaused, nil + case PlaybackStateStopped: + return PlaybackStatusStopped, nil + } + // Should not get here + return PlaybackStatusStopped, nil + +} +func (p mprisPlayer) Rate() (float64, error) { + return 1, nil +} +func (p mprisPlayer) SetRate(float64) error { + return nil +} +func (p mprisPlayer) Metadata() (Metadata, error) { + s := p.ctrl.GetCurrentSong() + objPath := mprisNoTrack + if s != nil { + objPath = encodeTrackId(s.ID) + } else { + s = &subsonic.Child{} + } + md := Metadata{ + TrackId: dbus.ObjectPath(objPath), + Length: secondsToMicroseconds(s.Duration), + Title: s.Title, + Album: s.Album, + Artist: []string{s.Artist}, + DiscNumber: s.DiscNumber, + Genre: []string{s.Genre}, + TrackNumber: s.Track, + UserRating: float64(s.UserRating), + UseCount: int(s.PlayCount), + } + artw := client.ArtCache.GetPath(s.CoverArt) + if artw != nil { + md.ArtUrl = fmt.Sprintf("file://%s", *artw) + } + return md, nil +} +func (p mprisPlayer) Volume() (float64, error) { + return 1, nil +} +func (p mprisPlayer) SetVolume(float64) error { + return nil +} +func (p mprisPlayer) Position() (int64, error) { + return int64(secondsToMicroseconds(int(p.ctrl.position))), nil +} +func (p mprisPlayer) MinimumRate() (float64, error) { + return 1, nil +} +func (p mprisPlayer) MaximumRate() (float64, error) { + return 1, nil +} +func (p mprisPlayer) CanGoNext() (bool, error) { + return p.ctrl.queue.HasNext(), nil +} +func (p mprisPlayer) CanGoPrevious() (bool, error) { + return p.ctrl.queue.HasPrev(), nil +} +func (p mprisPlayer) CanPlay() (bool, error) { + return p.ctrl.GetCurrentSong() != nil, nil +} +func (p mprisPlayer) CanPause() (bool, error) { + return true, nil +} +func (p mprisPlayer) CanSeek() (bool, error) { + return false, nil +} +func (p mprisPlayer) CanControl() (bool, error) { + return true, nil +} + +var _ DesktopPlayback = &mprisPlayback{} + +type mprisPlayback struct { + root mprisRoot + player mprisPlayer + eventHandler *events.EventHandler + server *server.Server + err error +} + +func (p *mprisPlayback) Start() { + go func() { + p.err = p.server.Listen() + }() +} + +func (p *mprisPlayback) Stop() error { + return p.server.Stop() +} + +func (p *mprisPlayback) OnPlayPause() { + if p.err != nil { + return + } + p.err = p.eventHandler.Player.OnPlayPause() +} +func (p *mprisPlayback) OnPlaylistChanged() { + if p.err != nil { + return + } + p.err = p.eventHandler.Player.OnOptions() +} +func (p *mprisPlayback) OnSongChanged() { + if p.err != nil { + return + } + p.err = p.eventHandler.Player.OnTitle() +} + +func (p *mprisPlayback) OnPositionChanged(position int) { + if p.err != nil { + return + } + p.err = p.eventHandler.Player.OnSeek(secondsToMicroseconds(position)) + if p.err != nil { + return + } + p.err = p.eventHandler.Player.OnOptions() +} + +func desktopPlayer(c *Controller) DesktopPlayback { + r := mprisRoot{} + p := mprisPlayer{ + ctrl: c, + } + s := server.NewServer(mprisPlayerNmae, r, p) + ev := events.NewEventHandler(s) + + return &mprisPlayback{ + root: r, + player: p, + server: s, + eventHandler: ev, + } +} + +func secondsToMicroseconds(s int) Microseconds { + return Microseconds(s * 1_000_000) +} + +func encodeTrackId(id string) string { + data := []byte(id) + return fmt.Sprintf("/%s/Track/%s", mprisPlayerNmae, base32.StdEncoding.WithPadding('0').EncodeToString(data)) +} diff --git a/internal/playback/queue.go b/internal/playback/queue.go new file mode 100644 index 0000000..2800f28 --- /dev/null +++ b/internal/playback/queue.go @@ -0,0 +1,84 @@ +package playback + +import "github.com/delucks/go-subsonic" + +type queue struct { + currentSong int + songQueue []*subsonic.Child +} + +func newQueue() *queue { + return &queue{ + currentSong: 0, + songQueue: []*subsonic.Child{}, + } +} + +func (q *queue) Set(songs []*subsonic.Child) { + q.currentSong = 0 + q.songQueue = songs +} + +func (q *queue) Clear() { + q.currentSong = 0 + q.songQueue = []*subsonic.Child{} +} + +// returns true if queue was empty before addition +func (q *queue) Add(songs ...*subsonic.Child) bool { + shouldStartPlaying := len(q.songQueue) == 0 + q.songQueue = append(q.songQueue, songs...) + if shouldStartPlaying { + q.currentSong = 0 + return true + } + return false +} + +// returns a song if position has changed +func (q *queue) SetPosition(position int) *subsonic.Child { + if position == q.currentSong || position < 0 || len(q.songQueue) < position { + return nil + } + q.currentSong = position + return q.GetCurrentSong() +} + +func (q *queue) GetPosition() int { + return q.currentSong +} + +func (q *queue) GetCurrentSong() *subsonic.Child { + if len(q.songQueue) == 0 { + return nil + } + return q.songQueue[q.currentSong] +} + +func (q *queue) HasPrev() bool { + return 0 < q.currentSong +} + +func (q *queue) HasNext() bool { + return q.currentSong < len(q.songQueue)-1 +} + +func (q *queue) Next() *subsonic.Child { + if len(q.songQueue) > q.currentSong+1 { + q.currentSong = q.currentSong + 1 + return q.GetCurrentSong() + } + return nil +} + +func (q *queue) Prev() *subsonic.Child { + if q.currentSong > 0 { + q.currentSong = q.currentSong - 1 + return q.GetCurrentSong() + } + return nil +} + +func (q *queue) Get() []*subsonic.Child { + return q.songQueue +} diff --git a/internal/tui/tui.go b/internal/tui/tui.go new file mode 100644 index 0000000..9927162 --- /dev/null +++ b/internal/tui/tui.go @@ -0,0 +1,124 @@ +package tui + +import ( + "fmt" + "os" + + "git.dayanhub.com/sagi/subsonic-tui/internal/client" + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "git.dayanhub.com/sagi/subsonic-tui/internal/playback" + "git.dayanhub.com/sagi/subsonic-tui/internal/tui/views" + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +type TUI struct { + app *tview.Application + layout views.View + + client *client.Client + playbackCtl *playback.Controller +} + +func NewLogin() *TUI { + app := tview.NewApplication() + layout := views.NewLoginView(func(u, p, url string) { + c := client.NewClient(url) + err := c.Authenticate(u, p) + if err != nil { + app.Stop() + fmt.Printf("[Error] Failed to login. Aborting %e", err) + os.Exit(1) + } + config.SetURL(url) + config.SetUsername(u) + config.SetPassword(p) + config.SaveConfig() + app.Stop() + }, func() { + app.Stop() + os.Exit(0) + }) + app.EnableMouse(true). + SetRoot(layout.GetView(), true) + + app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEsc { + app.Stop() + os.Exit(0) + } + return event + }) + + return &TUI{ + app: app, + layout: layout, + } +} + +func NewPlayer(client *client.Client, playbackCtl *playback.Controller) *TUI { + app := tview.NewApplication() + + layout := views.NewLayout(client, playbackCtl, func() { + go app.Draw() + }) + help := views.NewHelp() + + pages := tview.NewPages() + pages.AddPage("app", layout.GetView(), true, true) + pages.AddPage("help", help.GetView(), true, false) + + help.SetKeyPressedFunc(func() { + pages.SwitchToPage("app") + }) + app.EnableMouse(true). + SetRoot(pages, true) + + app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if currentPage, _ := pages.GetFrontPage(); currentPage == "help" { + pages.SwitchToPage("app") + help.GetView().Blur() + layout.GetView().Focus(nil) + return nil + } + if layout.Mode() == views.StatusModeSearch { + return event + } + if event.Rune() == 'q' { + app.Stop() + fmt.Println("Exiting..") + return nil + } else if event.Rune() == 'h' { + return tcell.NewEventKey(tcell.KeyLeft, rune(tcell.KeyLeft), event.Modifiers()) + } else if event.Rune() == 'j' { + return tcell.NewEventKey(tcell.KeyDown, rune(tcell.KeyDown), event.Modifiers()) + } else if event.Rune() == 'k' { + return tcell.NewEventKey(tcell.KeyUp, rune(tcell.KeyUp), event.Modifiers()) + } else if event.Rune() == 'l' { + return tcell.NewEventKey(tcell.KeyRight, rune(tcell.KeyRight), event.Modifiers()) + } else if event.Rune() == '?' { + pages.SwitchToPage("help") + layout.GetView().Blur() + go app.Draw() + } + return event + }) + app.SetAfterDrawFunc(func(screen tcell.Screen) { + layout.Update() + }) + app.GetFocus().Blur() + + //app.SetFocus(layout.GetView()) + + return &TUI{ + app: app, + layout: layout, + client: client, + playbackCtl: playbackCtl, + } + +} + +func (t *TUI) Run() error { + return t.app.Run() +} diff --git a/internal/tui/views/albums.go b/internal/tui/views/albums.go new file mode 100644 index 0000000..3cb9a50 --- /dev/null +++ b/internal/tui/views/albums.go @@ -0,0 +1,73 @@ +package views + +import ( + "git.dayanhub.com/sagi/subsonic-tui/internal/client" + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "github.com/delucks/go-subsonic" + "github.com/rivo/tview" +) + +var _ View = &albums{} + +type albums struct { + view *tview.Table + client *client.Client + albums []*subsonic.AlbumID3 + callback func(albumID string) +} + +func NewAlbums(client *client.Client) *albums { + + list := tview.NewTable() + + list.SetBackgroundColor(config.ColorBackground) + list.SetTitle("Albums [2]") + list.SetBorder(true) + + resp, _ := client.GetAlbums() + + obj := &albums{ + view: list, + client: client, + albums: resp, + } + + list.SetSelectedFunc(func(row, column int) { + obj.callback(obj.albums[row].ID) + }) + + list.SetFocusFunc(func() { + list.SetBorderColor(config.ColorSelectedBoarder) + list.SetSelectable(true, false) + }) + list.SetBlurFunc(func() { + list.SetBorderColor(config.ColorBluredBoarder) + list.SetSelectable(false, false) + }) + + obj.Update() + return obj +} + +func (a *albums) SetAlbums(al []*subsonic.AlbumID3) { + a.albums = al + a.Update() +} + +func (a *albums) Update() { + a.view.Clear() + for i, pl := range a.albums { + title := tview.NewTableCell(pl.Name).SetExpansion(1).SetMaxWidth(15) + artist := tview.NewTableCell(pl.Artist).SetExpansion(1).SetAlign(tview.AlignRight) + a.view.SetCell(i, 0, title) + a.view.SetCell(i, 1, artist) + } +} + +func (a *albums) SetCallback(f func(albumID string)) { + a.callback = f +} + +func (a *albums) GetView() tview.Primitive { + return a.view +} diff --git a/internal/tui/views/artists.go b/internal/tui/views/artists.go new file mode 100644 index 0000000..94f3d5c --- /dev/null +++ b/internal/tui/views/artists.go @@ -0,0 +1,70 @@ +package views + +import ( + "git.dayanhub.com/sagi/subsonic-tui/internal/client" + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "github.com/delucks/go-subsonic" + "github.com/rivo/tview" +) + +var _ View = &artists{} + +type artists struct { + view *tview.Table + client *client.Client + artists []*subsonic.ArtistID3 + selectArtistFunc func(artistId string) + openArtistFunc func(artistId string) +} + +func NewArtists(client *client.Client) *artists { + + list := tview.NewTable() + + list.SetBackgroundColor(config.ColorBackground) + list.SetTitle("Artists [1]") + list.SetBorder(true) + list.SetFocusFunc(func() { + list.SetBorderColor(config.ColorSelectedBoarder) + list.SetSelectable(true, false) + }) + list.SetBlurFunc(func() { + list.SetBorderColor(config.ColorBluredBoarder) + list.SetSelectable(false, false) + }) + + arts, _ := client.GetArtists() + + for i, artist := range arts { + cell := tview.NewTableCell(artist.Name).SetExpansion(1) + list.SetCell(i, 0, cell) + // list.AddItem(artist.Name, fmt.Sprintf("%s", artist.Name), '0', nil) + } + + resp := &artists{ + view: list, + client: client, + artists: arts, + } + + list.SetSelectedFunc(func(row, column int) { + resp.openArtistFunc(resp.artists[row].ID) + }) + + return resp +} + +func (a *artists) SetSelectArtistFunc(f func(artistId string)) { + a.selectArtistFunc = f +} +func (a *artists) SetOpenArtistFunc(f func(artistId string)) { + a.openArtistFunc = f +} + +func (a *artists) Update() { + +} + +func (a *artists) GetView() tview.Primitive { + return a.view +} diff --git a/internal/tui/views/button.go b/internal/tui/views/button.go new file mode 100644 index 0000000..847438a --- /dev/null +++ b/internal/tui/views/button.go @@ -0,0 +1,21 @@ +package views + +import ( + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +func NewButton(label string) *tview.Button { + style := tcell.Style{} + inactiveS := style.Background(config.ColorButtonBg). + Foreground(config.ColorButtonTxt).Bold(true) + activeS := style.Background(config.ColorButtonSelectedBg). + Foreground(config.ColorButtonSelectedTxt).Bold(true).Underline(true) + + b := tview.NewButton(label) + b.SetStyle(inactiveS) + b.SetActivatedStyle(activeS) + + return b +} diff --git a/internal/tui/views/help.go b/internal/tui/views/help.go new file mode 100644 index 0000000..0343076 --- /dev/null +++ b/internal/tui/views/help.go @@ -0,0 +1,113 @@ +package views + +import ( + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "github.com/rivo/tview" +) + +var _ View = &help{} + +type help struct { + view *tview.Flex + keyPressedFunc func() +} + +type keyMap struct { + key string + description string +} + +var keyMaps = []keyMap{ + { + "Arrows", "Navigation", + }, + { + "h, j, k, l", "Navigation. Use [shift] for moving panes", + }, + { + "g", "Jump to top", + }, + { + "G", "Jump to bottom", + }, + { + "p", "Toggle Play/Pause", + }, + { + "s", "Stop", + }, + { + "n", "Play next song", + }, + { + "N", "Play previous song", + }, + { + "c", "Stop and clear queue", + }, + { + "r", "Start song radio", + }, + { + "/", "Search", + }, + { + "q", "Quit", + }, +} + +func NewHelp() *help { + h := &help{} + view := tview.NewTable() + view.SetBackgroundColor(config.ColorBackground) + height := 16 + width := 100 + view.SetBorder(true) + view.SetTitle(" Keybindings ") + view.SetTitleAlign(tview.AlignCenter) + + for i, km := range keyMaps { + odd := i%2 != 0 + txtColor := config.ColorText + if odd { + txtColor = config.ColorTextAccent + } + keyCell := tview.NewTableCell(km.key). + SetExpansion(1). + SetAlign(tview.AlignCenter). + SetTextColor(txtColor) + descCell := tview.NewTableCell(km.description). + SetExpansion(1). + SetAlign(tview.AlignCenter). + SetTextColor(txtColor) + view.SetCell(i, 0, keyCell) + view.SetCell(i, 1, descCell) + } + + innerFlex := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(EmptyBox, 0, 1, false). + AddItem(view, height, 1, true). + AddItem(EmptyBox, 0, 1, false) + innerFlex.SetBackgroundColor(config.ColorBackground) + wrapper := tview.NewFlex(). + AddItem(EmptyBox, 0, 1, false). + AddItem(innerFlex, width, 1, true). + AddItem(EmptyBox, 0, 1, false) + wrapper.SetBackgroundColor((config.ColorBackground)) + + h.view = wrapper + + return h +} + +func (h *help) GetView() tview.Primitive { + return h.view +} + +func (h *help) Update() { + +} + +func (h *help) SetKeyPressedFunc(f func()) { + h.keyPressedFunc = f +} diff --git a/internal/tui/views/layout.go b/internal/tui/views/layout.go new file mode 100644 index 0000000..0b2bc6d --- /dev/null +++ b/internal/tui/views/layout.go @@ -0,0 +1,398 @@ +package views + +import ( + "time" + + "git.dayanhub.com/sagi/subsonic-tui/internal/client" + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "git.dayanhub.com/sagi/subsonic-tui/internal/playback" + "github.com/delucks/go-subsonic" + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +var _ View = &layout{} + +type layout struct { + view *tview.Pages + player View + currentFocusedView tview.Primitive + smallView *tview.Flex + largeView *tview.Grid + status *statusLine + mainView *main +} + +func NewLayout(client *client.Client, playbackCtl *playback.Controller, refreshUI func()) *layout { + layout := &layout{} + + largeView := tview.NewGrid().SetRows(0, 4, 1) + largeView.SetBackgroundColor(config.ColorBackground) + smallView := tview.NewFlex().SetDirection(tview.FlexRow) + smallView.SetBackgroundColor(config.ColorBackground) + layout.largeView = largeView + layout.smallView = smallView + pages := tview.NewPages() + pages.SetBackgroundColor(config.ColorBackground) + + // Status / command / search + statusLine := NewStatusLine() + layout.status = statusLine + + // Side Panel (Artist/Albums/Playlist) + albums := NewAlbums(client) + artists := NewArtists(client) + playlists := NewPlaylists(client) + + sidePanel := tview.NewGrid().SetRows(0, 0, 0) + sidePanel.AddItem(artists.GetView(), 0, 0, 1, 1, 0, 0, false) + sidePanel.AddItem(albums.GetView(), 1, 0, 1, 1, 0, 0, false) + sidePanel.AddItem(playlists.GetView(), 2, 0, 1, 1, 0, 0, false) + + // main pane + main := NewMainView(client, statusLine.Log) + layout.mainView = main + // Queue + queue := NewQueue() + + // Main view + mainView := tview.NewFlex() + mainView.SetBackgroundColor(config.ColorBackground) + mainView.AddItem(sidePanel, 0, 1, false) + mainView.AddItem(main.GetView(), 0, 2, false) + mainView.AddItem(queue.GetView(), 0, 1, false) + largeView.AddItem(mainView, 0, 0, 1, 3, 0, 0, false) + // Player + player := NewPlayer(client) + largeView.AddItem(player.GetView(), 1, 0, 1, 3, 0, 0, false) + + // Add status line + largeView.AddItem(statusLine.GetView(), 2, 0, 1, 3, 0, 0, false) + + // Callbacks + artists.SetSelectArtistFunc(func(artistId string) { + artists.view.Blur() + statusLine.Log("Fetching artist's albums...") + go func() { + a, _ := client.GetArtist(artistId) + albums.SetAlbums(a.Album) + albums.GetView().Focus(nil) + refreshUI() + }() + }) + artists.SetOpenArtistFunc(func(artistId string) { + artists.view.Blur() + statusLine.Log("Fetching artists...") + layout.currentFocusedView = main.GetView() + layout.rebuildSmallView() + go func() { + a, _ := client.GetArtist(artistId) + main.SetArtist(a) + main.GetView().Focus(nil) + refreshUI() + }() + }) + + albums.SetCallback(func(albumID string) { + albums.view.Blur() + statusLine.Log("Fetching album...") + layout.currentFocusedView = main.GetView() + layout.rebuildSmallView() + go func() { + a, _ := client.GetAlbum(albumID) + main.SetAlbum(a) + main.view.Focus(nil) + refreshUI() + }() + }) + + playlists.SetCallback(func(p *subsonic.Playlist) { + playlists.view.Blur() + statusLine.Log("Fetching playlist...") + layout.currentFocusedView = main.GetView() + layout.rebuildSmallView() + go func() { + playlist, _ := client.GetPlaylist(p.ID) + main.SetPlaylist(playlist) + main.view.Focus(nil) + refreshUI() + }() + }) + + main.SetPlayAllFunc(func(songs ...*subsonic.Child) { + statusLine.Log("Loaded #%d songs.", len(songs)) + playbackCtl.SetQueue(songs) + queue.Update(songs, playbackCtl.GetQueuePosition()) + }) + main.SetPlayAddSongFunc(func(song ...*subsonic.Child) { + statusLine.Log("Added #%d songs to queue.", len(song)) + playbackCtl.AddToQueue(song) + queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition()) + }) + + playbackCtl.SetSongElapsedFunc(func(song *subsonic.Child, elapsed time.Duration) { + player.SetSongInfo(song) + player.UpdateProgress(elapsed) + queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition()) + refreshUI() + }) + + playbackCtl.SetSongEndedFunc(func(song *subsonic.Child) { + statusLine.Log("") + queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition()) + if config.ScrobbleEnabled() { + // Scrobble + _ = client.Scrobble(song.ID) + } + }) + + queue.SetPlayFunc(func(position int) { + playbackCtl.SetQueuePosition(position) + queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition()) + }) + + statusLine.SetOnUpdateFunc(func() { + refreshUI() + }) + + statusLine.SetSearchFunc(func(quary string) { + if len(quary) == 0 { + layout.currentFocusedView.Focus(nil) + statusLine.Log("Search canceled") + return + } + // Search... + statusLine.Log("Searching for '%s'....", quary) + statusLine.view.Blur() + go func() { + result, _ := client.Search(quary) + layout.currentFocusedView.Blur() + main.SetSearch(result, quary) + layout.currentFocusedView = main.GetView() + layout.rebuildSmallView() + refreshUI() + }() + }) + + // Key Bindings + pages.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if statusLine.Mode() == StatusModeSearch { + return event + } + if event.Rune() == '1' { + // Focus Artists + artists.view.Blur() + albums.view.Blur() + playlists.view.Blur() + main.view.Blur() + queue.view.Blur() + artists.view.Focus(nil) + layout.currentFocusedView = artists.GetView() + layout.rebuildSmallView() + return nil + } else if event.Rune() == '2' { + // Focus Albums + artists.view.Blur() + albums.view.Blur() + playlists.view.Blur() + main.view.Blur() + queue.view.Blur() + albums.view.Focus(nil) + layout.currentFocusedView = albums.GetView() + layout.rebuildSmallView() + return nil + } else if event.Rune() == '3' { + // Focus Playlists + artists.view.Blur() + albums.view.Blur() + playlists.view.Blur() + main.view.Blur() + queue.view.Blur() + playlists.view.Focus(nil) + layout.currentFocusedView = playlists.GetView() + layout.rebuildSmallView() + return nil + } else if event.Rune() == '`' { + // Focus Songs + artists.view.Blur() + albums.view.Blur() + playlists.view.Blur() + main.view.Blur() + queue.view.Blur() + main.view.Focus(nil) + layout.currentFocusedView = main.GetView() + layout.rebuildSmallView() + return nil + } else if event.Rune() == '4' { + // Focus Queue + artists.view.Blur() + albums.view.Blur() + playlists.view.Blur() + main.view.Blur() + queue.view.Blur() + queue.view.Focus(nil) + layout.currentFocusedView = queue.GetView() + layout.rebuildSmallView() + return nil + } else if event.Rune() == 'n' { + playbackCtl.Next() + queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition()) + return nil + + } else if event.Rune() == 'N' { + playbackCtl.Prev() + queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition()) + return nil + } else if event.Rune() == 's' { + playbackCtl.Stop() + queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition()) + return nil + } else if event.Rune() == 'p' { + if playbackCtl.State() == playback.PlaybackStateStopped { + song := playbackCtl.GetCurrentSong() + if song != nil { + playbackCtl.Play(song) + } + queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition()) + return nil + } + playbackCtl.TogglePlayPause() + return nil + } else if event.Rune() == 'c' { + playbackCtl.ClearQueue() + queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition()) + return nil + } else if event.Rune() == '/' { + layout.currentFocusedView.Blur() + statusLine.Search() + refreshUI() + return nil + } else if event.Rune() == 'L' { + if layout.currentFocusedView == albums.GetView() { + layout.currentFocusedView.Blur() + main.view.Focus(nil) + layout.currentFocusedView = main.GetView() + layout.rebuildSmallView() + } else if layout.currentFocusedView == artists.GetView() { + layout.currentFocusedView.Blur() + main.view.Focus(nil) + layout.currentFocusedView = main.GetView() + layout.rebuildSmallView() + } else if layout.currentFocusedView == playlists.GetView() { + layout.currentFocusedView.Blur() + main.view.Focus(nil) + layout.currentFocusedView = main.GetView() + layout.rebuildSmallView() + } else if layout.currentFocusedView == main.GetView() { + layout.currentFocusedView.Blur() + queue.view.Focus(nil) + layout.currentFocusedView = queue.GetView() + layout.rebuildSmallView() + } + } else if event.Rune() == 'H' { + if layout.currentFocusedView == queue.GetView() { + layout.currentFocusedView.Blur() + main.view.Focus(nil) + layout.currentFocusedView = main.GetView() + layout.rebuildSmallView() + } else if layout.currentFocusedView == main.GetView() { + layout.currentFocusedView.Blur() + artists.view.Focus(nil) + layout.currentFocusedView = artists.GetView() + layout.rebuildSmallView() + } + } else if event.Rune() == 'J' { + if layout.currentFocusedView == albums.GetView() { + layout.currentFocusedView.Blur() + playlists.view.Focus(nil) + layout.currentFocusedView = playlists.GetView() + layout.rebuildSmallView() + } else if layout.currentFocusedView == artists.GetView() { + layout.currentFocusedView.Blur() + albums.view.Focus(nil) + layout.currentFocusedView = albums.GetView() + layout.rebuildSmallView() + } else if layout.currentFocusedView == playlists.GetView() { + layout.currentFocusedView.Blur() + artists.view.Focus(nil) + layout.currentFocusedView = artists.GetView() + layout.rebuildSmallView() + } + } else if event.Rune() == 'K' { + if layout.currentFocusedView == albums.GetView() { + layout.currentFocusedView.Blur() + artists.view.Focus(nil) + layout.currentFocusedView = artists.GetView() + layout.rebuildSmallView() + } else if layout.currentFocusedView == artists.GetView() { + layout.currentFocusedView.Blur() + playlists.view.Focus(nil) + layout.currentFocusedView = playlists.GetView() + layout.rebuildSmallView() + } else if layout.currentFocusedView == playlists.GetView() { + layout.currentFocusedView.Blur() + albums.view.Focus(nil) + layout.currentFocusedView = albums.GetView() + layout.rebuildSmallView() + } + } + + return event + }) + + largeView.SetFocusFunc(func() { + largeView.Blur() + layout.currentFocusedView.Focus(nil) + }) + smallView.SetFocusFunc(func() { + smallView.Blur() + layout.currentFocusedView.Focus(nil) + }) + + statusLine.Log("Press '?' for help") + + pages.AddPage("small", smallView, true, false) + pages.AddPage("large", largeView, true, true) + + layout.view = pages + layout.player = player + //Auto focus on artists + artists.GetView().Focus(nil) + layout.currentFocusedView = artists.GetView() + layout.rebuildSmallView() + return layout +} +func (l *layout) rebuildSmallView() { + l.smallView.Clear() + l.smallView.AddItem(l.currentFocusedView, 0, 1, false) + l.smallView.AddItem(l.player.GetView(), 4, 0, false) + l.smallView.AddItem(l.status.GetView(), 1, 0, false) +} + +func (l *layout) Mode() Statusmode { + return l.status.Mode() +} + +func (l *layout) GetView() tview.Primitive { + return l.view +} + +func (l *layout) Update() { + _, _, w, h := l.view.GetRect() + page, _ := l.view.GetFrontPage() + smallView := w < 100 || h < 30 + if smallView { + if page != "small" { + l.mainView.SetMiniView(true) + go l.view.SwitchToPage("small") + return + } + } else { + if page != "large" { + l.mainView.SetMiniView(false) + go l.view.SwitchToPage("large") + return + } + } + l.player.Update() +} diff --git a/internal/tui/views/login.go b/internal/tui/views/login.go new file mode 100644 index 0000000..85d70a2 --- /dev/null +++ b/internal/tui/views/login.go @@ -0,0 +1,73 @@ +package views + +import ( + "github.com/rivo/tview" +) + +var _ View = &login{} + +const ( + ViewEventLoginClicked = "login-clicked" +) + +type login struct { + view *tview.Flex + form *tview.Form + username string + password string + url string + loginFunc func(u, p, url string) + exitFunc func() +} + +func (l *login) GetView() tview.Primitive { + return l.view +} + +func (l *login) onFormLogin() { + l.loginFunc(l.username, l.password, l.url) +} + +func NewLoginView(loginFunc func(u, p, url string), exitFunc func()) View { + l := &login{} + l.loginFunc = loginFunc + l.exitFunc = exitFunc + + form := tview.NewForm(). + AddInputField("Server URL", "https://", 20, nil, func(text string) { + l.url = text + }). + AddInputField("Username", "", 20, nil, func(text string) { + l.username = text + }). + AddPasswordField("Password", "", 20, '*', func(text string) { + l.password = text + }). + AddButton("Login", l.onFormLogin). + AddButton("Quit", func() { + l.exitFunc() + }) + form.SetBorder(true).SetTitle(" Login ").SetTitleAlign(tview.AlignCenter) + + width := 50 + height := 20 + + wrapper := tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(form, height, 1, true). + AddItem(nil, 0, 1, false), width, 1, true). + AddItem(nil, 0, 1, false) + + l.view = wrapper + l.form = form + + l.GetView().Focus(func(p tview.Primitive) {}) + form.SetFocus(0) + return l +} + +func (l *login) Update() { + +} diff --git a/internal/tui/views/main.go b/internal/tui/views/main.go new file mode 100644 index 0000000..a6bb8a2 --- /dev/null +++ b/internal/tui/views/main.go @@ -0,0 +1,616 @@ +package views + +import ( + "fmt" + "strconv" + "time" + + "git.dayanhub.com/sagi/subsonic-tui/internal/client" + "git.dayanhub.com/sagi/subsonic-tui/internal/common" + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "github.com/delucks/go-subsonic" + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +var _ View = &main{} + +type mainviewmode int + +const ( + mainModeAlbum mainviewmode = iota + mainModePlaylist mainviewmode = iota + mainModeArtist mainviewmode = iota + mainModeSearch mainviewmode = iota +) + +type main struct { + view *tview.Flex + client *client.Client + mode mainviewmode + query string + miniView bool + album *subsonic.AlbumID3 + searchResult *subsonic.SearchResult3 + playlist *subsonic.Playlist + songList *tview.Table + artist *subsonic.ArtistID3 + artistInfo *subsonic.ArtistInfo2 + playAllFunc func(song ...*subsonic.Child) + addSongsFunc func(song ...*subsonic.Child) + log func(format string, a ...any) +} + +func NewMainView(client *client.Client, log func(format string, a ...any)) *main { + playlistAlbum := &main{ + client: client, + log: log, + } + + flex := tview.NewFlex().SetDirection(tview.FlexRow) + + flex.SetBackgroundColor(config.ColorBackground) + flex.SetTitle("Subsonic TUI [`]") + flex.SetBorder(true) + flex.SetFocusFunc(func() { + flex.SetBorderColor(config.ColorSelectedBoarder) + if playlistAlbum.songList != nil { + //playlistAlbum.view.Blur() + playlistAlbum.songList.Focus(nil) + } + }) + flex.SetBlurFunc(func() { + flex.SetBorderColor(config.ColorBluredBoarder) + if playlistAlbum.songList != nil { + playlistAlbum.songList.Blur() + } + }) + + playlistAlbum.view = flex + // Empty Box for starters... + playlistAlbum.view.AddItem(EmptyBox, 0, 1, false) + return playlistAlbum +} + +func (m *main) SetMiniView(mini bool) { + m.miniView = mini +} + +func (m *main) SetAlbum(album *subsonic.AlbumID3) { + m.mode = mainModeAlbum + m.album = album + m.Update() + m.log("") +} + +func (m *main) SetPlaylist(playlist *subsonic.Playlist) { + m.mode = mainModePlaylist + m.playlist = playlist + m.Update() + m.log("") +} + +func (m *main) SetArtist(artist *subsonic.ArtistID3) { + m.mode = mainModeArtist + m.artist = artist + info, _ := m.client.GetArtistInfo(artist.ID) + m.artistInfo = info + m.Update() + m.log("") +} + +func (m *main) SetSearch(result *subsonic.SearchResult3, query string) { + m.mode = mainModeSearch + m.searchResult = result + m.query = query + m.Update() + m.log("Found #%d artists, #%d albums and #%d songs", + len(result.Artist), + len(result.Album), + len(result.Song), + ) +} + +func (m *main) drawPlaylist() { + subtitle := fmt.Sprintf("%s\n\nCreated by: %s | %s", m.playlist.Comment, m.playlist.Owner, time.Duration(m.playlist.Duration*int(time.Second)).String()) + m.populateHeader(m.playlist.Name, subtitle, m.playlist.Duration, m.playlist.CoverArt) + playBtn := m.drawPlaylistAlbumButtons(m.playlist.Entry) + m.populateSongs(m.playlist.Entry, playBtn) +} + +func (m *main) drawAlbum() { + subtitle := fmt.Sprintf("%s\n\n%d | %s", m.album.Artist, m.album.Year, time.Duration(m.album.Duration*int(time.Second)).String()) + m.populateHeader(m.album.Name, subtitle, m.album.Duration, m.album.CoverArt) + playBtn := m.drawPlaylistAlbumButtons(m.album.Song) + m.populateSongs(m.album.Song, playBtn) + +} + +func (m *main) drawArtist() { + m.populateHeader(m.artist.Name, m.artistInfo.Biography, 0, m.artist.CoverArt) + btn := m.drawArtistButtons() + m.populateAlbums(btn) +} + +func (m *main) drawSearch() { + sub := fmt.Sprintf("Query: %s", m.query) + m.populateHeader("Search Results", sub, 0, "") + m.populateSearchResults() +} + +func (m *main) Update() { + m.view.Clear() + switch m.mode { + case mainModeAlbum: + m.drawAlbum() + case mainModePlaylist: + m.drawPlaylist() + case mainModeArtist: + m.drawArtist() + case mainModeSearch: + m.drawSearch() + } + m.songList.Focus(nil) +} + +func (m *main) drawArtistButtons() *tview.Button { + // Add buttons: Radio + songs, _ := m.client.GetTopSongs(m.artist.Name, 10) + f := tview.NewFlex() + f.SetBackgroundColor(config.ColorBackground) + f.SetBorderPadding(0, 0, 2, 2) + // Buttons + radio := NewButton("Radio") + top10 := NewButton("Top 10") + // Button callbacks + top10.SetSelectedFunc(func() { + top10.Blur() + m.log("Playing %s's top 10 songs", m.artist.Name) + m.playAllFunc(songs...) + m.songList.Focus(nil) + }) + radio.SetSelectedFunc(func() { + radio.Blur() + m.log("Generating %s's radio", m.artist.Name) + go func() { + radioSongs := []*subsonic.Child{} + if config.ExperimentalRadioAlgo() { + radioSongs, _ = m.client.GetExperimentalArtistRadio(m.artist, m.artistInfo, config.MaxRadioSongs()) + } + if len(radioSongs) == 0 { + radioSongs, _ = m.client.GetSimilarSongs(m.artist.ID, config.MaxRadioSongs()) + } + + common.ShuffleSlice(radioSongs) + m.playAllFunc(radioSongs...) + }() + m.songList.Focus(nil) + }) + radio.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyDown: + radio.Blur() + m.songList.Focus(nil) + return nil + case tcell.KeyRight: + radio.Blur() + top10.Focus(nil) + return nil + } + return event + }) + top10.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyDown: + top10.Blur() + m.songList.Focus(nil) + return nil + case tcell.KeyLeft: + top10.Blur() + radio.Focus(nil) + return nil + } + return event + }) + + f.AddItem(radio, 0, 1, false) + f.AddItem(EmptyBox, 0, 1, false) + if len(songs) > 0 { + f.AddItem(top10, 0, 1, false) + } + f.AddItem(EmptyBox, 0, 1, false) + + // Add the buttons to the view + m.view.AddItem(f, 1, 0, false) + // Margin bottom of 1 line + m.view.AddItem(EmptyBox, 1, 0, false) + + return radio +} + +func (m *main) populateAlbums(btn *tview.Button) { + table := tview.NewTable() + table.SetBackgroundColor(config.ColorBackground) + table.SetWrapSelection(true, false) + for i, album := range m.artist.Album { + year := tview.NewTableCell(fmt.Sprintf("%d", album.Year)).SetTextColor(config.ColorTextAccent) + name := tview.NewTableCell(album.Name).SetExpansion(2).SetAlign(tview.AlignCenter) + d := time.Second * time.Duration(album.Duration) + duration := tview.NewTableCell(d.String()).SetAlign(tview.AlignRight).SetExpansion(1) + + table.SetCell(i, 0, year) + table.SetCell(i, 1, name) + table.SetCell(i, 2, duration) + } + + table.SetFocusFunc(func() { + m.view.SetBorderColor(config.ColorSelectedBoarder) + table.SetSelectable(true, false) + }) + table.SetBlurFunc(func() { + m.view.SetBorderColor(config.ColorBluredBoarder) + table.SetSelectable(false, false) + }) + table.SetSelectedFunc(func(row, column int) { + alb, _ := m.client.GetAlbum(m.artist.Album[row].ID) + m.SetAlbum(alb) + m.view.Focus(nil) + }) + table.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + row, _ := m.songList.GetSelection() + if row == 0 && event.Key() == tcell.KeyUp { + table.Blur() + m.view.SetBorderColor(config.ColorSelectedBoarder) + btn.Focus(nil) + return nil + } + return event + }) + m.songList = table + + m.view.AddItem(table, 0, 1, false) + +} + +func (m *main) drawPlaylistAlbumButtons(songs []*subsonic.Child) *tview.Button { + // Add buttons: Play | Shuffle | Add to queue + f := tview.NewFlex() + f.SetBackgroundColor(config.ColorBackground) + f.SetBorderPadding(0, 0, 2, 2) + // Buttons + play := NewButton("Play") + shuffle := NewButton("Shuffle") + queue := NewButton("Queue") + artist := NewButton("Artist") + // Button callbacks + play.SetSelectedFunc(func() { + play.Blur() + m.playAllFunc(songs...) + m.songList.Focus(nil) + }) + play.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyLeft: + play.Blur() + artist.Focus(nil) + return nil + case tcell.KeyRight: + play.Blur() + shuffle.Focus(nil) + return nil + case tcell.KeyDown: + play.Blur() + m.songList.Focus(nil) + return nil + } + return event + }) + shuffle.SetSelectedFunc(func() { + shuffle.Blur() + cpy := make([]*subsonic.Child, len(songs)) + copy(cpy, songs) + common.ShuffleSlice(cpy) + m.playAllFunc(cpy...) + m.songList.Focus(nil) + }) + shuffle.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyLeft: + shuffle.Blur() + play.Focus(nil) + return nil + case tcell.KeyRight: + shuffle.Blur() + queue.Focus(nil) + return nil + case tcell.KeyDown: + shuffle.Blur() + m.songList.Focus(nil) + return nil + } + return event + }) + queue.SetSelectedFunc(func() { + queue.Blur() + m.addSongsFunc(songs...) + m.songList.Focus(nil) + }) + queue.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyLeft: + queue.Blur() + shuffle.Focus(nil) + return nil + case tcell.KeyRight: + queue.Blur() + artist.Focus(nil) + return nil + case tcell.KeyDown: + queue.Blur() + m.songList.Focus(nil) + return nil + } + return event + }) + artist.SetSelectedFunc(func() { + artist.Blur() + ar, _ := m.client.GetArtist(m.album.ArtistID) + m.SetArtist(ar) + m.songList.Focus(nil) + }) + artist.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyLeft: + artist.Blur() + queue.Focus(nil) + return nil + case tcell.KeyRight: + artist.Blur() + play.Focus(nil) + return nil + case tcell.KeyDown: + artist.Blur() + m.songList.Focus(nil) + return nil + } + return event + }) + + f.AddItem(play, 0, 1, true) + f.AddItem(EmptyBox, 0, 1, false) + f.AddItem(shuffle, 0, 1, false) + f.AddItem(EmptyBox, 0, 1, false) + f.AddItem(queue, 0, 1, false) + f.AddItem(EmptyBox, 0, 1, false) + f.AddItem(artist, 0, 1, false) + + // Add the buttons to the view + m.view.AddItem(f, 1, 0, false) + // Margin bottom of 1 line + m.view.AddItem(EmptyBox, 1, 0, false) + + return play +} + +func (m *main) populateSongs(songs []*subsonic.Child, play *tview.Button) { + table := tview.NewTable() + table.SetBackgroundColor(config.ColorBackground) + table.SetWrapSelection(true, false) + for i, song := range songs { + num := tview.NewTableCell(fmt.Sprintf("%d", i+1)).SetTextColor(config.ColorTextAccent) + title := tview.NewTableCell(song.Title).SetMaxWidth(15).SetExpansion(2) + artist := tview.NewTableCell(song.Artist).SetMaxWidth(15).SetExpansion(1) + d := time.Second * time.Duration(song.Duration) + duration := tview.NewTableCell(d.String()).SetAlign(tview.AlignRight).SetExpansion(1) + table.SetCell(i, 0, num) + table.SetCell(i, 1, title) + table.SetCell(i, 2, artist) + table.SetCell(i, 3, duration) + } + + m.songList = table + table.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + // on first line and pressing up -> play button + row, _ := table.GetSelection() + if row == 0 && event.Key() == tcell.KeyUp { + table.Blur() + m.view.SetBorderColor(config.ColorSelectedBoarder) + play.Focus(nil) + return nil + } else if event.Rune() == 'r' { + song := songs[row] + m.log("Generating song (%s) radio....", song.Title) + go func() { + radioSongs, _ := m.client.GetSimilarSongs(song.ID, config.MaxRadioSongs()) + m.playAllFunc(radioSongs...) + }() + } + return event + }) + table.SetFocusFunc(func() { + m.view.SetBorderColor(config.ColorSelectedBoarder) + table.SetSelectable(true, false) + }) + table.SetBlurFunc(func() { + m.view.SetBorderColor(config.ColorBluredBoarder) + table.SetSelectable(false, false) + }) + m.songList.SetSelectedFunc(func(row, column int) { + m.addSongsFunc(songs[row]) + }) + + m.view.AddItem(table, 0, 1, false) +} + +func (m *main) populateSearchResults() { + table := tview.NewTable() + table.SetBackgroundColor(config.ColorBackground) + table.SetWrapSelection(true, false) + row := 0 + lastArtist := 0 + lastAlbum := 0 + + // Artists + if len(m.searchResult.Artist) > 0 { + // Header + header := tview.NewTableCell("Artists").SetSelectable(false) + table.SetCell(row, 0, header) + row++ + //List + for i, artist := range m.searchResult.Artist { + index := tview.NewTableCell(fmt.Sprintf("%d", i+1)). + SetTextColor(config.ColorTextAccent) + a := tview.NewTableCell(artist.Name).SetExpansion(2).SetMaxWidth(15) + acount := tview.NewTableCell(fmt.Sprintf("%d Albums", artist.AlbumCount)). + SetExpansion(1).SetAlign(tview.AlignRight).SetMaxWidth(15) + table.SetCell(row, 0, index) + table.SetCell(row, 1, a) + table.SetCell(row, 2, acount) + row++ + } + lastArtist = row + } + + // Albums + + if len(m.searchResult.Album) > 0 { + // Header + header := tview.NewTableCell("Albums").SetSelectable(false) + table.SetCell(row, 0, header) + row++ + //List + for i, album := range m.searchResult.Album { + index := tview.NewTableCell(fmt.Sprintf("%d", i+1)). + SetTextColor(config.ColorTextAccent) + title := tview.NewTableCell(album.Name).SetExpansion(2).SetMaxWidth(15) + artist := tview.NewTableCell(album.Artist). + SetExpansion(1).SetAlign(tview.AlignRight).SetMaxWidth(15) + table.SetCell(row, 0, index) + table.SetCell(row, 1, title) + table.SetCell(row, 2, artist) + row++ + } + lastAlbum = row + } + // Songs + if len(m.searchResult.Song) > 0 { + // Header + header := tview.NewTableCell("Songs").SetSelectable(false) + table.SetCell(row, 0, header) + row++ + //List + for i, song := range m.searchResult.Song { + index := tview.NewTableCell(fmt.Sprintf("%d", i+1)). + SetTextColor(config.ColorTextAccent) + title := tview.NewTableCell(song.Title).SetExpansion(2).SetMaxWidth(15) + artist := tview.NewTableCell(song.Artist). + SetExpansion(1).SetAlign(tview.AlignRight).SetMaxWidth(15) + table.SetCell(row, 0, index) + table.SetCell(row, 1, title) + table.SetCell(row, 2, artist) + row++ + } + } + + m.songList = table + table.SetFocusFunc(func() { + m.view.SetBorderColor(config.ColorSelectedBoarder) + table.SetSelectable(true, false) + }) + table.SetBlurFunc(func() { + m.view.SetBorderColor(config.ColorBluredBoarder) + table.SetSelectable(false, false) + }) + table.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + row, _ := table.GetSelection() + if row <= lastAlbum { + return event + } + if event.Rune() != 'r' { + return event + } + cell := table.GetCell(row, 0) + index, err := strconv.Atoi(cell.Text) + if err != nil { + return nil + } + song := m.searchResult.Song[index-1] + m.log("Generating song (%s) radio....", song.Title) + go func() { + radioSongs, _ := m.client.GetSimilarSongs(song.ID, config.MaxRadioSongs()) + m.playAllFunc(radioSongs...) + }() + return nil + }) + m.songList.SetSelectedFunc(func(row, column int) { + cell := table.GetCell(row, 0) + index, err := strconv.Atoi(cell.Text) + if err != nil { + return + } + if row <= lastArtist { + artist, _ := m.client.GetArtist(m.searchResult.Artist[index-1].ID) + m.SetArtist(artist) + } else if lastArtist < row && row <= lastAlbum { + album, _ := m.client.GetAlbum(m.searchResult.Album[index-1].ID) + m.SetAlbum(album) + } else { + m.addSongsFunc(m.searchResult.Song[index-1]) + } + }) + + m.view.AddItem(table, 0, 1, false) +} + +func (m *main) populateHeader(title, subtitle string, + duration int, coverArtID string) { + + header := tview.NewFlex() + header.SetBackgroundColor(config.ColorBackground) + art := tview.NewImage() + art.SetBackgroundColor(config.ColorBackground) + + img, _ := m.client.GetCoverArt(coverArtID) + art.SetImage(img) + t := tview.NewTextView(). + SetTextColor(config.ColorTextAccent). + SetTextAlign(tview.AlignCenter). + SetText(title) + t.SetBackgroundColor(config.ColorBackground) + s := tview.NewTextView(). + SetTextColor(config.ColorText). + SetTextAlign(tview.AlignCenter). + SetText(subtitle).SetWordWrap(true) + s.SetBackgroundColor(config.ColorBackground) + + g := tview.NewFlex().SetDirection(tview.FlexRow) + g.SetBackgroundColor(config.ColorBackground) + g.AddItem(EmptyBox, 1, 1, false) + g.AddItem(t, 1, 1, false) + g.AddItem(s, 0, 1, false) + + header.AddItem(art, 0, 1, false) + header.AddItem(g, 0, 3, false) + size := 6 + if m.miniView { + size = 4 + } + m.view.AddItem(header, size, 1, false) + + // Margin bottom of 1 line + if !m.miniView { + m.view.AddItem(EmptyBox, 1, 0, false) + } +} + +func (m *main) SetPlayAllFunc(f func(song ...*subsonic.Child)) { + m.playAllFunc = f +} + +func (m *main) SetPlayAddSongFunc(f func(song ...*subsonic.Child)) { + m.addSongsFunc = f +} + +func (m *main) GetView() tview.Primitive { + return m.view +} diff --git a/internal/tui/views/player.go b/internal/tui/views/player.go new file mode 100644 index 0000000..45fb8d0 --- /dev/null +++ b/internal/tui/views/player.go @@ -0,0 +1,117 @@ +package views + +import ( + "fmt" + "time" + + "git.dayanhub.com/sagi/subsonic-tui/internal/client" + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "github.com/delucks/go-subsonic" + "github.com/rivo/tview" +) + +var _ View = &player{} + +type player struct { + client *client.Client + view tview.Primitive + grid *tview.Grid + artwork *tview.Image + progress *tview.Flex + songInfo *tview.TextView + song *subsonic.Child +} + +func NewPlayer(client *client.Client) *player { + grid := tview.NewGrid().SetColumns(9, 0) + grid.SetBackgroundColor(config.ColorBackground) + + //album art + art := tview.NewImage() + art.SetBackgroundColor(config.ColorBackground) + + grid.AddItem(art, 0, 0, 1, 1, 4, 4, false) + // Progress + progress := tview.NewFlex() + progress.SetBackgroundColor(config.ColorBackground) + + // Song songInfo + songInfo := tview.NewTextView() + songInfo.SetDynamicColors(true) + songInfo.SetBackgroundColor(config.ColorBackground) + + // Info + Progress + songProg := tview.NewFlex().SetDirection(tview.FlexRow) + songProg.AddItem(songInfo, 0, 4, false) + songProg.AddItem(progress, 0, 1, false) + grid.AddItem(songProg, 0, 1, 1, 1, 0, 0, false) + + player := &player{ + client: client, + view: grid, + artwork: art, + songInfo: songInfo, + grid: grid, + progress: progress, + } + + player.SetSongInfo(&subsonic.Child{ + Title: "Subsonic TUI", + Album: "MaVeZe", + Artist: "ZeGoomba", + CoverArt: "", + Duration: 0, + }) + player.UpdateProgress(time.Duration(0)) + + return player +} + +func (p *player) SetSongInfo(song *subsonic.Child) { + info := fmt.Sprintf("[yellow]Title:[white] %s\n[yellow]Album:[white] %s\n[yellow]Atrists:[white] %s", + song.Title, song.Album, song.Artist) + p.songInfo.SetText(info) + p.song = song + p.LoadAlbumArt(song.CoverArt) +} + +func (p *player) LoadAlbumArt(ID string) { + i, _ := p.client.GetCoverArt(ID) + p.artwork.SetImage(i) +} + +func (p *player) UpdateProgress(elapsed time.Duration) { + + if p.song.Duration == 0 { + // Startup... Show version number + versionInfo := tview.NewTextView().SetText("Version: 0.1") + versionInfo.SetBackgroundColor(config.ColorPlaybackProgressRemaining) + versionInfo.SetTextColor(config.ColorPlaybackProgressElapsed) + p.progress.AddItem(versionInfo, 0, 1, false) + return + } + + songDuration := time.Duration(p.song.Duration) * time.Second + overlappedBox := tview.NewTextView() + overlappedBox.SetBackgroundColor(config.ColorPlaybackProgressElapsed) + overlappedBox.SetTextColor(config.ColorPlaybackProgressRemaining) + overlappedBox.SetText(songDuration.String()) + remainingBox := tview.NewTextView() + remainingBox.SetBackgroundColor(config.ColorPlaybackProgressRemaining) + remainingBox.SetTextColor(config.ColorPlaybackProgressElapsed) + remainingBox.SetTextAlign(tview.AlignRight) + rm := time.Duration(songDuration.Seconds()-elapsed.Seconds()) * time.Second + remaining := fmt.Sprintf("-%s", rm.String()) + remainingBox.SetText(remaining) + p.progress.Clear() + p.progress.AddItem(overlappedBox, 0, int(elapsed.Seconds()), false) + p.progress.AddItem(remainingBox, 0, int(songDuration.Seconds())-int(elapsed.Seconds()), false) +} + +func (p *player) GetView() tview.Primitive { + return p.view +} + +func (p *player) Update() { + //p.UpdateProgress("00:00", 50) +} diff --git a/internal/tui/views/playlists.go b/internal/tui/views/playlists.go new file mode 100644 index 0000000..674720a --- /dev/null +++ b/internal/tui/views/playlists.go @@ -0,0 +1,63 @@ +package views + +import ( + "git.dayanhub.com/sagi/subsonic-tui/internal/client" + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "github.com/delucks/go-subsonic" + "github.com/rivo/tview" +) + +var _ View = &playlists{} + +type playlists struct { + view *tview.Table + client *client.Client + callback func(playlist *subsonic.Playlist) +} + +func NewPlaylists(client *client.Client) *playlists { + + obj := &playlists{ + client: client, + } + + list := tview.NewTable() + + list.SetBackgroundColor(config.ColorBackground) + list.SetTitle("Playlists [3]") + list.SetBorder(true) + list.SetFocusFunc(func() { + list.SetBorderColor(config.ColorSelectedBoarder) + list.SetSelectable(true, false) + }) + list.SetBlurFunc(func() { + list.SetBorderColor(config.ColorBluredBoarder) + list.SetSelectable(false, false) + }) + + pls, _ := client.GetPlaylists() + for i, pl := range pls { + cell := tview.NewTableCell(pl.Name).SetExpansion(1) + list.SetCell(i, 0, cell) + } + + list.SetSelectedFunc(func(row, column int) { + obj.callback(pls[row]) + }) + + obj.view = list + + return obj +} + +func (p *playlists) SetCallback(f func(playlist *subsonic.Playlist)) { + p.callback = f +} + +func (p *playlists) Update() { + +} + +func (p *playlists) GetView() tview.Primitive { + return p.view +} diff --git a/internal/tui/views/queue.go b/internal/tui/views/queue.go new file mode 100644 index 0000000..8ef7bb7 --- /dev/null +++ b/internal/tui/views/queue.go @@ -0,0 +1,81 @@ +package views + +import ( + "fmt" + "time" + + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "github.com/delucks/go-subsonic" + "github.com/rivo/tview" +) + +type queue struct { + view *tview.Table + playFunc func(index int) + currentSong int + songQueue []*subsonic.Child +} + +func NewQueue() *queue { + table := tview.NewTable() + table.SetBackgroundColor(config.ColorBackground) + table.SetTitle("Queue [4]") + table.SetBorder(true) + table.SetFocusFunc(func() { + table.SetBorderColor(config.ColorSelectedBoarder) + }) + table.SetBlurFunc(func() { + table.SetBorderColor(config.ColorBluredBoarder) + }) + + return &queue{ + view: table, + currentSong: 0, + } +} + +func (q *queue) SetPlayFunc(f func(index int)) { + q.playFunc = f +} + +func (q *queue) drawQueue() { + q.view.Clear() + list := q.view + list.SetWrapSelection(true, false) + list.SetSelectable(true, false) + for i, song := range q.songQueue { + isCurrentSong := q.currentSong == i + isPlayed := i < q.currentSong + bgColor := config.ColorBackground + if isCurrentSong { + bgColor = config.ColorQueuePlayingBg + } else if isPlayed { + bgColor = config.ColorQueuePlayedBg + } + num := tview.NewTableCell(fmt.Sprintf("%d", i+1)).SetTextColor(config.ColorTextAccent).SetBackgroundColor(bgColor) + title := tview.NewTableCell(song.Title).SetMaxWidth(15).SetExpansion(2).SetBackgroundColor(bgColor) + artist := tview.NewTableCell(song.Artist).SetMaxWidth(15).SetExpansion(1).SetBackgroundColor(bgColor) + d := time.Second * time.Duration(song.Duration) + duration := tview.NewTableCell(d.String()).SetAlign(tview.AlignRight).SetExpansion(1).SetBackgroundColor(bgColor) + list.SetCell(i, 0, num) + list.SetCell(i, 1, title) + list.SetCell(i, 2, artist) + list.SetCell(i, 3, duration) + } + + list.SetSelectedFunc(func(row, column int) { + q.currentSong = row + q.playFunc(row) + }) +} + +func (q *queue) Update(songs []*subsonic.Child, currentSong int) { + q.songQueue = songs + q.currentSong = currentSong + q.drawQueue() + +} + +func (q *queue) GetView() tview.Primitive { + return q.view +} diff --git a/internal/tui/views/status_line.go b/internal/tui/views/status_line.go new file mode 100644 index 0000000..4de74b6 --- /dev/null +++ b/internal/tui/views/status_line.go @@ -0,0 +1,98 @@ +package views + +import ( + "fmt" + + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +var _ View = &statusLine{} + +type statusLine struct { + view *tview.Flex + mode Statusmode + onUpdateFunc func() + onSearchFunc func(quary string) +} + +type Statusmode int + +const ( + StatusModeLog Statusmode = iota + StatusModeSearch Statusmode = iota +) + +func NewStatusLine() *statusLine { + status := tview.NewFlex() + status.SetBackgroundColor(config.ColorBackground) + // Default empty box + status.AddItem(EmptyBox, 0, 1, false) + + return &statusLine{ + view: status, + mode: StatusModeLog, + } +} + +func (s *statusLine) SetSearchFunc(f func(quary string)) { + s.onSearchFunc = f +} + +func (s *statusLine) Mode() Statusmode { + return s.mode +} + +func (s *statusLine) Search() { + s.mode = StatusModeSearch + s.view.Clear() + label := "Search: " + _, _, w, _ := s.view.GetRect() + query := "" + inputField := tview.NewInputField() + inputField. + SetLabel(label). + SetFieldWidth(w - len(label)). + SetDoneFunc(func(key tcell.Key) { + if key == tcell.KeyEnter { + s.mode = StatusModeLog + s.onSearchFunc(query) + } else if key == tcell.KeyEsc { + s.mode = StatusModeLog + s.onSearchFunc("") + } + }). + SetChangedFunc(func(text string) { + query = text + }) + inputField.Focus(nil) + inputField.SetBackgroundColor(config.ColorBackground) + inputField.SetFieldBackgroundColor(config.ColorBackground) + s.view.AddItem(inputField, 0, 1, true) + s.Update() +} + +func (s *statusLine) SetOnUpdateFunc(f func()) { + s.onUpdateFunc = f +} + +func (s *statusLine) Log(format string, a ...any) { + if s.mode != StatusModeLog { + return + } + str := fmt.Sprintf(format, a...) + s.view.Clear() + txt := tview.NewTextView().SetDynamicColors(true) + txt.SetBackgroundColor(config.ColorBackground) + txt.SetText(str) + s.view.AddItem(txt, 0, 1, false) + s.Update() +} + +func (s *statusLine) GetView() tview.Primitive { + return s.view +} +func (s *statusLine) Update() { + s.onUpdateFunc() +} diff --git a/internal/tui/views/view.go b/internal/tui/views/view.go new file mode 100644 index 0000000..cc5cb52 --- /dev/null +++ b/internal/tui/views/view.go @@ -0,0 +1,15 @@ +package views + +import ( + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "github.com/rivo/tview" +) + +type ViewEvent string + +var EmptyBox tview.Primitive = tview.NewBox().SetBackgroundColor(config.ColorBackground) + +type View interface { + GetView() tview.Primitive + Update() +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..3c8c8b6 --- /dev/null +++ b/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + + "git.dayanhub.com/sagi/subsonic-tui/internal/client" + "git.dayanhub.com/sagi/subsonic-tui/internal/config" + "git.dayanhub.com/sagi/subsonic-tui/internal/playback" + "git.dayanhub.com/sagi/subsonic-tui/internal/tui" +) + +func main() { + defer client.ArtCache.Destroy() + // Create Client + subsonicClient := client.NewClient(config.URL()) + err := subsonicClient.Authenticate(config.Username(), config.Password()) + if err != nil { + // We need to show Login... + login := tui.NewLogin() + err := login.Run() + if err != nil { + panic(err) + } + + } + fmt.Println("Trying to login...") + subsonicClient = client.NewClient(config.URL()) + err = subsonicClient.Authenticate(config.Username(), config.Password()) + if err != nil { + panic(err) + } + // Saving config - will result in adding new defaults to the file + config.SaveConfig() + + playbackCtl := playback.NewController(subsonicClient) + defer playbackCtl.Close() + + tui := tui.NewPlayer(subsonicClient, playbackCtl) + if err := tui.Run(); err != nil { + panic(err) + } +} diff --git a/vendor/github.com/creasty/defaults/.gitignore b/vendor/github.com/creasty/defaults/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/vendor/github.com/creasty/defaults/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/vendor/github.com/creasty/defaults/LICENSE b/vendor/github.com/creasty/defaults/LICENSE new file mode 100644 index 0000000..1483dd2 --- /dev/null +++ b/vendor/github.com/creasty/defaults/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2017-present Yuki Iwanaga + +MIT License + +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/vendor/github.com/creasty/defaults/Makefile b/vendor/github.com/creasty/defaults/Makefile new file mode 100644 index 0000000..404212a --- /dev/null +++ b/vendor/github.com/creasty/defaults/Makefile @@ -0,0 +1,29 @@ +SHELL := /bin/bash -eu -o pipefail + +GO_TEST_FLAGS := -v + +PACKAGE_DIRS := $(shell go list ./... 2> /dev/null | grep -v /vendor/) +SRC_FILES := $(shell find . -name '*.go' -not -path './vendor/*') + + +# Tasks +#----------------------------------------------- +.PHONY: lint +lint: + @gofmt -e -d -s $(SRC_FILES) | awk '{ e = 1; print $0 } END { if (e) exit(1) }' + @golangci-lint --disable errcheck,unused run + +.PHONY: test +test: lint + @go test $(GO_TEST_FLAGS) $(PACKAGE_DIRS) + +.PHONY: ci-test +ci-test: lint + @echo > coverage.txt + @for d in $(PACKAGE_DIRS); do \ + go test -coverprofile=profile.out -covermode=atomic -race -v $$d; \ + if [ -f profile.out ]; then \ + cat profile.out >> coverage.txt; \ + rm profile.out; \ + fi; \ + done diff --git a/vendor/github.com/creasty/defaults/README.md b/vendor/github.com/creasty/defaults/README.md new file mode 100644 index 0000000..0efdc44 --- /dev/null +++ b/vendor/github.com/creasty/defaults/README.md @@ -0,0 +1,75 @@ +defaults +======== + +[![CircleCI](https://circleci.com/gh/creasty/defaults/tree/master.svg?style=svg)](https://circleci.com/gh/creasty/defaults/tree/master) +[![codecov](https://codecov.io/gh/creasty/defaults/branch/master/graph/badge.svg)](https://codecov.io/gh/creasty/defaults) +[![GitHub release](https://img.shields.io/github/release/creasty/defaults.svg)](https://github.com/creasty/defaults/releases) +[![License](https://img.shields.io/github/license/creasty/defaults.svg)](./LICENSE) + +Initialize structs with default values + +- Supports almost all kind of types + - Scalar types + - `int/8/16/32/64`, `uint/8/16/32/64`, `float32/64` + - `uintptr`, `bool`, `string` + - Complex types + - `map`, `slice`, `struct` + - Nested types + - `map[K1]map[K2]Struct`, `[]map[K1]Struct[]` + - Aliased types + - `time.Duration` + - e.g., `type Enum string` + - Pointer types + - e.g., `*SampleStruct`, `*int` +- Recursively initializes fields in a struct +- Dynamically sets default values by [`defaults.Setter`](./setter.go) interface +- Preserves non-initial values from being reset with a default value + + +Usage +----- + +```go +type Gender string + +type Sample struct { + Name string `default:"John Smith"` + Age int `default:"27"` + Gender Gender `default:"m"` + + Slice []string `default:"[]"` + SliceByJSON []int `default:"[1, 2, 3]"` // Supports JSON + + Map map[string]int `default:"{}"` + MapByJSON map[string]int `default:"{\"foo\": 123}"` + MapOfStruct map[string]OtherStruct + MapOfPtrStruct map[string]*OtherStruct + MapOfStructWithTag map[string]OtherStruct `default:"{\"Key1\": {\"Foo\":123}}"` + + Struct OtherStruct `default:"{}"` + StructPtr *OtherStruct `default:"{\"Foo\": 123}"` + + NoTag OtherStruct // Recurses into a nested struct by default + OptOut OtherStruct `default:"-"` // Opt-out +} + +type OtherStruct struct { + Hello string `default:"world"` // Tags in a nested struct also work + Foo int `default:"-"` + Random int `default:"-"` +} + +// SetDefaults implements defaults.Setter interface +func (s *OtherStruct) SetDefaults() { + if defaults.CanUpdate(s.Random) { // Check if it's a zero value (recommended) + s.Random = rand.Int() // Set a dynamic value + } +} +``` + +```go +obj := &Sample{} +if err := defaults.Set(obj); err != nil { + panic(err) +} +``` diff --git a/vendor/github.com/creasty/defaults/defaults.go b/vendor/github.com/creasty/defaults/defaults.go new file mode 100644 index 0000000..b5e7eb9 --- /dev/null +++ b/vendor/github.com/creasty/defaults/defaults.go @@ -0,0 +1,244 @@ +package defaults + +import ( + "encoding" + "encoding/json" + "errors" + "reflect" + "strconv" + "time" +) + +var ( + errInvalidType = errors.New("not a struct pointer") +) + +const ( + fieldName = "default" +) + +// Set initializes members in a struct referenced by a pointer. +// Maps and slices are initialized by `make` and other primitive types are set with default values. +// `ptr` should be a struct pointer +func Set(ptr interface{}) error { + if reflect.TypeOf(ptr).Kind() != reflect.Ptr { + return errInvalidType + } + + v := reflect.ValueOf(ptr).Elem() + t := v.Type() + + if t.Kind() != reflect.Struct { + return errInvalidType + } + + for i := 0; i < t.NumField(); i++ { + if defaultVal := t.Field(i).Tag.Get(fieldName); defaultVal != "-" { + if err := setField(v.Field(i), defaultVal); err != nil { + return err + } + } + } + callSetter(ptr) + return nil +} + +// MustSet function is a wrapper of Set function +// It will call Set and panic if err not equals nil. +func MustSet(ptr interface{}) { + if err := Set(ptr); err != nil { + panic(err) + } +} + +func setField(field reflect.Value, defaultVal string) error { + if !field.CanSet() { + return nil + } + + if !shouldInitializeField(field, defaultVal) { + return nil + } + + isInitial := isInitialValue(field) + if isInitial { + if unmarshalByInterface(field, defaultVal) { + return nil + } + + switch field.Kind() { + case reflect.Bool: + if val, err := strconv.ParseBool(defaultVal); err == nil { + field.Set(reflect.ValueOf(val).Convert(field.Type())) + } + case reflect.Int: + if val, err := strconv.ParseInt(defaultVal, 0, strconv.IntSize); err == nil { + field.Set(reflect.ValueOf(int(val)).Convert(field.Type())) + } + case reflect.Int8: + if val, err := strconv.ParseInt(defaultVal, 0, 8); err == nil { + field.Set(reflect.ValueOf(int8(val)).Convert(field.Type())) + } + case reflect.Int16: + if val, err := strconv.ParseInt(defaultVal, 0, 16); err == nil { + field.Set(reflect.ValueOf(int16(val)).Convert(field.Type())) + } + case reflect.Int32: + if val, err := strconv.ParseInt(defaultVal, 0, 32); err == nil { + field.Set(reflect.ValueOf(int32(val)).Convert(field.Type())) + } + case reflect.Int64: + if val, err := time.ParseDuration(defaultVal); err == nil { + field.Set(reflect.ValueOf(val).Convert(field.Type())) + } else if val, err := strconv.ParseInt(defaultVal, 0, 64); err == nil { + field.Set(reflect.ValueOf(val).Convert(field.Type())) + } + case reflect.Uint: + if val, err := strconv.ParseUint(defaultVal, 0, strconv.IntSize); err == nil { + field.Set(reflect.ValueOf(uint(val)).Convert(field.Type())) + } + case reflect.Uint8: + if val, err := strconv.ParseUint(defaultVal, 0, 8); err == nil { + field.Set(reflect.ValueOf(uint8(val)).Convert(field.Type())) + } + case reflect.Uint16: + if val, err := strconv.ParseUint(defaultVal, 0, 16); err == nil { + field.Set(reflect.ValueOf(uint16(val)).Convert(field.Type())) + } + case reflect.Uint32: + if val, err := strconv.ParseUint(defaultVal, 0, 32); err == nil { + field.Set(reflect.ValueOf(uint32(val)).Convert(field.Type())) + } + case reflect.Uint64: + if val, err := strconv.ParseUint(defaultVal, 0, 64); err == nil { + field.Set(reflect.ValueOf(val).Convert(field.Type())) + } + case reflect.Uintptr: + if val, err := strconv.ParseUint(defaultVal, 0, strconv.IntSize); err == nil { + field.Set(reflect.ValueOf(uintptr(val)).Convert(field.Type())) + } + case reflect.Float32: + if val, err := strconv.ParseFloat(defaultVal, 32); err == nil { + field.Set(reflect.ValueOf(float32(val)).Convert(field.Type())) + } + case reflect.Float64: + if val, err := strconv.ParseFloat(defaultVal, 64); err == nil { + field.Set(reflect.ValueOf(val).Convert(field.Type())) + } + case reflect.String: + field.Set(reflect.ValueOf(defaultVal).Convert(field.Type())) + + case reflect.Slice: + ref := reflect.New(field.Type()) + ref.Elem().Set(reflect.MakeSlice(field.Type(), 0, 0)) + if defaultVal != "" && defaultVal != "[]" { + if err := json.Unmarshal([]byte(defaultVal), ref.Interface()); err != nil { + return err + } + } + field.Set(ref.Elem().Convert(field.Type())) + case reflect.Map: + ref := reflect.New(field.Type()) + ref.Elem().Set(reflect.MakeMap(field.Type())) + if defaultVal != "" && defaultVal != "{}" { + if err := json.Unmarshal([]byte(defaultVal), ref.Interface()); err != nil { + return err + } + } + field.Set(ref.Elem().Convert(field.Type())) + case reflect.Struct: + if defaultVal != "" && defaultVal != "{}" { + if err := json.Unmarshal([]byte(defaultVal), field.Addr().Interface()); err != nil { + return err + } + } + case reflect.Ptr: + field.Set(reflect.New(field.Type().Elem())) + } + } + + switch field.Kind() { + case reflect.Ptr: + if isInitial || field.Elem().Kind() == reflect.Struct { + setField(field.Elem(), defaultVal) + callSetter(field.Interface()) + } + case reflect.Struct: + if err := Set(field.Addr().Interface()); err != nil { + return err + } + case reflect.Slice: + for j := 0; j < field.Len(); j++ { + if err := setField(field.Index(j), defaultVal); err != nil { + return err + } + } + case reflect.Map: + for _, e := range field.MapKeys() { + var v = field.MapIndex(e) + + switch v.Kind() { + case reflect.Ptr: + switch v.Elem().Kind() { + case reflect.Struct, reflect.Slice, reflect.Map: + if err := setField(v.Elem(), ""); err != nil { + return err + } + } + case reflect.Struct, reflect.Slice, reflect.Map: + ref := reflect.New(v.Type()) + ref.Elem().Set(v) + if err := setField(ref.Elem(), ""); err != nil { + return err + } + field.SetMapIndex(e, ref.Elem().Convert(v.Type())) + } + } + } + + return nil +} + +func unmarshalByInterface(field reflect.Value, defaultVal string) bool { + asText, ok := field.Addr().Interface().(encoding.TextUnmarshaler) + if ok && defaultVal != "" { + // if field implements encode.TextUnmarshaler, try to use it before decode by kind + if err := asText.UnmarshalText([]byte(defaultVal)); err == nil { + return true + } + } + asJSON, ok := field.Addr().Interface().(json.Unmarshaler) + if ok && defaultVal != "" && defaultVal != "{}" && defaultVal != "[]" { + // if field implements json.Unmarshaler, try to use it before decode by kind + if err := asJSON.UnmarshalJSON([]byte(defaultVal)); err == nil { + return true + } + } + return false +} + +func isInitialValue(field reflect.Value) bool { + return reflect.DeepEqual(reflect.Zero(field.Type()).Interface(), field.Interface()) +} + +func shouldInitializeField(field reflect.Value, tag string) bool { + switch field.Kind() { + case reflect.Struct: + return true + case reflect.Ptr: + if !field.IsNil() && field.Elem().Kind() == reflect.Struct { + return true + } + case reflect.Slice: + return field.Len() > 0 || tag != "" + case reflect.Map: + return field.Len() > 0 || tag != "" + } + + return tag != "" +} + +// CanUpdate returns true when the given value is an initial value of its type +func CanUpdate(v interface{}) bool { + return isInitialValue(reflect.ValueOf(v)) +} diff --git a/vendor/github.com/creasty/defaults/setter.go b/vendor/github.com/creasty/defaults/setter.go new file mode 100644 index 0000000..1f64aa6 --- /dev/null +++ b/vendor/github.com/creasty/defaults/setter.go @@ -0,0 +1,12 @@ +package defaults + +// Setter is an interface for setting default values +type Setter interface { + SetDefaults() +} + +func callSetter(v interface{}) { + if ds, ok := v.(Setter); ok { + ds.SetDefaults() + } +} diff --git a/vendor/github.com/delucks/go-subsonic/.gitignore b/vendor/github.com/delucks/go-subsonic/.gitignore new file mode 100644 index 0000000..5d2a63f --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/.gitignore @@ -0,0 +1,4 @@ +*.swp +.idea +subsonic-rest-api-1.16.1.xsd +build/ diff --git a/vendor/github.com/delucks/go-subsonic/LICENSE.txt b/vendor/github.com/delucks/go-subsonic/LICENSE.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/LICENSE.txt @@ -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/vendor/github.com/delucks/go-subsonic/README.md b/vendor/github.com/delucks/go-subsonic/README.md new file mode 100644 index 0000000..27b2723 --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/README.md @@ -0,0 +1,156 @@ +# go-subsonic + +[![GoDoc](https://godoc.org/github.com/delucks/go-subsonic?status.svg)](https://godoc.org/github.com/delucks/go-subsonic) + +This is an API client library for Subsonic and Subsonic-compatible music servers. It is tested on Subsonic, Airsonic, and Navidrome. + +# Testing + +Tests for this library run live against instances of Subsonic-compatible servers. A docker-compose setup comes with this repo to run instances of Airsonic and Navidrome for testing purposes. To set up the test environment and run tests against the docker containers, run `test.sh`. This test runner does the following: + +- Download CC-licensed sample music into `build/music` +- Set up a location for configuration for both servers in `build/data` +- Run `docker-compose up` to initialize both servers with the music & configuration directories as volumes +- Wait until the servers are up +- Run tests for both Subsonic and Navidrome + +The tests for Subsonic run against the demo server, so please be mindful to exclude them from all but final test runs in respect for their server load. To manually tests for only one server type, use the `-run` argument to `go test` like so: + +``` +# Just Airsonic +go test . -test.v -run Airsonic +# Subsonic and Airsonic +go test . -test.v -run '(Sub|Air)sonic' +# Navidrome +go test . -test.v -run Navidrome +``` + +If you intend on testing with your own server, modify the top-level TestSubsonic, TestAirsonic, or TestNavidrome functions with your server's address and credentials. Please test this library with other Subsonic-compatible streaming servers, and file an issue if something is amiss! + +# API Support + +## System + +- [x] ping (1.0.0) +- [x] getLicense (1.0.0) + +## Browsing + +- [x] getMusicFolders (1.0.0) +- [x] getIndexes (1.0.0) +- [x] getMusicDirectory +- [x] getGenres (1.9.0) +- [x] getArtists (1.8.0) +- [x] getArtist (1.8.0) +- [x] getAlbum (1.0.0) +- [x] getSong (1.8.0) +- [x] getArtistInfo (1.11.0) +- [x] getArtistInfo2 (1.11.0) +- [x] getAlbumInfo (1.14.0) +- [x] getAlbumInfo2 (1.14.0) +- [x] getSimilarSongs (1.11.0) +- [x] getSimilarSongs2 (1.11.0) +- [x] getTopSongs (1.13.0) + +## Album/song lists + +- [x] getAlbumList (1.2.0) +- [x] getAlbumList2 (1.8.0) +- [x] getRandomSongs (1.2.0) +- [x] getSongsByGenre (1.9.0) +- [x] getNowPlaying (1.0.0) +- [x] getStarred (1.8.0) +- [x] getStarred2 (1.8.0) + +## Searching + +- [x] search2 (1.4.0) +- [x] search3 (1.8.0) + +## Playlists + +- [x] getPlaylists (1.0.0) +- [x] getPlaylist (1.0.0) +- [x] createPlaylist (1.2.0) +- [x] updatePlaylist (1.8.0) +- [x] deletePlaylist (1.2.0) + +## Media retrieval + +- [x] stream (1.0.0) +- [x] download (1.0.0) +- [x] getCoverArt (1.0.0) +- [ ] getLyrics (1.2.0) +- [x] getAvatar (1.8.0) + +## Media annotation + +- [x] star (1.8.0) +- [x] unstar (1.8.0) +- [x] setRating (1.6.0) +- [x] scrobble (1.5.0) + +## User management + +- [x] getUser (1.3.0) +- [x] getUsers (1.8.0) +- [x] createUser (1.1.0) +- [x] updateUser (1.10.1) +- [x] deleteUser (1.3.0) +- [x] changePassword (1.1.0) + +## Media library scanning + +- [x] getScanStatus (1.15.0) +- [x] startScan (1.15.0) + +## Bookmarks + +- [ ] getBookmarks (1.9.0) +- [ ] createBookmark (1.9.0) +- [ ] deleteBookmark (1.9.0) +- [ ] getPlayQueue (1.12.0) +- [ ] savePlayQueue (1.12.0) + +## Sharing + +- [ ] getShares (1.6.0) +- [ ] createShare (1.6.0) +- [ ] updateShare (1.6.0) +- [ ] deleteShare (1.6.0) + +## Podcast + +- [ ] getPodcasts (1.6.0) +- [ ] getNewestPodcasts (1.13.0) +- [ ] refreshPodcasts (1.9.0) +- [ ] createPodcastChannel (1.9.0) +- [ ] deletePodcastChannel (1.9.0) +- [ ] deletePodcastEpisode (1.9.0) +- [ ] downloadPodcastEpisode (1.9.0) + +## Jukebox + +- [ ] jukeboxControl (1.2.0) + +## Internet radio + +- [ ] getInternetRadioStations (1.9.0) +- [ ] createInternetRadioStation (1.16.0) +- [ ] updateInternetRadioStation (1.16.0) +- [ ] deleteInternetRadioStation (1.16.0) + +## Chat + +- [ ] getChatMessages (1.2.0) +- [ ] addChatMessage (1.2.0) + +## Out of Scope + +Video endpoints are currently out of scope- please file an issue if you would like support for them. The deprecated "search" endpoint is unimplemented. + +- [ ] hls.m3u8 (1.8.0) +- [ ] getVideos (1.8.0) +- [ ] getVideoInfo (1.14.0) +- [ ] getCaptions (1.14.0) +- [ ] search (1.0.0) diff --git a/vendor/github.com/delucks/go-subsonic/annotation.go b/vendor/github.com/delucks/go-subsonic/annotation.go new file mode 100644 index 0000000..dbc45fb --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/annotation.go @@ -0,0 +1,100 @@ +package subsonic + +import ( + "errors" + "fmt" + "net/url" + "strconv" +) + +// StarParameters are used to identify songs, albums, and artists (or some subset of those) at the same time. +// subsonic.Star and subsonic.Unstar both use StarParameters to identify things to star. +type StarParameters struct { + SongIDs []string + AlbumIDs []string + ArtistIDs []string +} + +// Star adds the star annotation to songs, albums, and artists. +func (s *Client) Star(parameters StarParameters) error { + params := url.Values{} + for _, song := range parameters.SongIDs { + params.Add("id", song) + } + for _, album := range parameters.AlbumIDs { + params.Add("albumId", album) + } + for _, artist := range parameters.ArtistIDs { + params.Add("artistId", artist) + } + _, err := s.getValues("star", params) + if err != nil { + return err + } + return nil +} + +// Unstar removes the star annotation from songs, albums, and artists. +func (s *Client) Unstar(parameters StarParameters) error { + params := url.Values{} + for _, song := range parameters.SongIDs { + params.Add("id", song) + } + for _, album := range parameters.AlbumIDs { + params.Add("albumId", album) + } + for _, artist := range parameters.ArtistIDs { + params.Add("artistId", artist) + } + _, err := s.getValues("unstar", params) + if err != nil { + return err + } + return nil +} + +// SetRating sets the rating of a music file. +func (s *Client) SetRating(id string, rating int) error { + if rating > 5 || rating < 0 { + return errors.New("Rating can only be in the range 0-5") + } + params := map[string]string{ + "id": id, + "rating": fmt.Sprintf("%d", rating), + } + _, err := s.Get("setRating", params) + if err != nil { + return err + } + return nil +} + +// Scrobble submits a song to last.fm if the user has configured their credentials to do so. +// +// Optional Parameters: +// time: (Since 1.8.0) The time (in milliseconds since 1 Jan 1970) at which the song was listened to. +// submission: Whether this is a "submission" (true) or a "now playing" (false) notification. Defaults to a submission. +func (s *Client) Scrobble(id string, parameters map[string]string) error { + params := map[string]string{ + "id": id, + } + if scrobbleTime, ok := parameters["time"]; ok { + _, err := strconv.Atoi(scrobbleTime) + if err != nil { + return fmt.Errorf("%s is not a unix-style timestamp", scrobbleTime) + } + params["time"] = scrobbleTime + } + if submission, ok := parameters["submission"]; ok { + _, err := strconv.ParseBool(submission) + if err != nil { + return fmt.Errorf("%s is not boolean", submission) + } + params["submission"] = submission + } + _, err := s.Get("scrobble", params) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/delucks/go-subsonic/browsing.go b/vendor/github.com/delucks/go-subsonic/browsing.go new file mode 100644 index 0000000..106fa9b --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/browsing.go @@ -0,0 +1,189 @@ +package subsonic + +// GetMusicFolders returns all configured top-level music folders. +func (s *Client) GetMusicFolders() ([]*MusicFolder, error) { + resp, err := s.Get("getMusicFolders", nil) + if err != nil { + return nil, err + } + return resp.MusicFolders.MusicFolder, nil +} + +// GetIndexes returns the index of entries by letter/number. +// +// Optional Parameters: +// musicFolderId: Only return songs in the music folder with the given ID. See getMusicFolders. +// ifModifiedSince: If specified, only return a result if the artist collection has changed since the given time (in milliseconds since 1 Jan 1970). +func (s *Client) GetIndexes(parameters map[string]string) (*Indexes, error) { + resp, err := s.Get("getIndexes", parameters) + if err != nil { + return nil, err + } + return resp.Indexes, nil +} + +// GetMusicDirectory returns a listing of all files in a music directory. Typically used to get list of albums for an artist, or list of songs for an album. +// The ID can be an album, song, or artist - anything considered within the directory hierarchy of Subsonic. +func (s *Client) GetMusicDirectory(id string) (*Directory, error) { + resp, err := s.Get("getMusicDirectory", map[string]string{"id": id}) + if err != nil { + return nil, err + } + return resp.Directory, nil +} + +// GetGenres returns all genres in the server. +func (s *Client) GetGenres() ([]*Genre, error) { + resp, err := s.Get("getGenres", nil) + if err != nil { + return nil, err + } + return resp.Genres.Genre, nil +} + +// GetArtists returns all artists in the server. +// +// Optional Parameters: +// musicFolderId: Only return songs in the music folder with the given ID. See getMusicFolders. +func (s *Client) GetArtists(parameters map[string]string) (*ArtistsID3, error) { + resp, err := s.Get("getArtists", parameters) + if err != nil { + return nil, err + } + return resp.Artists, nil +} + +// GetAlbum returns an Artist by ID. +func (s *Client) GetArtist(id string) (*ArtistID3, error) { + resp, err := s.Get("getArtist", map[string]string{"id": id}) + if err != nil { + return nil, err + } + return resp.Artist, nil +} + +// GetAlbum returns an Album by ID. +func (s *Client) GetAlbum(id string) (*AlbumID3, error) { + resp, err := s.Get("getAlbum", map[string]string{"id": id}) + if err != nil { + return nil, err + } + return resp.Album, nil +} + +// GetSong returns a Song by ID. +func (s *Client) GetSong(id string) (*Child, error) { + resp, err := s.Get("getSong", map[string]string{"id": id}) + if err != nil { + return nil, err + } + return resp.Song, nil +} + +// GetArtistInfo returns biography, image links, and similar artists from last.fm. +// +// Optional Parameters: +// count: Max number of similar artists to return. +// includeNotPresent: Whether to return artists that are not present in the media library. +func (s *Client) GetArtistInfo(id string, parameters map[string]string) (*ArtistInfo, error) { + params := make(map[string]string) + params["id"] = id + for k, v := range parameters { + params[k] = v + } + resp, err := s.Get("getArtistInfo", params) + if err != nil { + return nil, err + } + return resp.ArtistInfo, nil +} + +// GetArtistInfo2 returns biography, image links, and similar artists like GetArtistInfo, but using id3 tags. +// +// Optional Parameters: +// count: Max number of similar artists to return. +// includeNotPresent: Whether to return artists that are not present in the media library. +func (s *Client) GetArtistInfo2(id string, parameters map[string]string) (*ArtistInfo2, error) { + params := make(map[string]string) + params["id"] = id + for k, v := range parameters { + params[k] = v + } + resp, err := s.Get("getArtistInfo2", params) + if err != nil { + return nil, err + } + return resp.ArtistInfo2, nil +} + +// GetAlbumInfo returns album notes, image data, etc using data from last.fm. +// This accepts both album and song IDs. +func (s *Client) GetAlbumInfo(id string) (*AlbumInfo, error) { + resp, err := s.Get("getAlbumInfo", map[string]string{"id": id}) + if err != nil { + return nil, err + } + return resp.AlbumInfo, nil +} + +// GetAlbumInfo2 returns the same data as GetAlbumInfo, but organized by id3 tag. +// It only accepts album IDs. +func (s *Client) GetAlbumInfo2(id string) (*AlbumInfo, error) { + resp, err := s.Get("getAlbumInfo2", map[string]string{"id": id}) + if err != nil { + return nil, err + } + return resp.AlbumInfo, nil +} + +// GetSimilarSongs finds similar songs to an album, track, or artist. +// This is mostly used for radio features. This accepts artist, album, or song IDs. +// +// Optional Parameters: +// count: Number of songs to return +func (s *Client) GetSimilarSongs(id string, parameters map[string]string) ([]*Child, error) { + params := make(map[string]string) + params["id"] = id + for k, v := range parameters { + params[k] = v + } + resp, err := s.Get("getSimilarSongs", params) + if err != nil { + return nil, err + } + return resp.SimilarSongs.Song, nil +} + +// GetSimilarSongs2 finds similar songs like GetSimilarSongs, but using id3 tags. +// +// Optional Parameters: +// count: Number of songs to return +func (s *Client) GetSimilarSongs2(id string, parameters map[string]string) ([]*Child, error) { + params := make(map[string]string) + params["id"] = id + for k, v := range parameters { + params[k] = v + } + resp, err := s.Get("getSimilarSongs2", params) + if err != nil { + return nil, err + } + return resp.SimilarSongs2.Song, nil +} + +// GetTopSongs returns the top songs for a given artist by name. +// +// Optional Parameters: +// count: Number of songs to return +func (s *Client) GetTopSongs(name string, parameters map[string]string) ([]*Child, error) { + params := make(map[string]string) + params["artist"] = name + for k, v := range parameters { + params[k] = v + } + resp, err := s.Get("getTopSongs", params) + if err != nil { + return nil, err + } + return resp.TopSongs.Song, nil +} diff --git a/vendor/github.com/delucks/go-subsonic/client.go b/vendor/github.com/delucks/go-subsonic/client.go new file mode 100644 index 0000000..079f2fe --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/client.go @@ -0,0 +1,171 @@ +// Package subsonic implements an API client library for Subsonic-compatible music streaming servers. +// +// This project handles communication with a remote *sonic server, but does not handle playback of media. The library user should be prepared to do something with the stream of audio in bytes, like decoding and playing that audio on a sound card. +// The list of API endpoints implemented is available on the project's github page. +// +// The API is divided between functions with no suffix, and functions that have a "2" suffix (or "3" in the case of Search3). +// Generally, things with "2" on the end are organized by file tags rather than folder structure. This is how you'd expect most music players to work and is recommended. +// The variants without a suffix organize the library by directory structure; artists are a directory, albums are children of that directory, songs (subsonic.Child) are children of albums. +// This has some disadvantages: possibly duplicating items with identical directory names, treating songs and albums in much the same fashion, and being more difficult to query consistently. +package subsonic + +import ( + "crypto/md5" + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "log" + "math/rand" + "net/http" + "net/url" + "path" +) + +const ( + supportedApiVersion = "1.8.0" + libraryVersion = "0.0.5" +) + +type Client struct { + Client *http.Client + BaseUrl string + User string + ClientName string + PasswordAuth bool + password string + salt string + token string +} + +func generateSalt() string { + var corpus = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + // length is minimum 6, but let's use ten to start + b := make([]rune, 10) + for i := range b { + b[i] = corpus[rand.Intn(len(corpus))] + } + return string(b) +} + +// Authenticate authenticates the current user with a provided password. The password is salted before transmission and requires Subsonic > 1.13.0. +func (s *Client) Authenticate(password string) error { + if s.PasswordAuth { + s.password = password + } else { + salt := generateSalt() + h := md5.New() + _, err := io.WriteString(h, password) + if err != nil { + return err + } + _, err = io.WriteString(h, salt) + if err != nil { + return err + } + s.salt = salt + s.token = fmt.Sprintf("%x", h.Sum(nil)) + } + + // Test authentication + // Don't use the s.Ping method because that always returns true as long as the servers is up. + resp, err := s.Get("ping", nil) + if err != nil { + return fmt.Errorf("Authentication failed: %s", err) + } + + if resp.Error != nil { + return fmt.Errorf("Authentication failed: %s", resp.Error.Message) + } + + return nil +} + +// Request performs a HTTP request against the Subsonic server as the current user. +func (s *Client) Request(method string, endpoint string, params url.Values) (*http.Response, error) { + baseUrl, err := url.Parse(s.BaseUrl) + if err != nil { + return nil, err + } + baseUrl.Path = path.Join(baseUrl.Path, "/rest/", endpoint) + req, err := http.NewRequest(method, baseUrl.String(), nil) + if err != nil { + return nil, err + } + + q := req.URL.Query() + q.Add("f", "xml") + q.Add("v", supportedApiVersion) + q.Add("c", s.ClientName) + q.Add("u", s.User) + if s.PasswordAuth { + q.Add("p", s.password) + } else { + q.Add("t", s.token) + q.Add("s", s.salt) + } + + for key, values := range params { + for _, val := range values { + q.Add(key, val) + } + } + req.URL.RawQuery = q.Encode() + //log.Printf("%s %s", method, req.URL.String()) + + resp, err := s.Client.Do(req) + if err != nil { + return nil, err + } + return resp, nil +} + +// Get is a convenience interface to issue a GET request and parse the response body (99% of Subsonic API calls) +func (s *Client) Get(endpoint string, params map[string]string) (*Response, error) { + parameters := url.Values{} + for k, v := range params { + parameters.Add(k, v) + } + return s.getValues(endpoint, parameters) +} + +// getValues is a convenience interface to issue a GET request and parse the response body. It supports multiple values by way of the url.Values argument. +func (s *Client) getValues(endpoint string, params url.Values) (*Response, error) { + response, err := s.Request("GET", endpoint, params) + if err != nil { + return nil, err + } + responseBody, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + parsed := Response{} + err = xml.Unmarshal(responseBody, &parsed) + if err != nil { + return nil, err + } + if parsed.Error != nil { + return nil, fmt.Errorf("Error #%d: %s\n", parsed.Error.Code, parsed.Error.Message) + } + //log.Printf("%s: %s\n", endpoint, string(responseBody)) + return &parsed, nil +} + +// Ping is used to test connectivity with the server. It returns true if the server is up. +func (s *Client) Ping() bool { + _, err := s.Request("GET", "ping", nil) + if err != nil { + log.Println(err) + return false + } + return true +} + +// GetLicense retrieves details about the software license. Subsonic requires a license after a 30-day trial, compatible applications have a perpetually valid license. +func (s *Client) GetLicense() (*License, error) { + resp, err := s.Get("getLicense", nil) + if err != nil { + return nil, err + } + return resp.License, nil +} diff --git a/vendor/github.com/delucks/go-subsonic/docker-compose.yml b/vendor/github.com/delucks/go-subsonic/docker-compose.yml new file mode 100644 index 0000000..092857b --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/docker-compose.yml @@ -0,0 +1,26 @@ +version: "3" +services: + navidrome: + image: deluan/navidrome:latest + user: 1000:1000 # should be owner of volumes + ports: + - "4533:4533" + restart: unless-stopped + environment: + # Put your config options customization here + ND_SCANINTERVAL: 1m + ND_LOGLEVEL: info + ND_SESSIONTIMEOUT: 24h + ND_BASEURL: "" + volumes: + - "./build/data:/data" + - "./build/music:/music:ro" + airsonic: + image: airsonic/airsonic:latest + user: 1000:1000 + ports: + - "4040:4040" + restart: unless-stopped + volumes: + - "./build/data:/airsonic/data" + - "./build/music:/airsonic/music:ro" diff --git a/vendor/github.com/delucks/go-subsonic/lists.go b/vendor/github.com/delucks/go-subsonic/lists.go new file mode 100644 index 0000000..718b57a --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/lists.go @@ -0,0 +1,175 @@ +package subsonic + +import ( + "errors" + "fmt" +) + +func validateListType(input string) bool { + validTypes := map[string]bool{ + "random": true, + "newest": true, + "highest": true, + "frequent": true, + "recent": true, + "alphabeticalByName": true, + "alphabeticalByArtist": true, + "starred": true, + "byYear": true, + "byGenre": true, + } + _, ok := validTypes[input] + return ok +} + +// GetAlbumList returns a list of random, newest, highest rated etc. albums. Similar to the album lists on the home page of the Subsonic web interface. +// +// Optional Parameters: +// size: The number of albums to return. Max 500, default 10. +// offset: The list offset. Useful if you for example want to page through the list of newest albums. +// fromYear: The first year in the range. If fromYear > toYear a reverse chronological list is returned. +// toYear: The last year in the range. +// genre: The name of the genre, e.g., "Rock". +// musicFolderId: (Since 1.11.0) Only return albums in the music folder with the given ID. See getMusicFolders. +// +// toYear and fromYear are required parameters when type == "byYear". genre is a required parameter when type == "byGenre". +func (s *Client) GetAlbumList(listType string, parameters map[string]string) ([]*Child, error) { + if !validateListType(listType) { + return nil, fmt.Errorf("List type %s is invalid, see http://www.subsonic.org/pages/api.jsp#getAlbumList", listType) + } + if listType == "byYear" { + _, ok := parameters["fromYear"] + if !ok { + return nil, errors.New("Required argument fromYear was not found when using GetAlbumList byYear") + } + _, ok = parameters["toYear"] + if !ok { + return nil, errors.New("Required argument toYear was not found when using GetAlbumList byYear") + } + } else if listType == "byGenre" { + _, ok := parameters["genre"] + if !ok { + return nil, errors.New("Required argument genre was not found when using GetAlbumList byGenre") + } + } + params := make(map[string]string) + params["type"] = listType + for k, v := range parameters { + params[k] = v + } + resp, err := s.Get("getAlbumList", params) + if err != nil { + return nil, err + } + return resp.AlbumList.Album, nil +} + +// GetAlbumList2 returns a list of albums like GetAlbumList, but organized according to id3 tags. +// +// Optional Parameters: +// size: The number of albums to return. Max 500, default 10. +// offset: The list offset. Useful if you for example want to page through the list of newest albums. +// fromYear: The first year in the range. If fromYear > toYear a reverse chronological list is returned. +// toYear: The last year in the range. +// genre: The name of the genre, e.g., "Rock". +// musicFolderId: (Since 1.11.0) Only return albums in the music folder with the given ID. See getMusicFolders. +// +// toYear and fromYear are required parameters when type == "byYear". genre is a required parameter when type == "byGenre". +func (s *Client) GetAlbumList2(listType string, parameters map[string]string) ([]*AlbumID3, error) { + if !validateListType(listType) { + return nil, fmt.Errorf("List type %s is invalid, see http://www.subsonic.org/pages/api.jsp#getAlbumList", listType) + } + if listType == "byYear" { + _, ok := parameters["fromYear"] + if !ok { + return nil, errors.New("Required argument fromYear was not found when using GetAlbumList2 byYear") + } + _, ok = parameters["toYear"] + if !ok { + return nil, errors.New("Required argument toYear was not found when using GetAlbumList2 byYear") + } + } else if listType == "byGenre" { + _, ok := parameters["genre"] + if !ok { + return nil, errors.New("Required argument genre was not found when using GetAlbumList2 byGenre") + } + } + params := make(map[string]string) + params["type"] = listType + for k, v := range parameters { + params[k] = v + } + resp, err := s.Get("getAlbumList2", params) + if err != nil { + return nil, err + } + return resp.AlbumList2.Album, nil +} + +// GetRandomSongs returns a randomly selected set of songs limited by the optional parameters. +// +// Optional Parameters: +// size: The maximum number of songs to return. Max 500, default 10. +// genre: Only returns songs belonging to this genre. +// fromYear: Only return songs published after or in this year. +// toYear: Only return songs published before or in this year. +// musicFolderId: Only return songs in the music folder with the given ID. See getMusicFolders. +func (s *Client) GetRandomSongs(parameters map[string]string) ([]*Child, error) { + resp, err := s.Get("getRandomSongs", parameters) + if err != nil { + return nil, err + } + return resp.RandomSongs.Song, nil +} + +// GetSongsByGenre returns songs in a given genre name. +// +// Optional Parameters: +// count: The maximum number of songs to return. Max 500, default 10. +// offset: The offset. Useful if you want to page through the songs in a genre. +// musicFolderId: Only return songs in the music folder with the given ID. See getMusicFolders. +func (s *Client) GetSongsByGenre(name string, parameters map[string]string) ([]*Child, error) { + params := make(map[string]string) + params["genre"] = name + for k, v := range parameters { + params[k] = v + } + resp, err := s.Get("getSongsByGenre", params) + if err != nil { + return nil, err + } + return resp.SongsByGenre.Song, nil +} + +// GetNowPlaying returns what is currently being played by all users. +func (s *Client) GetNowPlaying() ([]*NowPlayingEntry, error) { + resp, err := s.Get("getNowPlaying", nil) + if err != nil { + return nil, err + } + return resp.NowPlaying.Entry, nil +} + +// GetStarred returns starred albums, artists, and songs. +// +// Optional Parameters: +// musicFolderId: Only return songs in the music folder with the given ID. See getMusicFolders. +func (s *Client) GetStarred(parameters map[string]string) (*Starred, error) { + resp, err := s.Get("getStarred", parameters) + if err != nil { + return nil, err + } + return resp.Starred, nil +} + +// GetStarred2 returns starred albums, artists, and songs arranged by id3 tag. +// +// Optional Parameters: +// musicFolderId: Only return songs in the music folder with the given ID. See getMusicFolders. +func (s *Client) GetStarred2(parameters map[string]string) (*Starred2, error) { + resp, err := s.Get("getStarred2", parameters) + if err != nil { + return nil, err + } + return resp.Starred2, nil +} diff --git a/vendor/github.com/delucks/go-subsonic/models.go b/vendor/github.com/delucks/go-subsonic/models.go new file mode 100644 index 0000000..a27ea56 --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/models.go @@ -0,0 +1,679 @@ +package subsonic + +/* This file was automatically generated from the xsd schema provided by Subsonic, then manually modified. + * http://www.subsonic.org/pages/inc/api/schema/subsonic-rest-api-1.16.1.xsd + * xsdgen -o xml.go -pkg subsonic -ns "http://subsonic.org/restapi" subsonic-rest-api-1.16.1.xsd + * Changes from the original include: + * - Adding missing name (value of xml element) for each genre + * - Capitalize "ID" in struct names and add missing ID fields. + * - Merge *With* variants of structs. + */ + +import ( + "bytes" + "encoding/xml" + "time" +) + +// AlbumID3 is an album that's organized by music file tags. +type AlbumID3 struct { + ID string `xml:"id,attr"` // Manually added + Song []*Child `xml:"http://subsonic.org/restapi song,omitempty"` // Merged from AlbumWithSongsID3 + Name string `xml:"name,attr"` + Artist string `xml:"artist,attr,omitempty"` + ArtistID string `xml:"artistId,attr,omitempty"` + CoverArt string `xml:"coverArt,attr,omitempty"` + SongCount int `xml:"songCount,attr"` + Duration int `xml:"duration,attr"` + PlayCount int64 `xml:"playCount,attr,omitempty"` + Created time.Time `xml:"created,attr"` + Starred time.Time `xml:"starred,attr,omitempty"` + Year int `xml:"year,attr,omitempty"` + Genre string `xml:"genre,attr,omitempty"` +} + +// AlbumInfo is a collection of notes and links describing an album. +type AlbumInfo struct { + Notes string `xml:"http://subsonic.org/restapi notes,omitempty"` + MusicBrainzID string `xml:"http://subsonic.org/restapi musicBrainzId,omitempty"` + LastFmUrl string `xml:"http://subsonic.org/restapi lastFmUrl,omitempty"` + SmallImageUrl string `xml:"http://subsonic.org/restapi smallImageUrl,omitempty"` + MediumImageUrl string `xml:"http://subsonic.org/restapi mediumImageUrl,omitempty"` + LargeImageUrl string `xml:"http://subsonic.org/restapi largeImageUrl,omitempty"` +} + +type albumList struct { + Album []*Child `xml:"http://subsonic.org/restapi album,omitempty"` +} + +type albumList2 struct { + Album []*AlbumID3 `xml:"http://subsonic.org/restapi album,omitempty"` +} + +// Artist is an artist from the server, organized in the folders pattern. +type Artist struct { + ID string `xml:"id,attr"` + Name string `xml:"name,attr"` + ArtistImageUrl string `xml:"artistImageUrl,attr,omitempty"` + Starred time.Time `xml:"starred,attr,omitempty"` + UserRating int `xml:"userRating,attr,omitempty"` + AverageRating float64 `xml:"averageRating,attr,omitempty"` +} + +// ArtistID3 is an artist from the server, organized by ID3 tag. +type ArtistID3 struct { + ID string `xml:"id,attr"` // Manually added + Album []*AlbumID3 `xml:"http://subsonic.org/restapi album,omitempty"` // Merged with ArtistWithAlbumsID3 + Name string `xml:"name,attr"` + CoverArt string `xml:"coverArt,attr,omitempty"` + ArtistImageUrl string `xml:"artistImageUrl,attr,omitempty"` + AlbumCount int `xml:"albumCount,attr"` + Starred time.Time `xml:"starred,attr,omitempty"` +} + +// ArtistInfo is all auxillary information about an artist from GetArtistInfo. +type ArtistInfo struct { + SimilarArtist []*Artist `xml:"http://subsonic.org/restapi similarArtist,omitempty"` + Biography string `xml:"http://subsonic.org/restapi biography,omitempty"` + MusicBrainzID string `xml:"http://subsonic.org/restapi musicBrainzId,omitempty"` + LastFmUrl string `xml:"http://subsonic.org/restapi lastFmUrl,omitempty"` + SmallImageUrl string `xml:"http://subsonic.org/restapi smallImageUrl,omitempty"` + MediumImageUrl string `xml:"http://subsonic.org/restapi mediumImageUrl,omitempty"` + LargeImageUrl string `xml:"http://subsonic.org/restapi largeImageUrl,omitempty"` +} + +// ArtistInfo2 is all auxillary information about an artist from GetArtistInfo2, with similar artists organized by ID3 tags. +type ArtistInfo2 struct { + SimilarArtist []*ArtistID3 `xml:"http://subsonic.org/restapi similarArtist,omitempty"` + Biography string `xml:"http://subsonic.org/restapi biography,omitempty"` + MusicBrainzID string `xml:"http://subsonic.org/restapi musicBrainzId,omitempty"` + LastFmUrl string `xml:"http://subsonic.org/restapi lastFmUrl,omitempty"` + SmallImageUrl string `xml:"http://subsonic.org/restapi smallImageUrl,omitempty"` + MediumImageUrl string `xml:"http://subsonic.org/restapi mediumImageUrl,omitempty"` + LargeImageUrl string `xml:"http://subsonic.org/restapi largeImageUrl,omitempty"` +} + +// ArtistsID3 is an index of every artist on the server organized by ID3 tag, from getArtists. +type ArtistsID3 struct { + Index []*IndexID3 `xml:"http://subsonic.org/restapi index,omitempty"` + IgnoredArticles string `xml:"ignoredArticles,attr"` +} + +type Bookmark struct { + Entry *Child `xml:"http://subsonic.org/restapi entry"` + Position int64 `xml:"position,attr"` + Username string `xml:"username,attr"` + Comment string `xml:"comment,attr,omitempty"` + Created time.Time `xml:"created,attr"` + Changed time.Time `xml:"changed,attr"` +} + +func (t *Bookmark) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type T Bookmark + var layout struct { + *T + Created *xsdDateTime `xml:"created,attr"` + Changed *xsdDateTime `xml:"changed,attr"` + } + layout.T = (*T)(t) + layout.Created = (*xsdDateTime)(&layout.T.Created) + layout.Changed = (*xsdDateTime)(&layout.T.Changed) + return e.EncodeElement(layout, start) +} +func (t *Bookmark) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type T Bookmark + var overlay struct { + *T + Created *xsdDateTime `xml:"created,attr"` + Changed *xsdDateTime `xml:"changed,attr"` + } + overlay.T = (*T)(t) + overlay.Created = (*xsdDateTime)(&overlay.T.Created) + overlay.Changed = (*xsdDateTime)(&overlay.T.Changed) + return d.DecodeElement(&overlay, &start) +} + +type bookmarks struct { + Bookmark []*Bookmark `xml:"http://subsonic.org/restapi bookmark,omitempty"` +} + +type ChatMessage struct { + Username string `xml:"username,attr"` + Time int64 `xml:"time,attr"` + Message string `xml:"message,attr"` +} + +type chatMessages struct { + ChatMessage []*ChatMessage `xml:"http://subsonic.org/restapi chatMessage,omitempty"` +} + +// Child is a song, or a generic entry in the hierarchical directory structure of the database. +// You can tell if Child is used as a song contextually based on what it was returned by, or if the IsDir boolean was set to true. +type Child struct { + ID string `xml:"id,attr"` // Manually added + Parent string `xml:"parent,attr,omitempty"` + IsDir bool `xml:"isDir,attr"` + Title string `xml:"title,attr"` + Album string `xml:"album,attr,omitempty"` + Artist string `xml:"artist,attr,omitempty"` + Track int `xml:"track,attr,omitempty"` + Year int `xml:"year,attr,omitempty"` + Genre string `xml:"genre,attr,omitempty"` + CoverArt string `xml:"coverArt,attr,omitempty"` + Size int64 `xml:"size,attr,omitempty"` + ContentType string `xml:"contentType,attr,omitempty"` + Suffix string `xml:"suffix,attr,omitempty"` + TranscodedContentType string `xml:"transcodedContentType,attr,omitempty"` + TranscodedSuffix string `xml:"transcodedSuffix,attr,omitempty"` + Duration int `xml:"duration,attr,omitempty"` + BitRate int `xml:"bitRate,attr,omitempty"` + Path string `xml:"path,attr,omitempty"` + IsVideo bool `xml:"isVideo,attr,omitempty"` + UserRating int `xml:"userRating,attr,omitempty"` + AverageRating float64 `xml:"averageRating,attr,omitempty"` + PlayCount int64 `xml:"playCount,attr,omitempty"` + DiscNumber int `xml:"discNumber,attr,omitempty"` + Created time.Time `xml:"created,attr,omitempty"` + Starred time.Time `xml:"starred,attr,omitempty"` + AlbumID string `xml:"albumId,attr,omitempty"` + ArtistID string `xml:"artistId,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` // May be one of music, podcast, audiobook, video + BookmarkPosition int64 `xml:"bookmarkPosition,attr,omitempty"` + OriginalWidth int `xml:"originalWidth,attr,omitempty"` + OriginalHeight int `xml:"originalHeight,attr,omitempty"` +} + +// Directory is an entry in the hierarchical folder structure organization of the server database. +type Directory struct { + ID string `xml:"id,attr"` // Manually added + Child []*Child `xml:"http://subsonic.org/restapi child,omitempty"` + Parent string `xml:"parent,attr,omitempty"` + Name string `xml:"name,attr"` + Starred time.Time `xml:"starred,attr,omitempty"` + UserRating int `xml:"userRating,attr,omitempty"` + AverageRating float64 `xml:"averageRating,attr,omitempty"` + PlayCount int64 `xml:"playCount,attr,omitempty"` +} + +type Error struct { + Code int `xml:"code,attr"` + Message string `xml:"message,attr,omitempty"` +} + +// Genre is a style tag for a collection of songs and albums. +type Genre struct { + Name string `xml:",chardata"` // Added manually + SongCount int `xml:"songCount,attr"` + AlbumCount int `xml:"albumCount,attr"` +} + +type genres struct { + Genre []*Genre `xml:"http://subsonic.org/restapi genre,omitempty"` +} + +// Index is a collection of artists that begin with the same first letter, along with that letter or category. +type Index struct { + Artist []*Artist `xml:"http://subsonic.org/restapi artist,omitempty"` + Name string `xml:"name,attr"` +} + +// Index is a collection of artists by ID3 tag that begin with the same first letter, along with that letter or category. +type IndexID3 struct { + Artist []*ArtistID3 `xml:"http://subsonic.org/restapi artist,omitempty"` + Name string `xml:"name,attr"` +} + +// Indexes is the full index of the database, returned by getIndex. +// It contains some Index structs for each letter of the DB, plus Child entries for individual tracks. +type Indexes struct { + Shortcut []*Artist `xml:"http://subsonic.org/restapi shortcut,omitempty"` + Index []*Index `xml:"http://subsonic.org/restapi index,omitempty"` + Child []*Child `xml:"http://subsonic.org/restapi child,omitempty"` + LastModified int64 `xml:"lastModified,attr"` + IgnoredArticles string `xml:"ignoredArticles,attr"` +} + +type InternetRadioStation struct { + Name string `xml:"name,attr"` + StreamUrl string `xml:"streamUrl,attr"` + HomePageUrl string `xml:"homePageUrl,attr,omitempty"` +} + +type internetRadioStations struct { + InternetRadioStation []*InternetRadioStation `xml:"http://subsonic.org/restapi internetRadioStation,omitempty"` +} + +type JukeboxPlaylist struct { + Entry []*Child `xml:"http://subsonic.org/restapi entry,omitempty"` + CurrentIndex int `xml:"currentIndex,attr"` + Playing bool `xml:"playing,attr"` + Gain float32 `xml:"gain,attr"` + Position int `xml:"position,attr,omitempty"` +} + +type JukeboxStatus struct { + CurrentIndex int `xml:"currentIndex,attr"` + Playing bool `xml:"playing,attr"` + Gain float32 `xml:"gain,attr"` + Position int `xml:"position,attr,omitempty"` +} + +// License contains information about the Subsonic server's license validity and contact information in the case of a trial subscription. +type License struct { + Valid bool `xml:"valid,attr"` + Email string `xml:"email,attr,omitempty"` + LicenseExpires time.Time `xml:"licenseExpires,attr,omitempty"` + TrialExpires time.Time `xml:"trialExpires,attr,omitempty"` +} + +type Lyrics struct { + Artist string `xml:"artist,attr,omitempty"` + Title string `xml:"title,attr,omitempty"` +} + +// MusicFolder is a representation of a source of music files added to the server. It is identified primarily by the numeric ID. +type MusicFolder struct { + ID string `xml:"id,attr"` + Name string `xml:"name,attr,omitempty"` +} + +type musicFolders struct { + MusicFolder []*MusicFolder `xml:"http://subsonic.org/restapi musicFolder,omitempty"` +} + +type newestPodcasts struct { + Episode []*PodcastEpisode `xml:"http://subsonic.org/restapi episode,omitempty"` +} + +type nowPlaying struct { + Entry []*NowPlayingEntry `xml:"http://subsonic.org/restapi entry,omitempty"` +} + +// NowPlayingEntry is one individual stream coming from the server along with information about who was streaming it. +type NowPlayingEntry struct { + Username string `xml:"username,attr"` + MinutesAgo int `xml:"minutesAgo,attr"` + PlayerID int `xml:"playerId,attr"` + PlayerName string `xml:"playerName,attr,omitempty"` + Parent string `xml:"parent,attr,omitempty"` + IsDir bool `xml:"isDir,attr"` + Title string `xml:"title,attr"` + Album string `xml:"album,attr,omitempty"` + Artist string `xml:"artist,attr,omitempty"` + Track int `xml:"track,attr,omitempty"` + Year int `xml:"year,attr,omitempty"` + Genre string `xml:"genre,attr,omitempty"` + CoverArt string `xml:"coverArt,attr,omitempty"` + Size int64 `xml:"size,attr,omitempty"` + ContentType string `xml:"contentType,attr,omitempty"` + Suffix string `xml:"suffix,attr,omitempty"` + TranscodedContentType string `xml:"transcodedContentType,attr,omitempty"` + TranscodedSuffix string `xml:"transcodedSuffix,attr,omitempty"` + Duration int `xml:"duration,attr,omitempty"` + BitRate int `xml:"bitRate,attr,omitempty"` + Path string `xml:"path,attr,omitempty"` + IsVideo bool `xml:"isVideo,attr,omitempty"` + UserRating int `xml:"userRating,attr,omitempty"` + AverageRating float64 `xml:"averageRating,attr,omitempty"` + PlayCount int64 `xml:"playCount,attr,omitempty"` + DiscNumber int `xml:"discNumber,attr,omitempty"` + Created time.Time `xml:"created,attr,omitempty"` + Starred time.Time `xml:"starred,attr,omitempty"` + AlbumID string `xml:"albumId,attr,omitempty"` + ArtistID string `xml:"artistId,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` // May be one of music, podcast, audiobook, video + BookmarkPosition int64 `xml:"bookmarkPosition,attr,omitempty"` + OriginalWidth int `xml:"originalWidth,attr,omitempty"` + OriginalHeight int `xml:"originalHeight,attr,omitempty"` +} + +type PlayQueue struct { + Entry []*Child `xml:"http://subsonic.org/restapi entry,omitempty"` + Current int `xml:"current,attr,omitempty"` + Position int64 `xml:"position,attr,omitempty"` + Username string `xml:"username,attr"` + Changed time.Time `xml:"changed,attr"` + ChangedBy string `xml:"changedBy,attr"` +} + +func (t *PlayQueue) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type T PlayQueue + var layout struct { + *T + Changed *xsdDateTime `xml:"changed,attr"` + } + layout.T = (*T)(t) + layout.Changed = (*xsdDateTime)(&layout.T.Changed) + return e.EncodeElement(layout, start) +} +func (t *PlayQueue) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type T PlayQueue + var overlay struct { + *T + Changed *xsdDateTime `xml:"changed,attr"` + } + overlay.T = (*T)(t) + overlay.Changed = (*xsdDateTime)(&overlay.T.Changed) + return d.DecodeElement(&overlay, &start) +} + +// Playlist is a collection of songs with metadata like a name, comment, and information about the total duration of the playlist. +type Playlist struct { + ID string `xml:"id,attr"` // Added manually + Entry []*Child `xml:"http://subsonic.org/restapi entry,omitempty"` // Merged from PlaylistWithSongs + AllowedUser []string `xml:"http://subsonic.org/restapi allowedUser,omitempty"` + Name string `xml:"name,attr"` + Comment string `xml:"comment,attr,omitempty"` + Owner string `xml:"owner,attr,omitempty"` + Public bool `xml:"public,attr,omitempty"` + SongCount int `xml:"songCount,attr"` + Duration int `xml:"duration,attr"` + Created time.Time `xml:"created,attr"` + Changed time.Time `xml:"changed,attr"` + CoverArt string `xml:"coverArt,attr,omitempty"` +} + +type playlists struct { + Playlist []*Playlist `xml:"http://subsonic.org/restapi playlist,omitempty"` +} + +type PodcastChannel struct { + Episode []*PodcastEpisode `xml:"http://subsonic.org/restapi episode,omitempty"` + Url string `xml:"url,attr"` + Title string `xml:"title,attr,omitempty"` + Description string `xml:"description,attr,omitempty"` + CoverArt string `xml:"coverArt,attr,omitempty"` + OriginalImageUrl string `xml:"originalImageUrl,attr,omitempty"` + Status string `xml:"status,attr"` // May be one of new, downloading, completed, error, deleted, skipped + ErrorMessage string `xml:"errorMessage,attr,omitempty"` +} + +type PodcastEpisode struct { + StreamID string `xml:"streamId,attr,omitempty"` + ChannelID string `xml:"channelId,attr"` + Description string `xml:"description,attr,omitempty"` + Status string `xml:"status,attr"` // May be one of new, downloading, completed, error, deleted, skipped + PublishDate time.Time `xml:"publishDate,attr,omitempty"` + Parent string `xml:"parent,attr,omitempty"` + IsDir bool `xml:"isDir,attr"` + Title string `xml:"title,attr"` + Album string `xml:"album,attr,omitempty"` + Artist string `xml:"artist,attr,omitempty"` + Track int `xml:"track,attr,omitempty"` + Year int `xml:"year,attr,omitempty"` + Genre string `xml:"genre,attr,omitempty"` + CoverArt string `xml:"coverArt,attr,omitempty"` + Size int64 `xml:"size,attr,omitempty"` + ContentType string `xml:"contentType,attr,omitempty"` + Suffix string `xml:"suffix,attr,omitempty"` + TranscodedContentType string `xml:"transcodedContentType,attr,omitempty"` + TranscodedSuffix string `xml:"transcodedSuffix,attr,omitempty"` + Duration int `xml:"duration,attr,omitempty"` + BitRate int `xml:"bitRate,attr,omitempty"` + Path string `xml:"path,attr,omitempty"` + IsVideo bool `xml:"isVideo,attr,omitempty"` + UserRating int `xml:"userRating,attr,omitempty"` + AverageRating float64 `xml:"averageRating,attr,omitempty"` + PlayCount int64 `xml:"playCount,attr,omitempty"` + DiscNumber int `xml:"discNumber,attr,omitempty"` + Created time.Time `xml:"created,attr,omitempty"` + Starred time.Time `xml:"starred,attr,omitempty"` + AlbumID string `xml:"albumId,attr,omitempty"` + ArtistID string `xml:"artistId,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` // May be one of music, podcast, audiobook, video + BookmarkPosition int64 `xml:"bookmarkPosition,attr,omitempty"` + OriginalWidth int `xml:"originalWidth,attr,omitempty"` + OriginalHeight int `xml:"originalHeight,attr,omitempty"` +} + +func (t *PodcastEpisode) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type T PodcastEpisode + var layout struct { + *T + PublishDate *xsdDateTime `xml:"publishDate,attr,omitempty"` + Created *xsdDateTime `xml:"created,attr,omitempty"` + Starred *xsdDateTime `xml:"starred,attr,omitempty"` + } + layout.T = (*T)(t) + layout.PublishDate = (*xsdDateTime)(&layout.T.PublishDate) + layout.Created = (*xsdDateTime)(&layout.T.Created) + layout.Starred = (*xsdDateTime)(&layout.T.Starred) + return e.EncodeElement(layout, start) +} +func (t *PodcastEpisode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type T PodcastEpisode + var overlay struct { + *T + PublishDate *xsdDateTime `xml:"publishDate,attr,omitempty"` + Created *xsdDateTime `xml:"created,attr,omitempty"` + Starred *xsdDateTime `xml:"starred,attr,omitempty"` + } + overlay.T = (*T)(t) + overlay.PublishDate = (*xsdDateTime)(&overlay.T.PublishDate) + overlay.Created = (*xsdDateTime)(&overlay.T.Created) + overlay.Starred = (*xsdDateTime)(&overlay.T.Starred) + return d.DecodeElement(&overlay, &start) +} + +type podcasts struct { + Channel []*PodcastChannel `xml:"http://subsonic.org/restapi channel,omitempty"` +} + +// Response is the main target for unmarshalling data from the API - everything within the "subsonic-response" key +type Response struct { + License *License `xml:"http://subsonic.org/restapi license"` + MusicFolders *musicFolders `xml:"http://subsonic.org/restapi musicFolders"` + Indexes *Indexes `xml:"http://subsonic.org/restapi indexes"` + Directory *Directory `xml:"http://subsonic.org/restapi directory"` + Genres *genres `xml:"http://subsonic.org/restapi genres"` + Artists *ArtistsID3 `xml:"http://subsonic.org/restapi artists"` + Artist *ArtistID3 `xml:"http://subsonic.org/restapi artist"` + Album *AlbumID3 `xml:"http://subsonic.org/restapi album"` + Song *Child `xml:"http://subsonic.org/restapi song"` + NowPlaying *nowPlaying `xml:"http://subsonic.org/restapi nowPlaying"` + SearchResult2 *SearchResult2 `xml:"http://subsonic.org/restapi searchResult2"` + SearchResult3 *SearchResult3 `xml:"http://subsonic.org/restapi searchResult3"` + Playlists *playlists `xml:"http://subsonic.org/restapi playlists"` + Playlist *Playlist `xml:"http://subsonic.org/restapi playlist"` + JukeboxStatus *JukeboxStatus `xml:"http://subsonic.org/restapi jukeboxStatus"` + JukeboxPlaylist *JukeboxPlaylist `xml:"http://subsonic.org/restapi jukeboxPlaylist"` + Users *users `xml:"http://subsonic.org/restapi users"` + User *User `xml:"http://subsonic.org/restapi user"` + ChatMessages *chatMessages `xml:"http://subsonic.org/restapi chatMessages"` + AlbumList *albumList `xml:"http://subsonic.org/restapi albumList"` + AlbumList2 *albumList2 `xml:"http://subsonic.org/restapi albumList2"` + RandomSongs *songs `xml:"http://subsonic.org/restapi randomSongs"` + SongsByGenre *songs `xml:"http://subsonic.org/restapi songsByGenre"` + Lyrics *Lyrics `xml:"http://subsonic.org/restapi lyrics"` + Podcasts *podcasts `xml:"http://subsonic.org/restapi podcasts"` + NewestPodcasts *newestPodcasts `xml:"http://subsonic.org/restapi newestPodcasts"` + InternetRadioStations *internetRadioStations `xml:"http://subsonic.org/restapi internetRadioStations"` + Bookmarks *bookmarks `xml:"http://subsonic.org/restapi bookmarks"` + PlayQueue *PlayQueue `xml:"http://subsonic.org/restapi playQueue"` + Shares *shares `xml:"http://subsonic.org/restapi shares"` + Starred *Starred `xml:"http://subsonic.org/restapi starred"` + Starred2 *Starred2 `xml:"http://subsonic.org/restapi starred2"` + AlbumInfo *AlbumInfo `xml:"http://subsonic.org/restapi albumInfo"` + ArtistInfo *ArtistInfo `xml:"http://subsonic.org/restapi artistInfo"` + ArtistInfo2 *ArtistInfo2 `xml:"http://subsonic.org/restapi artistInfo2"` + SimilarSongs *similarSongs `xml:"http://subsonic.org/restapi similarSongs"` + SimilarSongs2 *similarSongs2 `xml:"http://subsonic.org/restapi similarSongs2"` + TopSongs *topSongs `xml:"http://subsonic.org/restapi topSongs"` + ScanStatus *ScanStatus `xml:"http://subsonic.org/restapi scanStatus"` + Error *Error `xml:"http://subsonic.org/restapi error"` + Status string `xml:"status,attr"` // May be one of ok, failed + Version string `xml:"version,attr"` // Must match the pattern \d+\.\d+\.\d+ +} + +type ScanStatus struct { + Scanning bool `xml:"scanning,attr"` + Count int64 `xml:"count,attr,omitempty"` +} + +// SearchResult2 is a collection of songs, albums, and artists related to a query. +type SearchResult2 struct { + Artist []*Artist `xml:"http://subsonic.org/restapi artist,omitempty"` + Album []*Child `xml:"http://subsonic.org/restapi album,omitempty"` + Song []*Child `xml:"http://subsonic.org/restapi song,omitempty"` +} + +// SearchResult3 is a collection of songs, albums, and artists related to a query. +type SearchResult3 struct { + Artist []*ArtistID3 `xml:"http://subsonic.org/restapi artist,omitempty"` + Album []*AlbumID3 `xml:"http://subsonic.org/restapi album,omitempty"` + Song []*Child `xml:"http://subsonic.org/restapi song,omitempty"` +} + +type Share struct { + Entry []*Child `xml:"http://subsonic.org/restapi entry,omitempty"` + Url string `xml:"url,attr"` + Description string `xml:"description,attr,omitempty"` + Username string `xml:"username,attr"` + Created time.Time `xml:"created,attr"` + Expires time.Time `xml:"expires,attr,omitempty"` + LastVisited time.Time `xml:"lastVisited,attr,omitempty"` + VisitCount int `xml:"visitCount,attr"` +} + +func (t *Share) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type T Share + var layout struct { + *T + Created *xsdDateTime `xml:"created,attr"` + Expires *xsdDateTime `xml:"expires,attr,omitempty"` + LastVisited *xsdDateTime `xml:"lastVisited,attr,omitempty"` + } + layout.T = (*T)(t) + layout.Created = (*xsdDateTime)(&layout.T.Created) + layout.Expires = (*xsdDateTime)(&layout.T.Expires) + layout.LastVisited = (*xsdDateTime)(&layout.T.LastVisited) + return e.EncodeElement(layout, start) +} +func (t *Share) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type T Share + var overlay struct { + *T + Created *xsdDateTime `xml:"created,attr"` + Expires *xsdDateTime `xml:"expires,attr,omitempty"` + LastVisited *xsdDateTime `xml:"lastVisited,attr,omitempty"` + } + overlay.T = (*T)(t) + overlay.Created = (*xsdDateTime)(&overlay.T.Created) + overlay.Expires = (*xsdDateTime)(&overlay.T.Expires) + overlay.LastVisited = (*xsdDateTime)(&overlay.T.LastVisited) + return d.DecodeElement(&overlay, &start) +} + +type shares struct { + Share []*Share `xml:"http://subsonic.org/restapi share,omitempty"` +} + +type similarSongs struct { + Song []*Child `xml:"http://subsonic.org/restapi song,omitempty"` +} + +type similarSongs2 struct { + Song []*Child `xml:"http://subsonic.org/restapi song,omitempty"` +} + +type songs struct { + Song []*Child `xml:"http://subsonic.org/restapi song,omitempty"` +} + +// Starred is a collection of songs, albums, and artists annotated by a user as starred. +type Starred struct { + Artist []*Artist `xml:"http://subsonic.org/restapi artist,omitempty"` + Album []*Child `xml:"http://subsonic.org/restapi album,omitempty"` + Song []*Child `xml:"http://subsonic.org/restapi song,omitempty"` +} + +// Starred2 is a collection of songs, albums, and artists organized by ID3 tags annotated by a user as starred. +type Starred2 struct { + Artist []*ArtistID3 `xml:"http://subsonic.org/restapi artist,omitempty"` + Album []*AlbumID3 `xml:"http://subsonic.org/restapi album,omitempty"` + Song []*Child `xml:"http://subsonic.org/restapi song,omitempty"` +} + +type topSongs struct { + Song []*Child `xml:"http://subsonic.org/restapi song,omitempty"` +} + +type User struct { + Folder []int `xml:"http://subsonic.org/restapi folder,omitempty"` + Username string `xml:"username,attr"` + Email string `xml:"email,attr,omitempty"` + ScrobblingEnabled bool `xml:"scrobblingEnabled,attr"` + MaxBitRate int `xml:"maxBitRate,attr,omitempty"` + AdminRole bool `xml:"adminRole,attr"` + SettingsRole bool `xml:"settingsRole,attr"` + DownloadRole bool `xml:"downloadRole,attr"` + UploadRole bool `xml:"uploadRole,attr"` + PlaylistRole bool `xml:"playlistRole,attr"` + CoverArtRole bool `xml:"coverArtRole,attr"` + CommentRole bool `xml:"commentRole,attr"` + PodcastRole bool `xml:"podcastRole,attr"` + StreamRole bool `xml:"streamRole,attr"` + JukeboxRole bool `xml:"jukeboxRole,attr"` + ShareRole bool `xml:"shareRole,attr"` + VideoConversionRole bool `xml:"videoConversionRole,attr"` + AvatarLastChanged time.Time `xml:"avatarLastChanged,attr,omitempty"` +} + +func (t *User) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type T User + var layout struct { + *T + AvatarLastChanged *xsdDateTime `xml:"avatarLastChanged,attr,omitempty"` + } + layout.T = (*T)(t) + layout.AvatarLastChanged = (*xsdDateTime)(&layout.T.AvatarLastChanged) + return e.EncodeElement(layout, start) +} +func (t *User) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type T User + var overlay struct { + *T + AvatarLastChanged *xsdDateTime `xml:"avatarLastChanged,attr,omitempty"` + } + overlay.T = (*T)(t) + overlay.AvatarLastChanged = (*xsdDateTime)(&overlay.T.AvatarLastChanged) + return d.DecodeElement(&overlay, &start) +} + +type users struct { + User []*User `xml:"http://subsonic.org/restapi user,omitempty"` +} + +type xsdDateTime time.Time + +func (t *xsdDateTime) UnmarshalText(text []byte) error { + return _unmarshalTime(text, (*time.Time)(t), "2006-01-02T15:04:05.999999999") +} +func (t xsdDateTime) MarshalText() ([]byte, error) { + return []byte((time.Time)(t).Format("2006-01-02T15:04:05.999999999")), nil +} +func (t xsdDateTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if (time.Time)(t).IsZero() { + return nil + } + m, err := t.MarshalText() + if err != nil { + return err + } + return e.EncodeElement(m, start) +} +func (t xsdDateTime) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { + if (time.Time)(t).IsZero() { + return xml.Attr{}, nil + } + m, err := t.MarshalText() + return xml.Attr{Name: name, Value: string(m)}, err +} +func _unmarshalTime(text []byte, t *time.Time, format string) (err error) { + s := string(bytes.TrimSpace(text)) + *t, err = time.Parse(format, s) + if _, ok := err.(*time.ParseError); ok { + *t, err = time.Parse(format+"Z07:00", s) + } + return err +} diff --git a/vendor/github.com/delucks/go-subsonic/playlist.go b/vendor/github.com/delucks/go-subsonic/playlist.go new file mode 100644 index 0000000..016afa8 --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/playlist.go @@ -0,0 +1,76 @@ +package subsonic + +import "errors" + +// GetPlaylists returns all playlists a user is allowed to play. +// +// Optional Parameters: +// user: get playlists visible to this username rather than the current user. Must have admin permission. +func (s *Client) GetPlaylists(parameters map[string]string) ([]*Playlist, error) { + resp, err := s.Get("getPlaylists", parameters) + if err != nil { + return nil, err + } + return resp.Playlists.Playlist, nil +} + +// GetPlaylist returns a listing of files in a saved playlist. +func (s *Client) GetPlaylist(id string) (*Playlist, error) { + resp, err := s.Get("getPlaylist", map[string]string{"id": id}) + if err != nil { + return nil, err + } + return resp.Playlist, nil +} + +// CreatePlaylist creates (or updates) a playlist. +// +// Optional Parameters: +// songId: ID of a song in the playlist. Use one songId parameter for each song in the playlist. +// Mutually Exclusive Parameters: +// playlistId: The playlist ID. +// name: The human-readable name of the playlist. +// +// This returns a Playlist object in Subsonic > 1.14.0, so it cannot consistently return a *Playlist +func (s *Client) CreatePlaylist(parameters map[string]string) error { + _, idPresent := parameters["playlistId"] + _, namePresent := parameters["name"] + if !(idPresent || namePresent) { + return errors.New("One of name or playlistId is mandatory, to create or update a playlist respectively") + } + _, err := s.Get("createPlaylist", parameters) + if err != nil { + return err + } + return nil +} + +// UpdatePlaylist updates a playlist. Only the owner of a playlist is allowed to update it. +// +// Optional Parameters: +// name: The human-readable name of the playlist. +// comment: The playlist comment. +// public: true if the playlist should be visible to all users, false otherwise. +// songIdToAdd: Add this song with this ID to the playlist. Multiple parameters allowed. +// songIndexToRemove: Remove the song at this position in the playlist. Multiple parameters allowed. +func (s *Client) UpdatePlaylist(playlistId string, parameters map[string]string) error { + params := make(map[string]string) + for k, v := range parameters { + params[k] = v + } + params["playlistId"] = playlistId + _, err := s.Get("updatePlaylist", params) + if err != nil { + return err + } + return nil +} + +// DeletePlaylist deletes a saved playlist. +func (s *Client) DeletePlaylist(playlistId string) error { + _, err := s.Get("deletePlaylist", map[string]string{"id": playlistId}) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/delucks/go-subsonic/retrieval.go b/vendor/github.com/delucks/go-subsonic/retrieval.go new file mode 100644 index 0000000..cdae517 --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/retrieval.go @@ -0,0 +1,160 @@ +package subsonic + +import ( + "encoding/xml" + "fmt" + "image" + "io" + "io/ioutil" + "net/url" + "strings" + + _ "image/gif" + _ "image/jpeg" + _ "image/png" +) + +// Stream returns the contents of a song, optionally transcoded, from the server. +// +// Optional Parameters: +// maxBitRate: (Since 1.2.0) If specified, the server will attempt to limit the bitrate to this value, in kilobits per second. If set to zero, no limit is imposed. +// format: (Since 1.6.0) Specifies the preferred target format (e.g., "mp3" or "flv") in case there are multiple applicable transcodings. Starting with 1.9.0 you can use the special value "raw" to disable transcoding. +// timeOffset: Only applicable to video streaming. If specified, start streaming at the given offset (in seconds) into the video. Typically used to implement video skipping. +// size: (Since 1.6.0) Only applicable to video streaming. Requested video size specified as WxH, for instance "640x480". +// estimateContentLength: (Since 1.8.0). If set to "true", the Content-Length HTTP header will be set to an estimated value for transcoded or downsampled media. +// converted: (Since 1.14.0) Only applicable to video streaming. Subsonic can optimize videos for streaming by converting them to MP4. If a conversion exists for the video in question, then setting this parameter to "true" will cause the converted video to be returned instead of the original. +func (s *Client) Stream(id string, parameters map[string]string) (io.Reader, error) { + params := url.Values{} + params.Add("id", id) + for k, v := range parameters { + params.Add(k, v) + } + response, err := s.Request("GET", "stream", params) + if err != nil { + return nil, err + } + contentType := response.Header.Get("Content-Type") + if strings.HasPrefix(contentType, "text/xml") || strings.HasPrefix(contentType, "application/xml") { + // An error was returned + responseBody, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + resp := Response{} + err = xml.Unmarshal(responseBody, &resp) + if err != nil { + return nil, err + } + if resp.Error != nil { + err = fmt.Errorf("Error #%d: %s\n", resp.Error.Code, resp.Error.Message) + } else { + err = fmt.Errorf("An error occurred: %#v\n", resp) + } + return nil, err + } + return response.Body, nil +} + +// Download returns a given media file. Similar to stream, but this method returns the original media data without transcoding or downsampling. +func (s *Client) Download(id string) (io.Reader, error) { + params := url.Values{} + params.Add("id", id) + response, err := s.Request("GET", "download", params) + if err != nil { + return nil, err + } + contentType := response.Header.Get("Content-Type") + if strings.HasPrefix(contentType, "text/xml") || strings.HasPrefix(contentType, "application/xml") { + // An error was returned + responseBody, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + resp := Response{} + err = xml.Unmarshal(responseBody, &resp) + if err != nil { + return nil, err + } + if resp.Error != nil { + err = fmt.Errorf("Error #%d: %s\n", resp.Error.Code, resp.Error.Message) + } else { + err = fmt.Errorf("An error occurred: %#v\n", resp) + } + return nil, err + } + return response.Body, nil +} + +// GetCoverArt returns a cover art image for a song, album, or artist. +// +// Optional Parameters: +// size: If specified, scale image to this size. +func (s *Client) GetCoverArt(id string, parameters map[string]string) (image.Image, error) { + params := url.Values{} + params.Add("id", id) + if size, ok := parameters["size"]; ok { + params.Add("size", size) + } + response, err := s.Request("GET", "getCoverArt", params) + if err != nil { + return nil, err + } + contentType := response.Header.Get("Content-Type") + if strings.HasPrefix(contentType, "text/xml") || strings.HasPrefix(contentType, "application/xml") { + // An error was returned + responseBody, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + resp := Response{} + err = xml.Unmarshal(responseBody, &resp) + if err != nil { + return nil, err + } + if resp.Error != nil { + err = fmt.Errorf("Error #%d: %s\n", resp.Error.Code, resp.Error.Message) + } else { + err = fmt.Errorf("An error occurred: %#v\n", resp) + } + return nil, err + } + image, _, err := image.Decode(response.Body) + if err != nil { + return nil, err + } + return image, nil +} + +// GetAvatar returns the avatar (personal image) for a user. +func (s *Client) GetAvatar(username string) (image.Image, error) { + params := url.Values{} + params.Add("username", username) + response, err := s.Request("GET", "getAvatar", params) + if err != nil { + return nil, err + } + contentType := response.Header.Get("Content-Type") + if strings.HasPrefix(contentType, "text/xml") || strings.HasPrefix(contentType, "application/xml") { + // An error was returned + responseBody, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + resp := Response{} + err = xml.Unmarshal(responseBody, &resp) + if err != nil { + return nil, err + } + if resp.Error != nil { + err = fmt.Errorf("Error #%d: %s\n", resp.Error.Code, resp.Error.Message) + } else { + err = fmt.Errorf("An error occurred: %#v\n", resp) + } + return nil, err + } + image, _, err := image.Decode(response.Body) + if err != nil { + return nil, err + } + return image, nil +} diff --git a/vendor/github.com/delucks/go-subsonic/scanning.go b/vendor/github.com/delucks/go-subsonic/scanning.go new file mode 100644 index 0000000..a79c4bb --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/scanning.go @@ -0,0 +1,19 @@ +package subsonic + +// GetScanStatus returns the current status for media library scanning. +func (c *Client) GetScanStatus() (*ScanStatus, error) { + resp, err := c.Get("getScanStatus", nil) + if err != nil { + return nil, err + } + return resp.ScanStatus, nil +} + +// StartScan initiates a rescan of the media libraries. +func (c *Client) StartScan() (*ScanStatus, error) { + resp, err := c.Get("startScan", nil) + if err != nil { + return nil, err + } + return resp.ScanStatus, nil +} diff --git a/vendor/github.com/delucks/go-subsonic/search.go b/vendor/github.com/delucks/go-subsonic/search.go new file mode 100644 index 0000000..730ed3e --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/search.go @@ -0,0 +1,46 @@ +package subsonic + +// Search2 returns albums, artists and songs matching the given search criteria. Supports paging through the result. +// +// Optional Parameters: +// artistCount: Maximum number of artists to return. (Default 20) +// artistOffset: Search result offset for artists. Used for paging. +// albumCount: Maximum number of albums to return. (Default 20) +// albumOffset: Search result offset for albums. Used for paging. +// songCount: Maximum number of songs to return. (Default 20) +// songOffset: Search result offset for songs. Used for paging. +// musicFolderId: (Since 1.12.0) Only return results from the music folder with the given ID. See getMusicFolders. +func (s *Client) Search2(query string, parameters map[string]string) (*SearchResult2, error) { + params := make(map[string]string) + params["query"] = query + for k, v := range parameters { + params[k] = v + } + resp, err := s.Get("search2", params) + if err != nil { + return nil, err + } + return resp.SearchResult2, nil +} + +// Search3 returns albums, artists and songs matching the given search criteria like Search2, but organized according to id3 tags. +// Optional Parameters: +// artistCount: Maximum number of artists to return. (Default 20) +// artistOffset: Search result offset for artists. Used for paging. +// albumCount: Maximum number of albums to return. (Default 20) +// albumOffset: Search result offset for albums. Used for paging. +// songCount: Maximum number of songs to return. (Default 20) +// songOffset: Search result offset for songs. Used for paging. +// musicFolderId: (Since 1.12.0) Only return results from the music folder with the given ID. See getMusicFolders. +func (s *Client) Search3(query string, parameters map[string]string) (*SearchResult3, error) { + params := make(map[string]string) + params["query"] = query + for k, v := range parameters { + params[k] = v + } + resp, err := s.Get("search3", params) + if err != nil { + return nil, err + } + return resp.SearchResult3, nil +} diff --git a/vendor/github.com/delucks/go-subsonic/test.sh b/vendor/github.com/delucks/go-subsonic/test.sh new file mode 100644 index 0000000..6f2233c --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/test.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash + +# This script performs automated testing of go-subsonic on Airsonic and Navidrome music servers. +# It wraps docker-compose to download a sample music library before creating instances of these servers, +# allowing the go-subsonic tests to run fully against the sample instances. + +err() { + echo "$1" >&2 + exit 1 +} + +log() { + SEV="$1" + shift + echo "$(date +%Y-%m-%d\ %T) - ${SEV^^} - $*" >&2 +} + +for dependency in curl docker-compose; do + hash "$dependency" 2>/dev/null || err "$dependency must be installed" +done + +SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +NAVIDROME_PORT=4533 + +download_audionautix() { + # Audionautix Acoustic is a CC licensed album from Jason Shaw available from Free Music Archive. + # https://archive.org/details/Audionautix_Acoustic-9870 + BASEURL="https://archive.org/download/Audionautix_Acoustic-9870/Jason_Shaw_-_" + TRACKS=("12_MORNINGS.ogg" "ACOUSTIC_BLUES.ogg" "FUNKY_JUNKY.ogg" "JENNYS_THEME.ogg" "LAZY_DAY.ogg" "MOUNTAIN_SUN.ogg" "ONE_FINE_DAY.ogg" "RIVER_MEDITATION.ogg" "ROCKY_TOP.ogg" "RUNNING_WATERS.ogg" "SERENITY.ogg" "SIDEWALK.ogg" "SNAPPY.ogg" "SOLO_ACOUSTIC_GUITAR.ogg" "SOUTH_OF_THE_BORDER.ogg" "TENNESEE_HAYRIDE.ogg" "THINGAMAJIG.ogg" "TRAVEL_LIGHT.ogg" "WHEELS.ogg" "WORDS.ogg") + DESTINATION="${SOURCE_DIR}/build/music/Jason Shaw/Audionautix" + mkdir -p "$DESTINATION" + for track in "${TRACKS[@]}" + do + if test -f "$DESTINATION/$track" + then + log info "Skipping download of Audionautix $track" + else + curl -L -o "$DESTINATION/$track" "${BASEURL}${track}" + fi + done +} + +download_grabbag() { + # Grab Bag is a CC-licensed jazz album from Jahzzar available from Free Music Archive. + # https://archive.org/details/Grab_Bag-12446 + BASEURL="https://archive.org/download/Grab_Bag-12446/" + DESTINATION="${SOURCE_DIR}/build/music/Jahzzar/Grab Bag" + TRACKS=("01_-_Dummy.ogg" "02_-_Candlelight.ogg" "03_-_Trust.ogg" "04_-_Guilty.ogg" "05_-_Storm.ogg") + mkdir -p "$DESTINATION" + for track in "${TRACKS[@]}" + do + if test -f "$DESTINATION/$track" + then + log info "Skipping download of Grab Bag $track" + else + curl -L -o "$DESTINATION/$track" "${BASEURL}${track}" + fi + done +} + + +download_fourseasons() { + # This is a CC-licensed recording of Vivaldi's The Four Seasons performed by John Harrison with the Wichita State University Chamber Players + # https://archive.org/details/The_Four_Seasons_Vivaldi-10361 + BASEURL="https://archive.org/download/The_Four_Seasons_Vivaldi-10361/John_Harrison_with_the_Wichita_State_University_Chamber_Players_-_" + DESTINATION="${SOURCE_DIR}/build/music/Vivaldi/The Four Seasons" + TRACKS=("01_-_Spring_Mvt_1_Allegro.ogg" "02_-_Spring_Mvt_2_Largo.ogg" "03_-_Spring_Mvt_3_Allegro_pastorale.ogg" "04_-_Summer_Mvt_1_Allegro_non_molto.ogg" "05_-_Summer_Mvt_2_Adagio.ogg" "06_-_Summer_Mvt_3_Presto.ogg" "07_-_Autumn_Mvt_1_Allegro.ogg" "08_-_Autumn_Mvt_2_Adagio_molto.ogg" "09_-_Autumn_Mvt_3_Allegro.ogg" "10_-_Winter_Mvt_1_Allegro_non_molto.ogg" "11_-_Winter_Mvt_2_Largo.ogg" "12_-_Winter_Mvt_3_Allegro.ogg") + mkdir -p "$DESTINATION" + for track in "${TRACKS[@]}" + do + if test -f "$DESTINATION/$track" + then + log info "Skipping download of The Four Seasons $track" + else + curl -L -o "$DESTINATION/$track" "${BASEURL}${track}" + fi + done +} + +download_sample_audio() { + download_audionautix + download_grabbag + download_fourseasons +} + +configure_airsonic() { + cat << DOG > build/data/airsonic.properties +JWTKey=q7q8u331n25gkvgjiehutl3e4u +SettingsChanged=$(date +%s)000 +# Try to force immediate library scan +LastScanned=0 +IndexCreationInterval=1 +# We want to use id3 tags for most tests +OrganizeByFolderStructure=false +DOG +} + +create_navidrome_user() { + # navidrome does not ship with a built-in user, so we call the API to create a known user before test execution + curl -X POST -H "Content-Type: application/json" "http://localhost:${NAVIDROME_PORT}/auth/createAdmin" --data '{"username":"admin", "password":"admin"}' + echo +} + +clear_data_dir() { + rm -rf ./build/data/* +} + +main() { + log info "Downloading sample music into ./build/music" + download_sample_audio + # Create or restart the docker containers of Airsonic and Navidrome + if [[ $(docker-compose top) ]] + then + # If the current composition is running, restart it to pick up possible changes + log warn "Downing currently running docker containers" + docker-compose down + fi + log info "Removing excess data" + clear_data_dir + log info "Configuring Airsonic" + configure_airsonic # This must occur in the middle so settings aren't overwritten + log info "Bringing up containers" + docker-compose up -d + sleep 10 + log info "Creating Navidrome administrator (admin/admin)" + create_navidrome_user + go test . -test.v -run 'Navidrome' -count=1 + log info "Waiting 30 seconds total for Airsonic to scan the music library..." + sleep 20 + go test . -test.v -run 'Airsonic' -count=1 +} + +main diff --git a/vendor/github.com/delucks/go-subsonic/user.go b/vendor/github.com/delucks/go-subsonic/user.go new file mode 100644 index 0000000..da759e7 --- /dev/null +++ b/vendor/github.com/delucks/go-subsonic/user.go @@ -0,0 +1,104 @@ +package subsonic + +// GetUser gets details about a given user, including which authorization roles and folder access it has. Can be used to enable/disable certain features in the client, such as jukebox control. +func (c *Client) GetUser(username string) (*User, error) { + resp, err := c.Get("getUser", map[string]string{"username": username}) + if err != nil { + return nil, err + } + return resp.User, nil +} + +// GetUsers gets details about all users, including which authorization roles and folder access they have. Only users with admin privileges are allowed to call this method. +func (c *Client) GetUsers() ([]*User, error) { + resp, err := c.Get("getUsers", nil) + if err != nil { + return nil, err + } + return resp.Users.User, nil +} + +// CreateUser creates a new Subsonic user. +// +// Optional Parameters Default Description +// ldapAuthenticated false Whether the user is authenicated in LDAP. +// adminRole false Whether the user is administrator. +// settingsRole true Whether the user is allowed to change personal settings and password. +// streamRole true Whether the user is allowed to play files. +// jukeboxRole false Whether the user is allowed to play files in jukebox mode. +// downloadRole false Whether the user is allowed to download files. +// uploadRole false Whether the user is allowed to upload files. +// playlistRole false Whether the user is allowed to create and delete playlists. Since 1.8.0, changing this role has no effect. +// coverArtRole false Whether the user is allowed to change cover art and tags. +// commentRole false Whether the user is allowed to create and edit comments and ratings. +// podcastRole false Whether the user is allowed to administrate Podcasts. +// shareRole false (Since 1.8.0) Whether the user is allowed to share files with anyone. +// videoConversionRole false (Since 1.15.0) Whether the user is allowed to start video conversions. +// musicFolderId All (Since 1.12.0) IDs of the music folders the user is allowed access to. Include the parameter once for each folder. +func (c *Client) CreateUser(username, password, email string, parameters map[string]string) error { + params := make(map[string]string) + params["username"] = username + params["password"] = password + params["email"] = email + for k, v := range parameters { + params[k] = v + } + _, err := c.Get("createUser", params) + if err != nil { + return err + } + return nil +} + +// UpdateUser modifies an existing Subsonic user. +// +// Optional Parameters: +// password The password of the user, either in clear text of hex-encoded. +// email The email address of the user. +// ldapAuthenticated Whether the user is authenicated in LDAP. +// adminRole Whether the user is administrator. +// settingsRole Whether the user is allowed to change personal settings and password. +// streamRole Whether the user is allowed to play files. +// jukeboxRole Whether the user is allowed to play files in jukebox mode. +// downloadRole Whether the user is allowed to download files. +// uploadRole Whether the user is allowed to upload files. +// coverArtRole Whether the user is allowed to change cover art and tags. +// commentRole Whether the user is allowed to create and edit comments and ratings. +// podcastRole Whether the user is allowed to administrate Podcasts. +// shareRole (Since 1.8.0) Whether the user is allowed to share files with anyone. +// videoConversionRole (Since 1.15.0) Whether the user is allowed to start video conversions. +// musicFolderId (Since 1.12.0) IDs of the music folders the user is allowed access to. Include the parameter once for each folder. +// maxBitRate (Since 1.13.0) The maximum bit rate (in Kbps) for the user. Audio streams of higher bit rates are automatically downsampled to this bit rate. Legal values: 0 (no limit), 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320. +func (c *Client) UpdateUser(username string, parameters map[string]string) error { + params := make(map[string]string) + params["username"] = username + for k, v := range parameters { + params[k] = v + } + _, err := c.Get("updateUser", params) + if err != nil { + return err + } + return nil +} + +// DeleteUser deletes an existing Subsonic user. +func (s *Client) DeleteUser(username string) error { + _, err := s.Get("deleteUser", map[string]string{"username": username}) + if err != nil { + return err + } + return nil +} + +// ChangePassword changes the password of an existing Subsonic user, using the following parameters. You can only change your own password unless you have admin privileges. +func (c *Client) ChangePassword(username, password string) error { + _, err := c.Get("changePassword", map[string]string{ + "username": username, + "password": password, + }) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/ebitengine/oto/v3/.clang-format b/vendor/github.com/ebitengine/oto/v3/.clang-format new file mode 100644 index 0000000..e1d7fc9 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/.clang-format @@ -0,0 +1 @@ +CommentPragmas: '^go:build' diff --git a/vendor/github.com/ebitengine/oto/v3/.gitattributes b/vendor/github.com/ebitengine/oto/v3/.gitattributes new file mode 100644 index 0000000..8d7d19c --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/.gitattributes @@ -0,0 +1 @@ +internal/oboe/** linguist-vendored \ No newline at end of file diff --git a/vendor/github.com/ebitengine/oto/v3/.gitignore b/vendor/github.com/ebitengine/oto/v3/.gitignore new file mode 100644 index 0000000..538c8c5 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +*~ diff --git a/vendor/github.com/ebitengine/oto/v3/LICENSE b/vendor/github.com/ebitengine/oto/v3/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/ebitengine/oto/v3/README.md b/vendor/github.com/ebitengine/oto/v3/README.md new file mode 100644 index 0000000..317189f --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/README.md @@ -0,0 +1,234 @@ +# Oto (v3) + +[![Go Reference](https://pkg.go.dev/badge/github.com/ebitengine/oto/v3.svg)](https://pkg.go.dev/github.com/ebitengine/oto/v3) +[![Build Status](https://github.com/ebitengine/oto/actions/workflows/test.yml/badge.svg)](https://github.com/ebitengine/oto/actions?query=workflow%3Atest) + +A low-level library to play sound. + +- [Oto (v3)](#oto-v3) + - [Platforms](#platforms) + - [Prerequisite](#prerequisite) + - [macOS](#macos) + - [iOS](#ios) + - [Linux](#linux) + - [FreeBSD, OpenBSD](#freebsd-openbsd) + - [Usage](#usage) + - [Playing sounds from memory](#playing-sounds-from-memory) + - [Playing sounds by file streaming](#playing-sounds-by-file-streaming) + - [Advanced usage](#advanced-usage) + - [Crosscompiling](#crosscompiling) + +## Platforms + +- Windows (no Cgo required!) +- macOS (no Cgo required!) +- Linux +- FreeBSD +- OpenBSD +- Android +- iOS +- WebAssembly +- Nintendo Switch +- Xbox + +## Prerequisite + +On some platforms you will need a C/C++ compiler in your path that Go can use. + +- iOS: On newer macOS versions type `clang` on your terminal and a dialog with installation instructions will appear if you don't have it + - If you get an error with clang use xcode instead `xcode-select --install` +- Linux and other Unix systems: Should be installed by default, but if not try [GCC](https://gcc.gnu.org/) or [Clang](https://releases.llvm.org/download.html) + +### macOS + +Oto requires `AudioToolbox.framework`, but this is automatically linked. + +### iOS + +Oto requires these frameworks: + +- `AVFoundation.framework` +- `AudioToolbox.framework` + +Add them to "Linked Frameworks and Libraries" on your Xcode project. + +### Linux + +ALSA is required. On Ubuntu or Debian, run this command: + +```sh +apt install libasound2-dev +``` + +On RedHat-based linux distributions, run: + +```sh +dnf install alsa-lib-devel +``` + +In most cases this command must be run by root user or through `sudo` command. + +### FreeBSD, OpenBSD + +BSD systems are not tested well. If ALSA works, Oto should work. + +## Usage + +The two main components of Oto are a `Context` and `Players`. The context handles interactions with +the OS and audio drivers, and as such there can only be **one** context in your program. + +From a context you can create any number of different players, where each player is given an `io.Reader` that +it reads bytes representing sounds from and plays. + +Note that a single `io.Reader` must **not** be used by multiple players. + +### Playing sounds from memory + +The following is an example of loading and playing an MP3 file: + +```go +package main + +import ( + "time" + "os" + + "github.com/ebitengine/oto/v3" + "github.com/hajimehoshi/go-mp3" +) + +func main() { + // Read the mp3 file into memory + fileBytes, err := os.ReadFile("./my-file.mp3") + if err != nil { + panic("reading my-file.mp3 failed: " + err.Error()) + } + + // Convert the pure bytes into a reader object that can be used with the mp3 decoder + fileBytesReader := bytes.NewReader(fileBytes) + + // Decode file + decodedMp3, err := mp3.NewDecoder(fileBytesReader) + if err != nil { + panic("mp3.NewDecoder failed: " + err.Error()) + } + + // Prepare an Oto context (this will use your default audio device) that will + // play all our sounds. Its configuration can't be changed later. + + op := &oto.NewContextOptions{} + + // Usually 44100 or 48000. Other values might cause distortions in Oto + op.SampleRate = 44100 + + // Number of channels (aka locations) to play sounds from. Either 1 or 2. + // 1 is mono sound, and 2 is stereo (most speakers are stereo). + op.ChannelCount = 2 + + // Format of the source. go-mp3's format is signed 16bit integers. + op.Format = oto.FormatSignedInt16LE + + // Remember that you should **not** create more than one context + otoCtx, readyChan, err := oto.NewContext(op) + if err != nil { + panic("oto.NewContext failed: " + err.Error()) + } + // It might take a bit for the hardware audio devices to be ready, so we wait on the channel. + <-readyChan + + // Create a new 'player' that will handle our sound. Paused by default. + player := otoCtx.NewPlayer(decodedMp3) + + // Play starts playing the sound and returns without waiting for it (Play() is async). + player.Play() + + // We can wait for the sound to finish playing using something like this + for player.IsPlaying() { + time.Sleep(time.Millisecond) + } + + // Now that the sound finished playing, we can restart from the beginning (or go to any location in the sound) using seek + // newPos, err := player.(io.Seeker).Seek(0, io.SeekStart) + // if err != nil{ + // panic("player.Seek failed: " + err.Error()) + // } + // println("Player is now at position:", newPos) + // player.Play() + + // If you don't want the player/sound anymore simply close + err = player.Close() + if err != nil { + panic("player.Close failed: " + err.Error()) + } +} +``` + +### Playing sounds by file streaming + +The above example loads the entire file into memory and then plays it. This is great for smaller files +but might be an issue if you are playing a long song since it would take too much memory and too long to load. + +In such cases you might want to stream the file. Luckily this is very simple, just use `os.Open`: + +```go +package main + +import ( + "bytes" + "os" + "time" + + "github.com/hajimehoshi/go-mp3" + "github.com/hajimehoshi/oto/v3" +) + +func main() { + // Open the file for reading. Do NOT close before you finish playing! + file, err := os.Open("./my-file.mp3") + if err != nil { + panic("opening my-file.mp3 failed: " + err.Error()) + } + + // Decode file. This process is done as the file plays so it won't + // load the whole thing into memory. + decodedMp3, err := mp3.NewDecoder(file) + if err != nil { + panic("mp3.NewDecoder failed: " + err.Error()) + } + + // Rest is the same... + + // Close file only after you finish playing + file.Close() +} +``` + +The only thing to note about streaming is that the *file* object must be kept alive, otherwise +you might just play static. + +To keep it alive not only must you be careful about when you close it, but you might need to keep a reference +to the original file object alive (by for example keeping it in a struct). + +### Advanced usage + +Players have their own internal audio data buffer, so while for example 200 bytes have been read from the `io.Reader` that +doesn't mean they were all played from the audio device. + +Data is moved from io.Reader->internal buffer->audio device, and when the internal buffer moves data to the audio device +is not guaranteed, so there might be a small delay. The amount of data in the buffer can be retrieved +using `Player.UnplayedBufferSize()`. + +The size of the underlying buffer of a player can also be set by type-asserting the player object: + +```go +myPlayer.(oto.BufferSizeSetter).SetBufferSize(newBufferSize) +``` + +This works because players implement a `Player` interface and a `BufferSizeSetter` interface. + +## Crosscompiling + +Crosscompiling to macOS or Windows is as easy as setting `GOOS=darwin` or `GOOS=windows`, respectively. + +To crosscompile for other platforms, make sure the libraries for the target architecture are installed, and set +`CGO_ENABLED=1` as Go disables [Cgo](https://golang.org/cmd/cgo/#hdr-Using_cgo_with_the_go_command) on crosscompiles by default. diff --git a/vendor/github.com/ebitengine/oto/v3/api_darwin.go b/vendor/github.com/ebitengine/oto/v3/api_darwin.go new file mode 100644 index 0000000..795bf84 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/api_darwin.go @@ -0,0 +1,95 @@ +// Copyright 2022 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "unsafe" + + "github.com/ebitengine/purego" +) + +const ( + avAudioSessionErrorCodeCannotStartPlaying = 0x21706c61 // '!pla' + avAudioSessionErrorCodeCannotInterruptOthers = 0x21696e74 // '!int' + avAudioSessionErrorCodeSiriIsRecording = 0x73697269 // 'siri' +) + +const ( + kAudioFormatLinearPCM = 0x6C70636D //'lpcm' +) + +const ( + kAudioFormatFlagIsFloat = 1 << 0 // 0x1 +) + +type _AudioStreamBasicDescription struct { + mSampleRate float64 + mFormatID uint32 + mFormatFlags uint32 + mBytesPerPacket uint32 + mFramesPerPacket uint32 + mBytesPerFrame uint32 + mChannelsPerFrame uint32 + mBitsPerChannel uint32 + mReserved uint32 +} + +type _AudioQueueRef uintptr + +type _AudioTimeStamp uintptr + +type _AudioStreamPacketDescription struct { + mStartOffset int64 + mVariableFramesInPacket uint32 + mDataByteSize uint32 +} + +type _AudioQueueBufferRef *_AudioQueueBuffer + +type _AudioQueueBuffer struct { + mAudioDataBytesCapacity uint32 + mAudioData uintptr // void* + mAudioDataByteSize uint32 + mUserData uintptr // void* + + mPacketDescriptionCapacity uint32 + mPacketDescriptions *_AudioStreamPacketDescription + mPacketDescriptionCount uint32 +} + +type _AudioQueueOutputCallback func(inUserData unsafe.Pointer, inAQ _AudioQueueRef, inBuffer _AudioQueueBufferRef) + +func initializeAPI() error { + toolbox, err := purego.Dlopen("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox", purego.RTLD_LAZY|purego.RTLD_GLOBAL) + if err != nil { + return err + } + purego.RegisterLibFunc(&_AudioQueueNewOutput, toolbox, "AudioQueueNewOutput") + purego.RegisterLibFunc(&_AudioQueueAllocateBuffer, toolbox, "AudioQueueAllocateBuffer") + purego.RegisterLibFunc(&_AudioQueueEnqueueBuffer, toolbox, "AudioQueueEnqueueBuffer") + purego.RegisterLibFunc(&_AudioQueueStart, toolbox, "AudioQueueStart") + purego.RegisterLibFunc(&_AudioQueuePause, toolbox, "AudioQueuePause") + return nil +} + +var _AudioQueueNewOutput func(inFormat *_AudioStreamBasicDescription, inCallbackProc _AudioQueueOutputCallback, inUserData unsafe.Pointer, inCallbackRunLoop uintptr, inCallbackRunLoopMod uintptr, inFlags uint32, outAQ *_AudioQueueRef) uintptr + +var _AudioQueueAllocateBuffer func(inAQ _AudioQueueRef, inBufferByteSize uint32, outBuffer *_AudioQueueBufferRef) uintptr + +var _AudioQueueEnqueueBuffer func(inAQ _AudioQueueRef, inBuffer _AudioQueueBufferRef, inNumPacketDescs uint32, inPackets []_AudioStreamPacketDescription) uintptr + +var _AudioQueueStart func(inAQ _AudioQueueRef, inStartTime *_AudioTimeStamp) uintptr + +var _AudioQueuePause func(inAQ _AudioQueueRef) uintptr diff --git a/vendor/github.com/ebitengine/oto/v3/api_wasapi_windows.go b/vendor/github.com/ebitengine/oto/v3/api_wasapi_windows.go new file mode 100644 index 0000000..b5a051a --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/api_wasapi_windows.go @@ -0,0 +1,479 @@ +// Copyright 2022 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "fmt" + "runtime" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + ole32 = windows.NewLazySystemDLL("ole32") +) + +var ( + procCoCreateInstance = ole32.NewProc("CoCreateInstance") +) + +type _REFERENCE_TIME int64 + +var ( + uuidIAudioClient2 = windows.GUID{0x726778cd, 0xf60a, 0x4eda, [...]byte{0x82, 0xde, 0xe4, 0x76, 0x10, 0xcd, 0x78, 0xaa}} + uuidIAudioRenderClient = windows.GUID{0xf294acfc, 0x3146, 0x4483, [...]byte{0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2}} + uuidIMMDeviceEnumerator = windows.GUID{0xa95664d2, 0x9614, 0x4f35, [...]byte{0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6}} + uuidMMDeviceEnumerator = windows.GUID{0xbcde0395, 0xe52f, 0x467c, [...]byte{0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e}} +) + +const ( + _AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = 0x80000000 + _AUDCLNT_STREAMFLAGS_EVENTCALLBACK = 0x00040000 + _AUDCLNT_STREAMFLAGS_NOPERSIST = 0x00080000 + _COINIT_APARTMENTTHREADED = 0x2 + _COINIT_MULTITHREADED = 0 + _REFTIMES_PER_SEC = 10000000 + _SPEAKER_FRONT_CENTER = 0x4 + _SPEAKER_FRONT_LEFT = 0x1 + _SPEAKER_FRONT_RIGHT = 0x2 + _WAVE_FORMAT_EXTENSIBLE = 0xfffe +) + +var ( + _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = windows.GUID{0x00000003, 0x0000, 0x0010, [...]byte{0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}} + _KSDATAFORMAT_SUBTYPE_PCM = windows.GUID{0x00000001, 0x0000, 0x0010, [...]byte{0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}} +) + +type _AUDCLNT_ERR uint32 + +const ( + _AUDCLNT_E_DEVICE_INVALIDATED _AUDCLNT_ERR = 0x88890004 + _AUDCLNT_E_NOT_INITIALIZED _AUDCLNT_ERR = 0x88890001 + _AUDCLNT_E_RESOURCES_INVALIDATED _AUDCLNT_ERR = 0x88890026 +) + +func isAudclntErr(hresult uint32) bool { + return hresult&0xffff0000 == (1<<31)|(windows.FACILITY_AUDCLNT<<16) +} + +func (e _AUDCLNT_ERR) Error() string { + switch e { + case _AUDCLNT_E_DEVICE_INVALIDATED: + return "AUDCLNT_E_DEVICE_INVALIDATED" + case _AUDCLNT_E_RESOURCES_INVALIDATED: + return "AUDCLNT_E_RESOURCES_INVALIDATED" + default: + return fmt.Sprintf("AUDCLNT_ERR(%d)", e) + } +} + +type _AUDCLNT_SHAREMODE int32 + +const ( + _AUDCLNT_SHAREMODE_SHARED _AUDCLNT_SHAREMODE = 0 + _AUDCLNT_SHAREMODE_EXCLUSIVE _AUDCLNT_SHAREMODE = 1 +) + +type _AUDCLNT_STREAMOPTIONS int32 + +const ( + _AUDCLNT_STREAMOPTIONS_NONE _AUDCLNT_STREAMOPTIONS = 0x0 + _AUDCLNT_STREAMOPTIONS_RAW _AUDCLNT_STREAMOPTIONS = 0x1 + _AUDCLNT_STREAMOPTIONS_MATCH_FORMAT _AUDCLNT_STREAMOPTIONS = 0x2 + _AUDCLNT_STREAMOPTIONS_AMBISONICS _AUDCLNT_STREAMOPTIONS = 0x4 +) + +type _AUDIO_STREAM_CATEGORY int32 + +const ( + _AudioCategory_Other _AUDIO_STREAM_CATEGORY = 0 + _AudioCategory_ForegroundOnlyMedia _AUDIO_STREAM_CATEGORY = 1 + _AudioCategory_BackgroundCapableMedia _AUDIO_STREAM_CATEGORY = 2 + _AudioCategory_Communications _AUDIO_STREAM_CATEGORY = 3 + _AudioCategory_Alerts _AUDIO_STREAM_CATEGORY = 4 + _AudioCategory_SoundEffects _AUDIO_STREAM_CATEGORY = 5 + _AudioCategory_GameEffects _AUDIO_STREAM_CATEGORY = 6 + _AudioCategory_GameMedia _AUDIO_STREAM_CATEGORY = 7 + _AudioCategory_GameChat _AUDIO_STREAM_CATEGORY = 8 + _AudioCategory_Speech _AUDIO_STREAM_CATEGORY = 9 + _AudioCategory_Movie _AUDIO_STREAM_CATEGORY = 10 + _AudioCategory_Media _AUDIO_STREAM_CATEGORY = 11 +) + +type _CLSCTX int32 + +const ( + _CLSCTX_INPROC_SERVER _CLSCTX = 0x00000001 + _CLSCTX_INPROC_HANDLER _CLSCTX = 0x00000002 + _CLSCTX_LOCAL_SERVER _CLSCTX = 0x00000004 + _CLSCTX_REMOTE_SERVER _CLSCTX = 0x00000010 + _CLSCTX_ALL = _CLSCTX_INPROC_SERVER | _CLSCTX_INPROC_HANDLER | _CLSCTX_LOCAL_SERVER | _CLSCTX_REMOTE_SERVER +) + +type _EDataFlow int32 + +const ( + eRender _EDataFlow = 0 +) + +type _ERole int32 + +const ( + eConsole _ERole = 0 +) + +type _WIN32_ERR uint32 + +const ( + _E_NOTFOUND _WIN32_ERR = 0x80070490 +) + +func isWin32Err(hresult uint32) bool { + return hresult&0xffff0000 == (1<<31)|(windows.FACILITY_WIN32<<16) +} + +func (e _WIN32_ERR) Error() string { + switch e { + case _E_NOTFOUND: + return "E_NOTFOUND" + default: + return fmt.Sprintf("HRESULT(%d)", e) + } +} + +type _AudioClientProperties struct { + cbSize uint32 + bIsOffload int32 + eCategory _AUDIO_STREAM_CATEGORY + Options _AUDCLNT_STREAMOPTIONS +} + +type _PROPVARIANT struct { + // TODO: Implmeent this +} + +type _WAVEFORMATEXTENSIBLE struct { + wFormatTag uint16 + nChannels uint16 + nSamplesPerSec uint32 + nAvgBytesPerSec uint32 + nBlockAlign uint16 + wBitsPerSample uint16 + cbSize uint16 + Samples uint16 // union + dwChannelMask uint32 + SubFormat windows.GUID +} + +func _CoCreateInstance(rclsid *windows.GUID, pUnkOuter unsafe.Pointer, dwClsContext uint32, riid *windows.GUID) (unsafe.Pointer, error) { + var v unsafe.Pointer + r, _, _ := procCoCreateInstance.Call(uintptr(unsafe.Pointer(rclsid)), uintptr(pUnkOuter), uintptr(dwClsContext), uintptr(unsafe.Pointer(riid)), uintptr(unsafe.Pointer(&v))) + runtime.KeepAlive(rclsid) + runtime.KeepAlive(riid) + if uint32(r) != uint32(windows.S_OK) { + return nil, fmt.Errorf("oto: CoCreateInstance failed: HRESULT(%d)", uint32(r)) + } + return v, nil +} + +type _IAudioClient2 struct { + vtbl *_IAudioClient2_Vtbl +} + +type _IAudioClient2_Vtbl struct { + QueryInterface uintptr + AddRef uintptr + Release uintptr + + Initialize uintptr + GetBufferSize uintptr + GetStreamLatency uintptr + GetCurrentPadding uintptr + IsFormatSupported uintptr + GetMixFormat uintptr + GetDevicePeriod uintptr + Start uintptr + Stop uintptr + Reset uintptr + SetEventHandle uintptr + GetService uintptr + IsOffloadCapable uintptr + SetClientProperties uintptr + GetBufferSizeLimits uintptr +} + +func (i *_IAudioClient2) GetBufferSize() (uint32, error) { + var numBufferFrames uint32 + r, _, _ := syscall.Syscall(i.vtbl.GetBufferSize, 2, uintptr(unsafe.Pointer(i)), uintptr(unsafe.Pointer(&numBufferFrames)), 0) + if uint32(r) != uint32(windows.S_OK) { + if isAudclntErr(uint32(r)) { + return 0, fmt.Errorf("oto: IAudioClient2::GetBufferSize failed: %w", _AUDCLNT_ERR(r)) + } + return 0, fmt.Errorf("oto: IAudioClient2::GetBufferSize failed: HRESULT(%d)", uint32(r)) + } + return numBufferFrames, nil +} + +func (i *_IAudioClient2) GetCurrentPadding() (uint32, error) { + var numPaddingFrames uint32 + r, _, _ := syscall.Syscall(i.vtbl.GetCurrentPadding, 2, uintptr(unsafe.Pointer(i)), uintptr(unsafe.Pointer(&numPaddingFrames)), 0) + if uint32(r) != uint32(windows.S_OK) { + if isAudclntErr(uint32(r)) { + return 0, fmt.Errorf("oto: IAudioClient2::GetCurrentPadding failed: %w", _AUDCLNT_ERR(r)) + } + return 0, fmt.Errorf("oto: IAudioClient2::GetCurrentPadding failed: HRESULT(%d)", uint32(r)) + } + return numPaddingFrames, nil +} + +func (i *_IAudioClient2) GetDevicePeriod() (_REFERENCE_TIME, _REFERENCE_TIME, error) { + var defaultDevicePeriod _REFERENCE_TIME + var minimumDevicePeriod _REFERENCE_TIME + r, _, _ := syscall.Syscall(i.vtbl.GetDevicePeriod, 3, uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&defaultDevicePeriod)), uintptr(unsafe.Pointer(&minimumDevicePeriod))) + if uint32(r) != uint32(windows.S_OK) { + if isAudclntErr(uint32(r)) { + return 0, 0, fmt.Errorf("oto: IAudioClient2::GetDevicePeriod failed: %w", _AUDCLNT_ERR(r)) + } + return 0, 0, fmt.Errorf("oto: IAudioClient2::GetDevicePeriod failed: HRESULT(%d)", uint32(r)) + } + return defaultDevicePeriod, minimumDevicePeriod, nil +} + +func (i *_IAudioClient2) GetService(riid *windows.GUID) (unsafe.Pointer, error) { + var v unsafe.Pointer + r, _, _ := syscall.Syscall(i.vtbl.GetService, 3, uintptr(unsafe.Pointer(i)), uintptr(unsafe.Pointer(riid)), uintptr(unsafe.Pointer(&v))) + if uint32(r) != uint32(windows.S_OK) { + if isAudclntErr(uint32(r)) { + return nil, fmt.Errorf("oto: IAudioClient2::GetService failed: %w", _AUDCLNT_ERR(r)) + } + return nil, fmt.Errorf("oto: IAudioClient2::GetService failed: HRESULT(%d)", uint32(r)) + } + return v, nil +} + +func (i *_IAudioClient2) Initialize(shareMode _AUDCLNT_SHAREMODE, streamFlags uint32, hnsBufferDuration _REFERENCE_TIME, hnsPeriodicity _REFERENCE_TIME, pFormat *_WAVEFORMATEXTENSIBLE, audioSessionGuid *windows.GUID) error { + var r uintptr + if unsafe.Sizeof(uintptr(0)) == 8 { + // 64bits + r, _, _ = syscall.Syscall9(i.vtbl.Initialize, 7, uintptr(unsafe.Pointer(i)), + uintptr(shareMode), uintptr(streamFlags), uintptr(hnsBufferDuration), + uintptr(hnsPeriodicity), uintptr(unsafe.Pointer(pFormat)), uintptr(unsafe.Pointer(audioSessionGuid)), + 0, 0) + } else { + // 32bits + r, _, _ = syscall.Syscall9(i.vtbl.Initialize, 9, uintptr(unsafe.Pointer(i)), + uintptr(shareMode), uintptr(streamFlags), uintptr(hnsBufferDuration), + uintptr(hnsBufferDuration>>32), uintptr(hnsPeriodicity), uintptr(hnsPeriodicity>>32), + uintptr(unsafe.Pointer(pFormat)), uintptr(unsafe.Pointer(audioSessionGuid))) + } + runtime.KeepAlive(pFormat) + runtime.KeepAlive(audioSessionGuid) + if uint32(r) != uint32(windows.S_OK) { + if isAudclntErr(uint32(r)) { + return fmt.Errorf("oto: IAudioClient2::Initialize failed: %w", _AUDCLNT_ERR(r)) + } + return fmt.Errorf("oto: IAudioClient2::Initialize failed: HRESULT(%d)", uint32(r)) + } + return nil +} + +func (i *_IAudioClient2) IsFormatSupported(shareMode _AUDCLNT_SHAREMODE, pFormat *_WAVEFORMATEXTENSIBLE) (*_WAVEFORMATEXTENSIBLE, error) { + var closestMatch *_WAVEFORMATEXTENSIBLE + r, _, _ := syscall.Syscall6(i.vtbl.IsFormatSupported, 4, uintptr(unsafe.Pointer(i)), + uintptr(shareMode), uintptr(unsafe.Pointer(pFormat)), uintptr(unsafe.Pointer(&closestMatch)), + 0, 0) + if uint32(r) != uint32(windows.S_OK) { + if uint32(r) == uint32(windows.S_FALSE) { + var r _WAVEFORMATEXTENSIBLE + if closestMatch != nil { + r = *closestMatch + windows.CoTaskMemFree(unsafe.Pointer(closestMatch)) + } + return &r, nil + } + if isAudclntErr(uint32(r)) { + return nil, fmt.Errorf("oto: IAudioClient2::IsFormatSupported failed: %w", _AUDCLNT_ERR(r)) + } + return nil, fmt.Errorf("oto: IAudioClient2::IsFormatSupported failed: HRESULT(%d)", uint32(r)) + } + return nil, nil +} + +func (i *_IAudioClient2) Release() uint32 { + r, _, _ := syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0) + return uint32(r) +} + +func (i *_IAudioClient2) SetClientProperties(pProperties *_AudioClientProperties) error { + r, _, _ := syscall.Syscall(i.vtbl.SetClientProperties, 2, uintptr(unsafe.Pointer(i)), uintptr(unsafe.Pointer(pProperties)), 0) + runtime.KeepAlive(pProperties) + if uint32(r) != uint32(windows.S_OK) { + if isAudclntErr(uint32(r)) { + return fmt.Errorf("oto: IAudioClient2::SetClientProperties failed: %w", _AUDCLNT_ERR(r)) + } + return fmt.Errorf("oto: IAudioClient2::SetClientProperties failed: HRESULT(%d)", uint32(r)) + } + return nil +} + +func (i *_IAudioClient2) SetEventHandle(eventHandle windows.Handle) error { + r, _, _ := syscall.Syscall(i.vtbl.SetEventHandle, 2, uintptr(unsafe.Pointer(i)), uintptr(eventHandle), 0) + if uint32(r) != uint32(windows.S_OK) { + if isAudclntErr(uint32(r)) { + return fmt.Errorf("oto: IAudioClient2::SetEventHandle failed: %w", _AUDCLNT_ERR(r)) + } + return fmt.Errorf("oto: IAudioClient2::SetEventHandle failed: HRESULT(%d)", uint32(r)) + } + return nil +} + +func (i *_IAudioClient2) Start() error { + r, _, _ := syscall.Syscall(i.vtbl.Start, 1, uintptr(unsafe.Pointer(i)), 0, 0) + if uint32(r) != uint32(windows.S_OK) { + if isAudclntErr(uint32(r)) { + return fmt.Errorf("oto: IAudioClient2::Start failed: %w", _AUDCLNT_ERR(r)) + } + return fmt.Errorf("oto: IAudioClient2::Start failed: HRESULT(%d)", uint32(r)) + } + return nil +} + +func (i *_IAudioClient2) Stop() (bool, error) { + r, _, _ := syscall.Syscall(i.vtbl.Stop, 1, uintptr(unsafe.Pointer(i)), 0, 0) + if uint32(r) != uint32(windows.S_OK) && uint32(r) != uint32(windows.S_FALSE) { + if isAudclntErr(uint32(r)) { + return false, fmt.Errorf("oto: IAudioClient2::Stop failed: %w", _AUDCLNT_ERR(r)) + } + return false, fmt.Errorf("oto: IAudioClient2::Stop failed: HRESULT(%d)", uint32(r)) + } + return uint32(r) == uint32(windows.S_OK), nil +} + +type _IAudioRenderClient struct { + vtbl *_IAudioRenderClient_Vtbl +} + +type _IAudioRenderClient_Vtbl struct { + QueryInterface uintptr + AddRef uintptr + Release uintptr + + GetBuffer uintptr + ReleaseBuffer uintptr +} + +func (i *_IAudioRenderClient) GetBuffer(numFramesRequested uint32) (*byte, error) { + var data *byte + r, _, _ := syscall.Syscall(i.vtbl.GetBuffer, 3, uintptr(unsafe.Pointer(i)), uintptr(numFramesRequested), uintptr(unsafe.Pointer(&data))) + if uint32(r) != uint32(windows.S_OK) { + if isAudclntErr(uint32(r)) { + return nil, fmt.Errorf("oto: IAudioRenderClient::GetBuffer failed: %w", _AUDCLNT_ERR(r)) + } + return nil, fmt.Errorf("oto: IAudioRenderClient::GetBuffer failed: HRESULT(%d)", uint32(r)) + } + return data, nil +} + +func (i *_IAudioRenderClient) Release() uint32 { + r, _, _ := syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0) + return uint32(r) +} + +func (i *_IAudioRenderClient) ReleaseBuffer(numFramesWritten uint32, dwFlags uint32) error { + r, _, _ := syscall.Syscall(i.vtbl.ReleaseBuffer, 3, uintptr(unsafe.Pointer(i)), uintptr(numFramesWritten), uintptr(dwFlags)) + if uint32(r) != uint32(windows.S_OK) { + if isAudclntErr(uint32(r)) { + return fmt.Errorf("oto: IAudioRenderClient::ReleaseBuffer failed: %w", _AUDCLNT_ERR(r)) + } + return fmt.Errorf("oto: IAudioRenderClient::ReleaseBuffer failed: HRESULT(%d)", uint32(r)) + } + return nil +} + +type _IMMDevice struct { + vtbl *_IMMDevice_Vtbl +} + +type _IMMDevice_Vtbl struct { + QueryInterface uintptr + AddRef uintptr + Release uintptr + + Activate uintptr + OpenPropertyStore uintptr + GetId uintptr + GetState uintptr +} + +func (i *_IMMDevice) Activate(iid *windows.GUID, dwClsCtx uint32, pActivationParams *_PROPVARIANT) (unsafe.Pointer, error) { + var v unsafe.Pointer + r, _, _ := syscall.Syscall6(i.vtbl.Activate, 5, uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(iid)), uintptr(dwClsCtx), uintptr(unsafe.Pointer(pActivationParams)), uintptr(unsafe.Pointer(&v)), 0) + runtime.KeepAlive(iid) + runtime.KeepAlive(pActivationParams) + if uint32(r) != uint32(windows.S_OK) { + return nil, fmt.Errorf("oto: IMMDevice::Activate failed: HRESULT(%d)", uint32(r)) + } + return v, nil +} + +func (i *_IMMDevice) GetId() (string, error) { + var strId *uint16 + r, _, _ := syscall.Syscall(i.vtbl.GetId, 2, uintptr(unsafe.Pointer(i)), uintptr(unsafe.Pointer(&strId)), 0) + if uint32(r) != uint32(windows.S_OK) { + return "", fmt.Errorf("oto: IMMDevice::GetId failed: HRESULT(%d)", uint32(r)) + } + return windows.UTF16PtrToString(strId), nil +} + +func (i *_IMMDevice) Release() { + syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0) +} + +type _IMMDeviceEnumerator struct { + vtbl *_IMMDeviceEnumerator_Vtbl +} + +type _IMMDeviceEnumerator_Vtbl struct { + QueryInterface uintptr + AddRef uintptr + Release uintptr + + EnumAudioEndpoints uintptr + GetDefaultAudioEndpoint uintptr + GetDevice uintptr + RegisterEndpointNotificationCallback uintptr + UnregisterEndpointNotificationCallback uintptr +} + +func (i *_IMMDeviceEnumerator) GetDefaultAudioEndPoint(dataFlow _EDataFlow, role _ERole) (*_IMMDevice, error) { + var endPoint *_IMMDevice + r, _, _ := syscall.Syscall6(i.vtbl.GetDefaultAudioEndpoint, 4, uintptr(unsafe.Pointer(i)), + uintptr(dataFlow), uintptr(role), uintptr(unsafe.Pointer(&endPoint)), 0, 0) + if uint32(r) != uint32(windows.S_OK) { + if isWin32Err(uint32(r)) { + return nil, fmt.Errorf("oto: IMMDeviceEnumerator::GetDefaultAudioEndPoint failed: %w", _E_NOTFOUND) + } + return nil, fmt.Errorf("oto: IMMDeviceEnumerator::GetDefaultAudioEndPoint failed: HRESULT(%d)", uint32(r)) + } + return endPoint, nil +} + +func (i *_IMMDeviceEnumerator) Release() { + syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0) +} diff --git a/vendor/github.com/ebitengine/oto/v3/api_winmm_windows.go b/vendor/github.com/ebitengine/oto/v3/api_winmm_windows.go new file mode 100644 index 0000000..821299c --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/api_winmm_windows.go @@ -0,0 +1,174 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "fmt" + "runtime" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + winmm = windows.NewLazySystemDLL("winmm") +) + +var ( + procWaveOutOpen = winmm.NewProc("waveOutOpen") + procWaveOutClose = winmm.NewProc("waveOutClose") + procWaveOutPrepareHeader = winmm.NewProc("waveOutPrepareHeader") + procWaveOutUnprepareHeader = winmm.NewProc("waveOutUnprepareHeader") + procWaveOutWrite = winmm.NewProc("waveOutWrite") +) + +type _WAVEHDR struct { + lpData uintptr + dwBufferLength uint32 + dwBytesRecorded uint32 + dwUser uintptr + dwFlags uint32 + dwLoops uint32 + lpNext uintptr + reserved uintptr +} + +type _WAVEFORMATEX struct { + wFormatTag uint16 + nChannels uint16 + nSamplesPerSec uint32 + nAvgBytesPerSec uint32 + nBlockAlign uint16 + wBitsPerSample uint16 + cbSize uint16 +} + +const ( + _WAVE_FORMAT_IEEE_FLOAT = 3 + _WHDR_INQUEUE = 16 +) + +type _MMRESULT uint + +const ( + _MMSYSERR_NOERROR _MMRESULT = 0 + _MMSYSERR_ERROR _MMRESULT = 1 + _MMSYSERR_BADDEVICEID _MMRESULT = 2 + _MMSYSERR_ALLOCATED _MMRESULT = 4 + _MMSYSERR_INVALIDHANDLE _MMRESULT = 5 + _MMSYSERR_NODRIVER _MMRESULT = 6 + _MMSYSERR_NOMEM _MMRESULT = 7 + _WAVERR_BADFORMAT _MMRESULT = 32 + _WAVERR_STILLPLAYING _MMRESULT = 33 + _WAVERR_UNPREPARED _MMRESULT = 34 + _WAVERR_SYNC _MMRESULT = 35 +) + +func (m _MMRESULT) Error() string { + switch m { + case _MMSYSERR_NOERROR: + return "MMSYSERR_NOERROR" + case _MMSYSERR_ERROR: + return "MMSYSERR_ERROR" + case _MMSYSERR_BADDEVICEID: + return "MMSYSERR_BADDEVICEID" + case _MMSYSERR_ALLOCATED: + return "MMSYSERR_ALLOCATED" + case _MMSYSERR_INVALIDHANDLE: + return "MMSYSERR_INVALIDHANDLE" + case _MMSYSERR_NODRIVER: + return "MMSYSERR_NODRIVER" + case _MMSYSERR_NOMEM: + return "MMSYSERR_NOMEM" + case _WAVERR_BADFORMAT: + return "WAVERR_BADFORMAT" + case _WAVERR_STILLPLAYING: + return "WAVERR_STILLPLAYING" + case _WAVERR_UNPREPARED: + return "WAVERR_UNPREPARED" + case _WAVERR_SYNC: + return "WAVERR_SYNC" + } + return fmt.Sprintf("MMRESULT (%d)", m) +} + +func waveOutOpen(f *_WAVEFORMATEX, callback uintptr) (uintptr, error) { + const ( + waveMapper = 0xffffffff + callbackFunction = 0x30000 + ) + var w uintptr + var fdwOpen uintptr + if callback != 0 { + fdwOpen |= callbackFunction + } + r, _, e := procWaveOutOpen.Call(uintptr(unsafe.Pointer(&w)), waveMapper, uintptr(unsafe.Pointer(f)), + callback, 0, fdwOpen) + runtime.KeepAlive(f) + if _MMRESULT(r) != _MMSYSERR_NOERROR { + if e != nil && e != windows.ERROR_SUCCESS { + return 0, fmt.Errorf("oto: waveOutOpen failed: %w", e) + } + return 0, fmt.Errorf("oto: waveOutOpen failed: %w", _MMRESULT(r)) + } + return w, nil +} + +func waveOutClose(hwo uintptr) error { + r, _, e := procWaveOutClose.Call(hwo) + if _MMRESULT(r) != _MMSYSERR_NOERROR { + if e != nil && e != windows.ERROR_SUCCESS { + return fmt.Errorf("oto: waveOutClose failed: %w", e) + } + return fmt.Errorf("oto: waveOutClose failed: %w", _MMRESULT(r)) + } + return nil +} + +func waveOutPrepareHeader(hwo uintptr, pwh *_WAVEHDR) error { + r, _, e := procWaveOutPrepareHeader.Call(hwo, uintptr(unsafe.Pointer(pwh)), unsafe.Sizeof(_WAVEHDR{})) + runtime.KeepAlive(pwh) + if _MMRESULT(r) != _MMSYSERR_NOERROR { + if e != nil && e != windows.ERROR_SUCCESS { + return fmt.Errorf("oto: waveOutPrepareHeader failed: %w", e) + } + return fmt.Errorf("oto: waveOutPrepareHeader failed: %w", _MMRESULT(r)) + } + return nil +} + +func waveOutUnprepareHeader(hwo uintptr, pwh *_WAVEHDR) error { + r, _, e := procWaveOutUnprepareHeader.Call(hwo, uintptr(unsafe.Pointer(pwh)), unsafe.Sizeof(_WAVEHDR{})) + runtime.KeepAlive(pwh) + if _MMRESULT(r) != _MMSYSERR_NOERROR { + if e != nil && e != windows.ERROR_SUCCESS { + return fmt.Errorf("oto: waveOutUnprepareHeader failed: %w", e) + } + return fmt.Errorf("oto: waveOutUnprepareHeader failed: %w", _MMRESULT(r)) + } + return nil +} + +func waveOutWrite(hwo uintptr, pwh *_WAVEHDR) error { + r, _, e := procWaveOutWrite.Call(hwo, uintptr(unsafe.Pointer(pwh)), unsafe.Sizeof(_WAVEHDR{})) + runtime.KeepAlive(pwh) + if _MMRESULT(r) != _MMSYSERR_NOERROR { + if e != nil && e != windows.ERROR_SUCCESS { + return fmt.Errorf("oto: waveOutWrite failed: %w", e) + } + return fmt.Errorf("oto: waveOutWrite failed: %w", _MMRESULT(r)) + } + return nil +} diff --git a/vendor/github.com/ebitengine/oto/v3/context.go b/vendor/github.com/ebitengine/oto/v3/context.go new file mode 100644 index 0000000..b91ae5c --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/context.go @@ -0,0 +1,181 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "fmt" + "io" + "sync" + "time" + + "github.com/ebitengine/oto/v3/internal/mux" +) + +var ( + contextCreated bool + contextCreationMutex sync.Mutex +) + +// Context is the main object in Oto. It interacts with the audio drivers. +// +// To play sound with Oto, first create a context. Then use the context to create +// an arbitrary number of players. Then use the players to play sound. +// +// Creating multiple contexts is NOT supported. +type Context struct { + context *context +} + +// Format is the format of sources. +type Format int + +const ( + // FormatFloat32LE is the format of 32 bits floats little endian. + FormatFloat32LE Format = iota + + // FormatUnsignedInt8 is the format of 8 bits integers. + FormatUnsignedInt8 + + //FormatSignedInt16LE is the format of 16 bits integers little endian. + FormatSignedInt16LE +) + +// NewContextOptions represents options for NewContext. +type NewContextOptions struct { + // SampleRate specifies the number of samples that should be played during one second. + // Usual numbers are 44100 or 48000. One context has only one sample rate. You cannot play multiple audio + // sources with different sample rates at the same time. + SampleRate int + + // ChannelCount specifies the number of channels. One channel is mono playback. Two + // channels are stereo playback. No other values are supported. + ChannelCount int + + // Format specifies the format of sources. + Format Format + + // BufferSize specifies a buffer size in the underlying device. + // + // If 0 is specified, the driver's default buffer size is used. + // Set BufferSize to adjust the buffer size if you want to adjust latency or reduce noises. + // Too big buffer size can increase the latency time. + // On the other hand, too small buffer size can cause glitch noises due to buffer shortage. + BufferSize time.Duration +} + +// NewContext creates a new context with given options. +// A context creates and holds ready-to-use Player objects. +// NewContext returns a context, a channel that is closed when the context is ready, and an error if it exists. +// +// Creating multiple contexts is NOT supported. +func NewContext(options *NewContextOptions) (*Context, chan struct{}, error) { + contextCreationMutex.Lock() + defer contextCreationMutex.Unlock() + + if contextCreated { + return nil, nil, fmt.Errorf("oto: context is already created") + } + contextCreated = true + + var bufferSizeInBytes int + if options.BufferSize != 0 { + // The underying driver always uses 32bit floats. + bytesPerSample := options.ChannelCount * 4 + bytesPerSecond := options.SampleRate * bytesPerSample + bufferSizeInBytes = int(int64(options.BufferSize) * int64(bytesPerSecond) / int64(time.Second)) + bufferSizeInBytes = bufferSizeInBytes / bytesPerSample * bytesPerSample + } + ctx, ready, err := newContext(options.SampleRate, options.ChannelCount, mux.Format(options.Format), bufferSizeInBytes) + if err != nil { + return nil, nil, err + } + return &Context{context: ctx}, ready, nil +} + +// NewPlayer creates a new, ready-to-use Player belonging to the Context. +// It is safe to create multiple players. +// +// The format of r is as follows: +// +// [data] = [sample 1] [sample 2] [sample 3] ... +// [sample *] = [channel 1] [channel 2] ... +// [channel *] = [byte 1] [byte 2] ... +// +// Byte ordering is little endian. +// +// A player has some amount of an underlying buffer. +// Read data from r is queued to the player's underlying buffer. +// The underlying buffer is consumed by its playing. +// Then, r's position and the current playing position don't necessarily match. +// If you want to clear the underlying buffer for some reasons e.g., you want to seek the position of r, +// call the player's Reset function. +// +// You cannot share r by multiple players. +// +// The returned player implements Player, BufferSizeSetter, and io.Seeker. +// You can modify the buffer size of a player by the SetBufferSize function. +// A small buffer size is useful if you want to play a real-time PCM for example. +// Note that the audio quality might be affected if you modify the buffer size. +// +// If r does not implement io.Seeker, the returned player's Seek returns an error. +// +// NewPlayer is concurrent-safe. +// +// All the functions of a Player returned by NewPlayer are concurrent-safe. +func (c *Context) NewPlayer(r io.Reader) *Player { + return &Player{ + player: c.context.mux.NewPlayer(r), + } +} + +// Suspend suspends the entire audio play. +// +// Suspend is concurrent-safe. +func (c *Context) Suspend() error { + return c.context.Suspend() +} + +// Resume resumes the entire audio play, which was suspended by Suspend. +// +// Resume is concurrent-safe. +func (c *Context) Resume() error { + return c.context.Resume() +} + +// Err returns the current error. +// +// Err is concurrent-safe. +func (c *Context) Err() error { + return c.context.Err() +} + +type atomicError struct { + err error + m sync.Mutex +} + +func (a *atomicError) TryStore(err error) { + a.m.Lock() + defer a.m.Unlock() + if a.err == nil { + a.err = err + } +} + +func (a *atomicError) Load() error { + a.m.Lock() + defer a.m.Unlock() + return a.err +} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_android.go b/vendor/github.com/ebitengine/oto/v3/driver_android.go new file mode 100644 index 0000000..52e33f5 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_android.go @@ -0,0 +1,49 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "github.com/ebitengine/oto/v3/internal/mux" + "github.com/ebitengine/oto/v3/internal/oboe" +) + +type context struct { + mux *mux.Mux +} + +func newContext(sampleRate int, channelCount int, format mux.Format, bufferSizeInBytes int) (*context, chan struct{}, error) { + ready := make(chan struct{}) + close(ready) + + c := &context{ + mux: mux.New(sampleRate, channelCount, format), + } + if err := oboe.Play(sampleRate, channelCount, c.mux.ReadFloat32s, bufferSizeInBytes); err != nil { + return nil, nil, err + } + return c, ready, nil +} + +func (c *context) Suspend() error { + return oboe.Suspend() +} + +func (c *context) Resume() error { + return oboe.Resume() +} + +func (c *context) Err() error { + return nil +} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_darwin.go b/vendor/github.com/ebitengine/oto/v3/driver_darwin.go new file mode 100644 index 0000000..5ccf35a --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_darwin.go @@ -0,0 +1,263 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "fmt" + "sync" + "time" + "unsafe" + + "github.com/ebitengine/purego/objc" + + "github.com/ebitengine/oto/v3/internal/mux" +) + +const ( + float32SizeInBytes = 4 + + bufferCount = 4 + + noErr = 0 +) + +func newAudioQueue(sampleRate, channelCount int, oneBufferSizeInBytes int) (_AudioQueueRef, []_AudioQueueBufferRef, error) { + desc := _AudioStreamBasicDescription{ + mSampleRate: float64(sampleRate), + mFormatID: uint32(kAudioFormatLinearPCM), + mFormatFlags: uint32(kAudioFormatFlagIsFloat), + mBytesPerPacket: uint32(channelCount * float32SizeInBytes), + mFramesPerPacket: 1, + mBytesPerFrame: uint32(channelCount * float32SizeInBytes), + mChannelsPerFrame: uint32(channelCount), + mBitsPerChannel: uint32(8 * float32SizeInBytes), + } + + var audioQueue _AudioQueueRef + if osstatus := _AudioQueueNewOutput( + &desc, + render, + nil, + 0, //CFRunLoopRef + 0, //CFStringRef + 0, + &audioQueue); osstatus != noErr { + return 0, nil, fmt.Errorf("oto: AudioQueueNewFormat with StreamFormat failed: %d", osstatus) + } + + bufs := make([]_AudioQueueBufferRef, 0, bufferCount) + for len(bufs) < cap(bufs) { + var buf _AudioQueueBufferRef + if osstatus := _AudioQueueAllocateBuffer(audioQueue, uint32(oneBufferSizeInBytes), &buf); osstatus != noErr { + return 0, nil, fmt.Errorf("oto: AudioQueueAllocateBuffer failed: %d", osstatus) + } + buf.mAudioDataByteSize = uint32(oneBufferSizeInBytes) + bufs = append(bufs, buf) + } + + return audioQueue, bufs, nil +} + +type context struct { + audioQueue _AudioQueueRef + unqueuedBuffers []_AudioQueueBufferRef + + oneBufferSizeInBytes int + + cond *sync.Cond + + mux *mux.Mux + err atomicError +} + +// TODO: Convert the error code correctly. +// See https://stackoverflow.com/questions/2196869/how-do-you-convert-an-iphone-osstatus-code-to-something-useful + +var theContext *context + +func newContext(sampleRate int, channelCount int, format mux.Format, bufferSizeInBytes int) (*context, chan struct{}, error) { + var oneBufferSizeInBytes int + if bufferSizeInBytes != 0 { + oneBufferSizeInBytes = bufferSizeInBytes / bufferCount + } else { + oneBufferSizeInBytes = defaultOneBufferSizeInBytes + } + bytesPerSample := channelCount * 4 + oneBufferSizeInBytes = oneBufferSizeInBytes / bytesPerSample * bytesPerSample + + ready := make(chan struct{}) + + c := &context{ + cond: sync.NewCond(&sync.Mutex{}), + mux: mux.New(sampleRate, channelCount, format), + oneBufferSizeInBytes: oneBufferSizeInBytes, + } + theContext = c + + if err := initializeAPI(); err != nil { + return nil, nil, err + } + + go func() { + defer close(ready) + + q, bs, err := newAudioQueue(sampleRate, channelCount, oneBufferSizeInBytes) + if err != nil { + c.err.TryStore(err) + return + } + c.audioQueue = q + c.unqueuedBuffers = bs + + if err := setNotificationHandler(); err != nil { + c.err.TryStore(err) + return + } + + var retryCount int + try: + if osstatus := _AudioQueueStart(c.audioQueue, nil); osstatus != noErr { + if osstatus == avAudioSessionErrorCodeCannotStartPlaying && retryCount < 100 { + // TODO: use sleepTime() after investigating when this error happens. + time.Sleep(10 * time.Millisecond) + retryCount++ + goto try + } + c.err.TryStore(fmt.Errorf("oto: AudioQueueStart failed at newContext: %d", osstatus)) + return + } + + go c.loop() + }() + + return c, ready, nil +} + +func (c *context) wait() bool { + c.cond.L.Lock() + defer c.cond.L.Unlock() + + for len(c.unqueuedBuffers) == 0 && c.err.Load() == nil { + c.cond.Wait() + } + return c.err.Load() == nil +} + +func (c *context) loop() { + buf32 := make([]float32, c.oneBufferSizeInBytes/4) + for { + if !c.wait() { + return + } + c.appendBuffer(buf32) + } +} + +func (c *context) appendBuffer(buf32 []float32) { + c.cond.L.Lock() + defer c.cond.L.Unlock() + + if c.err.Load() != nil { + return + } + + buf := c.unqueuedBuffers[0] + copy(c.unqueuedBuffers, c.unqueuedBuffers[1:]) + c.unqueuedBuffers = c.unqueuedBuffers[:len(c.unqueuedBuffers)-1] + + c.mux.ReadFloat32s(buf32) + copy(unsafe.Slice((*float32)(unsafe.Pointer(buf.mAudioData)), buf.mAudioDataByteSize/float32SizeInBytes), buf32) + + if osstatus := _AudioQueueEnqueueBuffer(c.audioQueue, buf, 0, nil); osstatus != noErr { + c.err.TryStore(fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus)) + } +} + +func (c *context) Suspend() error { + c.cond.L.Lock() + defer c.cond.L.Unlock() + + if err := c.err.Load(); err != nil { + return err.(error) + } + if osstatus := _AudioQueuePause(c.audioQueue); osstatus != noErr { + return fmt.Errorf("oto: AudioQueuePause failed: %d", osstatus) + } + return nil +} + +func (c *context) Resume() error { + c.cond.L.Lock() + defer c.cond.L.Unlock() + + if err := c.err.Load(); err != nil { + return err.(error) + } + + var retryCount int +try: + if osstatus := _AudioQueueStart(c.audioQueue, nil); osstatus != noErr { + if (osstatus == avAudioSessionErrorCodeCannotStartPlaying || + osstatus == avAudioSessionErrorCodeCannotInterruptOthers) && + retryCount < 30 { + // It is uncertain that this error is temporary or not. Then let's use exponential-time sleeping. + time.Sleep(sleepTime(retryCount)) + retryCount++ + goto try + } + if osstatus == avAudioSessionErrorCodeSiriIsRecording { + // As this error should be temporary, it should be OK to use a short time for sleep anytime. + time.Sleep(10 * time.Millisecond) + goto try + } + return fmt.Errorf("oto: AudioQueueStart failed at Resume: %d", osstatus) + } + return nil +} + +func (c *context) Err() error { + if err := c.err.Load(); err != nil { + return err.(error) + } + return nil +} + +func render(inUserData unsafe.Pointer, inAQ _AudioQueueRef, inBuffer _AudioQueueBufferRef) { + theContext.cond.L.Lock() + defer theContext.cond.L.Unlock() + theContext.unqueuedBuffers = append(theContext.unqueuedBuffers, inBuffer) + theContext.cond.Signal() +} + +func setGlobalPause(self objc.ID, _cmd objc.SEL, notification objc.ID) { + theContext.Suspend() +} + +func setGlobalResume(self objc.ID, _cmd objc.SEL, notification objc.ID) { + theContext.Resume() +} + +func sleepTime(count int) time.Duration { + switch count { + case 0: + return 10 * time.Millisecond + case 1: + return 20 * time.Millisecond + case 2: + return 50 * time.Millisecond + default: + return 100 * time.Millisecond + } +} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_ios.go b/vendor/github.com/ebitengine/oto/v3/driver_ios.go new file mode 100644 index 0000000..d7a8e3f --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_ios.go @@ -0,0 +1,30 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build darwin && ios + +package oto + +// 12288 seems necessary at least on iPod touch (7th). +// With 48000[Hz] stereo, the maximum delay is (12288*4[buffers] / 4 / 2)[samples] / 48000 [Hz] = 100[ms]. +// '4' is float32 size in bytes. '2' is a number of channels for stereo. + +const defaultOneBufferSizeInBytes = 12288 + +func setNotificationHandler() error { + // AVAudioSessionInterruptionNotification is not reliable on iOS. Rely on + // applicationWillResignActive and applicationDidBecomeActive instead. See + // https://stackoverflow.com/questions/24404463/ios-siri-not-available-does-not-return-avaudiosessioninterruptionoptionshouldre + return nil +} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_js.go b/vendor/github.com/ebitengine/oto/v3/driver_js.go new file mode 100644 index 0000000..cae41f7 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_js.go @@ -0,0 +1,141 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "errors" + "runtime" + "syscall/js" + "unsafe" + + "github.com/ebitengine/oto/v3/internal/mux" +) + +type context struct { + audioContext js.Value + scriptProcessor js.Value + scriptProcessorCallback js.Func + ready bool + callbacks map[string]js.Func + + mux *mux.Mux +} + +func newContext(sampleRate int, channelCount int, format mux.Format, bufferSizeInBytes int) (*context, chan struct{}, error) { + ready := make(chan struct{}) + + class := js.Global().Get("AudioContext") + if !class.Truthy() { + class = js.Global().Get("webkitAudioContext") + } + if !class.Truthy() { + return nil, nil, errors.New("oto: AudioContext or webkitAudioContext was not found") + } + options := js.Global().Get("Object").New() + options.Set("sampleRate", sampleRate) + + d := &context{ + audioContext: class.New(options), + mux: mux.New(sampleRate, channelCount, format), + } + + if bufferSizeInBytes == 0 { + // 4096 was not great at least on Safari 15. + bufferSizeInBytes = 8192 * channelCount + } + + buf32 := make([]float32, bufferSizeInBytes/4) + chBuf32 := make([][]float32, channelCount) + for i := range chBuf32 { + chBuf32[i] = make([]float32, len(buf32)/channelCount) + } + + // TODO: Use AudioWorklet if available. + sp := d.audioContext.Call("createScriptProcessor", bufferSizeInBytes/4/channelCount, 0, channelCount) + f := js.FuncOf(func(this js.Value, arguments []js.Value) any { + d.mux.ReadFloat32s(buf32) + for i := 0; i < channelCount; i++ { + for j := range chBuf32[i] { + chBuf32[i][j] = buf32[j*channelCount+i] + } + } + + buf := arguments[0].Get("outputBuffer") + if buf.Get("copyToChannel").Truthy() { + for i := 0; i < channelCount; i++ { + buf.Call("copyToChannel", float32SliceToTypedArray(chBuf32[i]), i, 0) + } + } else { + // copyToChannel is not defined on Safari 11. + for i := 0; i < channelCount; i++ { + buf.Call("getChannelData", i).Call("set", float32SliceToTypedArray(chBuf32[i])) + } + } + return nil + }) + sp.Call("addEventListener", "audioprocess", f) + d.scriptProcessor = sp + d.scriptProcessorCallback = f + + sp.Call("connect", d.audioContext.Get("destination")) + + setCallback := func(event string) js.Func { + var f js.Func + f = js.FuncOf(func(this js.Value, arguments []js.Value) any { + if !d.ready { + d.audioContext.Call("resume") + d.ready = true + close(ready) + } + js.Global().Get("document").Call("removeEventListener", event, f) + return nil + }) + js.Global().Get("document").Call("addEventListener", event, f) + d.callbacks[event] = f + return f + } + + // Browsers require user interaction to start the audio. + // https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio + d.callbacks = map[string]js.Func{} + setCallback("touchend") + setCallback("keyup") + setCallback("mouseup") + + return d, ready, nil +} + +func (c *context) Suspend() error { + c.audioContext.Call("suspend") + return nil +} + +func (c *context) Resume() error { + c.audioContext.Call("resume") + return nil +} + +func (c *context) Err() error { + return nil +} + +func float32SliceToTypedArray(s []float32) js.Value { + bs := unsafe.Slice((*byte)(unsafe.Pointer(&s[0])), len(s)*4) + a := js.Global().Get("Uint8Array").New(len(bs)) + js.CopyBytesToJS(a, bs) + runtime.KeepAlive(s) + buf := a.Get("buffer") + return js.Global().Get("Float32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4) +} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_macos.go b/vendor/github.com/ebitengine/oto/v3/driver_macos.go new file mode 100644 index 0000000..65bca94 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_macos.go @@ -0,0 +1,78 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build darwin && !ios + +package oto + +import ( + "unsafe" + + "github.com/ebitengine/purego" + "github.com/ebitengine/purego/objc" +) + +const defaultOneBufferSizeInBytes = 2048 + +// setNotificationHandler sets a handler for sleep/wake notifications. +func setNotificationHandler() error { + appkit, err := purego.Dlopen("/System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit", purego.RTLD_GLOBAL) + if err != nil { + return err + } + + // Create the Observer object + var class objc.Class + class, err = objc.RegisterClass("OtoNotificationObserver", objc.GetClass("NSObject"), nil, nil, + []objc.MethodDef{ + { + Cmd: objc.RegisterName("receiveSleepNote:"), + Fn: setGlobalPause, + }, + { + Cmd: objc.RegisterName("receiveWakeNote:"), + Fn: setGlobalResume, + }, + }) + + observer := objc.ID(class).Send(objc.RegisterName("new")) + + notificationCenter := objc.ID(objc.GetClass("NSWorkspace")).Send(objc.RegisterName("sharedWorkspace")).Send(objc.RegisterName("notificationCenter")) + + // Dlsym returns a pointer to the object so dereference it + s, err := purego.Dlsym(appkit, "NSWorkspaceWillSleepNotification") + if err != nil { + return err + } + + notificationCenter.Send(objc.RegisterName("addObserver:selector:name:object:"), + observer, + objc.RegisterName("receiveSleepNote:"), + *(*uintptr)(unsafe.Pointer(s)), + 0, + ) + + s, err = purego.Dlsym(appkit, "NSWorkspaceDidWakeNotification") + if err != nil { + return err + } + + notificationCenter.Send(objc.RegisterName("addObserver:selector:name:object:"), + observer, + objc.RegisterName("receiveWakeNote:"), + *(*uintptr)(unsafe.Pointer(s)), + 0, + ) + return nil +} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_nintendosdk.cpp b/vendor/github.com/ebitengine/oto/v3/driver_nintendosdk.cpp new file mode 100644 index 0000000..0d4ecae --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_nintendosdk.cpp @@ -0,0 +1,25 @@ +// Copyright 2022 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build nintendosdk + +// The actual implementaiton will be provided by -overlay. + +#include + +typedef void (*oto_OnReadCallbackType)(float *buf, size_t length); + +extern "C" void oto_OpenAudio(int sample_rate, int channel_num, + oto_OnReadCallbackType on_read_callback, + int buffer_size_in_bytes) {} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_nintendosdk.go b/vendor/github.com/ebitengine/oto/v3/driver_nintendosdk.go new file mode 100644 index 0000000..3b3f4c7 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_nintendosdk.go @@ -0,0 +1,74 @@ +// Copyright 2022 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build nintendosdk + +package oto + +// #cgo !darwin LDFLAGS: -Wl,-unresolved-symbols=ignore-all +// #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup +// +// typedef void (*oto_OnReadCallbackType)(float* buf, size_t length); +// +// void oto_OpenAudio(int sample_rate, int channel_num, oto_OnReadCallbackType on_read_callback, int buffer_size_in_bytes); +// +// void oto_OnReadCallback(float* buf, size_t length); +// static void oto_OpenProxy(int sample_rate, int channel_num, int buffer_size_in_bytes) { +// oto_OpenAudio(sample_rate, channel_num, oto_OnReadCallback, buffer_size_in_bytes); +// } +import "C" + +import ( + "unsafe" + + "github.com/ebitengine/oto/v3/internal/mux" +) + +//export oto_OnReadCallback +func oto_OnReadCallback(buf *C.float, length C.size_t) { + theContext.mux.ReadFloat32s(unsafe.Slice((*float32)(unsafe.Pointer(buf)), length)) +} + +type context struct { + mux *mux.Mux +} + +var theContext *context + +func newContext(sampleRate int, channelCount int, format mux.Format, bufferSizeInBytes int) (*context, chan struct{}, error) { + ready := make(chan struct{}) + close(ready) + + c := &context{ + mux: mux.New(sampleRate, channelCount, format), + } + theContext = c + C.oto_OpenProxy(C.int(sampleRate), C.int(channelCount), C.int(bufferSizeInBytes)) + + return c, ready, nil +} + +func (c *context) Suspend() error { + // Do nothing so far. + return nil +} + +func (c *context) Resume() error { + // Do nothing so far. + return nil +} + +func (c *context) Err() error { + return nil +} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_unix.go b/vendor/github.com/ebitengine/oto/v3/driver_unix.go new file mode 100644 index 0000000..b1fc21d --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_unix.go @@ -0,0 +1,278 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !android && !darwin && !js && !windows && !nintendosdk + +package oto + +// #cgo pkg-config: alsa +// +// #include +import "C" + +import ( + "fmt" + "strings" + "sync" + "unsafe" + + "github.com/ebitengine/oto/v3/internal/mux" +) + +type context struct { + channelCount int + + suspended bool + + handle *C.snd_pcm_t + + cond *sync.Cond + + mux *mux.Mux + err atomicError + + ready chan struct{} +} + +var theContext *context + +func alsaError(name string, err C.int) error { + return fmt.Errorf("oto: ALSA error at %s: %s", name, C.GoString(C.snd_strerror(err))) +} + +func deviceCandidates() []string { + const getAllDevices = -1 + + cPCMInterfaceName := C.CString("pcm") + defer C.free(unsafe.Pointer(cPCMInterfaceName)) + + var hints *unsafe.Pointer + err := C.snd_device_name_hint(getAllDevices, cPCMInterfaceName, &hints) + if err != 0 { + return []string{"default", "plug:default"} + } + defer C.snd_device_name_free_hint(hints) + + var devices []string + + cIoHintName := C.CString("IOID") + defer C.free(unsafe.Pointer(cIoHintName)) + cNameHintName := C.CString("NAME") + defer C.free(unsafe.Pointer(cNameHintName)) + + for it := hints; *it != nil; it = (*unsafe.Pointer)(unsafe.Pointer(uintptr(unsafe.Pointer(it)) + unsafe.Sizeof(uintptr(0)))) { + io := C.snd_device_name_get_hint(*it, cIoHintName) + defer func() { + if io != nil { + C.free(unsafe.Pointer(io)) + } + }() + if C.GoString(io) == "Input" { + continue + } + + name := C.snd_device_name_get_hint(*it, cNameHintName) + defer func() { + if name != nil { + C.free(unsafe.Pointer(name)) + } + }() + if name == nil { + continue + } + goName := C.GoString(name) + if goName == "null" { + continue + } + if goName == "default" { + continue + } + devices = append(devices, goName) + } + + devices = append([]string{"default", "plug:default"}, devices...) + + return devices +} + +func newContext(sampleRate int, channelCount int, format mux.Format, bufferSizeInBytes int) (*context, chan struct{}, error) { + c := &context{ + channelCount: channelCount, + cond: sync.NewCond(&sync.Mutex{}), + mux: mux.New(sampleRate, channelCount, format), + ready: make(chan struct{}), + } + theContext = c + + go func() { + defer close(c.ready) + + // Open a default ALSA audio device for blocking stream playback + type openError struct { + device string + err C.int + } + var openErrs []openError + var found bool + + for _, name := range deviceCandidates() { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + if err := C.snd_pcm_open(&c.handle, cname, C.SND_PCM_STREAM_PLAYBACK, 0); err < 0 { + openErrs = append(openErrs, openError{ + device: name, + err: err, + }) + continue + } + found = true + break + } + if !found { + var msgs []string + for _, e := range openErrs { + msgs = append(msgs, fmt.Sprintf("%q: %s", e.device, C.GoString(C.snd_strerror(e.err)))) + } + c.err.TryStore(fmt.Errorf("oto: ALSA error at snd_pcm_open: %s", strings.Join(msgs, ", "))) + return + } + + // TODO: Should snd_pcm_hw_params_set_periods be called explicitly? + const periods = 2 + var periodSize C.snd_pcm_uframes_t + if bufferSizeInBytes != 0 { + periodSize = C.snd_pcm_uframes_t(bufferSizeInBytes / (channelCount * 4 * periods)) + } else { + periodSize = C.snd_pcm_uframes_t(1024) + } + bufferSize := periodSize * periods + if err := c.alsaPcmHwParams(sampleRate, channelCount, &bufferSize, &periodSize); err != nil { + c.err.TryStore(err) + return + } + + go func() { + buf32 := make([]float32, int(periodSize)*channelCount) + for { + if !c.readAndWrite(buf32) { + return + } + } + }() + }() + + return c, c.ready, nil +} + +func (c *context) alsaPcmHwParams(sampleRate, channelCount int, bufferSize, periodSize *C.snd_pcm_uframes_t) error { + var params *C.snd_pcm_hw_params_t + C.snd_pcm_hw_params_malloc(¶ms) + defer C.free(unsafe.Pointer(params)) + + if err := C.snd_pcm_hw_params_any(c.handle, params); err < 0 { + return alsaError("snd_pcm_hw_params_any", err) + } + if err := C.snd_pcm_hw_params_set_access(c.handle, params, C.SND_PCM_ACCESS_RW_INTERLEAVED); err < 0 { + return alsaError("snd_pcm_hw_params_set_access", err) + } + if err := C.snd_pcm_hw_params_set_format(c.handle, params, C.SND_PCM_FORMAT_FLOAT_LE); err < 0 { + return alsaError("snd_pcm_hw_params_set_format", err) + } + if err := C.snd_pcm_hw_params_set_channels(c.handle, params, C.unsigned(channelCount)); err < 0 { + return alsaError("snd_pcm_hw_params_set_channels", err) + } + if err := C.snd_pcm_hw_params_set_rate_resample(c.handle, params, 1); err < 0 { + return alsaError("snd_pcm_hw_params_set_rate_resample", err) + } + sr := C.unsigned(sampleRate) + if err := C.snd_pcm_hw_params_set_rate_near(c.handle, params, &sr, nil); err < 0 { + return alsaError("snd_pcm_hw_params_set_rate_near", err) + } + if err := C.snd_pcm_hw_params_set_buffer_size_near(c.handle, params, bufferSize); err < 0 { + return alsaError("snd_pcm_hw_params_set_buffer_size_near", err) + } + if err := C.snd_pcm_hw_params_set_period_size_near(c.handle, params, periodSize, nil); err < 0 { + return alsaError("snd_pcm_hw_params_set_period_size_near", err) + } + if err := C.snd_pcm_hw_params(c.handle, params); err < 0 { + return alsaError("snd_pcm_hw_params", err) + } + return nil +} + +func (c *context) readAndWrite(buf32 []float32) bool { + c.cond.L.Lock() + defer c.cond.L.Unlock() + + for c.suspended && c.err.Load() == nil { + c.cond.Wait() + } + if c.err.Load() != nil { + return false + } + + c.mux.ReadFloat32s(buf32) + + for len(buf32) > 0 { + n := C.snd_pcm_writei(c.handle, unsafe.Pointer(&buf32[0]), C.snd_pcm_uframes_t(len(buf32)/c.channelCount)) + if n < 0 { + n = C.long(C.snd_pcm_recover(c.handle, C.int(n), 1)) + } + if n < 0 { + c.err.TryStore(alsaError("snd_pcm_writei or snd_pcm_recover", C.int(n))) + return false + } + buf32 = buf32[int(n)*c.channelCount:] + } + return true +} + +func (c *context) Suspend() error { + <-c.ready + + c.cond.L.Lock() + defer c.cond.L.Unlock() + + if err := c.err.Load(); err != nil { + return err.(error) + } + + c.suspended = true + + // Do not use snd_pcm_pause as not all devices support this. + // Do not use snd_pcm_drop as this might hang (https://github.com/libsdl-org/SDL/blob/a5c610b0a3857d3138f3f3da1f6dc3172c5ea4a8/src/audio/alsa/SDL_alsa_audio.c#L478). + return nil +} + +func (c *context) Resume() error { + <-c.ready + + c.cond.L.Lock() + defer c.cond.L.Unlock() + + if err := c.err.Load(); err != nil { + return err.(error) + } + + c.suspended = false + c.cond.Signal() + return nil +} + +func (c *context) Err() error { + if err := c.err.Load(); err != nil { + return err.(error) + } + return nil +} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_wasapi_windows.go b/vendor/github.com/ebitengine/oto/v3/driver_wasapi_windows.go new file mode 100644 index 0000000..bfe4a3e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_wasapi_windows.go @@ -0,0 +1,470 @@ +// Copyright 2022 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "errors" + "fmt" + "runtime" + "sync" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/windows" + + "github.com/ebitengine/oto/v3/internal/mux" +) + +type comThread struct { + funcCh chan func() +} + +func newCOMThread() (*comThread, error) { + funcCh := make(chan func()) + errCh := make(chan error) + go func() { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + // S_FALSE is returned when CoInitializeEx is nested. This is a successful case. + if err := windows.CoInitializeEx(0, windows.COINIT_MULTITHREADED); err != nil && !errors.Is(err, syscall.Errno(windows.S_FALSE)) { + errCh <- err + } + // CoUninitialize should be called even when CoInitializeEx returns S_FALSE. + defer windows.CoUninitialize() + + close(errCh) + + for f := range funcCh { + f() + } + }() + + if err := <-errCh; err != nil { + return nil, err + } + + return &comThread{ + funcCh: funcCh, + }, nil +} + +func (c *comThread) Run(f func()) { + ch := make(chan struct{}) + c.funcCh <- func() { + f() + close(ch) + } + <-ch +} + +type wasapiContext struct { + sampleRate int + channelCount int + mux *mux.Mux + bufferSizeInBytes int + + comThread *comThread + err atomicError + suspended bool + suspendedCond *sync.Cond + + sampleReadyEvent windows.Handle + client *_IAudioClient2 + bufferFrames uint32 + renderClient *_IAudioRenderClient + currentDeviceID string + enumerator *_IMMDeviceEnumerator + + buf []float32 + + m sync.Mutex +} + +var ( + errDeviceSwitched = errors.New("oto: device switched") + errFormatNotSupported = errors.New("oto: the specified format is not supported (there is the closest format instead)") +) + +func newWASAPIContext(sampleRate, channelCount int, mux *mux.Mux, bufferSizeInBytes int) (context *wasapiContext, ferr error) { + t, err := newCOMThread() + if err != nil { + return nil, err + } + + c := &wasapiContext{ + sampleRate: sampleRate, + channelCount: channelCount, + mux: mux, + bufferSizeInBytes: bufferSizeInBytes, + comThread: t, + suspendedCond: sync.NewCond(&sync.Mutex{}), + } + + ev, err := windows.CreateEventEx(nil, nil, 0, windows.EVENT_ALL_ACCESS) + if err != nil { + return nil, err + } + defer func() { + if ferr != nil { + windows.CloseHandle(ev) + } + }() + c.sampleReadyEvent = ev + + if err := c.start(); err != nil { + return nil, err + } + + return c, nil +} + +func (c *wasapiContext) isDeviceSwitched() (bool, error) { + // If the audio is suspended, do nothing. + if c.isSuspended() { + return false, nil + } + + var switched bool + var cerr error + c.comThread.Run(func() { + device, err := c.enumerator.GetDefaultAudioEndPoint(eRender, eConsole) + if err != nil { + cerr = err + return + } + defer device.Release() + + id, err := device.GetId() + if err != nil { + cerr = err + return + } + + if c.currentDeviceID == id { + return + } + switched = true + }) + + return switched, cerr +} + +func (c *wasapiContext) start() error { + var cerr error + c.comThread.Run(func() { + if err := c.startOnCOMThread(); err != nil { + cerr = err + return + } + }) + if cerr != nil { + return cerr + } + + go func() { + if err := c.loop(); err != nil { + if !errors.Is(err, _AUDCLNT_E_DEVICE_INVALIDATED) && !errors.Is(err, _AUDCLNT_E_RESOURCES_INVALIDATED) && !errors.Is(err, errDeviceSwitched) { + c.err.TryStore(err) + return + } + + if err := c.restart(); err != nil { + c.err.TryStore(err) + return + } + } + }() + + return nil +} + +func (c *wasapiContext) startOnCOMThread() (ferr error) { + if c.enumerator == nil { + e, err := _CoCreateInstance(&uuidMMDeviceEnumerator, nil, uint32(_CLSCTX_ALL), &uuidIMMDeviceEnumerator) + if err != nil { + return err + } + c.enumerator = (*_IMMDeviceEnumerator)(e) + defer func() { + if ferr != nil { + c.enumerator.Release() + c.enumerator = nil + } + }() + } + + device, err := c.enumerator.GetDefaultAudioEndPoint(eRender, eConsole) + if err != nil { + if errors.Is(err, _E_NOTFOUND) { + return errDeviceNotFound + } + return err + } + defer device.Release() + + id, err := device.GetId() + if err != nil { + return err + } + c.currentDeviceID = id + + if c.client != nil { + c.client.Release() + c.client = nil + } + + client, err := device.Activate(&uuidIAudioClient2, uint32(_CLSCTX_ALL), nil) + if err != nil { + return err + } + c.client = (*_IAudioClient2)(client) + + if err := c.client.SetClientProperties(&_AudioClientProperties{ + cbSize: uint32(unsafe.Sizeof(_AudioClientProperties{})), + bIsOffload: 0, // false + eCategory: _AudioCategory_Other, // In the example, AudioCategory_ForegroundOnlyMedia was used, but this value is deprecated. + }); err != nil { + return err + } + + // Check the format is supported by WASAPI. + // Stereo with 48000 [Hz] is likely supported, but mono and/or other sample rates are unlikely supported. + // Fallback to WinMM in this case anyway. + const bitsPerSample = 32 + nBlockAlign := c.channelCount * bitsPerSample / 8 + var channelMask uint32 + switch c.channelCount { + case 1: + channelMask = _SPEAKER_FRONT_CENTER + case 2: + channelMask = _SPEAKER_FRONT_LEFT | _SPEAKER_FRONT_RIGHT + } + f := &_WAVEFORMATEXTENSIBLE{ + wFormatTag: _WAVE_FORMAT_EXTENSIBLE, + nChannels: uint16(c.channelCount), + nSamplesPerSec: uint32(c.sampleRate), + nAvgBytesPerSec: uint32(c.sampleRate * nBlockAlign), + nBlockAlign: uint16(nBlockAlign), + wBitsPerSample: bitsPerSample, + cbSize: 0x16, + Samples: bitsPerSample, + dwChannelMask: channelMask, + SubFormat: _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, + } + + var bufferSizeIn100ns _REFERENCE_TIME + if c.bufferSizeInBytes != 0 { + bufferSizeInFrames := int64(c.bufferSizeInBytes) / int64(nBlockAlign) + bufferSizeIn100ns = _REFERENCE_TIME(1e7 * bufferSizeInFrames / int64(c.sampleRate)) + } else { + // The default buffer size can be too small and might cause glitch noises. + // Specify 50[ms] as the buffer size. + bufferSizeIn100ns = _REFERENCE_TIME(50 * time.Millisecond / 100) + } + + // Even if the sample rate and/or the number of channels are not supported by the audio driver, + // AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM should convert the sample rate automatically (#215). + if err := c.client.Initialize(_AUDCLNT_SHAREMODE_SHARED, + _AUDCLNT_STREAMFLAGS_EVENTCALLBACK|_AUDCLNT_STREAMFLAGS_NOPERSIST|_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM, + bufferSizeIn100ns, 0, f, nil); err != nil { + return err + } + + frames, err := c.client.GetBufferSize() + if err != nil { + return err + } + c.bufferFrames = frames + + if c.renderClient != nil { + c.renderClient.Release() + c.renderClient = nil + } + + renderClient, err := c.client.GetService(&uuidIAudioRenderClient) + if err != nil { + return err + } + c.renderClient = (*_IAudioRenderClient)(renderClient) + + if err := c.client.SetEventHandle(c.sampleReadyEvent); err != nil { + return err + } + + // TODO: Should some errors be allowed? See WASAPIManager.cpp in the official example SimpleWASAPIPlaySound. + + if err := c.client.Start(); err != nil { + return err + } + + return nil +} + +func (c *wasapiContext) loop() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + // S_FALSE is returned when CoInitializeEx is nested. This is a successful case. + if err := windows.CoInitializeEx(0, windows.COINIT_MULTITHREADED); err != nil && !errors.Is(err, syscall.Errno(windows.S_FALSE)) { + _, _ = c.client.Stop() + return err + } + // CoUninitialize should be called even when CoInitializeEx returns S_FALSE. + defer windows.CoUninitialize() + + if err := c.loopOnRenderThread(); err != nil { + _, _ = c.client.Stop() + return err + } + + return nil +} + +func (c *wasapiContext) loopOnRenderThread() error { + last := time.Now() + for { + c.suspendedCond.L.Lock() + for c.suspended { + c.suspendedCond.Wait() + } + c.suspendedCond.L.Unlock() + + evt, err := windows.WaitForSingleObject(c.sampleReadyEvent, windows.INFINITE) + if err != nil { + return err + } + if evt != windows.WAIT_OBJECT_0 { + return fmt.Errorf("oto: WaitForSingleObject failed: returned value: %d", evt) + } + + if err := c.writeOnRenderThread(); err != nil { + return err + } + + // Checking the current default audio device might be an expensive operation. + // Check this repeatedly but with some time interval. + if now := time.Now(); now.Sub(last) >= 500*time.Millisecond { + switched, err := c.isDeviceSwitched() + if err != nil { + return err + } + if switched { + return errDeviceSwitched + } + last = now + } + } +} + +func (c *wasapiContext) writeOnRenderThread() error { + c.m.Lock() + defer c.m.Unlock() + + paddingFrames, err := c.client.GetCurrentPadding() + if err != nil { + return err + } + + frames := c.bufferFrames - paddingFrames + if frames <= 0 { + return nil + } + + // Get the destination buffer. + dstBuf, err := c.renderClient.GetBuffer(frames) + if err != nil { + return err + } + + // Calculate the buffer size. + if buflen := int(frames) * c.channelCount; cap(c.buf) < buflen { + c.buf = make([]float32, buflen) + } else { + c.buf = c.buf[:buflen] + } + + // Read the buffer from the players. + c.mux.ReadFloat32s(c.buf) + + // Copy the read buf to the destination buffer. + copy(unsafe.Slice((*float32)(unsafe.Pointer(dstBuf)), len(c.buf)), c.buf) + + // Release the buffer. + if err := c.renderClient.ReleaseBuffer(frames, 0); err != nil { + return err + } + + c.buf = c.buf[:0] + return nil +} + +func (c *wasapiContext) Suspend() error { + c.suspendedCond.L.Lock() + c.suspended = true + c.suspendedCond.L.Unlock() + c.suspendedCond.Signal() + + return nil +} + +func (c *wasapiContext) Resume() error { + c.suspendedCond.L.Lock() + c.suspended = false + c.suspendedCond.L.Unlock() + c.suspendedCond.Signal() + + return nil +} + +func (c *wasapiContext) isSuspended() bool { + c.suspendedCond.L.Lock() + defer c.suspendedCond.L.Unlock() + return c.suspended +} + +func (c *wasapiContext) Err() error { + return c.err.Load() +} + +func (c *wasapiContext) restart() error { + // Probably the driver is missing temporarily e.g. plugging out the headset. + // Recreate the device. + +retry: + c.suspendedCond.L.Lock() + for c.suspended { + c.suspendedCond.Wait() + } + c.suspendedCond.L.Unlock() + + if err := c.start(); err != nil { + // When a device is switched, the new device might not support the desired format, + // or all the audio devices might be disconnected. + // Instead of aborting this context, let's wait for the next device switch. + if !errors.Is(err, errFormatNotSupported) && !errors.Is(err, errDeviceNotFound) { + return err + } + + // Just read the buffer and discard it. Then, retry to search the device. + var buf32 [4096]float32 + sleep := time.Duration(float64(time.Second) * float64(len(buf32)) / float64(c.channelCount) / float64(c.sampleRate)) + c.mux.ReadFloat32s(buf32[:]) + time.Sleep(sleep) + goto retry + } + return nil +} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_windows.go b/vendor/github.com/ebitengine/oto/v3/driver_windows.go new file mode 100644 index 0000000..1a5abe1 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_windows.go @@ -0,0 +1,163 @@ +// Copyright 2022 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "errors" + "fmt" + "time" + + "github.com/ebitengine/oto/v3/internal/mux" +) + +var errDeviceNotFound = errors.New("oto: device not found") + +type context struct { + sampleRate int + channelCount int + + mux *mux.Mux + + wasapiContext *wasapiContext + winmmContext *winmmContext + nullContext *nullContext + + ready chan struct{} + err atomicError +} + +func newContext(sampleRate int, channelCount int, format mux.Format, bufferSizeInBytes int) (*context, chan struct{}, error) { + ctx := &context{ + sampleRate: sampleRate, + channelCount: channelCount, + mux: mux.New(sampleRate, channelCount, format), + ready: make(chan struct{}), + } + + // Initializing drivers might take some time. Do this asynchronously. + go func() { + defer close(ctx.ready) + + xc, err0 := newWASAPIContext(sampleRate, channelCount, ctx.mux, bufferSizeInBytes) + if err0 == nil { + ctx.wasapiContext = xc + return + } + + wc, err1 := newWinMMContext(sampleRate, channelCount, ctx.mux, bufferSizeInBytes) + if err1 == nil { + ctx.winmmContext = wc + return + } + + if errors.Is(err0, errDeviceNotFound) && errors.Is(err1, errDeviceNotFound) { + ctx.nullContext = newNullContext(sampleRate, channelCount, ctx.mux) + return + } + + ctx.err.TryStore(fmt.Errorf("oto: initialization failed: WASAPI: %v, WinMM: %v", err0, err1)) + }() + + return ctx, ctx.ready, nil +} + +func (c *context) Suspend() error { + <-c.ready + if c.wasapiContext != nil { + return c.wasapiContext.Suspend() + } + if c.winmmContext != nil { + return c.winmmContext.Suspend() + } + if c.nullContext != nil { + return c.nullContext.Suspend() + } + return nil +} + +func (c *context) Resume() error { + <-c.ready + if c.wasapiContext != nil { + return c.wasapiContext.Resume() + } + if c.winmmContext != nil { + return c.winmmContext.Resume() + } + if c.nullContext != nil { + return c.nullContext.Resume() + } + return nil +} + +func (c *context) Err() error { + if err := c.err.Load(); err != nil { + return err + } + + select { + case <-c.ready: + default: + return nil + } + + if c.wasapiContext != nil { + return c.wasapiContext.Err() + } + if c.winmmContext != nil { + return c.winmmContext.Err() + } + if c.nullContext != nil { + return c.nullContext.Err() + } + return nil +} + +type nullContext struct { + suspended bool +} + +func newNullContext(sampleRate int, channelCount int, mux *mux.Mux) *nullContext { + c := &nullContext{} + go c.loop(sampleRate, channelCount, mux) + return c +} + +func (c *nullContext) loop(sampleRate int, channelCount int, mux *mux.Mux) { + var buf32 [4096]float32 + sleep := time.Duration(float64(time.Second) * float64(len(buf32)) / float64(channelCount) / float64(sampleRate)) + for { + if c.suspended { + time.Sleep(time.Second) + continue + } + + mux.ReadFloat32s(buf32[:]) + time.Sleep(sleep) + } +} + +func (c *nullContext) Suspend() error { + c.suspended = true + return nil +} + +func (c *nullContext) Resume() error { + c.suspended = false + return nil +} + +func (*nullContext) Err() error { + return nil +} diff --git a/vendor/github.com/ebitengine/oto/v3/driver_winmm_windows.go b/vendor/github.com/ebitengine/oto/v3/driver_winmm_windows.go new file mode 100644 index 0000000..04f935e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/driver_winmm_windows.go @@ -0,0 +1,297 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "errors" + "fmt" + "sync" + "time" + "unsafe" + + "golang.org/x/sys/windows" + + "github.com/ebitengine/oto/v3/internal/mux" +) + +// Avoid goroutines on Windows (hajimehoshi/ebiten#1768). +// Apparently, switching contexts might take longer than other platforms. + +const defaultHeaderBufferSize = 4096 + +type header struct { + waveOut uintptr + buffer []float32 + waveHdr *_WAVEHDR +} + +func newHeader(waveOut uintptr, bufferSizeInBytes int) (*header, error) { + h := &header{ + waveOut: waveOut, + buffer: make([]float32, bufferSizeInBytes/4), + } + h.waveHdr = &_WAVEHDR{ + lpData: uintptr(unsafe.Pointer(&h.buffer[0])), + dwBufferLength: uint32(bufferSizeInBytes), + } + if err := waveOutPrepareHeader(waveOut, h.waveHdr); err != nil { + return nil, err + } + return h, nil +} + +func (h *header) Write(data []float32) error { + copy(h.buffer, data) + if err := waveOutWrite(h.waveOut, h.waveHdr); err != nil { + return err + } + return nil +} + +func (h *header) IsQueued() bool { + return h.waveHdr.dwFlags&_WHDR_INQUEUE != 0 +} + +func (h *header) Close() error { + return waveOutUnprepareHeader(h.waveOut, h.waveHdr) +} + +type winmmContext struct { + sampleRate int + channelCount int + bufferSizeInBytes int + + waveOut uintptr + headers []*header + + buf32 []float32 + + mux *mux.Mux + err atomicError + loopEndCh chan error + + cond *sync.Cond + + suspended bool + suspendedCond *sync.Cond +} + +var theWinMMContext *winmmContext + +func newWinMMContext(sampleRate, channelCount int, mux *mux.Mux, bufferSizeInBytes int) (*winmmContext, error) { + // winmm.dll is not available on Xbox. + if err := winmm.Load(); err != nil { + return nil, fmt.Errorf("oto: loading winmm.dll failed: %w", err) + } + + c := &winmmContext{ + sampleRate: sampleRate, + channelCount: channelCount, + bufferSizeInBytes: bufferSizeInBytes, + mux: mux, + cond: sync.NewCond(&sync.Mutex{}), + suspendedCond: sync.NewCond(&sync.Mutex{}), + } + theWinMMContext = c + + if err := c.start(); err != nil { + return nil, err + } + return c, nil +} + +func (c *winmmContext) start() error { + const bitsPerSample = 32 + nBlockAlign := c.channelCount * bitsPerSample / 8 + f := &_WAVEFORMATEX{ + wFormatTag: _WAVE_FORMAT_IEEE_FLOAT, + nChannels: uint16(c.channelCount), + nSamplesPerSec: uint32(c.sampleRate), + nAvgBytesPerSec: uint32(c.sampleRate * nBlockAlign), + nBlockAlign: uint16(nBlockAlign), + wBitsPerSample: bitsPerSample, + } + + // TOOD: What about using an event instead of a callback? PortAudio and other libraries do that. + w, err := waveOutOpen(f, waveOutOpenCallback) + if errors.Is(err, windows.ERROR_NOT_FOUND) { + // This can happen when no device is found (#77). + return errDeviceNotFound + } + if errors.Is(err, _MMSYSERR_BADDEVICEID) { + // This can happen when no device is found (hajimehoshi/ebiten#2316). + return errDeviceNotFound + } + if err != nil { + return err + } + + headerBufferSize := defaultHeaderBufferSize + if c.bufferSizeInBytes != 0 { + headerBufferSize = c.bufferSizeInBytes + } + + c.waveOut = w + c.headers = make([]*header, 0, 6) + for len(c.headers) < cap(c.headers) { + h, err := newHeader(c.waveOut, headerBufferSize) + if err != nil { + return err + } + c.headers = append(c.headers, h) + } + + c.buf32 = make([]float32, headerBufferSize/4) + go c.loop() + + return nil +} + +func (c *winmmContext) Suspend() error { + c.suspendedCond.L.Lock() + c.suspended = true + c.suspendedCond.L.Unlock() + c.suspendedCond.Signal() + + return nil +} + +func (c *winmmContext) Resume() (ferr error) { + c.suspendedCond.L.Lock() + c.suspended = false + c.suspendedCond.L.Unlock() + c.suspendedCond.Signal() + + return nil +} + +func (c *winmmContext) Err() error { + if err := c.err.Load(); err != nil { + return err.(error) + } + return nil +} + +func (c *winmmContext) isHeaderAvailable() bool { + for _, h := range c.headers { + if !h.IsQueued() { + return true + } + } + return false +} + +var waveOutOpenCallback = windows.NewCallback(func(hwo, uMsg, dwInstance, dwParam1, dwParam2 uintptr) uintptr { + // Queuing a header in this callback might not work especially when a headset is connected or disconnected. + // Just signal the condition vairable and don't do other things. + const womDone = 0x3bd + if uMsg != womDone { + return 0 + } + theWinMMContext.cond.Signal() + return 0 +}) + +func (c *winmmContext) waitUntilHeaderAvailable() bool { + c.cond.L.Lock() + defer c.cond.L.Unlock() + + for !c.isHeaderAvailable() && c.err.Load() == nil && c.loopEndCh == nil { + c.cond.Wait() + } + return c.err.Load() == nil && c.loopEndCh == nil +} + +func (c *winmmContext) loop() { + defer func() { + if err := c.closeLoop(); err != nil { + c.err.TryStore(err) + } + }() + for { + c.suspendedCond.L.Lock() + for c.suspended { + c.suspendedCond.Wait() + } + c.suspendedCond.L.Unlock() + + if !c.waitUntilHeaderAvailable() { + return + } + c.appendBuffers() + } +} + +func (c *winmmContext) closeLoop() (ferr error) { + c.cond.L.Lock() + defer c.cond.L.Unlock() + + defer func() { + if c.loopEndCh != nil { + if ferr != nil { + c.loopEndCh <- ferr + ferr = nil + } + close(c.loopEndCh) + c.loopEndCh = nil + } + }() + + for _, h := range c.headers { + if err := h.Close(); err != nil { + return err + } + } + c.headers = nil + + if err := waveOutClose(c.waveOut); err != nil { + return err + } + c.waveOut = 0 + return nil +} + +func (c *winmmContext) appendBuffers() { + c.cond.L.Lock() + defer c.cond.L.Unlock() + + if c.err.Load() != nil { + return + } + + c.mux.ReadFloat32s(c.buf32) + + for _, h := range c.headers { + if h.IsQueued() { + continue + } + + if err := h.Write(c.buf32); err != nil { + switch { + case errors.Is(err, _MMSYSERR_NOMEM): + continue + case errors.Is(err, _MMSYSERR_NODRIVER): + sleep := time.Duration(float64(time.Second) * float64(len(c.buf32)) / float64(c.channelCount) / float64(c.sampleRate)) + time.Sleep(sleep) + return + case errors.Is(err, windows.ERROR_NOT_FOUND): + // This error can happen when e.g. a new HDMI connection is detected (#51). + // TODO: Retry later. + } + c.err.TryStore(fmt.Errorf("oto: Queueing the header failed: %v", err)) + } + return + } +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/mux/mux.go b/vendor/github.com/ebitengine/oto/v3/internal/mux/mux.go new file mode 100644 index 0000000..28df789 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/mux/mux.go @@ -0,0 +1,560 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package mux offers APIs for a low-level multiplexer of audio players. +// Usually you don't have to use this. +package mux + +import ( + "errors" + "fmt" + "io" + "math" + "runtime" + "sync" + "time" +) + +// Format must sync with oto's Format. +type Format int + +const ( + FormatFloat32LE Format = iota + FormatUnsignedInt8 + FormatSignedInt16LE +) + +func (f Format) ByteLength() int { + switch f { + case FormatFloat32LE: + return 4 + case FormatUnsignedInt8: + return 1 + case FormatSignedInt16LE: + return 2 + } + panic(fmt.Sprintf("mux: unexpected format: %d", f)) +} + +// Mux is a low-level multiplexer of audio players. +type Mux struct { + sampleRate int + channelCount int + format Format + + players map[*playerImpl]struct{} + buf []float32 + cond *sync.Cond +} + +// New creates a new Mux. +func New(sampleRate int, channelCount int, format Format) *Mux { + m := &Mux{ + sampleRate: sampleRate, + channelCount: channelCount, + format: format, + cond: sync.NewCond(&sync.Mutex{}), + } + go m.loop() + return m +} + +func (m *Mux) shouldWait() bool { + for p := range m.players { + if p.canReadSourceToBuffer() { + return false + } + } + return true +} + +func (m *Mux) wait() { + m.cond.L.Lock() + defer m.cond.L.Unlock() + + for m.shouldWait() { + m.cond.Wait() + } +} + +func (m *Mux) loop() { + var players []*playerImpl + for { + m.wait() + + m.cond.L.Lock() + for i := range players { + players[i] = nil + } + players = players[:0] + for p := range m.players { + players = append(players, p) + } + m.cond.L.Unlock() + + allZero := true + for _, p := range players { + n := p.readSourceToBuffer() + if n != 0 { + allZero = false + } + } + + // Sleeping is necessary especially on browsers. + // Sometimes a player continues to read 0 bytes from the source and this loop can be a busy loop in such case. + if allZero { + time.Sleep(time.Millisecond) + } + } +} + +func (m *Mux) addPlayer(player *playerImpl) { + m.cond.L.Lock() + defer m.cond.L.Unlock() + + if m.players == nil { + m.players = map[*playerImpl]struct{}{} + } + m.players[player] = struct{}{} + m.cond.Signal() +} + +func (m *Mux) removePlayer(player *playerImpl) { + m.cond.L.Lock() + defer m.cond.L.Unlock() + + delete(m.players, player) + m.cond.Signal() +} + +// ReadFloat32s fills buf with the multiplexed data of the players as float32 values. +func (m *Mux) ReadFloat32s(buf []float32) { + m.cond.L.Lock() + players := make([]*playerImpl, 0, len(m.players)) + for p := range m.players { + players = append(players, p) + } + m.cond.L.Unlock() + + for i := range buf { + buf[i] = 0 + } + for _, p := range players { + p.readBufferAndAdd(buf) + } + m.cond.Signal() +} + +type Player struct { + p *playerImpl +} + +type playerState int + +const ( + playerPaused playerState = iota + playerPlay + playerClosed +) + +type playerImpl struct { + mux *Mux + src io.Reader + prevVolume float64 + volume float64 + err error + state playerState + tmpbuf []byte + buf []byte + eof bool + bufferSize int + + m sync.Mutex +} + +func (m *Mux) NewPlayer(src io.Reader) *Player { + pl := &Player{ + p: &playerImpl{ + mux: m, + src: src, + prevVolume: 1, + volume: 1, + bufferSize: m.defaultBufferSize(), + }, + } + runtime.SetFinalizer(pl, (*Player).Close) + return pl +} + +func (p *Player) Err() error { + return p.p.Err() +} + +func (p *playerImpl) Err() error { + p.m.Lock() + defer p.m.Unlock() + + return p.err +} + +func (p *Player) Play() { + p.p.Play() +} + +func (p *playerImpl) Play() { + // Goroutines don't work effiently on Windows. Avoid using them (hajimehoshi/ebiten#1768). + if runtime.GOOS == "windows" { + p.m.Lock() + defer p.m.Unlock() + + p.playImpl() + } else { + ch := make(chan struct{}) + go func() { + p.m.Lock() + defer p.m.Unlock() + + close(ch) + p.playImpl() + }() + <-ch + } +} + +func (p *Player) SetBufferSize(bufferSize int) { + p.p.setBufferSize(bufferSize) +} + +func (p *playerImpl) setBufferSize(bufferSize int) { + p.m.Lock() + defer p.m.Unlock() + + orig := p.bufferSize + p.bufferSize = bufferSize + if bufferSize == 0 { + p.bufferSize = p.mux.defaultBufferSize() + } + if orig != p.bufferSize { + p.tmpbuf = nil + } +} + +func (p *playerImpl) ensureTmpBuf() []byte { + if p.tmpbuf == nil { + p.tmpbuf = make([]byte, p.bufferSize) + } + return p.tmpbuf +} + +// read reads the source to buf. +// read unlocks the mutex temporarily and locks when reading finishes. +// This avoids locking during an external function call Read (#188). +// +// When read is called, the mutex m must be locked. +func (p *playerImpl) read(buf []byte) (int, error) { + p.m.Unlock() + defer p.m.Lock() + return p.src.Read(buf) +} + +// addToPlayers adds p to the players set. +// +// When addToPlayers is called, the mutex m must be locked. +func (p *playerImpl) addToPlayers() { + p.m.Unlock() + defer p.m.Lock() + p.mux.addPlayer(p) +} + +// removeFromPlayers removes p from the players set. +// +// When removeFromPlayers is called, the mutex m must be locked. +func (p *playerImpl) removeFromPlayers() { + p.m.Unlock() + defer p.m.Lock() + p.mux.removePlayer(p) +} + +func (p *playerImpl) playImpl() { + if p.err != nil { + return + } + if p.state != playerPaused { + return + } + p.state = playerPlay + + if !p.eof { + buf := p.ensureTmpBuf() + for len(p.buf) < p.bufferSize { + n, err := p.read(buf) + if err != nil && err != io.EOF { + p.setErrorImpl(err) + return + } + p.buf = append(p.buf, buf[:n]...) + if err == io.EOF { + p.eof = true + break + } + } + } + + if p.eof && len(p.buf) == 0 { + p.state = playerPaused + } + + p.addToPlayers() +} + +func (p *Player) Pause() { + p.p.Pause() +} + +func (p *playerImpl) Pause() { + p.m.Lock() + defer p.m.Unlock() + + if p.state != playerPlay { + return + } + p.state = playerPaused +} + +func (p *Player) Seek(offset int64, whence int) (int64, error) { + return p.p.Seek(offset, whence) +} + +func (p *playerImpl) Seek(offset int64, whence int) (int64, error) { + p.m.Lock() + defer p.m.Unlock() + + // If a player is playing, keep playing even after this seeking. + if p.state == playerPlay { + defer p.playImpl() + } + + // Reset the internal buffer. + p.resetImpl() + + // Check if the source implements io.Seeker. + s, ok := p.src.(io.Seeker) + if !ok { + return 0, errors.New("mux: the source must implement io.Seeker") + } + return s.Seek(offset, whence) +} + +func (p *Player) Reset() { + p.p.Reset() +} + +func (p *playerImpl) Reset() { + p.m.Lock() + defer p.m.Unlock() + p.resetImpl() +} + +func (p *playerImpl) resetImpl() { + if p.state == playerClosed { + return + } + p.state = playerPaused + p.buf = p.buf[:0] + p.eof = false +} + +func (p *Player) IsPlaying() bool { + return p.p.IsPlaying() +} + +func (p *playerImpl) IsPlaying() bool { + p.m.Lock() + defer p.m.Unlock() + return p.state == playerPlay +} + +func (p *Player) Volume() float64 { + return p.p.Volume() +} + +func (p *playerImpl) Volume() float64 { + p.m.Lock() + defer p.m.Unlock() + return p.volume +} + +func (p *Player) SetVolume(volume float64) { + p.p.SetVolume(volume) +} + +func (p *playerImpl) SetVolume(volume float64) { + p.m.Lock() + defer p.m.Unlock() + p.volume = volume + if p.state != playerPlay { + p.prevVolume = volume + } +} + +func (p *Player) BufferedSize() int { + return p.p.BufferedSize() +} + +func (p *playerImpl) BufferedSize() int { + p.m.Lock() + defer p.m.Unlock() + return len(p.buf) +} + +func (p *Player) Close() error { + runtime.SetFinalizer(p, nil) + return p.p.Close() +} + +func (p *playerImpl) Close() error { + p.m.Lock() + defer p.m.Unlock() + return p.closeImpl() +} + +func (p *playerImpl) closeImpl() error { + p.removeFromPlayers() + + if p.state == playerClosed { + return p.err + } + p.state = playerClosed + p.buf = nil + return p.err +} + +func (p *playerImpl) readBufferAndAdd(buf []float32) int { + p.m.Lock() + defer p.m.Unlock() + + if p.state != playerPlay { + return 0 + } + + format := p.mux.format + bitDepthInBytes := format.ByteLength() + n := len(p.buf) / bitDepthInBytes + if n > len(buf) { + n = len(buf) + } + + prevVolume := float32(p.prevVolume) + volume := float32(p.volume) + + channelCount := p.mux.channelCount + rateDenom := float32(n / channelCount) + + src := p.buf[:n*bitDepthInBytes] + + for i := 0; i < n; i++ { + var v float32 + switch format { + case FormatFloat32LE: + v = math.Float32frombits(uint32(src[4*i]) | uint32(src[4*i+1])<<8 | uint32(src[4*i+2])<<16 | uint32(src[4*i+3])<<24) + case FormatUnsignedInt8: + v8 := src[i] + v = float32(v8-(1<<7)) / (1 << 7) + case FormatSignedInt16LE: + v16 := int16(src[2*i]) | (int16(src[2*i+1]) << 8) + v = float32(v16) / (1 << 15) + default: + panic(fmt.Sprintf("mux: unexpected format: %d", format)) + } + if volume == prevVolume { + buf[i] += v * volume + } else { + rate := float32(i/channelCount) / rateDenom + if rate > 1 { + rate = 1 + } + buf[i] += v * (volume*rate + prevVolume*(1-rate)) + } + } + + p.prevVolume = p.volume + + copy(p.buf, p.buf[n*bitDepthInBytes:]) + p.buf = p.buf[:len(p.buf)-n*bitDepthInBytes] + + if p.eof && len(p.buf) == 0 { + p.state = playerPaused + } + + return n +} + +func (p *playerImpl) canReadSourceToBuffer() bool { + p.m.Lock() + defer p.m.Unlock() + + if p.eof { + return false + } + return len(p.buf) < p.bufferSize +} + +func (p *playerImpl) readSourceToBuffer() int { + p.m.Lock() + defer p.m.Unlock() + + if p.err != nil { + return 0 + } + if p.state == playerClosed { + return 0 + } + + if len(p.buf) >= p.bufferSize { + return 0 + } + + buf := p.ensureTmpBuf() + n, err := p.read(buf) + + if err != nil && err != io.EOF { + p.setErrorImpl(err) + return 0 + } + + p.buf = append(p.buf, buf[:n]...) + if err == io.EOF { + p.eof = true + if len(p.buf) == 0 { + p.state = playerPaused + } + } + return n +} + +func (p *playerImpl) setErrorImpl(err error) { + p.err = err + p.closeImpl() +} + +// TODO: The term 'buffer' is confusing. Name each buffer with good terms. + +// defaultBufferSize returns the default size of the buffer for the audio source. +// This buffer is used when unreading on pausing the player. +func (m *Mux) defaultBufferSize() int { + bytesPerSample := m.channelCount * m.format.ByteLength() + s := m.sampleRate * bytesPerSample / 2 // 0.5[s] + // Align s in multiples of bytes per sample, or a buffer could have extra bytes. + return s / bytesPerSample * bytesPerSample +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/.clang-format b/vendor/github.com/ebitengine/oto/v3/internal/oboe/.clang-format new file mode 100644 index 0000000..9d15924 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: false diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/LICENSE-oboe b/vendor/github.com/ebitengine/oto/v3/internal/oboe/LICENSE-oboe new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/LICENSE-oboe @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/README-oboe.md b/vendor/github.com/ebitengine/oto/v3/internal/oboe/README-oboe.md new file mode 100644 index 0000000..44acf9f --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/README-oboe.md @@ -0,0 +1,54 @@ +# Oboe [![Build CI](https://github.com/google/oboe/workflows/Build%20CI/badge.svg)](https://github.com/google/oboe/actions) + +[![Introduction to Oboe video](docs/images/getting-started-video.jpg)](https://www.youtube.com/watch?v=csfHAbr5ilI&list=PLWz5rJ2EKKc_duWv9IPNvx9YBudNMmLSa) + +Oboe is a C++ library which makes it easy to build high-performance audio apps on Android. It was created primarily to allow developers to target a simplified API that works across multiple API levels back to API level 16 (Jelly Bean). + +## Features +- Compatible with API 16 onwards - runs on 99% of Android devices +- Chooses the audio API (OpenSL ES on API 16+ or AAudio on API 27+) which will give the best audio performance on the target Android device +- Automatic latency tuning +- Modern C++ allowing you to write clean, elegant code +- Workarounds for some known issues +- [Used by popular apps and frameworks](https://github.com/google/oboe/wiki/AppsUsingOboe) + +## Documentation +- [Getting Started Guide](docs/GettingStarted.md) +- [Full Guide to Oboe](docs/FullGuide.md) +- [API reference](https://google.github.io/oboe) +- [Tech Notes](docs/notes/) +- [History of Audio features/bugs by Android version](docs/AndroidAudioHistory.md) +- [Migration guide for apps using OpenSL ES](docs/OpenSLESMigration.md) +- [Frequently Asked Questions](docs/FAQ.md) (FAQ) +- [Our roadmap](https://github.com/google/oboe/milestones) - Vote on a feature/issue by adding a thumbs up to the first comment. + +### Community +- Reddit: [r/androidaudiodev](https://www.reddit.com/r/androidaudiodev/) +- StackOverflow: [#oboe](https://stackoverflow.com/questions/tagged/oboe) + +## Testing +- [**OboeTester** app for measuring latency, glitches, etc.](apps/OboeTester/docs) +- [Oboe unit tests](tests) + +## Videos +- [Getting started with Oboe](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_duWv9IPNvx9YBudNMmLSa) +- [Low Latency Audio - Because Your Ears Are Worth It](https://www.youtube.com/watch?v=8vOf_fDtur4) (Android Dev Summit '18) +- [Winning on Android](https://www.youtube.com/watch?v=tWBojmBpS74) - How to optimize an Android audio app. (ADC '18) + +## Sample code and apps +- Sample apps can be found in the [samples directory](samples). +- A complete "effects processor" app called FXLab can be found in the [apps/fxlab folder](apps/fxlab). +- Also check out the [Rhythm Game codelab](https://developer.android.com/codelabs/musicalgame-using-oboe?hl=en#0). + +### Third party sample code +- [Ableton Link integration demo](https://github.com/jbloit/AndroidLinkAudio) (author: jbloit) + +## Contributing +We would love to receive your pull requests. Before we can though, please read the [contributing](CONTRIBUTING.md) guidelines. + +## Version history +View the [releases page](../../releases). + +## License +[LICENSE](LICENSE) + diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/binding_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/binding_android.cpp new file mode 100644 index 0000000..48b9699 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/binding_android.cpp @@ -0,0 +1,191 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "binding_android.h" + +#include "_cgo_export.h" +#include "oboe_oboe_Oboe_android.h" + +#include +#include +#include +#include +#include + +namespace { + +class Player; + +class Stream : public oboe::AudioStreamDataCallback { +public: + // GetInstance returns the instance of Stream. Only one Stream object is used + // in one process. It is because multiple streams can be problematic in both + // AAudio and OpenSL (#1656, #1660). + static Stream &GetInstance(); + + const char *Play(int sample_rate, int channel_num, int buffer_size_in_bytes); + const char *Pause(); + const char *Resume(); + const char *Close(); + const char *AppendBuffer(float *buf, size_t len); + + oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboe_stream, + void *audio_data, + int32_t num_frames) override; + +private: + Stream(); + void Loop(int num_frames); + + int sample_rate_ = 0; + int channel_num_ = 0; + + std::shared_ptr stream_; + + // All the member variables other than the thread must be initialized before + // the thread. + std::vector buf_; + std::mutex mutex_; + std::condition_variable cond_; + std::unique_ptr thread_; +}; + +Stream &Stream::GetInstance() { + static Stream *stream = new Stream(); + return *stream; +} + +const char *Stream::Play(int sample_rate, int channel_num, + int buffer_size_in_bytes) { + sample_rate_ = sample_rate; + channel_num_ = channel_num; + + if (!stream_) { + oboe::AudioStreamBuilder builder; + builder.setDirection(oboe::Direction::Output) + ->setPerformanceMode(oboe::PerformanceMode::LowLatency) + ->setSharingMode(oboe::SharingMode::Shared) + ->setFormat(oboe::AudioFormat::Float) + ->setChannelCount(channel_num_) + ->setSampleRate(sample_rate_) + ->setDataCallback(this); + if (buffer_size_in_bytes) { + int buffer_size_in_frames = buffer_size_in_bytes / channel_num / 4; + builder.setBufferCapacityInFrames(buffer_size_in_frames); + } + oboe::Result result = builder.openStream(stream_); + if (result != oboe::Result::OK) { + return oboe::convertToText(result); + } + } + if (stream_->getSharingMode() != oboe::SharingMode::Shared) { + return "oboe::SharingMode::Shared is not available"; + } + + int num_frames = stream_->getBufferSizeInFrames(); + thread_ = + std::make_unique([this, num_frames]() { Loop(num_frames); }); + + // What if the buffer size is not enough? + if (oboe::Result result = stream_->start(); result != oboe::Result::OK) { + return oboe::convertToText(result); + } + return nullptr; +} + +const char *Stream::Pause() { + if (!stream_) { + return nullptr; + } + if (oboe::Result result = stream_->pause(); result != oboe::Result::OK) { + return oboe::convertToText(result); + } + return nullptr; +} + +const char *Stream::Resume() { + if (!stream_) { + return "Play is not called yet at Resume"; + } + if (oboe::Result result = stream_->start(); result != oboe::Result::OK) { + return oboe::convertToText(result); + } + return nullptr; +} + +const char *Stream::Close() { + // Nobody calls this so far. + if (!stream_) { + return nullptr; + } + if (oboe::Result result = stream_->stop(); result != oboe::Result::OK) { + return oboe::convertToText(result); + } + if (oboe::Result result = stream_->close(); result != oboe::Result::OK) { + return oboe::convertToText(result); + } + stream_.reset(); + return nullptr; +} + +oboe::DataCallbackResult Stream::onAudioReady(oboe::AudioStream *oboe_stream, + void *audio_data, + int32_t num_frames) { + size_t num = num_frames * channel_num_; + // TODO: Do not use a lock in onAudioReady. + // https://google.github.io/oboe/reference/classoboe_1_1_audio_stream_data_callback.html#ad8a3a9f609df5fd3a5d885cbe1b2204d + { + std::unique_lock lock{mutex_}; + cond_.wait(lock, [this, num] { return buf_.size() >= num; }); + std::copy(buf_.begin(), buf_.begin() + num, + reinterpret_cast(audio_data)); + buf_.erase(buf_.begin(), buf_.begin() + num); + cond_.notify_one(); + } + return oboe::DataCallbackResult::Continue; +} + +Stream::Stream() = default; + +void Stream::Loop(int num_frames) { + std::vector tmp(num_frames * channel_num_ * 3); + for (;;) { + { + std::unique_lock lock{mutex_}; + cond_.wait(lock, [this, &tmp] { return buf_.size() < tmp.size(); }); + } + oto_oboe_read(&tmp[0], tmp.size()); + { + std::lock_guard lock{mutex_}; + buf_.insert(buf_.end(), tmp.begin(), tmp.end()); + cond_.notify_one(); + } + } +} + +} // namespace + +extern "C" { + +const char *oto_oboe_Play(int sample_rate, int channel_num, + int buffer_size_in_bytes) { + return Stream::GetInstance().Play(sample_rate, channel_num, + buffer_size_in_bytes); +} + +const char *oto_oboe_Suspend() { return Stream::GetInstance().Pause(); } + +const char *oto_oboe_Resume() { return Stream::GetInstance().Resume(); } + +} // extern "C" diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/binding_android.go b/vendor/github.com/ebitengine/oto/v3/internal/oboe/binding_android.go new file mode 100644 index 0000000..0fceb71 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/binding_android.go @@ -0,0 +1,60 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oboe + +// Disable AAudio (hajimehoshi/ebiten#1634). +// AAudio doesn't care about plugging in/out of a headphone. +// See https://github.com/google/oboe/wiki/TechNote_Disconnect + +// #cgo CXXFLAGS: -std=c++17 -DOBOE_ENABLE_AAUDIO=0 +// #cgo LDFLAGS: -llog -lOpenSLES -static-libstdc++ +// +// #include "binding_android.h" +import "C" + +import ( + "fmt" + "unsafe" +) + +var theReadFunc func(buf []float32) + +func Play(sampleRate int, channelCount int, readFunc func(buf []float32), bufferSizeInBytes int) error { + // Play can invoke the callback. Set the callback before Play. + theReadFunc = readFunc + if msg := C.oto_oboe_Play(C.int(sampleRate), C.int(channelCount), C.int(bufferSizeInBytes)); msg != nil { + return fmt.Errorf("oboe: Play failed: %s", C.GoString(msg)) + } + return nil +} + +func Suspend() error { + if msg := C.oto_oboe_Suspend(); msg != nil { + return fmt.Errorf("oboe: Suspend failed: %s", C.GoString(msg)) + } + return nil +} + +func Resume() error { + if msg := C.oto_oboe_Resume(); msg != nil { + return fmt.Errorf("oboe: Resume failed: %s", C.GoString(msg)) + } + return nil +} + +//export oto_oboe_read +func oto_oboe_read(buf *C.float, len C.size_t) { + theReadFunc(unsafe.Slice((*float32)(unsafe.Pointer(buf)), len)) +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/binding_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/binding_android.h new file mode 100644 index 0000000..82c5c07 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/binding_android.h @@ -0,0 +1,37 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OBOE_ANDROID_H_ +#define OBOE_ANDROID_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uintptr_t PlayerID; + +const char *oto_oboe_Play(int sample_rate, int channel_num, + int buffer_size_in_bytes); +const char *oto_oboe_Suspend(); +const char *oto_oboe_Resume(); + +#ifdef __cplusplus +} +#endif + +#endif // OBOE_ANDROID_H_ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/generate.go b/vendor/github.com/ebitengine/oto/v3/internal/oboe/generate.go new file mode 100644 index 0000000..0f6dbe1 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/generate.go @@ -0,0 +1,17 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:generate go run gen.go + +package oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AAudioExtensions_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AAudioExtensions_android.h new file mode 100644 index 0000000..3bbccf9 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AAudioExtensions_android.h @@ -0,0 +1,179 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_AAUDIO_EXTENSIONS_H +#define OBOE_AAUDIO_EXTENSIONS_H + +#include +#include + +#include + +#include "oboe_common_OboeDebug_android.h" +#include "oboe_oboe_Oboe_android.h" +#include "oboe_aaudio_AAudioLoader_android.h" + +namespace oboe { + +#define LIB_AAUDIO_NAME "libaaudio.so" +#define FUNCTION_IS_MMAP "AAudioStream_isMMapUsed" +#define FUNCTION_SET_MMAP_POLICY "AAudio_setMMapPolicy" +#define FUNCTION_GET_MMAP_POLICY "AAudio_getMMapPolicy" + +#define AAUDIO_ERROR_UNAVAILABLE static_cast(Result::ErrorUnavailable) + +typedef struct AAudioStreamStruct AAudioStream; + +/** + * Call some AAudio test routines that are not part of the normal API. + */ +class AAudioExtensions { +public: + AAudioExtensions() { + int32_t policy = getIntegerProperty("aaudio.mmap_policy", 0); + mMMapSupported = isPolicyEnabled(policy); + + policy = getIntegerProperty("aaudio.mmap_exclusive_policy", 0); + mMMapExclusiveSupported = isPolicyEnabled(policy); + } + + static bool isPolicyEnabled(int32_t policy) { + return (policy == AAUDIO_POLICY_AUTO || policy == AAUDIO_POLICY_ALWAYS); + } + + static AAudioExtensions &getInstance() { + static AAudioExtensions instance; + return instance; + } + + bool isMMapUsed(oboe::AudioStream *oboeStream) { + AAudioStream *aaudioStream = (AAudioStream *) oboeStream->getUnderlyingStream(); + return isMMapUsed(aaudioStream); + } + + bool isMMapUsed(AAudioStream *aaudioStream) { + if (loadSymbols()) return false; + if (mAAudioStream_isMMap == nullptr) return false; + return mAAudioStream_isMMap(aaudioStream); + } + + /** + * Controls whether the MMAP data path can be selected when opening a stream. + * It has no effect after the stream has been opened. + * It only affects the application that calls it. Other apps are not affected. + * + * @param enabled + * @return 0 or a negative error code + */ + int32_t setMMapEnabled(bool enabled) { + if (loadSymbols()) return AAUDIO_ERROR_UNAVAILABLE; + if (mAAudio_setMMapPolicy == nullptr) return false; + return mAAudio_setMMapPolicy(enabled ? AAUDIO_POLICY_AUTO : AAUDIO_POLICY_NEVER); + } + + bool isMMapEnabled() { + if (loadSymbols()) return false; + if (mAAudio_getMMapPolicy == nullptr) return false; + int32_t policy = mAAudio_getMMapPolicy(); + return isPolicyEnabled(policy); + } + + bool isMMapSupported() { + return mMMapSupported; + } + + bool isMMapExclusiveSupported() { + return mMMapExclusiveSupported; + } + +private: + + enum { + AAUDIO_POLICY_NEVER = 1, + AAUDIO_POLICY_AUTO, + AAUDIO_POLICY_ALWAYS + }; + typedef int32_t aaudio_policy_t; + + int getIntegerProperty(const char *name, int defaultValue) { + int result = defaultValue; + char valueText[PROP_VALUE_MAX] = {0}; + if (__system_property_get(name, valueText) != 0) { + result = atoi(valueText); + } + return result; + } + + /** + * Load the function pointers. + * This can be called multiple times. + * It should only be called from one thread. + * + * @return 0 if successful or negative error. + */ + aaudio_result_t loadSymbols() { + if (mAAudio_getMMapPolicy != nullptr) { + return 0; + } + + AAudioLoader *libLoader = AAudioLoader::getInstance(); + int openResult = libLoader->open(); + if (openResult != 0) { + LOGD("%s() could not open " LIB_AAUDIO_NAME, __func__); + return AAUDIO_ERROR_UNAVAILABLE; + } + + void *libHandle = AAudioLoader::getInstance()->getLibHandle(); + if (libHandle == nullptr) { + LOGE("%s() could not find " LIB_AAUDIO_NAME, __func__); + return AAUDIO_ERROR_UNAVAILABLE; + } + + mAAudioStream_isMMap = (bool (*)(AAudioStream *stream)) + dlsym(libHandle, FUNCTION_IS_MMAP); + if (mAAudioStream_isMMap == nullptr) { + LOGI("%s() could not find " FUNCTION_IS_MMAP, __func__); + return AAUDIO_ERROR_UNAVAILABLE; + } + + mAAudio_setMMapPolicy = (int32_t (*)(aaudio_policy_t policy)) + dlsym(libHandle, FUNCTION_SET_MMAP_POLICY); + if (mAAudio_setMMapPolicy == nullptr) { + LOGI("%s() could not find " FUNCTION_SET_MMAP_POLICY, __func__); + return AAUDIO_ERROR_UNAVAILABLE; + } + + mAAudio_getMMapPolicy = (aaudio_policy_t (*)()) + dlsym(libHandle, FUNCTION_GET_MMAP_POLICY); + if (mAAudio_getMMapPolicy == nullptr) { + LOGI("%s() could not find " FUNCTION_GET_MMAP_POLICY, __func__); + return AAUDIO_ERROR_UNAVAILABLE; + } + + return 0; + } + + bool mMMapSupported = false; + bool mMMapExclusiveSupported = false; + + bool (*mAAudioStream_isMMap)(AAudioStream *stream) = nullptr; + int32_t (*mAAudio_setMMapPolicy)(aaudio_policy_t policy) = nullptr; + aaudio_policy_t (*mAAudio_getMMapPolicy)() = nullptr; +}; + +} // namespace oboe + +#endif //OBOE_AAUDIO_EXTENSIONS_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AAudioLoader_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AAudioLoader_android.cpp new file mode 100644 index 0000000..ae0f45f --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AAudioLoader_android.cpp @@ -0,0 +1,450 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "oboe_oboe_Utilities_android.h" +#include "oboe_common_OboeDebug_android.h" +#include "oboe_aaudio_AAudioLoader_android.h" + +#define LIB_AAUDIO_NAME "libaaudio.so" + +namespace oboe { + +AAudioLoader::~AAudioLoader() { + // Issue 360: thread_local variables with non-trivial destructors + // will cause segfaults if the containing library is dlclose()ed on + // devices running M or newer, or devices before M when using a static STL. + // The simple workaround is to not call dlclose. + // https://github.com/android/ndk/wiki/Changelog-r22#known-issues + // + // The libaaudio and libaaudioclient do not use thread_local. + // But, to be safe, we should avoid dlclose() if possible. + // Because AAudioLoader is a static Singleton, we can safely skip + // calling dlclose() without causing a resource leak. + LOGI("%s() dlclose(%s) not called, OK", __func__, LIB_AAUDIO_NAME); +} + +AAudioLoader* AAudioLoader::getInstance() { + static AAudioLoader instance; + return &instance; +} + +int AAudioLoader::open() { + if (mLibHandle != nullptr) { + return 0; + } + + // Use RTLD_NOW to avoid the unpredictable behavior that RTLD_LAZY can cause. + // Also resolving all the links now will prevent a run-time penalty later. + mLibHandle = dlopen(LIB_AAUDIO_NAME, RTLD_NOW); + if (mLibHandle == nullptr) { + LOGI("AAudioLoader::open() could not find " LIB_AAUDIO_NAME); + return -1; // TODO review return code + } else { + LOGD("AAudioLoader(): dlopen(%s) returned %p", LIB_AAUDIO_NAME, mLibHandle); + } + + // Load all the function pointers. + createStreamBuilder = load_I_PPB("AAudio_createStreamBuilder"); + builder_openStream = load_I_PBPPS("AAudioStreamBuilder_openStream"); + + builder_setChannelCount = load_V_PBI("AAudioStreamBuilder_setChannelCount"); + if (builder_setChannelCount == nullptr) { + // Use old deprecated alias if needed. + builder_setChannelCount = load_V_PBI("AAudioStreamBuilder_setSamplesPerFrame"); + } + + builder_setBufferCapacityInFrames = load_V_PBI("AAudioStreamBuilder_setBufferCapacityInFrames"); + builder_setDeviceId = load_V_PBI("AAudioStreamBuilder_setDeviceId"); + builder_setDirection = load_V_PBI("AAudioStreamBuilder_setDirection"); + builder_setFormat = load_V_PBI("AAudioStreamBuilder_setFormat"); + builder_setFramesPerDataCallback = load_V_PBI("AAudioStreamBuilder_setFramesPerDataCallback"); + builder_setSharingMode = load_V_PBI("AAudioStreamBuilder_setSharingMode"); + builder_setPerformanceMode = load_V_PBI("AAudioStreamBuilder_setPerformanceMode"); + builder_setSampleRate = load_V_PBI("AAudioStreamBuilder_setSampleRate"); + + if (getSdkVersion() >= __ANDROID_API_P__){ + builder_setUsage = load_V_PBI("AAudioStreamBuilder_setUsage"); + builder_setContentType = load_V_PBI("AAudioStreamBuilder_setContentType"); + builder_setInputPreset = load_V_PBI("AAudioStreamBuilder_setInputPreset"); + builder_setSessionId = load_V_PBI("AAudioStreamBuilder_setSessionId"); + } + + if (getSdkVersion() >= __ANDROID_API_S__){ + builder_setPackageName = load_V_PBCPH("AAudioStreamBuilder_setPackageName"); + builder_setAttributionTag = load_V_PBCPH("AAudioStreamBuilder_setAttributionTag"); + } + + if (getSdkVersion() >= __ANDROID_API_S_V2__) { + builder_setChannelMask = load_V_PBU("AAudioStreamBuilder_setChannelMask"); + } + + builder_delete = load_I_PB("AAudioStreamBuilder_delete"); + + + builder_setDataCallback = load_V_PBPDPV("AAudioStreamBuilder_setDataCallback"); + builder_setErrorCallback = load_V_PBPEPV("AAudioStreamBuilder_setErrorCallback"); + + stream_read = load_I_PSPVIL("AAudioStream_read"); + + stream_write = load_I_PSCPVIL("AAudioStream_write"); + + stream_waitForStateChange = load_I_PSTPTL("AAudioStream_waitForStateChange"); + + stream_getTimestamp = load_I_PSKPLPL("AAudioStream_getTimestamp"); + + stream_getChannelCount = load_I_PS("AAudioStream_getChannelCount"); + if (stream_getChannelCount == nullptr) { + // Use old alias if needed. + stream_getChannelCount = load_I_PS("AAudioStream_getSamplesPerFrame"); + } + + stream_close = load_I_PS("AAudioStream_close"); + + stream_getBufferSize = load_I_PS("AAudioStream_getBufferSizeInFrames"); + stream_getDeviceId = load_I_PS("AAudioStream_getDeviceId"); + stream_getBufferCapacity = load_I_PS("AAudioStream_getBufferCapacityInFrames"); + stream_getFormat = load_F_PS("AAudioStream_getFormat"); + stream_getFramesPerBurst = load_I_PS("AAudioStream_getFramesPerBurst"); + stream_getFramesRead = load_L_PS("AAudioStream_getFramesRead"); + stream_getFramesWritten = load_L_PS("AAudioStream_getFramesWritten"); + stream_getPerformanceMode = load_I_PS("AAudioStream_getPerformanceMode"); + stream_getSampleRate = load_I_PS("AAudioStream_getSampleRate"); + stream_getSharingMode = load_I_PS("AAudioStream_getSharingMode"); + stream_getState = load_I_PS("AAudioStream_getState"); + stream_getXRunCount = load_I_PS("AAudioStream_getXRunCount"); + + stream_requestStart = load_I_PS("AAudioStream_requestStart"); + stream_requestPause = load_I_PS("AAudioStream_requestPause"); + stream_requestFlush = load_I_PS("AAudioStream_requestFlush"); + stream_requestStop = load_I_PS("AAudioStream_requestStop"); + + stream_setBufferSize = load_I_PSI("AAudioStream_setBufferSizeInFrames"); + + convertResultToText = load_CPH_I("AAudio_convertResultToText"); + + if (getSdkVersion() >= __ANDROID_API_P__){ + stream_getUsage = load_I_PS("AAudioStream_getUsage"); + stream_getContentType = load_I_PS("AAudioStream_getContentType"); + stream_getInputPreset = load_I_PS("AAudioStream_getInputPreset"); + stream_getSessionId = load_I_PS("AAudioStream_getSessionId"); + } + + if (getSdkVersion() >= __ANDROID_API_S_V2__) { + stream_getChannelMask = load_U_PS("AAudioStream_getChannelMask"); + } + return 0; +} + +static void AAudioLoader_check(void *proc, const char *functionName) { + if (proc == nullptr) { + LOGW("AAudioLoader could not find %s", functionName); + } +} + +AAudioLoader::signature_I_PPB AAudioLoader::load_I_PPB(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_CPH_I AAudioLoader::load_CPH_I(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_V_PBI AAudioLoader::load_V_PBI(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_V_PBCPH AAudioLoader::load_V_PBCPH(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_V_PBPDPV AAudioLoader::load_V_PBPDPV(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_V_PBPEPV AAudioLoader::load_V_PBPEPV(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_I_PSI AAudioLoader::load_I_PSI(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_I_PS AAudioLoader::load_I_PS(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_L_PS AAudioLoader::load_L_PS(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_F_PS AAudioLoader::load_F_PS(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_B_PS AAudioLoader::load_B_PS(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_I_PB AAudioLoader::load_I_PB(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_I_PBPPS AAudioLoader::load_I_PBPPS(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_I_PSCPVIL AAudioLoader::load_I_PSCPVIL(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_I_PSPVIL AAudioLoader::load_I_PSPVIL(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_I_PSTPTL AAudioLoader::load_I_PSTPTL(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_I_PSKPLPL AAudioLoader::load_I_PSKPLPL(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_V_PBU AAudioLoader::load_V_PBU(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_U_PS AAudioLoader::load_U_PS(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +// Ensure that all AAudio primitive data types are int32_t +#define ASSERT_INT32(type) static_assert(std::is_same::value, \ +#type" must be int32_t") + +// Ensure that all AAudio primitive data types are uint32_t +#define ASSERT_UINT32(type) static_assert(std::is_same::value, \ +#type" must be uint32_t") + +#define ERRMSG "Oboe constants must match AAudio constants." + +// These asserts help verify that the Oboe definitions match the equivalent AAudio definitions. +// This code is in this .cpp file so it only gets tested once. +#ifdef AAUDIO_AAUDIO_H + + ASSERT_INT32(aaudio_stream_state_t); + ASSERT_INT32(aaudio_direction_t); + ASSERT_INT32(aaudio_format_t); + ASSERT_INT32(aaudio_data_callback_result_t); + ASSERT_INT32(aaudio_result_t); + ASSERT_INT32(aaudio_sharing_mode_t); + ASSERT_INT32(aaudio_performance_mode_t); + + static_assert((int32_t)StreamState::Uninitialized == AAUDIO_STREAM_STATE_UNINITIALIZED, ERRMSG); + static_assert((int32_t)StreamState::Unknown == AAUDIO_STREAM_STATE_UNKNOWN, ERRMSG); + static_assert((int32_t)StreamState::Open == AAUDIO_STREAM_STATE_OPEN, ERRMSG); + static_assert((int32_t)StreamState::Starting == AAUDIO_STREAM_STATE_STARTING, ERRMSG); + static_assert((int32_t)StreamState::Started == AAUDIO_STREAM_STATE_STARTED, ERRMSG); + static_assert((int32_t)StreamState::Pausing == AAUDIO_STREAM_STATE_PAUSING, ERRMSG); + static_assert((int32_t)StreamState::Paused == AAUDIO_STREAM_STATE_PAUSED, ERRMSG); + static_assert((int32_t)StreamState::Flushing == AAUDIO_STREAM_STATE_FLUSHING, ERRMSG); + static_assert((int32_t)StreamState::Flushed == AAUDIO_STREAM_STATE_FLUSHED, ERRMSG); + static_assert((int32_t)StreamState::Stopping == AAUDIO_STREAM_STATE_STOPPING, ERRMSG); + static_assert((int32_t)StreamState::Stopped == AAUDIO_STREAM_STATE_STOPPED, ERRMSG); + static_assert((int32_t)StreamState::Closing == AAUDIO_STREAM_STATE_CLOSING, ERRMSG); + static_assert((int32_t)StreamState::Closed == AAUDIO_STREAM_STATE_CLOSED, ERRMSG); + static_assert((int32_t)StreamState::Disconnected == AAUDIO_STREAM_STATE_DISCONNECTED, ERRMSG); + + static_assert((int32_t)Direction::Output == AAUDIO_DIRECTION_OUTPUT, ERRMSG); + static_assert((int32_t)Direction::Input == AAUDIO_DIRECTION_INPUT, ERRMSG); + + static_assert((int32_t)AudioFormat::Invalid == AAUDIO_FORMAT_INVALID, ERRMSG); + static_assert((int32_t)AudioFormat::Unspecified == AAUDIO_FORMAT_UNSPECIFIED, ERRMSG); + static_assert((int32_t)AudioFormat::I16 == AAUDIO_FORMAT_PCM_I16, ERRMSG); + static_assert((int32_t)AudioFormat::Float == AAUDIO_FORMAT_PCM_FLOAT, ERRMSG); + + static_assert((int32_t)DataCallbackResult::Continue == AAUDIO_CALLBACK_RESULT_CONTINUE, ERRMSG); + static_assert((int32_t)DataCallbackResult::Stop == AAUDIO_CALLBACK_RESULT_STOP, ERRMSG); + + static_assert((int32_t)Result::OK == AAUDIO_OK, ERRMSG); + static_assert((int32_t)Result::ErrorBase == AAUDIO_ERROR_BASE, ERRMSG); + static_assert((int32_t)Result::ErrorDisconnected == AAUDIO_ERROR_DISCONNECTED, ERRMSG); + static_assert((int32_t)Result::ErrorIllegalArgument == AAUDIO_ERROR_ILLEGAL_ARGUMENT, ERRMSG); + static_assert((int32_t)Result::ErrorInternal == AAUDIO_ERROR_INTERNAL, ERRMSG); + static_assert((int32_t)Result::ErrorInvalidState == AAUDIO_ERROR_INVALID_STATE, ERRMSG); + static_assert((int32_t)Result::ErrorInvalidHandle == AAUDIO_ERROR_INVALID_HANDLE, ERRMSG); + static_assert((int32_t)Result::ErrorUnimplemented == AAUDIO_ERROR_UNIMPLEMENTED, ERRMSG); + static_assert((int32_t)Result::ErrorUnavailable == AAUDIO_ERROR_UNAVAILABLE, ERRMSG); + static_assert((int32_t)Result::ErrorNoFreeHandles == AAUDIO_ERROR_NO_FREE_HANDLES, ERRMSG); + static_assert((int32_t)Result::ErrorNoMemory == AAUDIO_ERROR_NO_MEMORY, ERRMSG); + static_assert((int32_t)Result::ErrorNull == AAUDIO_ERROR_NULL, ERRMSG); + static_assert((int32_t)Result::ErrorTimeout == AAUDIO_ERROR_TIMEOUT, ERRMSG); + static_assert((int32_t)Result::ErrorWouldBlock == AAUDIO_ERROR_WOULD_BLOCK, ERRMSG); + static_assert((int32_t)Result::ErrorInvalidFormat == AAUDIO_ERROR_INVALID_FORMAT, ERRMSG); + static_assert((int32_t)Result::ErrorOutOfRange == AAUDIO_ERROR_OUT_OF_RANGE, ERRMSG); + static_assert((int32_t)Result::ErrorNoService == AAUDIO_ERROR_NO_SERVICE, ERRMSG); + static_assert((int32_t)Result::ErrorInvalidRate == AAUDIO_ERROR_INVALID_RATE, ERRMSG); + + static_assert((int32_t)SharingMode::Exclusive == AAUDIO_SHARING_MODE_EXCLUSIVE, ERRMSG); + static_assert((int32_t)SharingMode::Shared == AAUDIO_SHARING_MODE_SHARED, ERRMSG); + + static_assert((int32_t)PerformanceMode::None == AAUDIO_PERFORMANCE_MODE_NONE, ERRMSG); + static_assert((int32_t)PerformanceMode::PowerSaving + == AAUDIO_PERFORMANCE_MODE_POWER_SAVING, ERRMSG); + static_assert((int32_t)PerformanceMode::LowLatency + == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, ERRMSG); + +// The aaudio_ usage, content and input_preset types were added in NDK 17, +// which is the first version to support Android Pie (API 28). +#if __NDK_MAJOR__ >= 17 + + ASSERT_INT32(aaudio_usage_t); + ASSERT_INT32(aaudio_content_type_t); + ASSERT_INT32(aaudio_input_preset_t); + + static_assert((int32_t)Usage::Media == AAUDIO_USAGE_MEDIA, ERRMSG); + static_assert((int32_t)Usage::VoiceCommunication == AAUDIO_USAGE_VOICE_COMMUNICATION, ERRMSG); + static_assert((int32_t)Usage::VoiceCommunicationSignalling + == AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING, ERRMSG); + static_assert((int32_t)Usage::Alarm == AAUDIO_USAGE_ALARM, ERRMSG); + static_assert((int32_t)Usage::Notification == AAUDIO_USAGE_NOTIFICATION, ERRMSG); + static_assert((int32_t)Usage::NotificationRingtone == AAUDIO_USAGE_NOTIFICATION_RINGTONE, ERRMSG); + static_assert((int32_t)Usage::NotificationEvent == AAUDIO_USAGE_NOTIFICATION_EVENT, ERRMSG); + static_assert((int32_t)Usage::AssistanceAccessibility == AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY, ERRMSG); + static_assert((int32_t)Usage::AssistanceNavigationGuidance + == AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, ERRMSG); + static_assert((int32_t)Usage::AssistanceSonification == AAUDIO_USAGE_ASSISTANCE_SONIFICATION, ERRMSG); + static_assert((int32_t)Usage::Game == AAUDIO_USAGE_GAME, ERRMSG); + static_assert((int32_t)Usage::Assistant == AAUDIO_USAGE_ASSISTANT, ERRMSG); + + static_assert((int32_t)ContentType::Speech == AAUDIO_CONTENT_TYPE_SPEECH, ERRMSG); + static_assert((int32_t)ContentType::Music == AAUDIO_CONTENT_TYPE_MUSIC, ERRMSG); + static_assert((int32_t)ContentType::Movie == AAUDIO_CONTENT_TYPE_MOVIE, ERRMSG); + static_assert((int32_t)ContentType::Sonification == AAUDIO_CONTENT_TYPE_SONIFICATION, ERRMSG); + + static_assert((int32_t)InputPreset::Generic == AAUDIO_INPUT_PRESET_GENERIC, ERRMSG); + static_assert((int32_t)InputPreset::Camcorder == AAUDIO_INPUT_PRESET_CAMCORDER, ERRMSG); + static_assert((int32_t)InputPreset::VoiceRecognition == AAUDIO_INPUT_PRESET_VOICE_RECOGNITION, ERRMSG); + static_assert((int32_t)InputPreset::VoiceCommunication + == AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION, ERRMSG); + static_assert((int32_t)InputPreset::Unprocessed == AAUDIO_INPUT_PRESET_UNPROCESSED, ERRMSG); + + static_assert((int32_t)SessionId::None == AAUDIO_SESSION_ID_NONE, ERRMSG); + static_assert((int32_t)SessionId::Allocate == AAUDIO_SESSION_ID_ALLOCATE, ERRMSG); + +#endif // __NDK_MAJOR__ >= 17 + +// The aaudio channel masks were added in NDK 24, +// which is the first version to support Android SC_V2 (API 32). +#if __NDK_MAJOR__ >= 24 + + ASSERT_UINT32(aaudio_channel_mask_t); + + static_assert((uint32_t)ChannelMask::FrontLeft == AAUDIO_CHANNEL_FRONT_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontRight == AAUDIO_CHANNEL_FRONT_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontCenter == AAUDIO_CHANNEL_FRONT_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::LowFrequency == AAUDIO_CHANNEL_LOW_FREQUENCY, ERRMSG); + static_assert((uint32_t)ChannelMask::BackLeft == AAUDIO_CHANNEL_BACK_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::BackRight == AAUDIO_CHANNEL_BACK_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontLeftOfCenter == AAUDIO_CHANNEL_FRONT_LEFT_OF_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontRightOfCenter == AAUDIO_CHANNEL_FRONT_RIGHT_OF_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::BackCenter == AAUDIO_CHANNEL_BACK_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::SideLeft == AAUDIO_CHANNEL_SIDE_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::SideRight == AAUDIO_CHANNEL_SIDE_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopCenter == AAUDIO_CHANNEL_TOP_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::TopFrontLeft == AAUDIO_CHANNEL_TOP_FRONT_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopFrontCenter == AAUDIO_CHANNEL_TOP_FRONT_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::TopFrontRight == AAUDIO_CHANNEL_TOP_FRONT_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopBackLeft == AAUDIO_CHANNEL_TOP_BACK_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopBackCenter == AAUDIO_CHANNEL_TOP_BACK_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::TopBackRight == AAUDIO_CHANNEL_TOP_BACK_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopSideLeft == AAUDIO_CHANNEL_TOP_SIDE_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopSideRight == AAUDIO_CHANNEL_TOP_SIDE_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::BottomFrontLeft == AAUDIO_CHANNEL_BOTTOM_FRONT_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::BottomFrontCenter == AAUDIO_CHANNEL_BOTTOM_FRONT_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::BottomFrontRight == AAUDIO_CHANNEL_BOTTOM_FRONT_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::LowFrequency2 == AAUDIO_CHANNEL_LOW_FREQUENCY_2, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontWideLeft == AAUDIO_CHANNEL_FRONT_WIDE_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontWideRight == AAUDIO_CHANNEL_FRONT_WIDE_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::Mono == AAUDIO_CHANNEL_MONO, ERRMSG); + static_assert((uint32_t)ChannelMask::Stereo == AAUDIO_CHANNEL_STEREO, ERRMSG); + static_assert((uint32_t)ChannelMask::CM2Point1 == AAUDIO_CHANNEL_2POINT1, ERRMSG); + static_assert((uint32_t)ChannelMask::Tri == AAUDIO_CHANNEL_TRI, ERRMSG); + static_assert((uint32_t)ChannelMask::TriBack == AAUDIO_CHANNEL_TRI_BACK, ERRMSG); + static_assert((uint32_t)ChannelMask::CM3Point1 == AAUDIO_CHANNEL_3POINT1, ERRMSG); + static_assert((uint32_t)ChannelMask::CM2Point0Point2 == AAUDIO_CHANNEL_2POINT0POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::CM2Point1Point2 == AAUDIO_CHANNEL_2POINT1POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::CM3Point0Point2 == AAUDIO_CHANNEL_3POINT0POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::CM3Point1Point2 == AAUDIO_CHANNEL_3POINT1POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::Quad == AAUDIO_CHANNEL_QUAD, ERRMSG); + static_assert((uint32_t)ChannelMask::QuadSide == AAUDIO_CHANNEL_QUAD_SIDE, ERRMSG); + static_assert((uint32_t)ChannelMask::Surround == AAUDIO_CHANNEL_SURROUND, ERRMSG); + static_assert((uint32_t)ChannelMask::Penta == AAUDIO_CHANNEL_PENTA, ERRMSG); + static_assert((uint32_t)ChannelMask::CM5Point1 == AAUDIO_CHANNEL_5POINT1, ERRMSG); + static_assert((uint32_t)ChannelMask::CM5Point1Side == AAUDIO_CHANNEL_5POINT1_SIDE, ERRMSG); + static_assert((uint32_t)ChannelMask::CM6Point1 == AAUDIO_CHANNEL_6POINT1, ERRMSG); + static_assert((uint32_t)ChannelMask::CM7Point1 == AAUDIO_CHANNEL_7POINT1, ERRMSG); + static_assert((uint32_t)ChannelMask::CM5Point1Point2 == AAUDIO_CHANNEL_5POINT1POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::CM5Point1Point4 == AAUDIO_CHANNEL_5POINT1POINT4, ERRMSG); + static_assert((uint32_t)ChannelMask::CM7Point1Point2 == AAUDIO_CHANNEL_7POINT1POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::CM7Point1Point4 == AAUDIO_CHANNEL_7POINT1POINT4, ERRMSG); + static_assert((uint32_t)ChannelMask::CM9Point1Point4 == AAUDIO_CHANNEL_9POINT1POINT4, ERRMSG); + static_assert((uint32_t)ChannelMask::CM9Point1Point6 == AAUDIO_CHANNEL_9POINT1POINT6, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontBack == AAUDIO_CHANNEL_FRONT_BACK, ERRMSG); + +#endif + +#endif // AAUDIO_AAUDIO_H + +} // namespace oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AAudioLoader_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AAudioLoader_android.h new file mode 100644 index 0000000..8f23558 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AAudioLoader_android.h @@ -0,0 +1,263 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_AAUDIO_LOADER_H_ +#define OBOE_AAUDIO_LOADER_H_ + +#include +#include "oboe_oboe_Definitions_android.h" + +// If the NDK is before O then define this in your build +// so that AAudio.h will not be included. +#ifdef OBOE_NO_INCLUDE_AAUDIO + +// Define missing types from AAudio.h +typedef int32_t aaudio_stream_state_t; +typedef int32_t aaudio_direction_t; +typedef int32_t aaudio_format_t; +typedef int32_t aaudio_data_callback_result_t; +typedef int32_t aaudio_result_t; +typedef int32_t aaudio_sharing_mode_t; +typedef int32_t aaudio_performance_mode_t; + +typedef struct AAudioStreamStruct AAudioStream; +typedef struct AAudioStreamBuilderStruct AAudioStreamBuilder; + +typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t numFrames); + +typedef void (*AAudioStream_errorCallback)( + AAudioStream *stream, + void *userData, + aaudio_result_t error); + +// These were defined in P +typedef int32_t aaudio_usage_t; +typedef int32_t aaudio_content_type_t; +typedef int32_t aaudio_input_preset_t; +typedef int32_t aaudio_session_id_t; + +// There are a few definitions used by Oboe. +#define AAUDIO_OK static_cast(Result::OK) +#define AAUDIO_ERROR_TIMEOUT static_cast(Result::ErrorTimeout) +#define AAUDIO_STREAM_STATE_STARTING static_cast(StreamState::Starting) +#define AAUDIO_STREAM_STATE_STARTED static_cast(StreamState::Started) +#else +#include +#endif + +#ifndef __NDK_MAJOR__ +#define __NDK_MAJOR__ 0 +#endif + +#if __NDK_MAJOR__ < 24 +// Defined in SC_V2 +typedef uint32_t aaudio_channel_mask_t; +#endif + +#ifndef __ANDROID_API_S__ +#define __ANDROID_API_S__ 31 +#endif + +#ifndef __ANDROID_API_S_V2__ +#define __ANDROID_API_S_V2__ 32 +#endif + +namespace oboe { + +/** + * The AAudio API was not available in early versions of Android. + * To avoid linker errors, we dynamically link with the functions by name using dlsym(). + * On older versions this linkage will safely fail. + */ +class AAudioLoader { + public: + // Use signatures for common functions. + // Key to letter abbreviations. + // S = Stream + // B = Builder + // I = int32_t + // L = int64_t + // T = sTate + // K = clocKid_t + // P = Pointer to following data type + // C = Const prefix + // H = cHar + // U = uint32_t + typedef int32_t (*signature_I_PPB)(AAudioStreamBuilder **builder); + + typedef const char * (*signature_CPH_I)(int32_t); + + typedef int32_t (*signature_I_PBPPS)(AAudioStreamBuilder *, + AAudioStream **stream); // AAudioStreamBuilder_open() + + typedef int32_t (*signature_I_PB)(AAudioStreamBuilder *); // AAudioStreamBuilder_delete() + // AAudioStreamBuilder_setSampleRate() + typedef void (*signature_V_PBI)(AAudioStreamBuilder *, int32_t); + + // AAudioStreamBuilder_setChannelMask() + typedef void (*signature_V_PBU)(AAudioStreamBuilder *, uint32_t); + + typedef void (*signature_V_PBCPH)(AAudioStreamBuilder *, const char *); + + typedef int32_t (*signature_I_PS)(AAudioStream *); // AAudioStream_getSampleRate() + typedef int64_t (*signature_L_PS)(AAudioStream *); // AAudioStream_getFramesRead() + // AAudioStream_setBufferSizeInFrames() + typedef int32_t (*signature_I_PSI)(AAudioStream *, int32_t); + + typedef void (*signature_V_PBPDPV)(AAudioStreamBuilder *, + AAudioStream_dataCallback, + void *); + + typedef void (*signature_V_PBPEPV)(AAudioStreamBuilder *, + AAudioStream_errorCallback, + void *); + + typedef aaudio_format_t (*signature_F_PS)(AAudioStream *stream); + + typedef int32_t (*signature_I_PSPVIL)(AAudioStream *, void *, int32_t, int64_t); + typedef int32_t (*signature_I_PSCPVIL)(AAudioStream *, const void *, int32_t, int64_t); + + typedef int32_t (*signature_I_PSTPTL)(AAudioStream *, + aaudio_stream_state_t, + aaudio_stream_state_t *, + int64_t); + + typedef int32_t (*signature_I_PSKPLPL)(AAudioStream *, clockid_t, int64_t *, int64_t *); + + typedef bool (*signature_B_PS)(AAudioStream *); + + typedef uint32_t (*signature_U_PS)(AAudioStream *); + + static AAudioLoader* getInstance(); // singleton + + /** + * Open the AAudio shared library and load the function pointers. + * This can be called multiple times. + * It should only be called from one thread. + * + * The destructor will clean up after the open. + * + * @return 0 if successful or negative error. + */ + int open(); + + void *getLibHandle() const { return mLibHandle; } + + // Function pointers into the AAudio shared library. + signature_I_PPB createStreamBuilder = nullptr; + + signature_I_PBPPS builder_openStream = nullptr; + + signature_V_PBI builder_setBufferCapacityInFrames = nullptr; + signature_V_PBI builder_setChannelCount = nullptr; + signature_V_PBI builder_setDeviceId = nullptr; + signature_V_PBI builder_setDirection = nullptr; + signature_V_PBI builder_setFormat = nullptr; + signature_V_PBI builder_setFramesPerDataCallback = nullptr; + signature_V_PBI builder_setPerformanceMode = nullptr; + signature_V_PBI builder_setSampleRate = nullptr; + signature_V_PBI builder_setSharingMode = nullptr; + signature_V_PBU builder_setChannelMask = nullptr; + + signature_V_PBI builder_setUsage = nullptr; + signature_V_PBI builder_setContentType = nullptr; + signature_V_PBI builder_setInputPreset = nullptr; + signature_V_PBI builder_setSessionId = nullptr; + + signature_V_PBCPH builder_setPackageName = nullptr; + signature_V_PBCPH builder_setAttributionTag = nullptr; + + signature_V_PBPDPV builder_setDataCallback = nullptr; + signature_V_PBPEPV builder_setErrorCallback = nullptr; + + signature_I_PB builder_delete = nullptr; + + signature_F_PS stream_getFormat = nullptr; + + signature_I_PSPVIL stream_read = nullptr; + signature_I_PSCPVIL stream_write = nullptr; + + signature_I_PSTPTL stream_waitForStateChange = nullptr; + + signature_I_PSKPLPL stream_getTimestamp = nullptr; + + signature_I_PS stream_close = nullptr; + + signature_I_PS stream_getChannelCount = nullptr; + signature_I_PS stream_getDeviceId = nullptr; + + signature_I_PS stream_getBufferSize = nullptr; + signature_I_PS stream_getBufferCapacity = nullptr; + signature_I_PS stream_getFramesPerBurst = nullptr; + signature_I_PS stream_getState = nullptr; + signature_I_PS stream_getPerformanceMode = nullptr; + signature_I_PS stream_getSampleRate = nullptr; + signature_I_PS stream_getSharingMode = nullptr; + signature_I_PS stream_getXRunCount = nullptr; + + signature_I_PSI stream_setBufferSize = nullptr; + signature_I_PS stream_requestStart = nullptr; + signature_I_PS stream_requestPause = nullptr; + signature_I_PS stream_requestFlush = nullptr; + signature_I_PS stream_requestStop = nullptr; + + signature_L_PS stream_getFramesRead = nullptr; + signature_L_PS stream_getFramesWritten = nullptr; + + signature_CPH_I convertResultToText = nullptr; + + signature_I_PS stream_getUsage = nullptr; + signature_I_PS stream_getContentType = nullptr; + signature_I_PS stream_getInputPreset = nullptr; + signature_I_PS stream_getSessionId = nullptr; + + signature_U_PS stream_getChannelMask = nullptr; + + private: + AAudioLoader() {} + ~AAudioLoader(); + + // Load function pointers for specific signatures. + signature_I_PPB load_I_PPB(const char *name); + signature_CPH_I load_CPH_I(const char *name); + signature_V_PBI load_V_PBI(const char *name); + signature_V_PBCPH load_V_PBCPH(const char *name); + signature_V_PBPDPV load_V_PBPDPV(const char *name); + signature_V_PBPEPV load_V_PBPEPV(const char *name); + signature_I_PB load_I_PB(const char *name); + signature_I_PBPPS load_I_PBPPS(const char *name); + signature_I_PS load_I_PS(const char *name); + signature_L_PS load_L_PS(const char *name); + signature_F_PS load_F_PS(const char *name); + signature_B_PS load_B_PS(const char *name); + signature_I_PSI load_I_PSI(const char *name); + signature_I_PSPVIL load_I_PSPVIL(const char *name); + signature_I_PSCPVIL load_I_PSCPVIL(const char *name); + signature_I_PSTPTL load_I_PSTPTL(const char *name); + signature_I_PSKPLPL load_I_PSKPLPL(const char *name); + signature_V_PBU load_V_PBU(const char *name); + signature_U_PS load_U_PS(const char *name); + + void *mLibHandle = nullptr; +}; + +} // namespace oboe + +#endif //OBOE_AAUDIO_LOADER_H_ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AudioStreamAAudio_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AudioStreamAAudio_android.cpp new file mode 100644 index 0000000..2a987ac --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AudioStreamAAudio_android.cpp @@ -0,0 +1,729 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "oboe_aaudio_AAudioLoader_android.h" +#include "oboe_aaudio_AudioStreamAAudio_android.h" +#include "oboe_common_AudioClock_android.h" +#include "oboe_common_OboeDebug_android.h" +#include "oboe_oboe_Utilities_android.h" +#include "oboe_aaudio_AAudioExtensions_android.h" + +#ifdef __ANDROID__ +#include +#include "oboe_common_QuirksManager_android.h" + +#endif + +#ifndef OBOE_FIX_FORCE_STARTING_TO_STARTED +// Workaround state problems in AAudio +// TODO Which versions does this occur in? Verify fixed in Q. +#define OBOE_FIX_FORCE_STARTING_TO_STARTED 1 +#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED + +using namespace oboe; +AAudioLoader *AudioStreamAAudio::mLibLoader = nullptr; + +// 'C' wrapper for the data callback method +static aaudio_data_callback_result_t oboe_aaudio_data_callback_proc( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t numFrames) { + + AudioStreamAAudio *oboeStream = reinterpret_cast(userData); + if (oboeStream != nullptr) { + return static_cast( + oboeStream->callOnAudioReady(stream, audioData, numFrames)); + + } else { + return static_cast(DataCallbackResult::Stop); + } +} + +// This runs in its own thread. +// Only one of these threads will be launched from internalErrorCallback(). +// It calls app error callbacks from a static function in case the stream gets deleted. +static void oboe_aaudio_error_thread_proc(AudioStreamAAudio *oboeStream, + Result error) { + LOGD("%s(,%d) - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", __func__, error); + AudioStreamErrorCallback *errorCallback = oboeStream->getErrorCallback(); + if (errorCallback == nullptr) return; // should be impossible + bool isErrorHandled = errorCallback->onError(oboeStream, error); + + if (!isErrorHandled) { + oboeStream->requestStop(); + errorCallback->onErrorBeforeClose(oboeStream, error); + oboeStream->close(); + // Warning, oboeStream may get deleted by this callback. + errorCallback->onErrorAfterClose(oboeStream, error); + } + LOGD("%s() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", __func__); +} + +// This runs in its own thread. +// Only one of these threads will be launched from internalErrorCallback(). +// Prevents deletion of the stream if the app is using AudioStreamBuilder::openSharedStream() +static void oboe_aaudio_error_thread_proc_shared(std::shared_ptr sharedStream, + Result error) { + AudioStreamAAudio *oboeStream = reinterpret_cast(sharedStream.get()); + oboe_aaudio_error_thread_proc(oboeStream, error); +} + +namespace oboe { + +/* + * Create a stream that uses Oboe Audio API. + */ +AudioStreamAAudio::AudioStreamAAudio(const AudioStreamBuilder &builder) + : AudioStream(builder) + , mAAudioStream(nullptr) { + mCallbackThreadEnabled.store(false); + mLibLoader = AAudioLoader::getInstance(); +} + +bool AudioStreamAAudio::isSupported() { + mLibLoader = AAudioLoader::getInstance(); + int openResult = mLibLoader->open(); + return openResult == 0; +} + +// Static method for the error callback. +// We use a method so we can access protected methods on the stream. +// Launch a thread to handle the error. +// That other thread can safely stop, close and delete the stream. +void AudioStreamAAudio::internalErrorCallback( + AAudioStream *stream, + void *userData, + aaudio_result_t error) { + oboe::Result oboeResult = static_cast(error); + AudioStreamAAudio *oboeStream = reinterpret_cast(userData); + + // Coerce the error code if needed to workaround a regression in RQ1A that caused + // the wrong code to be passed when headsets plugged in. See b/173928197. + if (OboeGlobals::areWorkaroundsEnabled() + && getSdkVersion() == __ANDROID_API_R__ + && oboeResult == oboe::Result::ErrorTimeout) { + oboeResult = oboe::Result::ErrorDisconnected; + LOGD("%s() ErrorTimeout changed to ErrorDisconnected to fix b/173928197", __func__); + } + + oboeStream->mErrorCallbackResult = oboeResult; + + // Prevents deletion of the stream if the app is using AudioStreamBuilder::openStream(shared_ptr) + std::shared_ptr sharedStream = oboeStream->lockWeakThis(); + + // These checks should be enough because we assume that the stream close() + // will join() any active callback threads and will not allow new callbacks. + if (oboeStream->wasErrorCallbackCalled()) { // block extra error callbacks + LOGE("%s() multiple error callbacks called!", __func__); + } else if (stream != oboeStream->getUnderlyingStream()) { + LOGW("%s() stream already closed or closing", __func__); // might happen if there are bugs + } else if (sharedStream) { + // Handle error on a separate thread using shared pointer. + std::thread t(oboe_aaudio_error_thread_proc_shared, sharedStream, oboeResult); + t.detach(); + } else { + // Handle error on a separate thread. + std::thread t(oboe_aaudio_error_thread_proc, oboeStream, oboeResult); + t.detach(); + } +} + +void AudioStreamAAudio::logUnsupportedAttributes() { + int sdkVersion = getSdkVersion(); + + // These attributes are not supported pre Android "P" + if (sdkVersion < __ANDROID_API_P__) { + if (mUsage != Usage::Media) { + LOGW("Usage [AudioStreamBuilder::setUsage()] " + "is not supported on AAudio streams running on pre-Android P versions."); + } + + if (mContentType != ContentType::Music) { + LOGW("ContentType [AudioStreamBuilder::setContentType()] " + "is not supported on AAudio streams running on pre-Android P versions."); + } + + if (mSessionId != SessionId::None) { + LOGW("SessionId [AudioStreamBuilder::setSessionId()] " + "is not supported on AAudio streams running on pre-Android P versions."); + } + } +} + +Result AudioStreamAAudio::open() { + Result result = Result::OK; + + if (mAAudioStream != nullptr) { + return Result::ErrorInvalidState; + } + + result = AudioStream::open(); + if (result != Result::OK) { + return result; + } + + AAudioStreamBuilder *aaudioBuilder; + result = static_cast(mLibLoader->createStreamBuilder(&aaudioBuilder)); + if (result != Result::OK) { + return result; + } + + // Do not set INPUT capacity below 4096 because that prevents us from getting a FAST track + // when using the Legacy data path. + // If the app requests > 4096 then we allow it but we are less likely to get LowLatency. + // See internal bug b/80308183 for more details. + // Fixed in Q but let's still clip the capacity because high input capacity + // does not increase latency. + int32_t capacity = mBufferCapacityInFrames; + constexpr int kCapacityRequiredForFastLegacyTrack = 4096; // matches value in AudioFinger + if (OboeGlobals::areWorkaroundsEnabled() + && mDirection == oboe::Direction::Input + && capacity != oboe::Unspecified + && capacity < kCapacityRequiredForFastLegacyTrack + && mPerformanceMode == oboe::PerformanceMode::LowLatency) { + capacity = kCapacityRequiredForFastLegacyTrack; + LOGD("AudioStreamAAudio.open() capacity changed from %d to %d for lower latency", + static_cast(mBufferCapacityInFrames), capacity); + } + mLibLoader->builder_setBufferCapacityInFrames(aaudioBuilder, capacity); + + // Channel mask was added in SC_V2. Given the corresponding channel count of selected channel + // mask may be different from selected channel count, the last set value will be respected. + // If channel count is set after channel mask, the previously set channel mask will be cleared. + // If channel mask is set after channel count, the channel count will be automatically + // calculated from selected channel mask. In that case, only set channel mask when the API + // is available and the channel mask is specified. + if (mLibLoader->builder_setChannelMask != nullptr && mChannelMask != ChannelMask::Unspecified) { + mLibLoader->builder_setChannelMask(aaudioBuilder, + static_cast(mChannelMask)); + } else { + mLibLoader->builder_setChannelCount(aaudioBuilder, mChannelCount); + } + mLibLoader->builder_setDeviceId(aaudioBuilder, mDeviceId); + mLibLoader->builder_setDirection(aaudioBuilder, static_cast(mDirection)); + mLibLoader->builder_setFormat(aaudioBuilder, static_cast(mFormat)); + mLibLoader->builder_setSampleRate(aaudioBuilder, mSampleRate); + mLibLoader->builder_setSharingMode(aaudioBuilder, + static_cast(mSharingMode)); + mLibLoader->builder_setPerformanceMode(aaudioBuilder, + static_cast(mPerformanceMode)); + + // These were added in P so we have to check for the function pointer. + if (mLibLoader->builder_setUsage != nullptr) { + mLibLoader->builder_setUsage(aaudioBuilder, + static_cast(mUsage)); + } + + if (mLibLoader->builder_setContentType != nullptr) { + mLibLoader->builder_setContentType(aaudioBuilder, + static_cast(mContentType)); + } + + if (mLibLoader->builder_setInputPreset != nullptr) { + aaudio_input_preset_t inputPreset = mInputPreset; + if (getSdkVersion() <= __ANDROID_API_P__ && inputPreset == InputPreset::VoicePerformance) { + LOGD("InputPreset::VoicePerformance not supported before Q. Using VoiceRecognition."); + inputPreset = InputPreset::VoiceRecognition; // most similar preset + } + mLibLoader->builder_setInputPreset(aaudioBuilder, + static_cast(inputPreset)); + } + + if (mLibLoader->builder_setSessionId != nullptr) { + mLibLoader->builder_setSessionId(aaudioBuilder, + static_cast(mSessionId)); + } + + // These were added in S so we have to check for the function pointer. + if (mLibLoader->builder_setPackageName != nullptr && !mPackageName.empty()) { + mLibLoader->builder_setPackageName(aaudioBuilder, + mPackageName.c_str()); + } + + if (mLibLoader->builder_setAttributionTag != nullptr && !mAttributionTag.empty()) { + mLibLoader->builder_setAttributionTag(aaudioBuilder, + mAttributionTag.c_str()); + } + + if (isDataCallbackSpecified()) { + mLibLoader->builder_setDataCallback(aaudioBuilder, oboe_aaudio_data_callback_proc, this); + mLibLoader->builder_setFramesPerDataCallback(aaudioBuilder, getFramesPerDataCallback()); + + if (!isErrorCallbackSpecified()) { + // The app did not specify a callback so we should specify + // our own so the stream gets closed and stopped. + mErrorCallback = &mDefaultErrorCallback; + } + mLibLoader->builder_setErrorCallback(aaudioBuilder, internalErrorCallback, this); + } + // Else if the data callback is not being used then the write method will return an error + // and the app can stop and close the stream. + + // ============= OPEN THE STREAM ================ + { + AAudioStream *stream = nullptr; + result = static_cast(mLibLoader->builder_openStream(aaudioBuilder, &stream)); + mAAudioStream.store(stream); + } + if (result != Result::OK) { + // Warn developer because ErrorInternal is not very informative. + if (result == Result::ErrorInternal && mDirection == Direction::Input) { + LOGW("AudioStreamAAudio.open() may have failed due to lack of " + "audio recording permission."); + } + goto error2; + } + + // Query and cache the stream properties + mDeviceId = mLibLoader->stream_getDeviceId(mAAudioStream); + mChannelCount = mLibLoader->stream_getChannelCount(mAAudioStream); + mSampleRate = mLibLoader->stream_getSampleRate(mAAudioStream); + mFormat = static_cast(mLibLoader->stream_getFormat(mAAudioStream)); + mSharingMode = static_cast(mLibLoader->stream_getSharingMode(mAAudioStream)); + mPerformanceMode = static_cast( + mLibLoader->stream_getPerformanceMode(mAAudioStream)); + mBufferCapacityInFrames = mLibLoader->stream_getBufferCapacity(mAAudioStream); + mBufferSizeInFrames = mLibLoader->stream_getBufferSize(mAAudioStream); + mFramesPerBurst = mLibLoader->stream_getFramesPerBurst(mAAudioStream); + + // These were added in P so we have to check for the function pointer. + if (mLibLoader->stream_getUsage != nullptr) { + mUsage = static_cast(mLibLoader->stream_getUsage(mAAudioStream)); + } + if (mLibLoader->stream_getContentType != nullptr) { + mContentType = static_cast(mLibLoader->stream_getContentType(mAAudioStream)); + } + if (mLibLoader->stream_getInputPreset != nullptr) { + mInputPreset = static_cast(mLibLoader->stream_getInputPreset(mAAudioStream)); + } + if (mLibLoader->stream_getSessionId != nullptr) { + mSessionId = static_cast(mLibLoader->stream_getSessionId(mAAudioStream)); + } else { + mSessionId = SessionId::None; + } + + if (mLibLoader->stream_getChannelMask != nullptr) { + mChannelMask = static_cast(mLibLoader->stream_getChannelMask(mAAudioStream)); + } + + LOGD("AudioStreamAAudio.open() format=%d, sampleRate=%d, capacity = %d", + static_cast(mFormat), static_cast(mSampleRate), + static_cast(mBufferCapacityInFrames)); + + calculateDefaultDelayBeforeCloseMillis(); + +error2: + mLibLoader->builder_delete(aaudioBuilder); + LOGD("AudioStreamAAudio.open: AAudioStream_Open() returned %s", + mLibLoader->convertResultToText(static_cast(result))); + return result; +} + +Result AudioStreamAAudio::close() { + // Prevent two threads from closing the stream at the same time and crashing. + // This could occur, for example, if an application called close() at the same + // time that an onError callback was being executed because of a disconnect. + std::lock_guard lock(mLock); + + AudioStream::close(); + + AAudioStream *stream = nullptr; + { + // Wait for any methods using mAAudioStream to finish. + std::unique_lock lock2(mAAudioStreamLock); + // Closing will delete *mAAudioStream so we need to null out the pointer atomically. + stream = mAAudioStream.exchange(nullptr); + } + if (stream != nullptr) { + if (OboeGlobals::areWorkaroundsEnabled()) { + // Make sure we are really stopped. Do it under mLock + // so another thread cannot call requestStart() right before the close. + requestStop_l(stream); + sleepBeforeClose(); + } + return static_cast(mLibLoader->stream_close(stream)); + } else { + return Result::ErrorClosed; + } +} + +static void oboe_stop_thread_proc(AudioStream *oboeStream) { + if (oboeStream != nullptr) { + oboeStream->requestStop(); + } +} + +void AudioStreamAAudio::launchStopThread() { + // Prevent multiple stop threads from being launched. + if (mStopThreadAllowed.exchange(false)) { + // Stop this stream on a separate thread + std::thread t(oboe_stop_thread_proc, this); + t.detach(); + } +} + +DataCallbackResult AudioStreamAAudio::callOnAudioReady(AAudioStream * /*stream*/, + void *audioData, + int32_t numFrames) { + DataCallbackResult result = fireDataCallback(audioData, numFrames); + if (result == DataCallbackResult::Continue) { + return result; + } else { + if (result == DataCallbackResult::Stop) { + LOGD("Oboe callback returned DataCallbackResult::Stop"); + } else { + LOGE("Oboe callback returned unexpected value = %d", result); + } + + // Returning Stop caused various problems before S. See #1230 + if (OboeGlobals::areWorkaroundsEnabled() && getSdkVersion() <= __ANDROID_API_R__) { + launchStopThread(); + return DataCallbackResult::Continue; + } else { + return DataCallbackResult::Stop; // OK >= API_S + } + } +} + +Result AudioStreamAAudio::requestStart() { + std::lock_guard lock(mLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + // Avoid state machine errors in O_MR1. + if (getSdkVersion() <= __ANDROID_API_O_MR1__) { + StreamState state = static_cast(mLibLoader->stream_getState(stream)); + if (state == StreamState::Starting || state == StreamState::Started) { + // WARNING: On P, AAudio is returning ErrorInvalidState for Output and OK for Input. + return Result::OK; + } + } + if (isDataCallbackSpecified()) { + setDataCallbackEnabled(true); + } + mStopThreadAllowed = true; + return static_cast(mLibLoader->stream_requestStart(stream)); + } else { + return Result::ErrorClosed; + } +} + +Result AudioStreamAAudio::requestPause() { + std::lock_guard lock(mLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + // Avoid state machine errors in O_MR1. + if (getSdkVersion() <= __ANDROID_API_O_MR1__) { + StreamState state = static_cast(mLibLoader->stream_getState(stream)); + if (state == StreamState::Pausing || state == StreamState::Paused) { + return Result::OK; + } + } + return static_cast(mLibLoader->stream_requestPause(stream)); + } else { + return Result::ErrorClosed; + } +} + +Result AudioStreamAAudio::requestFlush() { + std::lock_guard lock(mLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + // Avoid state machine errors in O_MR1. + if (getSdkVersion() <= __ANDROID_API_O_MR1__) { + StreamState state = static_cast(mLibLoader->stream_getState(stream)); + if (state == StreamState::Flushing || state == StreamState::Flushed) { + return Result::OK; + } + } + return static_cast(mLibLoader->stream_requestFlush(stream)); + } else { + return Result::ErrorClosed; + } +} + +Result AudioStreamAAudio::requestStop() { + std::lock_guard lock(mLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + return requestStop_l(stream); + } else { + return Result::ErrorClosed; + } +} + +// Call under mLock +Result AudioStreamAAudio::requestStop_l(AAudioStream *stream) { + // Avoid state machine errors in O_MR1. + if (getSdkVersion() <= __ANDROID_API_O_MR1__) { + StreamState state = static_cast(mLibLoader->stream_getState(stream)); + if (state == StreamState::Stopping || state == StreamState::Stopped) { + return Result::OK; + } + } + return static_cast(mLibLoader->stream_requestStop(stream)); +} + +ResultWithValue AudioStreamAAudio::write(const void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + std::shared_lock lock(mAAudioStreamLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + int32_t result = mLibLoader->stream_write(mAAudioStream, buffer, + numFrames, timeoutNanoseconds); + return ResultWithValue::createBasedOnSign(result); + } else { + return ResultWithValue(Result::ErrorClosed); + } +} + +ResultWithValue AudioStreamAAudio::read(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + std::shared_lock lock(mAAudioStreamLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + int32_t result = mLibLoader->stream_read(mAAudioStream, buffer, + numFrames, timeoutNanoseconds); + return ResultWithValue::createBasedOnSign(result); + } else { + return ResultWithValue(Result::ErrorClosed); + } +} + + +// AAudioStream_waitForStateChange() can crash if it is waiting on a stream and that stream +// is closed from another thread. We do not want to lock the stream for the duration of the call. +// So we call AAudioStream_waitForStateChange() with a timeout of zero so that it will not block. +// Then we can do our own sleep with the lock unlocked. +Result AudioStreamAAudio::waitForStateChange(StreamState currentState, + StreamState *nextState, + int64_t timeoutNanoseconds) { + Result oboeResult = Result::ErrorTimeout; + int64_t sleepTimeNanos = 20 * kNanosPerMillisecond; // arbitrary + aaudio_stream_state_t currentAAudioState = static_cast(currentState); + + aaudio_result_t result = AAUDIO_OK; + int64_t timeLeftNanos = timeoutNanoseconds; + + mLock.lock(); + while (true) { + // Do we still have an AAudio stream? If not then stream must have been closed. + AAudioStream *stream = mAAudioStream.load(); + if (stream == nullptr) { + if (nextState != nullptr) { + *nextState = StreamState::Closed; + } + oboeResult = Result::ErrorClosed; + break; + } + + // Update and query state change with no blocking. + aaudio_stream_state_t aaudioNextState; + result = mLibLoader->stream_waitForStateChange( + mAAudioStream, + currentAAudioState, + &aaudioNextState, + 0); // timeout=0 for non-blocking + // AAudio will return AAUDIO_ERROR_TIMEOUT if timeout=0 and the state does not change. + if (result != AAUDIO_OK && result != AAUDIO_ERROR_TIMEOUT) { + oboeResult = static_cast(result); + break; + } +#if OBOE_FIX_FORCE_STARTING_TO_STARTED + if (OboeGlobals::areWorkaroundsEnabled() + && aaudioNextState == static_cast(StreamState::Starting)) { + aaudioNextState = static_cast(StreamState::Started); + } +#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED + if (nextState != nullptr) { + *nextState = static_cast(aaudioNextState); + } + if (currentAAudioState != aaudioNextState) { // state changed? + oboeResult = Result::OK; + break; + } + + // Did we timeout or did user ask for non-blocking? + if (timeLeftNanos <= 0) { + break; + } + + // No change yet so sleep. + mLock.unlock(); // Don't sleep while locked. + if (sleepTimeNanos > timeLeftNanos) { + sleepTimeNanos = timeLeftNanos; // last little bit + } + AudioClock::sleepForNanos(sleepTimeNanos); + timeLeftNanos -= sleepTimeNanos; + mLock.lock(); + } + + mLock.unlock(); + return oboeResult; +} + +ResultWithValue AudioStreamAAudio::setBufferSizeInFrames(int32_t requestedFrames) { + int32_t adjustedFrames = requestedFrames; + if (adjustedFrames > mBufferCapacityInFrames) { + adjustedFrames = mBufferCapacityInFrames; + } + // This calls getBufferSize() so avoid recursive lock. + adjustedFrames = QuirksManager::getInstance().clipBufferSize(*this, adjustedFrames); + + std::shared_lock lock(mAAudioStreamLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, adjustedFrames); + // Cache the result if it's valid + if (newBufferSize > 0) mBufferSizeInFrames = newBufferSize; + return ResultWithValue::createBasedOnSign(newBufferSize); + } else { + return ResultWithValue(Result::ErrorClosed); + } +} + +StreamState AudioStreamAAudio::getState() { + std::shared_lock lock(mAAudioStreamLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + aaudio_stream_state_t aaudioState = mLibLoader->stream_getState(stream); +#if OBOE_FIX_FORCE_STARTING_TO_STARTED + if (OboeGlobals::areWorkaroundsEnabled() + && aaudioState == AAUDIO_STREAM_STATE_STARTING) { + aaudioState = AAUDIO_STREAM_STATE_STARTED; + } +#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED + return static_cast(aaudioState); + } else { + return StreamState::Closed; + } +} + +int32_t AudioStreamAAudio::getBufferSizeInFrames() { + std::shared_lock lock(mAAudioStreamLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + mBufferSizeInFrames = mLibLoader->stream_getBufferSize(stream); + } + return mBufferSizeInFrames; +} + +void AudioStreamAAudio::updateFramesRead() { + std::shared_lock lock(mAAudioStreamLock); + AAudioStream *stream = mAAudioStream.load(); +// Set to 1 for debugging race condition #1180 with mAAudioStream. +// See also DEBUG_CLOSE_RACE in OboeTester. +// This was left in the code so that we could test the fix again easily in the future. +// We could not trigger the race condition without adding these get calls and the sleeps. +#define DEBUG_CLOSE_RACE 0 +#if DEBUG_CLOSE_RACE + // This is used when testing race conditions with close(). + // See DEBUG_CLOSE_RACE in OboeTester + AudioClock::sleepForNanos(400 * kNanosPerMillisecond); +#endif // DEBUG_CLOSE_RACE + if (stream != nullptr) { + mFramesRead = mLibLoader->stream_getFramesRead(stream); + } +} + +void AudioStreamAAudio::updateFramesWritten() { + std::shared_lock lock(mAAudioStreamLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + mFramesWritten = mLibLoader->stream_getFramesWritten(stream); + } +} + +ResultWithValue AudioStreamAAudio::getXRunCount() { + std::shared_lock lock(mAAudioStreamLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + return ResultWithValue::createBasedOnSign(mLibLoader->stream_getXRunCount(stream)); + } else { + return ResultWithValue(Result::ErrorNull); + } +} + +Result AudioStreamAAudio::getTimestamp(clockid_t clockId, + int64_t *framePosition, + int64_t *timeNanoseconds) { + if (getState() != StreamState::Started) { + return Result::ErrorInvalidState; + } + std::shared_lock lock(mAAudioStreamLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + return static_cast(mLibLoader->stream_getTimestamp(stream, clockId, + framePosition, timeNanoseconds)); + } else { + return Result::ErrorNull; + } +} + +ResultWithValue AudioStreamAAudio::calculateLatencyMillis() { + // Get the time that a known audio frame was presented. + int64_t hardwareFrameIndex; + int64_t hardwareFrameHardwareTime; + auto result = getTimestamp(CLOCK_MONOTONIC, + &hardwareFrameIndex, + &hardwareFrameHardwareTime); + if (result != oboe::Result::OK) { + return ResultWithValue(static_cast(result)); + } + + // Get counter closest to the app. + bool isOutput = (getDirection() == oboe::Direction::Output); + int64_t appFrameIndex = isOutput ? getFramesWritten() : getFramesRead(); + + // Assume that the next frame will be processed at the current time + using namespace std::chrono; + int64_t appFrameAppTime = + duration_cast(steady_clock::now().time_since_epoch()).count(); + + // Calculate the number of frames between app and hardware + int64_t frameIndexDelta = appFrameIndex - hardwareFrameIndex; + + // Calculate the time which the next frame will be or was presented + int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / getSampleRate(); + int64_t appFrameHardwareTime = hardwareFrameHardwareTime + frameTimeDelta; + + // The current latency is the difference in time between when the current frame is at + // the app and when it is at the hardware. + double latencyNanos = static_cast(isOutput + ? (appFrameHardwareTime - appFrameAppTime) // hardware is later + : (appFrameAppTime - appFrameHardwareTime)); // hardware is earlier + double latencyMillis = latencyNanos / kNanosPerMillisecond; + + return ResultWithValue(latencyMillis); +} + +bool AudioStreamAAudio::isMMapUsed() { + std::shared_lock lock(mAAudioStreamLock); + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + return AAudioExtensions::getInstance().isMMapUsed(stream); + } else { + return false; + } +} + +} // namespace oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AudioStreamAAudio_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AudioStreamAAudio_android.h new file mode 100644 index 0000000..9a37eb9 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_aaudio_AudioStreamAAudio_android.h @@ -0,0 +1,142 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_STREAM_AAUDIO_H_ +#define OBOE_STREAM_AAUDIO_H_ + +#include +#include +#include +#include + +#include "oboe_oboe_AudioStreamBuilder_android.h" +#include "oboe_oboe_AudioStream_android.h" +#include "oboe_oboe_Definitions_android.h" +#include "oboe_aaudio_AAudioLoader_android.h" + +namespace oboe { + +/** + * Implementation of OboeStream that uses AAudio. + * + * Do not create this class directly. + * Use an OboeStreamBuilder to create one. + */ +class AudioStreamAAudio : public AudioStream { +public: + AudioStreamAAudio(); + explicit AudioStreamAAudio(const AudioStreamBuilder &builder); + + virtual ~AudioStreamAAudio() = default; + + /** + * + * @return true if AAudio is supported on this device. + */ + static bool isSupported(); + + // These functions override methods in AudioStream. + // See AudioStream for documentation. + Result open() override; + Result close() override; + + Result requestStart() override; + Result requestPause() override; + Result requestFlush() override; + Result requestStop() override; + + ResultWithValue write(const void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) override; + + ResultWithValue read(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) override; + + ResultWithValue setBufferSizeInFrames(int32_t requestedFrames) override; + int32_t getBufferSizeInFrames() override; + ResultWithValue getXRunCount() override; + bool isXRunCountSupported() const override { return true; } + + ResultWithValue calculateLatencyMillis() override; + + Result waitForStateChange(StreamState currentState, + StreamState *nextState, + int64_t timeoutNanoseconds) override; + + Result getTimestamp(clockid_t clockId, + int64_t *framePosition, + int64_t *timeNanoseconds) override; + + StreamState getState() override; + + AudioApi getAudioApi() const override { + return AudioApi::AAudio; + } + + DataCallbackResult callOnAudioReady(AAudioStream *stream, + void *audioData, + int32_t numFrames); + + bool isMMapUsed(); + +protected: + static void internalErrorCallback( + AAudioStream *stream, + void *userData, + aaudio_result_t error); + + void *getUnderlyingStream() const override { + return mAAudioStream.load(); + } + + void updateFramesRead() override; + void updateFramesWritten() override; + + void logUnsupportedAttributes(); + +private: + // Must call under mLock. And stream must NOT be nullptr. + Result requestStop_l(AAudioStream *stream); + + /** + * Launch a thread that will stop the stream. + */ + void launchStopThread(); + +public: + int32_t getMDelayBeforeCloseMillis() const; + + void setDelayBeforeCloseMillis(int32_t mDelayBeforeCloseMillis); + +private: + + std::atomic mCallbackThreadEnabled; + std::atomic mStopThreadAllowed{false}; + + // pointer to the underlying 'C' AAudio stream, valid if open, null if closed + std::atomic mAAudioStream{nullptr}; + std::shared_mutex mAAudioStreamLock; // to protect mAAudioStream while closing + + static AAudioLoader *mLibLoader; + + // We may not use this but it is so small that it is not worth allocating dynamically. + AudioStreamErrorCallback mDefaultErrorCallback; +}; + +} // namespace oboe + +#endif // OBOE_STREAM_AAUDIO_H_ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioClock_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioClock_android.h new file mode 100644 index 0000000..69ad61b --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioClock_android.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_AUDIO_CLOCK_H +#define OBOE_AUDIO_CLOCK_H + +#include +#include +#include "oboe_oboe_Definitions_android.h" + +namespace oboe { + +// TODO: Move this class into the public headers because it is useful when calculating stream latency +class AudioClock { +public: + static int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC) { + struct timespec time; + int result = clock_gettime(clockId, &time); + if (result < 0) { + return result; + } + return (time.tv_sec * kNanosPerSecond) + time.tv_nsec; + } + + /** + * Sleep until the specified time. + * + * @param nanoTime time to wake up + * @param clockId CLOCK_MONOTONIC is default + * @return 0 or a negative error, eg. -EINTR + */ + + static int sleepUntilNanoTime(int64_t nanoTime, clockid_t clockId = CLOCK_MONOTONIC) { + struct timespec time; + time.tv_sec = nanoTime / kNanosPerSecond; + time.tv_nsec = nanoTime - (time.tv_sec * kNanosPerSecond); + return 0 - clock_nanosleep(clockId, TIMER_ABSTIME, &time, NULL); + } + + /** + * Sleep for the specified number of nanoseconds in real-time. + * Return immediately with 0 if a negative nanoseconds is specified. + * + * @param nanoseconds time to sleep + * @param clockId CLOCK_REALTIME is default + * @return 0 or a negative error, eg. -EINTR + */ + + static int sleepForNanos(int64_t nanoseconds, clockid_t clockId = CLOCK_REALTIME) { + if (nanoseconds > 0) { + struct timespec time; + time.tv_sec = nanoseconds / kNanosPerSecond; + time.tv_nsec = nanoseconds - (time.tv_sec * kNanosPerSecond); + return 0 - clock_nanosleep(clockId, 0, &time, NULL); + } + return 0; + } +}; + +} // namespace oboe + +#endif //OBOE_AUDIO_CLOCK_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioSourceCaller_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioSourceCaller_android.cpp new file mode 100644 index 0000000..d1a9ac1 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioSourceCaller_android.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "oboe_common_AudioSourceCaller_android.h" + +using namespace oboe; +using namespace flowgraph; + +int32_t AudioSourceCaller::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) { + AudioStreamDataCallback *callback = mStream->getDataCallback(); + int32_t result = 0; + int32_t numFrames = numBytes / mStream->getBytesPerFrame(); + if (callback != nullptr) { + DataCallbackResult callbackResult = callback->onAudioReady(mStream, buffer, numFrames); + // onAudioReady() does not return the number of bytes processed so we have to assume all. + result = (callbackResult == DataCallbackResult::Continue) + ? numBytes + : -1; + } else { + auto readResult = mStream->read(buffer, numFrames, mTimeoutNanos); + if (!readResult) return (int32_t) readResult.error(); + result = readResult.value() * mStream->getBytesPerFrame(); + } + return result; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioSourceCaller_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioSourceCaller_android.h new file mode 100644 index 0000000..5ac784e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioSourceCaller_android.h @@ -0,0 +1,83 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_AUDIO_SOURCE_CALLER_H +#define OBOE_AUDIO_SOURCE_CALLER_H + +#include "oboe_common_OboeDebug_android.h" +#include "oboe_oboe_Oboe_android.h" + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_common_FixedBlockReader_android.h" + +namespace oboe { + +class AudioStreamCallback; +class AudioStream; + +/** + * For output streams that use a callback, call the application for more data. + * For input streams that do not use a callback, read from the stream. + */ +class AudioSourceCaller : public flowgraph::FlowGraphSource, public FixedBlockProcessor { +public: + AudioSourceCaller(int32_t channelCount, int32_t framesPerCallback, int32_t bytesPerSample) + : FlowGraphSource(channelCount) + , mBlockReader(*this) { + mBlockReader.open(channelCount * framesPerCallback * bytesPerSample); + } + + /** + * Set the stream to use as a source of data. + * @param stream + */ + void setStream(oboe::AudioStream *stream) { + mStream = stream; + } + + oboe::AudioStream *getStream() { + return mStream; + } + + /** + * Timeout value to use when calling audioStream->read(). + * @param timeoutNanos Zero for no timeout or time in nanoseconds. + */ + void setTimeoutNanos(int64_t timeoutNanos) { + mTimeoutNanos = timeoutNanos; + } + + int64_t getTimeoutNanos() const { + return mTimeoutNanos; + } + + /** + * Called internally for block size adaptation. + * @param buffer + * @param numBytes + * @return + */ + int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override; + +protected: + oboe::AudioStream *mStream = nullptr; + int64_t mTimeoutNanos = 0; + + FixedBlockReader mBlockReader; +}; + +} +#endif //OBOE_AUDIO_SOURCE_CALLER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioStreamBuilder_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioStreamBuilder_android.cpp new file mode 100644 index 0000000..1fd5b3a --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioStreamBuilder_android.cpp @@ -0,0 +1,224 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + + +#include "oboe_aaudio_AAudioExtensions_android.h" +#include "oboe_aaudio_AudioStreamAAudio_android.h" +#include "oboe_common_FilterAudioStream_android.h" +#include "oboe_common_OboeDebug_android.h" +#include "oboe_oboe_Oboe_android.h" +#include "oboe_oboe_AudioStreamBuilder_android.h" +#include "oboe_opensles_AudioInputStreamOpenSLES_android.h" +#include "oboe_opensles_AudioOutputStreamOpenSLES_android.h" +#include "oboe_opensles_AudioStreamOpenSLES_android.h" +#include "oboe_common_QuirksManager_android.h" + +bool oboe::OboeGlobals::mWorkaroundsEnabled = true; + +namespace oboe { + +/** + * The following default values are used when oboe does not have any better way of determining the optimal values + * for an audio stream. This can happen when: + * + * - Client is creating a stream on API < 26 (OpenSLES) but has not supplied the optimal sample + * rate and/or frames per burst + * - Client is creating a stream on API 16 (OpenSLES) where AudioManager.PROPERTY_OUTPUT_* values + * are not available + */ +int32_t DefaultStreamValues::SampleRate = 48000; // Common rate for mobile audio and video +int32_t DefaultStreamValues::FramesPerBurst = 192; // 4 msec at 48000 Hz +int32_t DefaultStreamValues::ChannelCount = 2; // Stereo + +constexpr int kBufferSizeInBurstsForLowLatencyStreams = 2; + +#ifndef OBOE_ENABLE_AAUDIO +// Set OBOE_ENABLE_AAUDIO to 0 if you want to disable the AAudio API. +// This might be useful if you want to force all the unit tests to use OpenSL ES. +#define OBOE_ENABLE_AAUDIO 1 +#endif + +bool AudioStreamBuilder::isAAudioSupported() { + return AudioStreamAAudio::isSupported() && OBOE_ENABLE_AAUDIO; +} + +bool AudioStreamBuilder::isAAudioRecommended() { + // See https://github.com/google/oboe/issues/40, + // AAudio may not be stable on Android O, depending on how it is used. + // To be safe, use AAudio only on O_MR1 and above. + return (getSdkVersion() >= __ANDROID_API_O_MR1__) && isAAudioSupported(); +} + +AudioStream *AudioStreamBuilder::build() { + AudioStream *stream = nullptr; + if (isAAudioRecommended() && mAudioApi != AudioApi::OpenSLES) { + stream = new AudioStreamAAudio(*this); + } else if (isAAudioSupported() && mAudioApi == AudioApi::AAudio) { + stream = new AudioStreamAAudio(*this); + LOGE("Creating AAudio stream on 8.0 because it was specified. This is error prone."); + } else { + if (getDirection() == oboe::Direction::Output) { + stream = new AudioOutputStreamOpenSLES(*this); + } else if (getDirection() == oboe::Direction::Input) { + stream = new AudioInputStreamOpenSLES(*this); + } + } + return stream; +} + +bool AudioStreamBuilder::isCompatible(AudioStreamBase &other) { + return (getSampleRate() == oboe::Unspecified || getSampleRate() == other.getSampleRate()) + && (getFormat() == (AudioFormat)oboe::Unspecified || getFormat() == other.getFormat()) + && (getFramesPerDataCallback() == oboe::Unspecified || getFramesPerDataCallback() == other.getFramesPerDataCallback()) + && (getChannelCount() == oboe::Unspecified || getChannelCount() == other.getChannelCount()); +} + +Result AudioStreamBuilder::openStream(AudioStream **streamPP) { + auto result = isValidConfig(); + if (result != Result::OK) { + LOGW("%s() invalid config %d", __func__, result); + return result; + } + + LOGI("%s() %s -------- %s --------", + __func__, getDirection() == Direction::Input ? "INPUT" : "OUTPUT", getVersionText()); + + if (streamPP == nullptr) { + return Result::ErrorNull; + } + *streamPP = nullptr; + + AudioStream *streamP = nullptr; + + // Maybe make a FilterInputStream. + AudioStreamBuilder childBuilder(*this); + // Check need for conversion and modify childBuilder for optimal stream. + bool conversionNeeded = QuirksManager::getInstance().isConversionNeeded(*this, childBuilder); + // Do we need to make a child stream and convert. + if (conversionNeeded) { + AudioStream *tempStream; + result = childBuilder.openStream(&tempStream); + if (result != Result::OK) { + return result; + } + + if (isCompatible(*tempStream)) { + // The child stream would work as the requested stream so we can just use it directly. + *streamPP = tempStream; + return result; + } else { + AudioStreamBuilder parentBuilder = *this; + // Build a stream that is as close as possible to the childStream. + if (getFormat() == oboe::AudioFormat::Unspecified) { + parentBuilder.setFormat(tempStream->getFormat()); + } + if (getChannelCount() == oboe::Unspecified) { + parentBuilder.setChannelCount(tempStream->getChannelCount()); + } + if (getSampleRate() == oboe::Unspecified) { + parentBuilder.setSampleRate(tempStream->getSampleRate()); + } + if (getFramesPerDataCallback() == oboe::Unspecified) { + parentBuilder.setFramesPerCallback(tempStream->getFramesPerDataCallback()); + } + + // Use childStream in a FilterAudioStream. + LOGI("%s() create a FilterAudioStream for data conversion.", __func__); + FilterAudioStream *filterStream = new FilterAudioStream(parentBuilder, tempStream); + result = filterStream->configureFlowGraph(); + if (result != Result::OK) { + filterStream->close(); + delete filterStream; + // Just open streamP the old way. + } else { + streamP = static_cast(filterStream); + } + } + } + + if (streamP == nullptr) { + streamP = build(); + if (streamP == nullptr) { + return Result::ErrorNull; + } + } + + // If MMAP has a problem in this case then disable it temporarily. + bool wasMMapOriginallyEnabled = AAudioExtensions::getInstance().isMMapEnabled(); + bool wasMMapTemporarilyDisabled = false; + if (wasMMapOriginallyEnabled) { + bool isMMapSafe = QuirksManager::getInstance().isMMapSafe(childBuilder); + if (!isMMapSafe) { + AAudioExtensions::getInstance().setMMapEnabled(false); + wasMMapTemporarilyDisabled = true; + } + } + result = streamP->open(); + if (wasMMapTemporarilyDisabled) { + AAudioExtensions::getInstance().setMMapEnabled(wasMMapOriginallyEnabled); // restore original + } + if (result == Result::OK) { + + int32_t optimalBufferSize = -1; + // Use a reasonable default buffer size. + if (streamP->getDirection() == Direction::Input) { + // For input, small size does not improve latency because the stream is usually + // run close to empty. And a low size can result in XRuns so always use the maximum. + optimalBufferSize = streamP->getBufferCapacityInFrames(); + } else if (streamP->getPerformanceMode() == PerformanceMode::LowLatency + && streamP->getDirection() == Direction::Output) { // Output check is redundant. + optimalBufferSize = streamP->getFramesPerBurst() * + kBufferSizeInBurstsForLowLatencyStreams; + } + if (optimalBufferSize >= 0) { + auto setBufferResult = streamP->setBufferSizeInFrames(optimalBufferSize); + if (!setBufferResult) { + LOGW("Failed to setBufferSizeInFrames(%d). Error was %s", + optimalBufferSize, + convertToText(setBufferResult.error())); + } + } + + *streamPP = streamP; + } else { + delete streamP; + } + return result; +} + +Result AudioStreamBuilder::openManagedStream(oboe::ManagedStream &stream) { + stream.reset(); + AudioStream *streamptr; + auto result = openStream(&streamptr); + stream.reset(streamptr); + return result; +} + +Result AudioStreamBuilder::openStream(std::shared_ptr &sharedStream) { + sharedStream.reset(); + AudioStream *streamptr; + auto result = openStream(&streamptr); + if (result == Result::OK) { + sharedStream.reset(streamptr); + // Save a weak_ptr in the stream for use with callbacks. + streamptr->setWeakThis(sharedStream); + } + return result; +} + +} // namespace oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioStream_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioStream_android.cpp new file mode 100644 index 0000000..bb7e894 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_AudioStream_android.cpp @@ -0,0 +1,208 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "oboe_oboe_AudioStream_android.h" +#include "oboe_common_OboeDebug_android.h" +#include "oboe_common_AudioClock_android.h" +#include "oboe_oboe_Utilities_android.h" + +namespace oboe { + +/* + * AudioStream + */ +AudioStream::AudioStream(const AudioStreamBuilder &builder) + : AudioStreamBase(builder) { +} + +Result AudioStream::close() { + // Update local counters so they can be read after the close. + updateFramesWritten(); + updateFramesRead(); + return Result::OK; +} + +// Call this from fireDataCallback() if you want to monitor CPU scheduler. +void AudioStream::checkScheduler() { + int scheduler = sched_getscheduler(0) & ~SCHED_RESET_ON_FORK; // for current thread + if (scheduler != mPreviousScheduler) { + LOGD("AudioStream::%s() scheduler = %s", __func__, + ((scheduler == SCHED_FIFO) ? "SCHED_FIFO" : + ((scheduler == SCHED_OTHER) ? "SCHED_OTHER" : + ((scheduler == SCHED_RR) ? "SCHED_RR" : "UNKNOWN"))) + ); + mPreviousScheduler = scheduler; + } +} + +DataCallbackResult AudioStream::fireDataCallback(void *audioData, int32_t numFrames) { + if (!isDataCallbackEnabled()) { + LOGW("AudioStream::%s() called with data callback disabled!", __func__); + return DataCallbackResult::Stop; // Should not be getting called + } + + DataCallbackResult result; + if (mDataCallback) { + result = mDataCallback->onAudioReady(this, audioData, numFrames); + } else { + result = onDefaultCallback(audioData, numFrames); + } + // On Oreo, we might get called after returning stop. + // So block that here. + setDataCallbackEnabled(result == DataCallbackResult::Continue); + + return result; +} + +Result AudioStream::waitForStateTransition(StreamState startingState, + StreamState endingState, + int64_t timeoutNanoseconds) +{ + StreamState state; + { + std::lock_guard lock(mLock); + state = getState(); + if (state == StreamState::Closed) { + return Result::ErrorClosed; + } else if (state == StreamState::Disconnected) { + return Result::ErrorDisconnected; + } + } + + StreamState nextState = state; + // TODO Should this be a while()?! + if (state == startingState && state != endingState) { + Result result = waitForStateChange(state, &nextState, timeoutNanoseconds); + if (result != Result::OK) { + return result; + } + } + + if (nextState != endingState) { + return Result::ErrorInvalidState; + } else { + return Result::OK; + } +} + +Result AudioStream::start(int64_t timeoutNanoseconds) +{ + Result result = requestStart(); + if (result != Result::OK) return result; + if (timeoutNanoseconds <= 0) return result; + return waitForStateTransition(StreamState::Starting, + StreamState::Started, timeoutNanoseconds); +} + +Result AudioStream::pause(int64_t timeoutNanoseconds) +{ + Result result = requestPause(); + if (result != Result::OK) return result; + if (timeoutNanoseconds <= 0) return result; + return waitForStateTransition(StreamState::Pausing, + StreamState::Paused, timeoutNanoseconds); +} + +Result AudioStream::flush(int64_t timeoutNanoseconds) +{ + Result result = requestFlush(); + if (result != Result::OK) return result; + if (timeoutNanoseconds <= 0) return result; + return waitForStateTransition(StreamState::Flushing, + StreamState::Flushed, timeoutNanoseconds); +} + +Result AudioStream::stop(int64_t timeoutNanoseconds) +{ + Result result = requestStop(); + if (result != Result::OK) return result; + if (timeoutNanoseconds <= 0) return result; + return waitForStateTransition(StreamState::Stopping, + StreamState::Stopped, timeoutNanoseconds); +} + +int32_t AudioStream::getBytesPerSample() const { + return convertFormatToSizeInBytes(mFormat); +} + +int64_t AudioStream::getFramesRead() { + updateFramesRead(); + return mFramesRead; +} + +int64_t AudioStream::getFramesWritten() { + updateFramesWritten(); + return mFramesWritten; +} + +ResultWithValue AudioStream::getAvailableFrames() { + int64_t readCounter = getFramesRead(); + if (readCounter < 0) return ResultWithValue::createBasedOnSign(readCounter); + int64_t writeCounter = getFramesWritten(); + if (writeCounter < 0) return ResultWithValue::createBasedOnSign(writeCounter); + int32_t framesAvailable = writeCounter - readCounter; + return ResultWithValue(framesAvailable); +} + +ResultWithValue AudioStream::waitForAvailableFrames(int32_t numFrames, + int64_t timeoutNanoseconds) { + if (numFrames == 0) return Result::OK; + if (numFrames < 0) return Result::ErrorOutOfRange; + + int64_t framesAvailable = 0; + int64_t burstInNanos = getFramesPerBurst() * kNanosPerSecond / getSampleRate(); + bool ready = false; + int64_t deadline = AudioClock::getNanoseconds() + timeoutNanoseconds; + do { + ResultWithValue result = getAvailableFrames(); + if (!result) return result; + framesAvailable = result.value(); + ready = (framesAvailable >= numFrames); + if (!ready) { + int64_t now = AudioClock::getNanoseconds(); + if (now > deadline) break; + AudioClock::sleepForNanos(burstInNanos); + } + } while (!ready); + return (!ready) + ? ResultWithValue(Result::ErrorTimeout) + : ResultWithValue(framesAvailable); +} + +ResultWithValue AudioStream::getTimestamp(clockid_t clockId) { + FrameTimestamp frame; + Result result = getTimestamp(clockId, &frame.position, &frame.timestamp); + if (result == Result::OK){ + return ResultWithValue(frame); + } else { + return ResultWithValue(static_cast(result)); + } +} + +void AudioStream::calculateDefaultDelayBeforeCloseMillis() { + // Calculate delay time before close based on burst duration. + // Start with a burst duration then add 1 msec as a safety margin. + mDelayBeforeCloseMillis = std::max(kMinDelayBeforeCloseMillis, + 1 + ((mFramesPerBurst * 1000) / getSampleRate())); + LOGD("calculateDefaultDelayBeforeCloseMillis() default = %d", + static_cast(mDelayBeforeCloseMillis)); +} + +} // namespace oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_DataConversionFlowGraph_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_DataConversionFlowGraph_android.cpp new file mode 100644 index 0000000..66d12ad --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_DataConversionFlowGraph_android.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "oboe_common_OboeDebug_android.h" +#include "oboe_common_DataConversionFlowGraph_android.h" +#include "oboe_common_SourceFloatCaller_android.h" +#include "oboe_common_SourceI16Caller_android.h" +#include "oboe_common_SourceI24Caller_android.h" +#include "oboe_common_SourceI32Caller_android.h" + +#include "oboe_flowgraph_ClipToRange_android.h" +#include "oboe_flowgraph_MonoToMultiConverter_android.h" +#include "oboe_flowgraph_MultiToMonoConverter_android.h" +#include "oboe_flowgraph_RampLinear_android.h" +#include "oboe_flowgraph_SinkFloat_android.h" +#include "oboe_flowgraph_SinkI16_android.h" +#include "oboe_flowgraph_SinkI24_android.h" +#include "oboe_flowgraph_SinkI32_android.h" +#include "oboe_flowgraph_SourceFloat_android.h" +#include "oboe_flowgraph_SourceI16_android.h" +#include "oboe_flowgraph_SourceI24_android.h" +#include "oboe_flowgraph_SourceI32_android.h" +#include "oboe_flowgraph_SampleRateConverter_android.h" + +using namespace oboe; +using namespace flowgraph; +using namespace resampler; + +void DataConversionFlowGraph::setSource(const void *buffer, int32_t numFrames) { + mSource->setData(buffer, numFrames); +} + +static MultiChannelResampler::Quality convertOboeSRQualityToMCR(SampleRateConversionQuality quality) { + switch (quality) { + case SampleRateConversionQuality::Fastest: + return MultiChannelResampler::Quality::Fastest; + case SampleRateConversionQuality::Low: + return MultiChannelResampler::Quality::Low; + default: + case SampleRateConversionQuality::Medium: + return MultiChannelResampler::Quality::Medium; + case SampleRateConversionQuality::High: + return MultiChannelResampler::Quality::High; + case SampleRateConversionQuality::Best: + return MultiChannelResampler::Quality::Best; + } +} + +// Chain together multiple processors. +// Callback Output +// Use SourceCaller that calls original app callback from the flowgraph. +// The child callback from FilteredAudioStream read()s from the flowgraph. +// Callback Input +// Child callback from FilteredAudioStream writes()s to the flowgraph. +// The output of the flowgraph goes through a BlockWriter to the app callback. +// Blocking Write +// Write buffer is set on an AudioSource. +// Data is pulled through the graph and written to the child stream. +// Blocking Read +// Reads in a loop from the flowgraph Sink to fill the read buffer. +// A SourceCaller then does a blocking read from the child Stream. +// +Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream *sinkStream) { + + FlowGraphPortFloatOutput *lastOutput = nullptr; + + bool isOutput = sourceStream->getDirection() == Direction::Output; + bool isInput = !isOutput; + mFilterStream = isOutput ? sourceStream : sinkStream; + + AudioFormat sourceFormat = sourceStream->getFormat(); + int32_t sourceChannelCount = sourceStream->getChannelCount(); + int32_t sourceSampleRate = sourceStream->getSampleRate(); + int32_t sourceFramesPerCallback = sourceStream->getFramesPerDataCallback(); + + AudioFormat sinkFormat = sinkStream->getFormat(); + int32_t sinkChannelCount = sinkStream->getChannelCount(); + int32_t sinkSampleRate = sinkStream->getSampleRate(); + int32_t sinkFramesPerCallback = sinkStream->getFramesPerDataCallback(); + + LOGI("%s() flowgraph converts channels: %d to %d, format: %d to %d" + ", rate: %d to %d, cbsize: %d to %d, qual = %d", + __func__, + sourceChannelCount, sinkChannelCount, + sourceFormat, sinkFormat, + sourceSampleRate, sinkSampleRate, + sourceFramesPerCallback, sinkFramesPerCallback, + sourceStream->getSampleRateConversionQuality()); + + // Source + // IF OUTPUT and using a callback then call back to the app using a SourceCaller. + // OR IF INPUT and NOT using a callback then read from the child stream using a SourceCaller. + bool isDataCallbackSpecified = sourceStream->isDataCallbackSpecified(); + if ((isDataCallbackSpecified && isOutput) + || (!isDataCallbackSpecified && isInput)) { + int32_t actualSourceFramesPerCallback = (sourceFramesPerCallback == kUnspecified) + ? sourceStream->getFramesPerBurst() + : sourceFramesPerCallback; + switch (sourceFormat) { + case AudioFormat::Float: + mSourceCaller = std::make_unique(sourceChannelCount, + actualSourceFramesPerCallback); + break; + case AudioFormat::I16: + mSourceCaller = std::make_unique(sourceChannelCount, + actualSourceFramesPerCallback); + break; + case AudioFormat::I24: + mSourceCaller = std::make_unique(sourceChannelCount, + actualSourceFramesPerCallback); + break; + case AudioFormat::I32: + mSourceCaller = std::make_unique(sourceChannelCount, + actualSourceFramesPerCallback); + break; + default: + LOGE("%s() Unsupported source caller format = %d", __func__, sourceFormat); + return Result::ErrorIllegalArgument; + } + mSourceCaller->setStream(sourceStream); + lastOutput = &mSourceCaller->output; + } else { + // IF OUTPUT and NOT using a callback then write to the child stream using a BlockWriter. + // OR IF INPUT and using a callback then write to the app using a BlockWriter. + switch (sourceFormat) { + case AudioFormat::Float: + mSource = std::make_unique(sourceChannelCount); + break; + case AudioFormat::I16: + mSource = std::make_unique(sourceChannelCount); + break; + case AudioFormat::I24: + mSource = std::make_unique(sourceChannelCount); + break; + case AudioFormat::I32: + mSource = std::make_unique(sourceChannelCount); + break; + default: + LOGE("%s() Unsupported source format = %d", __func__, sourceFormat); + return Result::ErrorIllegalArgument; + } + if (isInput) { + int32_t actualSinkFramesPerCallback = (sinkFramesPerCallback == kUnspecified) + ? sinkStream->getFramesPerBurst() + : sinkFramesPerCallback; + // The BlockWriter is after the Sink so use the SinkStream size. + mBlockWriter.open(actualSinkFramesPerCallback * sinkStream->getBytesPerFrame()); + mAppBuffer = std::make_unique( + kDefaultBufferSize * sinkStream->getBytesPerFrame()); + } + lastOutput = &mSource->output; + } + + // If we are going to reduce the number of channels then do it before the + // sample rate converter. + if (sourceChannelCount > sinkChannelCount) { + if (sinkChannelCount == 1) { + mMultiToMonoConverter = std::make_unique(sourceChannelCount); + lastOutput->connect(&mMultiToMonoConverter->input); + lastOutput = &mMultiToMonoConverter->output; + } else { + mChannelCountConverter = std::make_unique( + sourceChannelCount, + sinkChannelCount); + lastOutput->connect(&mChannelCountConverter->input); + lastOutput = &mChannelCountConverter->output; + } + } + + // Sample Rate conversion + if (sourceSampleRate != sinkSampleRate) { + // Create a resampler to do the math. + mResampler.reset(MultiChannelResampler::make(lastOutput->getSamplesPerFrame(), + sourceSampleRate, + sinkSampleRate, + convertOboeSRQualityToMCR( + sourceStream->getSampleRateConversionQuality()))); + // Make a flowgraph node that uses the resampler. + mRateConverter = std::make_unique(lastOutput->getSamplesPerFrame(), + *mResampler.get()); + lastOutput->connect(&mRateConverter->input); + lastOutput = &mRateConverter->output; + } + + // Expand the number of channels if required. + if (sourceChannelCount < sinkChannelCount) { + if (sourceChannelCount == 1) { + mMonoToMultiConverter = std::make_unique(sinkChannelCount); + lastOutput->connect(&mMonoToMultiConverter->input); + lastOutput = &mMonoToMultiConverter->output; + } else { + mChannelCountConverter = std::make_unique( + sourceChannelCount, + sinkChannelCount); + lastOutput->connect(&mChannelCountConverter->input); + lastOutput = &mChannelCountConverter->output; + } + } + + // Sink + switch (sinkFormat) { + case AudioFormat::Float: + mSink = std::make_unique(sinkChannelCount); + break; + case AudioFormat::I16: + mSink = std::make_unique(sinkChannelCount); + break; + case AudioFormat::I24: + mSink = std::make_unique(sinkChannelCount); + break; + case AudioFormat::I32: + mSink = std::make_unique(sinkChannelCount); + break; + default: + LOGE("%s() Unsupported sink format = %d", __func__, sinkFormat); + return Result::ErrorIllegalArgument;; + } + lastOutput->connect(&mSink->input); + + return Result::OK; +} + +int32_t DataConversionFlowGraph::read(void *buffer, int32_t numFrames, int64_t timeoutNanos) { + if (mSourceCaller) { + mSourceCaller->setTimeoutNanos(timeoutNanos); + } + int32_t numRead = mSink->read(buffer, numFrames); + return numRead; +} + +// This is similar to pushing data through the flowgraph. +int32_t DataConversionFlowGraph::write(void *inputBuffer, int32_t numFrames) { + // Put the data from the input at the head of the flowgraph. + mSource->setData(inputBuffer, numFrames); + while (true) { + // Pull and read some data in app format into a small buffer. + int32_t framesRead = mSink->read(mAppBuffer.get(), flowgraph::kDefaultBufferSize); + if (framesRead <= 0) break; + // Write to a block adapter, which will call the destination whenever it has enough data. + int32_t bytesRead = mBlockWriter.write(mAppBuffer.get(), + framesRead * mFilterStream->getBytesPerFrame()); + if (bytesRead < 0) return bytesRead; // TODO review + } + return numFrames; +} + +int32_t DataConversionFlowGraph::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) { + int32_t numFrames = numBytes / mFilterStream->getBytesPerFrame(); + mCallbackResult = mFilterStream->getDataCallback()->onAudioReady(mFilterStream, buffer, numFrames); + // TODO handle STOP from callback, process data remaining in the block adapter + return numBytes; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_DataConversionFlowGraph_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_DataConversionFlowGraph_android.h new file mode 100644 index 0000000..53baee9 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_DataConversionFlowGraph_android.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_OBOE_FLOW_GRAPH_H +#define OBOE_OBOE_FLOW_GRAPH_H + +#include +#include +#include + +#include "oboe_flowgraph_ChannelCountConverter_android.h" +#include "oboe_flowgraph_MonoToMultiConverter_android.h" +#include "oboe_flowgraph_MultiToMonoConverter_android.h" +#include "oboe_flowgraph_SampleRateConverter_android.h" +#include "oboe_oboe_Definitions_android.h" +#include "oboe_common_AudioSourceCaller_android.h" +#include "oboe_common_FixedBlockWriter_android.h" + +namespace oboe { + +class AudioStream; +class AudioSourceCaller; + +/** + * Convert PCM channels, format and sample rate for optimal latency. + */ +class DataConversionFlowGraph : public FixedBlockProcessor { +public: + + DataConversionFlowGraph() + : mBlockWriter(*this) {} + + void setSource(const void *buffer, int32_t numFrames); + + /** Connect several modules together to convert from source to sink. + * This should only be called once for each instance. + * + * @param sourceFormat + * @param sourceChannelCount + * @param sinkFormat + * @param sinkChannelCount + * @return + */ + oboe::Result configure(oboe::AudioStream *sourceStream, oboe::AudioStream *sinkStream); + + int32_t read(void *buffer, int32_t numFrames, int64_t timeoutNanos); + + int32_t write(void *buffer, int32_t numFrames); + + int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override; + + DataCallbackResult getDataCallbackResult() { + return mCallbackResult; + } + +private: + std::unique_ptr mSource; + std::unique_ptr mSourceCaller; + std::unique_ptr mMonoToMultiConverter; + std::unique_ptr mMultiToMonoConverter; + std::unique_ptr mChannelCountConverter; + std::unique_ptr mResampler; + std::unique_ptr mRateConverter; + std::unique_ptr mSink; + + FixedBlockWriter mBlockWriter; + DataCallbackResult mCallbackResult = DataCallbackResult::Continue; + AudioStream *mFilterStream = nullptr; + std::unique_ptr mAppBuffer; +}; + +} +#endif //OBOE_OBOE_FLOW_GRAPH_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FilterAudioStream_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FilterAudioStream_android.cpp new file mode 100644 index 0000000..768263f --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FilterAudioStream_android.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "oboe_common_OboeDebug_android.h" +#include "oboe_common_FilterAudioStream_android.h" + +using namespace oboe; +using namespace flowgraph; + +// Output callback uses FixedBlockReader::read() +// <= SourceFloatCaller::onProcess() +// <=== DataConversionFlowGraph::read() +// <== FilterAudioStream::onAudioReady() +// +// Output blocking uses no block adapter because AAudio can accept +// writes of any size. It uses DataConversionFlowGraph::read() <== FilterAudioStream::write() <= app +// +// Input callback uses FixedBlockWriter::write() +// <= DataConversionFlowGraph::write() +// <= FilterAudioStream::onAudioReady() +// +// Input blocking uses FixedBlockReader::read() // TODO may not need block adapter +// <= SourceFloatCaller::onProcess() +// <=== SinkFloat::read() +// <= DataConversionFlowGraph::read() +// <== FilterAudioStream::read() +// <= app + +Result FilterAudioStream::configureFlowGraph() { + mFlowGraph = std::make_unique(); + bool isOutput = getDirection() == Direction::Output; + + AudioStream *sourceStream = isOutput ? this : mChildStream.get(); + AudioStream *sinkStream = isOutput ? mChildStream.get() : this; + + mRateScaler = ((double) getSampleRate()) / mChildStream->getSampleRate(); + + return mFlowGraph->configure(sourceStream, sinkStream); +} + +// Put the data to be written at the source end of the flowgraph. +// Then read (pull) the data from the flowgraph and write it to the +// child stream. +ResultWithValue FilterAudioStream::write(const void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + int32_t framesWritten = 0; + mFlowGraph->setSource(buffer, numFrames); + while (true) { + int32_t numRead = mFlowGraph->read(mBlockingBuffer.get(), + getFramesPerBurst(), + timeoutNanoseconds); + if (numRead < 0) { + return ResultWithValue::createBasedOnSign(numRead); + } + if (numRead == 0) { + break; // finished processing the source buffer + } + auto writeResult = mChildStream->write(mBlockingBuffer.get(), + numRead, + timeoutNanoseconds); + if (!writeResult) { + return writeResult; + } + framesWritten += writeResult.value(); + } + return ResultWithValue::createBasedOnSign(framesWritten); +} + +// Read (pull) the data we want from the sink end of the flowgraph. +// The necessary data will be read from the child stream using a flowgraph callback. +ResultWithValue FilterAudioStream::read(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + int32_t framesRead = mFlowGraph->read(buffer, numFrames, timeoutNanoseconds); + return ResultWithValue::createBasedOnSign(framesRead); +} + +DataCallbackResult FilterAudioStream::onAudioReady(AudioStream *oboeStream, + void *audioData, + int32_t numFrames) { + int32_t framesProcessed; + if (oboeStream->getDirection() == Direction::Output) { + framesProcessed = mFlowGraph->read(audioData, numFrames, 0 /* timeout */); + } else { + framesProcessed = mFlowGraph->write(audioData, numFrames); + } + return (framesProcessed < numFrames) + ? DataCallbackResult::Stop + : mFlowGraph->getDataCallbackResult(); +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FilterAudioStream_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FilterAudioStream_android.h new file mode 100644 index 0000000..6cc6108 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FilterAudioStream_android.h @@ -0,0 +1,223 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_FILTER_AUDIO_STREAM_H +#define OBOE_FILTER_AUDIO_STREAM_H + +#include +#include "oboe_oboe_AudioStream_android.h" +#include "oboe_common_DataConversionFlowGraph_android.h" + +namespace oboe { + +/** + * An AudioStream that wraps another AudioStream and provides audio data conversion. + * Operations may include channel conversion, data format conversion and/or sample rate conversion. + */ +class FilterAudioStream : public AudioStream, AudioStreamCallback { +public: + + /** + * Construct an `AudioStream` using the given `AudioStreamBuilder` and a child AudioStream. + * + * This should only be called internally by AudioStreamBuilder. + * Ownership of childStream will be passed to this object. + * + * @param builder containing all the stream's attributes + */ + FilterAudioStream(const AudioStreamBuilder &builder, AudioStream *childStream) + : AudioStream(builder) + , mChildStream(childStream) { + // Intercept the callback if used. + if (builder.isErrorCallbackSpecified()) { + mErrorCallback = mChildStream->swapErrorCallback(this); + } + if (builder.isDataCallbackSpecified()) { + mDataCallback = mChildStream->swapDataCallback(this); + } else { + const int size = childStream->getFramesPerBurst() * childStream->getBytesPerFrame(); + mBlockingBuffer = std::make_unique(size); + } + + // Copy parameters that may not match builder. + mBufferCapacityInFrames = mChildStream->getBufferCapacityInFrames(); + mPerformanceMode = mChildStream->getPerformanceMode(); + mInputPreset = mChildStream->getInputPreset(); + mFramesPerBurst = mChildStream->getFramesPerBurst(); + mDeviceId = mChildStream->getDeviceId(); + } + + virtual ~FilterAudioStream() = default; + + AudioStream *getChildStream() const { + return mChildStream.get(); + } + + Result configureFlowGraph(); + + // Close child and parent. + Result close() override { + const Result result1 = mChildStream->close(); + const Result result2 = AudioStream::close(); + return (result1 != Result::OK ? result1 : result2); + } + + /** + * Start the stream asynchronously. Returns immediately (does not block). Equivalent to calling + * `start(0)`. + */ + Result requestStart() override { + return mChildStream->requestStart(); + } + + /** + * Pause the stream asynchronously. Returns immediately (does not block). Equivalent to calling + * `pause(0)`. + */ + Result requestPause() override { + return mChildStream->requestPause(); + } + + /** + * Flush the stream asynchronously. Returns immediately (does not block). Equivalent to calling + * `flush(0)`. + */ + Result requestFlush() override { + return mChildStream->requestFlush(); + } + + /** + * Stop the stream asynchronously. Returns immediately (does not block). Equivalent to calling + * `stop(0)`. + */ + Result requestStop() override { + return mChildStream->requestStop(); + } + + ResultWithValue read(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) override; + + ResultWithValue write(const void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) override; + + StreamState getState() override { + return mChildStream->getState(); + } + + Result waitForStateChange( + StreamState inputState, + StreamState *nextState, + int64_t timeoutNanoseconds) override { + return mChildStream->waitForStateChange(inputState, nextState, timeoutNanoseconds); + } + + bool isXRunCountSupported() const override { + return mChildStream->isXRunCountSupported(); + } + + AudioApi getAudioApi() const override { + return mChildStream->getAudioApi(); + } + + void updateFramesWritten() override { + // TODO for output, just count local writes? + mFramesWritten = static_cast(mChildStream->getFramesWritten() * mRateScaler); + } + + void updateFramesRead() override { + // TODO for input, just count local reads? + mFramesRead = static_cast(mChildStream->getFramesRead() * mRateScaler); + } + + void *getUnderlyingStream() const override { + return mChildStream->getUnderlyingStream(); + } + + ResultWithValue setBufferSizeInFrames(int32_t requestedFrames) override { + return mChildStream->setBufferSizeInFrames(requestedFrames); + } + + int32_t getBufferSizeInFrames() override { + mBufferSizeInFrames = mChildStream->getBufferSizeInFrames(); + return mBufferSizeInFrames; + } + + ResultWithValue getXRunCount() override { + return mChildStream->getXRunCount(); + } + + ResultWithValue calculateLatencyMillis() override { + // This will automatically include the latency of the flowgraph? + return mChildStream->calculateLatencyMillis(); + } + + Result getTimestamp(clockid_t clockId, + int64_t *framePosition, + int64_t *timeNanoseconds) override { + int64_t childPosition = 0; + Result result = mChildStream->getTimestamp(clockId, &childPosition, timeNanoseconds); + // It is OK if framePosition is null. + if (framePosition) { + *framePosition = childPosition * mRateScaler; + } + return result; + } + + DataCallbackResult onAudioReady(AudioStream *oboeStream, + void *audioData, + int32_t numFrames) override; + + bool onError(AudioStream * /*audioStream*/, Result error) override { + if (mErrorCallback != nullptr) { + return mErrorCallback->onError(this, error); + } + return false; + } + + void onErrorBeforeClose(AudioStream * /*oboeStream*/, Result error) override { + if (mErrorCallback != nullptr) { + mErrorCallback->onErrorBeforeClose(this, error); + } + } + + void onErrorAfterClose(AudioStream * /*oboeStream*/, Result error) override { + // Close this parent stream because the callback will only close the child. + AudioStream::close(); + if (mErrorCallback != nullptr) { + mErrorCallback->onErrorAfterClose(this, error); + } + } + + /** + * @return last result passed from an error callback + */ + oboe::Result getLastErrorCallbackResult() const override { + return mChildStream->getLastErrorCallbackResult(); + } + +private: + + std::unique_ptr mChildStream; // this stream wraps the child stream + std::unique_ptr mFlowGraph; // for converting data + std::unique_ptr mBlockingBuffer; // temp buffer for write() + double mRateScaler = 1.0; // ratio parent/child sample rates +}; + +} // oboe + +#endif //OBOE_FILTER_AUDIO_STREAM_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockAdapter_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockAdapter_android.cpp new file mode 100644 index 0000000..048af8e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockAdapter_android.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "oboe_common_FixedBlockAdapter_android.h" + +FixedBlockAdapter::~FixedBlockAdapter() { +} + +int32_t FixedBlockAdapter::open(int32_t bytesPerFixedBlock) +{ + mSize = bytesPerFixedBlock; + mStorage = std::make_unique(bytesPerFixedBlock); + mPosition = 0; + return 0; +} + +int32_t FixedBlockAdapter::close() +{ + mStorage.reset(nullptr); + mSize = 0; + mPosition = 0; + return 0; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockAdapter_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockAdapter_android.h new file mode 100644 index 0000000..76e961c --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockAdapter_android.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAUDIO_FIXED_BLOCK_ADAPTER_H +#define AAUDIO_FIXED_BLOCK_ADAPTER_H + +#include +#include +#include + +/** + * Interface for a class that needs fixed-size blocks. + */ +class FixedBlockProcessor { +public: + virtual ~FixedBlockProcessor() = default; + /** + * + * @param buffer Pointer to first byte of data. + * @param numBytes This will be a fixed size specified in FixedBlockAdapter::open(). + * @return Number of bytes processed or a negative error code. + */ + virtual int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) = 0; +}; + +/** + * Base class for a variable-to-fixed-size block adapter. + */ +class FixedBlockAdapter +{ +public: + FixedBlockAdapter(FixedBlockProcessor &fixedBlockProcessor) + : mFixedBlockProcessor(fixedBlockProcessor) {} + + virtual ~FixedBlockAdapter(); + + /** + * Allocate internal resources needed for buffering data. + */ + virtual int32_t open(int32_t bytesPerFixedBlock); + + /** + * Free internal resources. + */ + int32_t close(); + +protected: + FixedBlockProcessor &mFixedBlockProcessor; + std::unique_ptr mStorage; // Store data here while assembling buffers. + int32_t mSize = 0; // Size in bytes of the fixed size buffer. + int32_t mPosition = 0; // Offset of the last byte read or written. +}; + +#endif /* AAUDIO_FIXED_BLOCK_ADAPTER_H */ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockReader_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockReader_android.cpp new file mode 100644 index 0000000..6a94a9a --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockReader_android.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "oboe_common_FixedBlockAdapter_android.h" + +#include "oboe_common_FixedBlockReader_android.h" + + +FixedBlockReader::FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor) + : FixedBlockAdapter(fixedBlockProcessor) { + mPosition = mSize; +} + +int32_t FixedBlockReader::open(int32_t bytesPerFixedBlock) { + int32_t result = FixedBlockAdapter::open(bytesPerFixedBlock); + mPosition = 0; + mValid = 0; + return result; +} + +int32_t FixedBlockReader::readFromStorage(uint8_t *buffer, int32_t numBytes) { + int32_t bytesToRead = numBytes; + int32_t dataAvailable = mValid - mPosition; + if (bytesToRead > dataAvailable) { + bytesToRead = dataAvailable; + } + memcpy(buffer, mStorage.get() + mPosition, bytesToRead); + mPosition += bytesToRead; + return bytesToRead; +} + +int32_t FixedBlockReader::read(uint8_t *buffer, int32_t numBytes) { + int32_t bytesRead; + int32_t bytesLeft = numBytes; + while(bytesLeft > 0) { + if (mPosition < mValid) { + // Use up bytes currently in storage. + bytesRead = readFromStorage(buffer, bytesLeft); + buffer += bytesRead; + bytesLeft -= bytesRead; + } else if (bytesLeft >= mSize) { + // Nothing in storage. Read through if enough for a complete block. + bytesRead = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize); + if (bytesRead < 0) return bytesRead; + buffer += bytesRead; + bytesLeft -= bytesRead; + } else { + // Just need a partial block so we have to reload storage. + bytesRead = mFixedBlockProcessor.onProcessFixedBlock(mStorage.get(), mSize); + if (bytesRead < 0) return bytesRead; + mPosition = 0; + mValid = bytesRead; + if (bytesRead == 0) break; + } + } + return numBytes - bytesLeft; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockReader_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockReader_android.h new file mode 100644 index 0000000..ffa1eca --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockReader_android.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAUDIO_FIXED_BLOCK_READER_H +#define AAUDIO_FIXED_BLOCK_READER_H + +#include + +#include "oboe_common_FixedBlockAdapter_android.h" + +/** + * Read from a fixed-size block to a variable sized block. + * + * This can be used to convert a pull data flow from fixed sized buffers to variable sized buffers. + * An example would be an audio output callback that reads from the app. + */ +class FixedBlockReader : public FixedBlockAdapter +{ +public: + FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor); + + virtual ~FixedBlockReader() = default; + + int32_t open(int32_t bytesPerFixedBlock) override; + + /** + * Read into a variable sized block. + * + * Note that if the fixed-sized blocks must be aligned, then the variable-sized blocks + * must have the same alignment. + * For example, if the fixed-size blocks must be a multiple of 8, then the variable-sized + * blocks must also be a multiple of 8. + * + * @param buffer + * @param numBytes + * @return Number of bytes read or a negative error code. + */ + int32_t read(uint8_t *buffer, int32_t numBytes); + +private: + int32_t readFromStorage(uint8_t *buffer, int32_t numBytes); + + int32_t mValid = 0; // Number of valid bytes in mStorage. +}; + + +#endif /* AAUDIO_FIXED_BLOCK_READER_H */ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockWriter_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockWriter_android.cpp new file mode 100644 index 0000000..1ef2a6e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockWriter_android.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "oboe_common_FixedBlockAdapter_android.h" +#include "oboe_common_FixedBlockWriter_android.h" + +FixedBlockWriter::FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor) + : FixedBlockAdapter(fixedBlockProcessor) {} + + +int32_t FixedBlockWriter::writeToStorage(uint8_t *buffer, int32_t numBytes) { + int32_t bytesToStore = numBytes; + int32_t roomAvailable = mSize - mPosition; + if (bytesToStore > roomAvailable) { + bytesToStore = roomAvailable; + } + memcpy(mStorage.get() + mPosition, buffer, bytesToStore); + mPosition += bytesToStore; + return bytesToStore; +} + +int32_t FixedBlockWriter::write(uint8_t *buffer, int32_t numBytes) { + int32_t bytesLeft = numBytes; + + // If we already have data in storage then add to it. + if (mPosition > 0) { + int32_t bytesWritten = writeToStorage(buffer, bytesLeft); + buffer += bytesWritten; + bytesLeft -= bytesWritten; + // If storage full then flush it out + if (mPosition == mSize) { + bytesWritten = mFixedBlockProcessor.onProcessFixedBlock(mStorage.get(), mSize); + if (bytesWritten < 0) return bytesWritten; + mPosition = 0; + if (bytesWritten < mSize) { + // Only some of the data was written! This should not happen. + return -1; + } + } + } + + // Write through if enough for a complete block. + while(bytesLeft > mSize) { + int32_t bytesWritten = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize); + if (bytesWritten < 0) return bytesWritten; + buffer += bytesWritten; + bytesLeft -= bytesWritten; + } + + // Save any remaining partial blocks for next time. + if (bytesLeft > 0) { + int32_t bytesWritten = writeToStorage(buffer, bytesLeft); + bytesLeft -= bytesWritten; + } + + return numBytes - bytesLeft; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockWriter_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockWriter_android.h new file mode 100644 index 0000000..b36ab21 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_FixedBlockWriter_android.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAUDIO_FIXED_BLOCK_WRITER_H +#define AAUDIO_FIXED_BLOCK_WRITER_H + +#include + +#include "oboe_common_FixedBlockAdapter_android.h" + +/** + * This can be used to convert a push data flow from variable sized buffers to fixed sized buffers. + * An example would be an audio input callback. + */ +class FixedBlockWriter : public FixedBlockAdapter +{ +public: + FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor); + + virtual ~FixedBlockWriter() = default; + + /** + * Write from a variable sized block. + * + * Note that if the fixed-sized blocks must be aligned, then the variable-sized blocks + * must have the same alignment. + * For example, if the fixed-size blocks must be a multiple of 8, then the variable-sized + * blocks must also be a multiple of 8. + * + * @param buffer + * @param numBytes + * @return Number of bytes written or a negative error code. + */ + int32_t write(uint8_t *buffer, int32_t numBytes); + +private: + + int32_t writeToStorage(uint8_t *buffer, int32_t numBytes); +}; + +#endif /* AAUDIO_FIXED_BLOCK_WRITER_H */ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_LatencyTuner_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_LatencyTuner_android.cpp new file mode 100644 index 0000000..8aca447 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_LatencyTuner_android.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "oboe_oboe_LatencyTuner_android.h" + +using namespace oboe; + +LatencyTuner::LatencyTuner(AudioStream &stream) + : LatencyTuner(stream, stream.getBufferCapacityInFrames()) { +} + +LatencyTuner::LatencyTuner(oboe::AudioStream &stream, int32_t maximumBufferSize) + : mStream(stream) + , mMaxBufferSize(maximumBufferSize) { + int32_t burstSize = stream.getFramesPerBurst(); + setMinimumBufferSize(kDefaultNumBursts * burstSize); + setBufferSizeIncrement(burstSize); + reset(); +} + +Result LatencyTuner::tune() { + if (mState == State::Unsupported) { + return Result::ErrorUnimplemented; + } + + Result result = Result::OK; + + // Process reset requests. + int32_t numRequests = mLatencyTriggerRequests.load(); + if (numRequests != mLatencyTriggerResponses.load()) { + mLatencyTriggerResponses.store(numRequests); + reset(); + } + + // Set state to Active if the idle countdown has reached zero. + if (mState == State::Idle && --mIdleCountDown <= 0) { + mState = State::Active; + } + + // When state is Active attempt to change the buffer size if the number of xRuns has increased. + if (mState == State::Active) { + + auto xRunCountResult = mStream.getXRunCount(); + if (xRunCountResult == Result::OK) { + if ((xRunCountResult.value() - mPreviousXRuns) > 0) { + mPreviousXRuns = xRunCountResult.value(); + int32_t oldBufferSize = mStream.getBufferSizeInFrames(); + int32_t requestedBufferSize = oldBufferSize + getBufferSizeIncrement(); + + // Do not request more than the maximum buffer size (which was either user-specified + // or was from stream->getBufferCapacityInFrames()) + if (requestedBufferSize > mMaxBufferSize) requestedBufferSize = mMaxBufferSize; + + // Note that this will not allocate more memory. It simply determines + // how much of the existing buffer capacity will be used. The size will be + // clipped to the bufferCapacity by AAudio. + auto setBufferResult = mStream.setBufferSizeInFrames(requestedBufferSize); + if (setBufferResult != Result::OK) { + result = setBufferResult; + mState = State::Unsupported; + } else if (setBufferResult.value() == oldBufferSize) { + mState = State::AtMax; + } + } + } else { + mState = State::Unsupported; + } + } + + if (mState == State::Unsupported) { + result = Result::ErrorUnimplemented; + } + + if (mState == State::AtMax) { + result = Result::OK; + } + return result; +} + +void LatencyTuner::requestReset() { + if (mState != State::Unsupported) { + mLatencyTriggerRequests++; + } +} + +void LatencyTuner::reset() { + mState = State::Idle; + mIdleCountDown = kIdleCount; + // Set to minimal latency + mStream.setBufferSizeInFrames(getMinimumBufferSize()); +} + +bool LatencyTuner::isAtMaximumBufferSize() { + return mState == State::AtMax; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_MonotonicCounter_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_MonotonicCounter_android.h new file mode 100644 index 0000000..00c979c --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_MonotonicCounter_android.h @@ -0,0 +1,112 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMON_MONOTONIC_COUNTER_H +#define COMMON_MONOTONIC_COUNTER_H + +#include + +/** + * Maintain a 64-bit monotonic counter. + * Can be used to track a 32-bit counter that wraps or gets reset. + * + * Note that this is not atomic and has no interior locks. + * A caller will need to provide their own exterior locking + * if they need to use it from multiple threads. + */ +class MonotonicCounter { + +public: + MonotonicCounter() {} + virtual ~MonotonicCounter() {} + + /** + * @return current value of the counter + */ + int64_t get() const { + return mCounter64; + } + + /** + * set the current value of the counter + */ + void set(int64_t counter) { + mCounter64 = counter; + } + + /** + * Advance the counter if delta is positive. + * @return current value of the counter + */ + int64_t increment(int64_t delta) { + if (delta > 0) { + mCounter64 += delta; + } + return mCounter64; + } + + /** + * Advance the 64-bit counter if (current32 - previousCurrent32) > 0. + * This can be used to convert a 32-bit counter that may be wrapping into + * a monotonic 64-bit counter. + * + * This counter32 should NOT be allowed to advance by more than 0x7FFFFFFF between calls. + * Think of the wrapping counter like a sine wave. If the frequency of the signal + * is more than half the sampling rate (Nyquist rate) then you cannot measure it properly. + * If the counter wraps around every 24 hours then we should measure it with a period + * of less than 12 hours. + * + * @return current value of the 64-bit counter + */ + int64_t update32(int32_t counter32) { + int32_t delta = counter32 - mCounter32; + // protect against the mCounter64 going backwards + if (delta > 0) { + mCounter64 += delta; + mCounter32 = counter32; + } + return mCounter64; + } + + /** + * Reset the stored value of the 32-bit counter. + * This is used if your counter32 has been reset to zero. + */ + void reset32() { + mCounter32 = 0; + } + + /** + * Round 64-bit counter up to a multiple of the period. + * + * The period must be positive. + * + * @param period might be, for example, a buffer capacity + */ + void roundUp64(int32_t period) { + if (period > 0) { + int64_t numPeriods = (mCounter64 + period - 1) / period; + mCounter64 = numPeriods * period; + } + } + +private: + int64_t mCounter64 = 0; + int32_t mCounter32 = 0; +}; + + +#endif //COMMON_MONOTONIC_COUNTER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_OboeDebug_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_OboeDebug_android.h new file mode 100644 index 0000000..dc7434c --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_OboeDebug_android.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef OBOE_DEBUG_H +#define OBOE_DEBUG_H + +#include + +#ifndef MODULE_NAME +#define MODULE_NAME "OboeAudio" +#endif + +// Always log INFO and errors. +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, MODULE_NAME, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, MODULE_NAME, __VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, MODULE_NAME, __VA_ARGS__) + +#if OBOE_ENABLE_LOGGING +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, MODULE_NAME, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, MODULE_NAME, __VA_ARGS__) +#else +#define LOGV(...) +#define LOGD(...) +#endif + +#endif //OBOE_DEBUG_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_OboeExtensions_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_OboeExtensions_android.cpp new file mode 100644 index 0000000..5ef1eb3 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_OboeExtensions_android.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "oboe_oboe_OboeExtensions_android.h" +#include "oboe_aaudio_AAudioExtensions_android.h" + +using namespace oboe; + +bool OboeExtensions::isMMapSupported(){ + return AAudioExtensions::getInstance().isMMapSupported(); +} + +bool OboeExtensions::isMMapEnabled(){ + return AAudioExtensions::getInstance().isMMapEnabled(); +} + +int32_t OboeExtensions::setMMapEnabled(bool enabled){ + return AAudioExtensions::getInstance().setMMapEnabled(enabled); +} + +bool OboeExtensions::isMMapUsed(oboe::AudioStream *oboeStream){ + return AAudioExtensions::getInstance().isMMapUsed(oboeStream); +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_QuirksManager_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_QuirksManager_android.cpp new file mode 100644 index 0000000..ba25d1a --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_QuirksManager_android.cpp @@ -0,0 +1,304 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "oboe_oboe_AudioStreamBuilder_android.h" +#include "oboe_oboe_Oboe_android.h" + +#include "oboe_common_OboeDebug_android.h" +#include "oboe_common_QuirksManager_android.h" + +using namespace oboe; + +int32_t QuirksManager::DeviceQuirks::clipBufferSize(AudioStream &stream, + int32_t requestedSize) { + if (!OboeGlobals::areWorkaroundsEnabled()) { + return requestedSize; + } + int bottomMargin = kDefaultBottomMarginInBursts; + int topMargin = kDefaultTopMarginInBursts; + if (isMMapUsed(stream)) { + if (stream.getSharingMode() == SharingMode::Exclusive) { + bottomMargin = getExclusiveBottomMarginInBursts(); + topMargin = getExclusiveTopMarginInBursts(); + } + } else { + bottomMargin = kLegacyBottomMarginInBursts; + } + + int32_t burst = stream.getFramesPerBurst(); + int32_t minSize = bottomMargin * burst; + int32_t adjustedSize = requestedSize; + if (adjustedSize < minSize ) { + adjustedSize = minSize; + } else { + int32_t maxSize = stream.getBufferCapacityInFrames() - (topMargin * burst); + if (adjustedSize > maxSize ) { + adjustedSize = maxSize; + } + } + return adjustedSize; +} + +bool QuirksManager::DeviceQuirks::isAAudioMMapPossible(const AudioStreamBuilder &builder) const { + bool isSampleRateCompatible = + builder.getSampleRate() == oboe::Unspecified + || builder.getSampleRate() == kCommonNativeRate + || builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None; + return builder.getPerformanceMode() == PerformanceMode::LowLatency + && isSampleRateCompatible + && builder.getChannelCount() <= kChannelCountStereo; +} + +bool QuirksManager::DeviceQuirks::shouldConvertFloatToI16ForOutputStreams() { + std::string productManufacturer = getPropertyString("ro.product.manufacturer"); + if (getSdkVersion() < __ANDROID_API_L__) { + return true; + } else if ((productManufacturer == "vivo") && (getSdkVersion() < __ANDROID_API_M__)) { + return true; + } + return false; +} + +/** + * This is for Samsung Exynos quirks. Samsung Mobile uses Qualcomm chips so + * the QualcommDeviceQuirks would apply. + */ +class SamsungExynosDeviceQuirks : public QuirksManager::DeviceQuirks { +public: + SamsungExynosDeviceQuirks() { + std::string chipname = getPropertyString("ro.hardware.chipname"); + isExynos9810 = (chipname == "exynos9810"); + isExynos990 = (chipname == "exynos990"); + isExynos850 = (chipname == "exynos850"); + + mBuildChangelist = getPropertyInteger("ro.build.changelist", 0); + } + + virtual ~SamsungExynosDeviceQuirks() = default; + + int32_t getExclusiveBottomMarginInBursts() const override { + return kBottomMargin; + } + + int32_t getExclusiveTopMarginInBursts() const override { + return kTopMargin; + } + + // See Oboe issues #824 and #1247 for more information. + bool isMonoMMapActuallyStereo() const override { + return isExynos9810 || isExynos850; // TODO We can make this version specific if it gets fixed. + } + + bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const override { + return DeviceQuirks::isAAudioMMapPossible(builder) + // Samsung says they use Legacy for Camcorder + && builder.getInputPreset() != oboe::InputPreset::Camcorder; + } + + bool isMMapSafe(const AudioStreamBuilder &builder) override { + const bool isInput = builder.getDirection() == Direction::Input; + // This detects b/159066712 , S20 LSI has corrupt low latency audio recording + // and turns off MMAP. + // See also https://github.com/google/oboe/issues/892 + bool isRecordingCorrupted = isInput + && isExynos990 + && mBuildChangelist < 19350896; + + // Certain S9+ builds record silence when using MMAP and not using the VoiceCommunication + // preset. + // See https://github.com/google/oboe/issues/1110 + bool wouldRecordSilence = isInput + && isExynos9810 + && mBuildChangelist <= 18847185 + && (builder.getInputPreset() != InputPreset::VoiceCommunication); + + if (wouldRecordSilence){ + LOGI("QuirksManager::%s() Requested stream configuration would result in silence on " + "this device. Switching off MMAP.", __func__); + } + + return !isRecordingCorrupted && !wouldRecordSilence; + } + +private: + // Stay farther away from DSP position on Exynos devices. + static constexpr int32_t kBottomMargin = 2; + static constexpr int32_t kTopMargin = 1; + bool isExynos9810 = false; + bool isExynos990 = false; + bool isExynos850 = false; + int mBuildChangelist = 0; +}; + +class QualcommDeviceQuirks : public QuirksManager::DeviceQuirks { +public: + QualcommDeviceQuirks() { + std::string modelName = getPropertyString("ro.soc.model"); + isSM8150 = (modelName == "SDM8150"); + } + + virtual ~QualcommDeviceQuirks() = default; + + int32_t getExclusiveBottomMarginInBursts() const override { + return kBottomMargin; + } + + bool isMMapSafe(const AudioStreamBuilder &builder) override { + // See https://github.com/google/oboe/issues/1121#issuecomment-897957749 + bool isMMapBroken = false; + if (isSM8150 && (getSdkVersion() <= __ANDROID_API_P__)) { + LOGI("QuirksManager::%s() MMAP not actually supported on this chip." + " Switching off MMAP.", __func__); + isMMapBroken = true; + } + + return !isMMapBroken; + } + +private: + bool isSM8150 = false; + static constexpr int32_t kBottomMargin = 1; +}; + +QuirksManager::QuirksManager() { + std::string productManufacturer = getPropertyString("ro.product.manufacturer"); + if (productManufacturer == "samsung") { + std::string arch = getPropertyString("ro.arch"); + bool isExynos = (arch.rfind("exynos", 0) == 0); // starts with? + if (isExynos) { + mDeviceQuirks = std::make_unique(); + } + } + if (!mDeviceQuirks) { + std::string socManufacturer = getPropertyString("ro.soc.manufacturer"); + if (socManufacturer == "Qualcomm") { + // This may include Samsung Mobile devices. + mDeviceQuirks = std::make_unique(); + } else { + mDeviceQuirks = std::make_unique(); + } + } +} + +bool QuirksManager::isConversionNeeded( + const AudioStreamBuilder &builder, + AudioStreamBuilder &childBuilder) { + bool conversionNeeded = false; + const bool isLowLatency = builder.getPerformanceMode() == PerformanceMode::LowLatency; + const bool isInput = builder.getDirection() == Direction::Input; + const bool isFloat = builder.getFormat() == AudioFormat::Float; + + // There are multiple bugs involving using callback with a specified callback size. + // Issue #778: O to Q had a problem with Legacy INPUT streams for FLOAT streams + // and a specified callback size. It would assert because of a bad buffer size. + // + // Issue #973: O to R had a problem with Legacy output streams using callback and a specified callback size. + // An AudioTrack stream could still be running when the AAudio FixedBlockReader was closed. + // Internally b/161914201#comment25 + // + // Issue #983: O to R would glitch if the framesPerCallback was too small. + // + // Most of these problems were related to Legacy stream. MMAP was OK. But we don't + // know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe. + if (OboeGlobals::areWorkaroundsEnabled() + && builder.willUseAAudio() + && builder.isDataCallbackSpecified() + && builder.getFramesPerDataCallback() != 0 + && getSdkVersion() <= __ANDROID_API_R__) { + LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__); + childBuilder.setFramesPerCallback(oboe::Unspecified); + conversionNeeded = true; + } + + // If a SAMPLE RATE is specified for low latency, let the native code choose an optimal rate. + // This isn't really a workaround. It is an Oboe feature that is convenient to place here. + // TODO There may be a problem if the devices supports low latency + // at a higher rate than the default. + if (builder.getSampleRate() != oboe::Unspecified + && builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None + && isLowLatency + ) { + childBuilder.setSampleRate(oboe::Unspecified); // native API decides the best sample rate + conversionNeeded = true; + } + + // Data Format + // OpenSL ES and AAudio before P do not support FAST path for FLOAT capture. + if (OboeGlobals::areWorkaroundsEnabled() + && isFloat + && isInput + && builder.isFormatConversionAllowed() + && isLowLatency + && (!builder.willUseAAudio() || (getSdkVersion() < __ANDROID_API_P__)) + ) { + childBuilder.setFormat(AudioFormat::I16); // needed for FAST track + conversionNeeded = true; + LOGI("QuirksManager::%s() forcing internal format to I16 for low latency", __func__); + } + + // Add quirk for float output when needed. + if (OboeGlobals::areWorkaroundsEnabled() + && isFloat + && !isInput + && builder.isFormatConversionAllowed() + && mDeviceQuirks->shouldConvertFloatToI16ForOutputStreams() + ) { + childBuilder.setFormat(AudioFormat::I16); + conversionNeeded = true; + LOGI("QuirksManager::%s() float was requested but not supported on pre-L devices " + "and some devices like Vivo devices may have issues on L devices, " + "creating an underlying I16 stream and using format conversion to provide a float " + "stream", __func__); + } + + // Channel Count conversions + if (OboeGlobals::areWorkaroundsEnabled() + && builder.isChannelConversionAllowed() + && builder.getChannelCount() == kChannelCountStereo + && isInput + && isLowLatency + && (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__)) + ) { + // Workaround for heap size regression in O. + // b/66967812 AudioRecord does not allow FAST track for stereo capture in O + childBuilder.setChannelCount(kChannelCountMono); + conversionNeeded = true; + LOGI("QuirksManager::%s() using mono internally for low latency on O", __func__); + } else if (OboeGlobals::areWorkaroundsEnabled() + && builder.getChannelCount() == kChannelCountMono + && isInput + && mDeviceQuirks->isMonoMMapActuallyStereo() + && builder.willUseAAudio() + // Note: we might use this workaround on a device that supports + // MMAP but will use Legacy for this stream. But this will only happen + // on devices that have the broken mono. + && mDeviceQuirks->isAAudioMMapPossible(builder) + ) { + // Workaround for mono actually running in stereo mode. + childBuilder.setChannelCount(kChannelCountStereo); // Use stereo and extract first channel. + conversionNeeded = true; + LOGI("QuirksManager::%s() using stereo internally to avoid broken mono", __func__); + } + // Note that MMAP does not support mono in 8.1. But that would only matter on Pixel 1 + // phones and they have almost all been updated to 9.0. + + return conversionNeeded; +} + +bool QuirksManager::isMMapSafe(AudioStreamBuilder &builder) { + if (!OboeGlobals::areWorkaroundsEnabled()) return true; + return mDeviceQuirks->isMMapSafe(builder); +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_QuirksManager_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_QuirksManager_android.h new file mode 100644 index 0000000..c1ead82 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_QuirksManager_android.h @@ -0,0 +1,134 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_QUIRKS_MANAGER_H +#define OBOE_QUIRKS_MANAGER_H + +#include +#include "oboe_oboe_AudioStreamBuilder_android.h" +#include "oboe_aaudio_AudioStreamAAudio_android.h" + +#ifndef __ANDROID_API_R__ +#define __ANDROID_API_R__ 30 +#endif + +namespace oboe { + +/** + * INTERNAL USE ONLY. + * + * Based on manufacturer, model and Android version number + * decide whether data conversion needs to occur. + * + * This also manages device and version specific workarounds. + */ + +class QuirksManager { +public: + + static QuirksManager &getInstance() { + static QuirksManager instance; // singleton + return instance; + } + + QuirksManager(); + virtual ~QuirksManager() = default; + + /** + * Do we need to do channel, format or rate conversion to provide a low latency + * stream for this builder? If so then provide a builder for the native child stream + * that will be used to get low latency. + * + * @param builder builder provided by application + * @param childBuilder modified builder appropriate for the underlying device + * @return true if conversion is needed + */ + bool isConversionNeeded(const AudioStreamBuilder &builder, AudioStreamBuilder &childBuilder); + + static bool isMMapUsed(AudioStream &stream) { + bool answer = false; + if (stream.getAudioApi() == AudioApi::AAudio) { + AudioStreamAAudio *streamAAudio = + reinterpret_cast(&stream); + answer = streamAAudio->isMMapUsed(); + } + return answer; + } + + virtual int32_t clipBufferSize(AudioStream &stream, int32_t bufferSize) { + return mDeviceQuirks->clipBufferSize(stream, bufferSize); + } + + class DeviceQuirks { + public: + virtual ~DeviceQuirks() = default; + + /** + * Restrict buffer size. This is mainly to avoid glitches caused by MMAP + * timestamp inaccuracies. + * @param stream + * @param requestedSize + * @return + */ + int32_t clipBufferSize(AudioStream &stream, int32_t requestedSize); + + // Exclusive MMAP streams can have glitches because they are using a timing + // model of the DSP to control IO instead of direct synchronization. + virtual int32_t getExclusiveBottomMarginInBursts() const { + return kDefaultBottomMarginInBursts; + } + + virtual int32_t getExclusiveTopMarginInBursts() const { + return kDefaultTopMarginInBursts; + } + + // On some devices, you can open a mono stream but it is actually running in stereo! + virtual bool isMonoMMapActuallyStereo() const { + return false; + } + + virtual bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const; + + virtual bool isMMapSafe(const AudioStreamBuilder & /* builder */ ) { + return true; + } + + // On some devices, Float does not work so it should be converted to I16. + static bool shouldConvertFloatToI16ForOutputStreams(); + + static constexpr int32_t kDefaultBottomMarginInBursts = 0; + static constexpr int32_t kDefaultTopMarginInBursts = 0; + + // For Legacy streams, do not let the buffer go below one burst. + // b/129545119 | AAudio Legacy allows setBufferSizeInFrames too low + // Fixed in Q + static constexpr int32_t kLegacyBottomMarginInBursts = 1; + static constexpr int32_t kCommonNativeRate = 48000; // very typical native sample rate + }; + + bool isMMapSafe(AudioStreamBuilder &builder); + +private: + + static constexpr int32_t kChannelCountMono = 1; + static constexpr int32_t kChannelCountStereo = 2; + + std::unique_ptr mDeviceQuirks{}; + +}; + +} +#endif //OBOE_QUIRKS_MANAGER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceFloatCaller_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceFloatCaller_android.cpp new file mode 100644 index 0000000..bcba239 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceFloatCaller_android.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_common_SourceFloatCaller_android.h" + +using namespace oboe; +using namespace flowgraph; + +int32_t SourceFloatCaller::onProcess(int32_t numFrames) { + int32_t numBytes = mStream->getBytesPerFrame() * numFrames; + int32_t bytesRead = mBlockReader.read((uint8_t *) output.getBuffer(), numBytes); + int32_t framesRead = bytesRead / mStream->getBytesPerFrame(); + return framesRead; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceFloatCaller_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceFloatCaller_android.h new file mode 100644 index 0000000..52924f1 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceFloatCaller_android.h @@ -0,0 +1,44 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_SOURCE_FLOAT_CALLER_H +#define OBOE_SOURCE_FLOAT_CALLER_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_common_AudioSourceCaller_android.h" +#include "oboe_common_FixedBlockReader_android.h" + +namespace oboe { +/** + * AudioSource that uses callback to get more float data. + */ +class SourceFloatCaller : public AudioSourceCaller { +public: + SourceFloatCaller(int32_t channelCount, int32_t framesPerCallback) + : AudioSourceCaller(channelCount, framesPerCallback, (int32_t)sizeof(float)) {} + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceFloatCaller"; + } +}; + +} +#endif //OBOE_SOURCE_FLOAT_CALLER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI16Caller_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI16Caller_android.cpp new file mode 100644 index 0000000..716469d --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI16Caller_android.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_common_SourceI16Caller_android.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace oboe; +using namespace flowgraph; + +int32_t SourceI16Caller::onProcess(int32_t numFrames) { + int32_t numBytes = mStream->getBytesPerFrame() * numFrames; + int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes); + int32_t framesRead = bytesRead / mStream->getBytesPerFrame(); + + float *floatData = output.getBuffer(); + const int16_t *shortData = mConversionBuffer.get(); + int32_t numSamples = framesRead * output.getSamplesPerFrame(); + +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_float_from_i16(floatData, shortData, numSamples); +#else + for (int i = 0; i < numSamples; i++) { + *floatData++ = *shortData++ * (1.0f / 32768); + } +#endif + + return framesRead; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI16Caller_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI16Caller_android.h new file mode 100644 index 0000000..ee6f477 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI16Caller_android.h @@ -0,0 +1,49 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_SOURCE_I16_CALLER_H +#define OBOE_SOURCE_I16_CALLER_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_common_AudioSourceCaller_android.h" +#include "oboe_common_FixedBlockReader_android.h" + +namespace oboe { +/** + * AudioSource that uses callback to get more data. + */ +class SourceI16Caller : public AudioSourceCaller { +public: + SourceI16Caller(int32_t channelCount, int32_t framesPerCallback) + : AudioSourceCaller(channelCount, framesPerCallback, sizeof(int16_t)) { + mConversionBuffer = std::make_unique(static_cast(channelCount) + * static_cast(output.getFramesPerBuffer())); + } + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceI16Caller"; + } +private: + std::unique_ptr mConversionBuffer; +}; + +} +#endif //OBOE_SOURCE_I16_CALLER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI24Caller_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI24Caller_android.cpp new file mode 100644 index 0000000..ee40b8f --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI24Caller_android.cpp @@ -0,0 +1,56 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_common_SourceI24Caller_android.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace oboe; +using namespace flowgraph; + +int32_t SourceI24Caller::onProcess(int32_t numFrames) { + int32_t numBytes = mStream->getBytesPerFrame() * numFrames; + int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes); + int32_t framesRead = bytesRead / mStream->getBytesPerFrame(); + + float *floatData = output.getBuffer(); + const uint8_t *byteData = mConversionBuffer.get(); + int32_t numSamples = framesRead * output.getSamplesPerFrame(); + +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_float_from_p24(floatData, byteData, numSamples); +#else + static const float scale = 1. / (float)(1UL << 31); + for (int i = 0; i < numSamples; i++) { + // Assemble the data assuming Little Endian format. + int32_t pad = byteData[2]; + pad <<= 8; + pad |= byteData[1]; + pad <<= 8; + pad |= byteData[0]; + pad <<= 8; // Shift to 32 bit data so the sign is correct. + byteData += kBytesPerI24Packed; + *floatData++ = pad * scale; // scale to range -1.0 to 1.0 + } +#endif + + return framesRead; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI24Caller_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI24Caller_android.h new file mode 100644 index 0000000..2cc8952 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI24Caller_android.h @@ -0,0 +1,53 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_SOURCE_I24_CALLER_H +#define OBOE_SOURCE_I24_CALLER_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_common_AudioSourceCaller_android.h" +#include "oboe_common_FixedBlockReader_android.h" + +namespace oboe { + +/** + * AudioSource that uses callback to get more data. + */ +class SourceI24Caller : public AudioSourceCaller { +public: + SourceI24Caller(int32_t channelCount, int32_t framesPerCallback) + : AudioSourceCaller(channelCount, framesPerCallback, kBytesPerI24Packed) { + mConversionBuffer = std::make_unique(static_cast(kBytesPerI24Packed) + * static_cast(channelCount) + * static_cast(output.getFramesPerBuffer())); + } + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceI24Caller"; + } + +private: + std::unique_ptr mConversionBuffer; + static constexpr int kBytesPerI24Packed = 3; +}; + +} +#endif //OBOE_SOURCE_I16_CALLER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI32Caller_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI32Caller_android.cpp new file mode 100644 index 0000000..7b33227 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI32Caller_android.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_common_SourceI32Caller_android.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace oboe; +using namespace flowgraph; + +int32_t SourceI32Caller::onProcess(int32_t numFrames) { + int32_t numBytes = mStream->getBytesPerFrame() * numFrames; + int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes); + int32_t framesRead = bytesRead / mStream->getBytesPerFrame(); + + float *floatData = output.getBuffer(); + const int32_t *intData = mConversionBuffer.get(); + int32_t numSamples = framesRead * output.getSamplesPerFrame(); + +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_float_from_i32(floatData, shortData, numSamples); +#else + for (int i = 0; i < numSamples; i++) { + *floatData++ = *intData++ * kScale; + } +#endif + + return framesRead; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI32Caller_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI32Caller_android.h new file mode 100644 index 0000000..2e2b163 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_SourceI32Caller_android.h @@ -0,0 +1,53 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_SOURCE_I32_CALLER_H +#define OBOE_SOURCE_I32_CALLER_H + +#include +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_common_AudioSourceCaller_android.h" +#include "oboe_common_FixedBlockReader_android.h" + +namespace oboe { + +/** + * AudioSource that uses callback to get more data. + */ +class SourceI32Caller : public AudioSourceCaller { +public: + SourceI32Caller(int32_t channelCount, int32_t framesPerCallback) + : AudioSourceCaller(channelCount, framesPerCallback, sizeof(int32_t)) { + mConversionBuffer = std::make_unique(static_cast(channelCount) + * static_cast(output.getFramesPerBuffer())); + } + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceI32Caller"; + } + +private: + std::unique_ptr mConversionBuffer; + static constexpr float kScale = 1.0 / (1UL << 31); +}; + +} +#endif //OBOE_SOURCE_I32_CALLER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_StabilizedCallback_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_StabilizedCallback_android.cpp new file mode 100644 index 0000000..ffca64e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_StabilizedCallback_android.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "oboe_oboe_StabilizedCallback_android.h" +#include "oboe_common_AudioClock_android.h" +#include "oboe_common_Trace_android.h" + +constexpr int32_t kLoadGenerationStepSizeNanos = 20000; +constexpr float kPercentageOfCallbackToUse = 0.8; + +using namespace oboe; + +StabilizedCallback::StabilizedCallback(AudioStreamCallback *callback) : mCallback(callback){ + Trace::initialize(); +} + +/** + * An audio callback which attempts to do work for a fixed amount of time. + * + * @param oboeStream + * @param audioData + * @param numFrames + * @return + */ +DataCallbackResult +StabilizedCallback::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) { + + int64_t startTimeNanos = AudioClock::getNanoseconds(); + + if (mFrameCount == 0){ + mEpochTimeNanos = startTimeNanos; + } + + int64_t durationSinceEpochNanos = startTimeNanos - mEpochTimeNanos; + + // In an ideal world the callback start time will be exactly the same as the duration of the + // frames already read/written into the stream. In reality the callback can start early + // or late. By finding the delta we can calculate the target duration for our stabilized + // callback. + int64_t idealStartTimeNanos = (mFrameCount * kNanosPerSecond) / oboeStream->getSampleRate(); + int64_t lateStartNanos = durationSinceEpochNanos - idealStartTimeNanos; + + if (lateStartNanos < 0){ + // This was an early start which indicates that our previous epoch was a late callback. + // Update our epoch to this more accurate time. + mEpochTimeNanos = startTimeNanos; + mFrameCount = 0; + } + + int64_t numFramesAsNanos = (numFrames * kNanosPerSecond) / oboeStream->getSampleRate(); + int64_t targetDurationNanos = static_cast( + (numFramesAsNanos * kPercentageOfCallbackToUse) - lateStartNanos); + + Trace::beginSection("Actual load"); + DataCallbackResult result = mCallback->onAudioReady(oboeStream, audioData, numFrames); + Trace::endSection(); + + int64_t executionDurationNanos = AudioClock::getNanoseconds() - startTimeNanos; + int64_t stabilizingLoadDurationNanos = targetDurationNanos - executionDurationNanos; + + Trace::beginSection("Stabilized load for %lldns", stabilizingLoadDurationNanos); + generateLoad(stabilizingLoadDurationNanos); + Trace::endSection(); + + // Wraparound: At 48000 frames per second mFrameCount wraparound will occur after 6m years, + // significantly longer than the average lifetime of an Android phone. + mFrameCount += numFrames; + return result; +} + +void StabilizedCallback::generateLoad(int64_t durationNanos) { + + int64_t currentTimeNanos = AudioClock::getNanoseconds(); + int64_t deadlineTimeNanos = currentTimeNanos + durationNanos; + + // opsPerStep gives us an estimated number of operations which need to be run to fully utilize + // the CPU for a fixed amount of time (specified by kLoadGenerationStepSizeNanos). + // After each step the opsPerStep value is re-calculated based on the actual time taken to + // execute those operations. + auto opsPerStep = (int)(mOpsPerNano * kLoadGenerationStepSizeNanos); + int64_t stepDurationNanos = 0; + int64_t previousTimeNanos = 0; + + while (currentTimeNanos <= deadlineTimeNanos){ + + for (int i = 0; i < opsPerStep; i++) cpu_relax(); + + previousTimeNanos = currentTimeNanos; + currentTimeNanos = AudioClock::getNanoseconds(); + stepDurationNanos = currentTimeNanos - previousTimeNanos; + + // Calculate exponential moving average to smooth out values, this acts as a low pass filter. + // @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + static const float kFilterCoefficient = 0.1; + auto measuredOpsPerNano = (double) opsPerStep / stepDurationNanos; + mOpsPerNano = kFilterCoefficient * measuredOpsPerNano + (1.0 - kFilterCoefficient) * mOpsPerNano; + opsPerStep = (int) (mOpsPerNano * kLoadGenerationStepSizeNanos); + } +} \ No newline at end of file diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Trace_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Trace_android.cpp new file mode 100644 index 0000000..e10c78d --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Trace_android.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_common_Trace_android.h" +#include "oboe_common_OboeDebug_android.h" + +static char buffer[256]; + +// Tracing functions +static void *(*ATrace_beginSection)(const char *sectionName); + +static void *(*ATrace_endSection)(); + +typedef void *(*fp_ATrace_beginSection)(const char *sectionName); + +typedef void *(*fp_ATrace_endSection)(); + +bool Trace::mIsTracingSupported = false; + +void Trace::beginSection(const char *format, ...){ + + if (mIsTracingSupported) { + va_list va; + va_start(va, format); + vsprintf(buffer, format, va); + ATrace_beginSection(buffer); + va_end(va); + } else { + LOGE("Tracing is either not initialized (call Trace::initialize()) " + "or not supported on this device"); + } +} + +void Trace::endSection() { + + if (mIsTracingSupported) { + ATrace_endSection(); + } +} + +void Trace::initialize() { + + // Using dlsym allows us to use tracing on API 21+ without needing android/trace.h which wasn't + // published until API 23 + void *lib = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL); + if (lib == nullptr) { + LOGE("Could not open libandroid.so to dynamically load tracing symbols"); + } else { + ATrace_beginSection = + reinterpret_cast( + dlsym(lib, "ATrace_beginSection")); + ATrace_endSection = + reinterpret_cast( + dlsym(lib, "ATrace_endSection")); + + if (ATrace_beginSection != nullptr && ATrace_endSection != nullptr){ + mIsTracingSupported = true; + } + } +} \ No newline at end of file diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Trace_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Trace_android.h new file mode 100644 index 0000000..c7965f9 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Trace_android.h @@ -0,0 +1,31 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_TRACE_H +#define OBOE_TRACE_H + +class Trace { + +public: + static void beginSection(const char *format, ...); + static void endSection(); + static void initialize(); + +private: + static bool mIsTracingSupported; +}; + +#endif //OBOE_TRACE_H \ No newline at end of file diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Utilities_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Utilities_android.cpp new file mode 100644 index 0000000..e3fbe97 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Utilities_android.cpp @@ -0,0 +1,317 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include +#include + +#ifdef __ANDROID__ +#include +#endif + +#include "oboe_oboe_AudioStream_android.h" +#include "oboe_oboe_Definitions_android.h" +#include "oboe_oboe_Utilities_android.h" + +namespace oboe { + +constexpr float kScaleI16ToFloat = (1.0f / 32768.0f); + +void convertFloatToPcm16(const float *source, int16_t *destination, int32_t numSamples) { + for (int i = 0; i < numSamples; i++) { + float fval = source[i]; + fval += 1.0; // to avoid discontinuity at 0.0 caused by truncation + fval *= 32768.0f; + auto sample = static_cast(fval); + // clip to 16-bit range + if (sample < 0) sample = 0; + else if (sample > 0x0FFFF) sample = 0x0FFFF; + sample -= 32768; // center at zero + destination[i] = static_cast(sample); + } +} + +void convertPcm16ToFloat(const int16_t *source, float *destination, int32_t numSamples) { + for (int i = 0; i < numSamples; i++) { + destination[i] = source[i] * kScaleI16ToFloat; + } +} + +int32_t convertFormatToSizeInBytes(AudioFormat format) { + int32_t size = 0; + switch (format) { + case AudioFormat::I16: + size = sizeof(int16_t); + break; + case AudioFormat::Float: + size = sizeof(float); + break; + case AudioFormat::I24: + size = 3; // packed 24-bit data + break; + case AudioFormat::I32: + size = sizeof(int32_t); + break; + default: + break; + } + return size; +} + +template<> +const char *convertToText(Result returnCode) { + switch (returnCode) { + case Result::OK: return "OK"; + case Result::ErrorDisconnected: return "ErrorDisconnected"; + case Result::ErrorIllegalArgument: return "ErrorIllegalArgument"; + case Result::ErrorInternal: return "ErrorInternal"; + case Result::ErrorInvalidState: return "ErrorInvalidState"; + case Result::ErrorInvalidHandle: return "ErrorInvalidHandle"; + case Result::ErrorUnimplemented: return "ErrorUnimplemented"; + case Result::ErrorUnavailable: return "ErrorUnavailable"; + case Result::ErrorNoFreeHandles: return "ErrorNoFreeHandles"; + case Result::ErrorNoMemory: return "ErrorNoMemory"; + case Result::ErrorNull: return "ErrorNull"; + case Result::ErrorTimeout: return "ErrorTimeout"; + case Result::ErrorWouldBlock: return "ErrorWouldBlock"; + case Result::ErrorInvalidFormat: return "ErrorInvalidFormat"; + case Result::ErrorOutOfRange: return "ErrorOutOfRange"; + case Result::ErrorNoService: return "ErrorNoService"; + case Result::ErrorInvalidRate: return "ErrorInvalidRate"; + case Result::ErrorClosed: return "ErrorClosed"; + default: return "Unrecognized result"; + } +} + +template<> +const char *convertToText(AudioFormat format) { + switch (format) { + case AudioFormat::Invalid: return "Invalid"; + case AudioFormat::Unspecified: return "Unspecified"; + case AudioFormat::I16: return "I16"; + case AudioFormat::Float: return "Float"; + case AudioFormat::I24: return "I24"; + case AudioFormat::I32: return "I32"; + default: return "Unrecognized format"; + } +} + +template<> +const char *convertToText(PerformanceMode mode) { + switch (mode) { + case PerformanceMode::LowLatency: return "LowLatency"; + case PerformanceMode::None: return "None"; + case PerformanceMode::PowerSaving: return "PowerSaving"; + default: return "Unrecognized performance mode"; + } +} + +template<> +const char *convertToText(SharingMode mode) { + switch (mode) { + case SharingMode::Exclusive: return "Exclusive"; + case SharingMode::Shared: return "Shared"; + default: return "Unrecognized sharing mode"; + } +} + +template<> +const char *convertToText(DataCallbackResult result) { + switch (result) { + case DataCallbackResult::Continue: return "Continue"; + case DataCallbackResult::Stop: return "Stop"; + default: return "Unrecognized data callback result"; + } +} + +template<> +const char *convertToText(Direction direction) { + switch (direction) { + case Direction::Input: return "Input"; + case Direction::Output: return "Output"; + default: return "Unrecognized direction"; + } +} + +template<> +const char *convertToText(StreamState state) { + switch (state) { + case StreamState::Closed: return "Closed"; + case StreamState::Closing: return "Closing"; + case StreamState::Disconnected: return "Disconnected"; + case StreamState::Flushed: return "Flushed"; + case StreamState::Flushing: return "Flushing"; + case StreamState::Open: return "Open"; + case StreamState::Paused: return "Paused"; + case StreamState::Pausing: return "Pausing"; + case StreamState::Started: return "Started"; + case StreamState::Starting: return "Starting"; + case StreamState::Stopped: return "Stopped"; + case StreamState::Stopping: return "Stopping"; + case StreamState::Uninitialized: return "Uninitialized"; + case StreamState::Unknown: return "Unknown"; + default: return "Unrecognized stream state"; + } +} + +template<> +const char *convertToText(AudioApi audioApi) { + + switch (audioApi) { + case AudioApi::Unspecified: return "Unspecified"; + case AudioApi::OpenSLES: return "OpenSLES"; + case AudioApi::AAudio: return "AAudio"; + default: return "Unrecognized audio API"; + } +} + +template<> +const char *convertToText(AudioStream* stream) { + static std::string streamText; + std::stringstream s; + + s<<"StreamID: "<< static_cast(stream)<getDeviceId()<getDirection())<getAudioApi())<getBufferCapacityInFrames()<getBufferSizeInFrames()<getFramesPerBurst()<getFramesPerDataCallback()<getSampleRate()<getChannelCount()<getFormat())<getSharingMode())<getPerformanceMode()) + <getState())<getXRunCount()<getFramesRead()<getFramesWritten()< +const char *convertToText(Usage usage) { + + switch (usage) { + case Usage::Media: return "Media"; + case Usage::VoiceCommunication: return "VoiceCommunication"; + case Usage::VoiceCommunicationSignalling: return "VoiceCommunicationSignalling"; + case Usage::Alarm: return "Alarm"; + case Usage::Notification: return "Notification"; + case Usage::NotificationRingtone: return "NotificationRingtone"; + case Usage::NotificationEvent: return "NotificationEvent"; + case Usage::AssistanceAccessibility: return "AssistanceAccessibility"; + case Usage::AssistanceNavigationGuidance: return "AssistanceNavigationGuidance"; + case Usage::AssistanceSonification: return "AssistanceSonification"; + case Usage::Game: return "Game"; + case Usage::Assistant: return "Assistant"; + default: return "Unrecognized usage"; + } +} + +template<> +const char *convertToText(ContentType contentType) { + + switch (contentType) { + case ContentType::Speech: return "Speech"; + case ContentType::Music: return "Music"; + case ContentType::Movie: return "Movie"; + case ContentType::Sonification: return "Sonification"; + default: return "Unrecognized content type"; + } +} + +template<> +const char *convertToText(InputPreset inputPreset) { + + switch (inputPreset) { + case InputPreset::Generic: return "Generic"; + case InputPreset::Camcorder: return "Camcorder"; + case InputPreset::VoiceRecognition: return "VoiceRecognition"; + case InputPreset::VoiceCommunication: return "VoiceCommunication"; + case InputPreset::Unprocessed: return "Unprocessed"; + case InputPreset::VoicePerformance: return "VoicePerformance"; + default: return "Unrecognized input preset"; + } +} + +template<> +const char *convertToText(SessionId sessionId) { + + switch (sessionId) { + case SessionId::None: return "None"; + case SessionId::Allocate: return "Allocate"; + default: return "Unrecognized session id"; + } +} + +template<> +const char *convertToText(ChannelCount channelCount) { + + switch (channelCount) { + case ChannelCount::Unspecified: return "Unspecified"; + case ChannelCount::Mono: return "Mono"; + case ChannelCount::Stereo: return "Stereo"; + default: return "Unrecognized channel count"; + } +} + +std::string getPropertyString(const char * name) { + std::string result; +#ifdef __ANDROID__ + char valueText[PROP_VALUE_MAX] = {0}; + if (__system_property_get(name, valueText) != 0) { + result = valueText; + } +#else + (void) name; +#endif + return result; +} + +int getPropertyInteger(const char * name, int defaultValue) { + int result = defaultValue; +#ifdef __ANDROID__ + char valueText[PROP_VALUE_MAX] = {0}; + if (__system_property_get(name, valueText) != 0) { + result = atoi(valueText); + } +#else + (void) name; +#endif + return result; +} + +int getSdkVersion() { + static int sCachedSdkVersion = -1; +#ifdef __ANDROID__ + if (sCachedSdkVersion == -1) { + sCachedSdkVersion = getPropertyInteger("ro.build.version.sdk", -1); + } +#endif + return sCachedSdkVersion; +} + +int getChannelCountFromChannelMask(ChannelMask channelMask) { + return __builtin_popcount(static_cast(channelMask)); +} + +}// namespace oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Version_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Version_android.cpp new file mode 100644 index 0000000..a9b2ff3 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_common_Version_android.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "oboe_oboe_Version_android.h" + +namespace oboe { + + // This variable enables the version information to be read from the resulting binary e.g. + // by running `objdump -s --section=.data ` + // Please do not optimize or change in any way. + char kVersionText[] = "OboeVersion" OBOE_VERSION_TEXT; + + const char * getVersionText(){ + return kVersionText; + } +} // namespace oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoBuffer_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoBuffer_android.cpp new file mode 100644 index 0000000..f1917e8 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoBuffer_android.cpp @@ -0,0 +1,178 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "oboe_oboe_FifoControllerBase_android.h" +#include "oboe_fifo_FifoController_android.h" +#include "oboe_fifo_FifoControllerIndirect_android.h" +#include "oboe_oboe_FifoBuffer_android.h" + +namespace oboe { + +FifoBuffer::FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames) + : mBytesPerFrame(bytesPerFrame) + , mStorage(nullptr) + , mFramesReadCount(0) + , mFramesUnderrunCount(0) +{ + mFifo = std::make_unique(capacityInFrames); + // allocate buffer + int32_t bytesPerBuffer = bytesPerFrame * capacityInFrames; + mStorage = new uint8_t[bytesPerBuffer]; + mStorageOwned = true; +} + +FifoBuffer::FifoBuffer( uint32_t bytesPerFrame, + uint32_t capacityInFrames, + std::atomic *readCounterAddress, + std::atomic *writeCounterAddress, + uint8_t *dataStorageAddress + ) + : mBytesPerFrame(bytesPerFrame) + , mStorage(dataStorageAddress) + , mFramesReadCount(0) + , mFramesUnderrunCount(0) +{ + mFifo = std::make_unique(capacityInFrames, + readCounterAddress, + writeCounterAddress); + mStorage = dataStorageAddress; + mStorageOwned = false; +} + +FifoBuffer::~FifoBuffer() { + if (mStorageOwned) { + delete[] mStorage; + } +} + +int32_t FifoBuffer::convertFramesToBytes(int32_t frames) { + return frames * mBytesPerFrame; +} + +int32_t FifoBuffer::read(void *buffer, int32_t numFrames) { + if (numFrames <= 0) { + return 0; + } + // safe because numFrames is guaranteed positive + uint32_t framesToRead = static_cast(numFrames); + uint32_t framesAvailable = mFifo->getFullFramesAvailable(); + framesToRead = std::min(framesToRead, framesAvailable); + + uint32_t readIndex = mFifo->getReadIndex(); // ranges 0 to capacity + uint8_t *destination = reinterpret_cast(buffer); + uint8_t *source = &mStorage[convertFramesToBytes(readIndex)]; + if ((readIndex + framesToRead) > mFifo->getFrameCapacity()) { + // read in two parts, first part here is at the end of the mStorage buffer + int32_t frames1 = static_cast(mFifo->getFrameCapacity() - readIndex); + int32_t numBytes = convertFramesToBytes(frames1); + if (numBytes < 0) { + return static_cast(Result::ErrorOutOfRange); + } + memcpy(destination, source, static_cast(numBytes)); + destination += numBytes; + // read second part, which is at the beginning of mStorage + source = &mStorage[0]; + int32_t frames2 = static_cast(framesToRead - frames1); + numBytes = convertFramesToBytes(frames2); + if (numBytes < 0) { + return static_cast(Result::ErrorOutOfRange); + } + memcpy(destination, source, static_cast(numBytes)); + } else { + // just read in one shot + int32_t numBytes = convertFramesToBytes(framesToRead); + if (numBytes < 0) { + return static_cast(Result::ErrorOutOfRange); + } + memcpy(destination, source, static_cast(numBytes)); + } + mFifo->advanceReadIndex(framesToRead); + + return framesToRead; +} + +int32_t FifoBuffer::write(const void *buffer, int32_t numFrames) { + if (numFrames <= 0) { + return 0; + } + // Guaranteed positive. + uint32_t framesToWrite = static_cast(numFrames); + uint32_t framesAvailable = mFifo->getEmptyFramesAvailable(); + framesToWrite = std::min(framesToWrite, framesAvailable); + + uint32_t writeIndex = mFifo->getWriteIndex(); + int byteIndex = convertFramesToBytes(writeIndex); + const uint8_t *source = reinterpret_cast(buffer); + uint8_t *destination = &mStorage[byteIndex]; + if ((writeIndex + framesToWrite) > mFifo->getFrameCapacity()) { + // write in two parts, first part here + int32_t frames1 = static_cast(mFifo->getFrameCapacity() - writeIndex); + int32_t numBytes = convertFramesToBytes(frames1); + if (numBytes < 0) { + return static_cast(Result::ErrorOutOfRange); + } + memcpy(destination, source, static_cast(numBytes)); + // read second part + source += convertFramesToBytes(frames1); + destination = &mStorage[0]; + int frames2 = static_cast(framesToWrite - frames1); + numBytes = convertFramesToBytes(frames2); + if (numBytes < 0) { + return static_cast(Result::ErrorOutOfRange); + } + memcpy(destination, source, static_cast(numBytes)); + } else { + // just write in one shot + int32_t numBytes = convertFramesToBytes(framesToWrite); + if (numBytes < 0) { + return static_cast(Result::ErrorOutOfRange); + } + memcpy(destination, source, static_cast(numBytes)); + } + mFifo->advanceWriteIndex(framesToWrite); + + return framesToWrite; +} + +int32_t FifoBuffer::readNow(void *buffer, int32_t numFrames) { + int32_t framesRead = read(buffer, numFrames); + if (framesRead < 0) { + return framesRead; + } + int32_t framesLeft = numFrames - framesRead; + mFramesReadCount += framesRead; + mFramesUnderrunCount += framesLeft; + // Zero out any samples we could not set. + if (framesLeft > 0) { + uint8_t *destination = reinterpret_cast(buffer); + destination += convertFramesToBytes(framesRead); // point to first byte not set + int32_t bytesToZero = convertFramesToBytes(framesLeft); + memset(destination, 0, static_cast(bytesToZero)); + } + + return framesRead; +} + + +uint32_t FifoBuffer::getBufferCapacityInFrames() const { + return mFifo->getFrameCapacity(); +} + +} // namespace oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoControllerBase_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoControllerBase_android.cpp new file mode 100644 index 0000000..b6a4601 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoControllerBase_android.cpp @@ -0,0 +1,68 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "oboe_oboe_FifoControllerBase_android.h" + +namespace oboe { + +FifoControllerBase::FifoControllerBase(uint32_t capacityInFrames) + : mTotalFrames(capacityInFrames) +{ + // Avoid ridiculously large buffers and the arithmetic wraparound issues that can follow. + assert(capacityInFrames <= (UINT32_MAX / 4)); +} + +uint32_t FifoControllerBase::getFullFramesAvailable() const { + uint64_t writeCounter = getWriteCounter(); + uint64_t readCounter = getReadCounter(); + if (readCounter > writeCounter) { + return 0; + } + uint64_t delta = writeCounter - readCounter; + if (delta >= mTotalFrames) { + return mTotalFrames; + } + // delta is now guaranteed to fit within the range of a uint32_t + return static_cast(delta); +} + +uint32_t FifoControllerBase::getReadIndex() const { + // % works with non-power of two sizes + return static_cast(getReadCounter() % mTotalFrames); +} + +void FifoControllerBase::advanceReadIndex(uint32_t numFrames) { + incrementReadCounter(numFrames); +} + +uint32_t FifoControllerBase::getEmptyFramesAvailable() const { + return static_cast(mTotalFrames - getFullFramesAvailable()); +} + +uint32_t FifoControllerBase::getWriteIndex() const { + // % works with non-power of two sizes + return static_cast(getWriteCounter() % mTotalFrames); +} + +void FifoControllerBase::advanceWriteIndex(uint32_t numFrames) { + incrementWriteCounter(numFrames); +} + +} // namespace oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoControllerIndirect_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoControllerIndirect_android.cpp new file mode 100644 index 0000000..42a4057 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoControllerIndirect_android.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "oboe_fifo_FifoControllerIndirect_android.h" + +namespace oboe { + +FifoControllerIndirect::FifoControllerIndirect(uint32_t numFrames, + std::atomic *readCounterAddress, + std::atomic *writeCounterAddress) + : FifoControllerBase(numFrames) + , mReadCounterAddress(readCounterAddress) + , mWriteCounterAddress(writeCounterAddress) +{ +} + +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoControllerIndirect_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoControllerIndirect_android.h new file mode 100644 index 0000000..449989e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoControllerIndirect_android.h @@ -0,0 +1,66 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NATIVEOBOE_FIFOCONTROLLERINDIRECT_H +#define NATIVEOBOE_FIFOCONTROLLERINDIRECT_H + +#include +#include + +#include "oboe_oboe_FifoControllerBase_android.h" + +namespace oboe { + +/** + * A FifoControllerBase with counters external to the class. + */ +class FifoControllerIndirect : public FifoControllerBase { + +public: + FifoControllerIndirect(uint32_t bufferSize, + std::atomic *readCounterAddress, + std::atomic *writeCounterAddress); + virtual ~FifoControllerIndirect() = default; + + virtual uint64_t getReadCounter() const override { + return mReadCounterAddress->load(std::memory_order_acquire); + } + virtual void setReadCounter(uint64_t n) override { + mReadCounterAddress->store(n, std::memory_order_release); + } + virtual void incrementReadCounter(uint64_t n) override { + mReadCounterAddress->fetch_add(n, std::memory_order_acq_rel); + } + virtual uint64_t getWriteCounter() const override { + return mWriteCounterAddress->load(std::memory_order_acquire); + } + virtual void setWriteCounter(uint64_t n) override { + mWriteCounterAddress->store(n, std::memory_order_release); + } + virtual void incrementWriteCounter(uint64_t n) override { + mWriteCounterAddress->fetch_add(n, std::memory_order_acq_rel); + } + +private: + + std::atomic *mReadCounterAddress; + std::atomic *mWriteCounterAddress; + +}; + +} // namespace oboe + +#endif //NATIVEOBOE_FIFOCONTROLLERINDIRECT_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoController_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoController_android.cpp new file mode 100644 index 0000000..ae46b53 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoController_android.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "oboe_fifo_FifoController_android.h" + +namespace oboe { + +FifoController::FifoController(uint32_t numFrames) + : FifoControllerBase(numFrames) +{ + setReadCounter(0); + setWriteCounter(0); +} + +} // namespace oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoController_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoController_android.h new file mode 100644 index 0000000..a040ab4 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_fifo_FifoController_android.h @@ -0,0 +1,62 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NATIVEOBOE_FIFOCONTROLLER_H +#define NATIVEOBOE_FIFOCONTROLLER_H + +#include +#include + +#include "oboe_oboe_FifoControllerBase_android.h" + +namespace oboe { + +/** + * A FifoControllerBase with counters contained in the class. + */ +class FifoController : public FifoControllerBase +{ +public: + FifoController(uint32_t bufferSize); + virtual ~FifoController() = default; + + virtual uint64_t getReadCounter() const override { + return mReadCounter.load(std::memory_order_acquire); + } + virtual void setReadCounter(uint64_t n) override { + mReadCounter.store(n, std::memory_order_release); + } + virtual void incrementReadCounter(uint64_t n) override { + mReadCounter.fetch_add(n, std::memory_order_acq_rel); + } + virtual uint64_t getWriteCounter() const override { + return mWriteCounter.load(std::memory_order_acquire); + } + virtual void setWriteCounter(uint64_t n) override { + mWriteCounter.store(n, std::memory_order_release); + } + virtual void incrementWriteCounter(uint64_t n) override { + mWriteCounter.fetch_add(n, std::memory_order_acq_rel); + } + +private: + std::atomic mReadCounter{}; + std::atomic mWriteCounter{}; +}; + +} // namespace oboe + +#endif //NATIVEOBOE_FIFOCONTROLLER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ChannelCountConverter_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ChannelCountConverter_android.cpp new file mode 100644 index 0000000..91eae22 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ChannelCountConverter_android.cpp @@ -0,0 +1,52 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_ChannelCountConverter_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +ChannelCountConverter::ChannelCountConverter( + int32_t inputChannelCount, + int32_t outputChannelCount) + : input(*this, inputChannelCount) + , output(*this, outputChannelCount) { +} + +ChannelCountConverter::~ChannelCountConverter() = default; + +int32_t ChannelCountConverter::onProcess(int32_t numFrames) { + const float *inputBuffer = input.getBuffer(); + float *outputBuffer = output.getBuffer(); + int32_t inputChannelCount = input.getSamplesPerFrame(); + int32_t outputChannelCount = output.getSamplesPerFrame(); + for (int i = 0; i < numFrames; i++) { + int inputChannel = 0; + for (int outputChannel = 0; outputChannel < outputChannelCount; outputChannel++) { + // Copy input channels to output channels. + // Wrap if we run out of inputs. + // Discard if we run out of outputs. + outputBuffer[outputChannel] = inputBuffer[inputChannel]; + inputChannel = (inputChannel == inputChannelCount) + ? 0 : inputChannel + 1; + } + inputBuffer += inputChannelCount; + outputBuffer += outputChannelCount; + } + return numFrames; +} + diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ChannelCountConverter_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ChannelCountConverter_android.h new file mode 100644 index 0000000..dec8cf1 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ChannelCountConverter_android.h @@ -0,0 +1,52 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H +#define FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * Change the number of number of channels without mixing. + * When increasing the channel count, duplicate input channels. + * When decreasing the channel count, drop input channels. + */ + class ChannelCountConverter : public FlowGraphNode { + public: + explicit ChannelCountConverter( + int32_t inputChannelCount, + int32_t outputChannelCount); + + virtual ~ChannelCountConverter(); + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "ChannelCountConverter"; + } + + FlowGraphPortFloatInput input; + FlowGraphPortFloatOutput output; + }; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ClipToRange_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ClipToRange_android.cpp new file mode 100644 index 0000000..d4a0731 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ClipToRange_android.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_ClipToRange_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +ClipToRange::ClipToRange(int32_t channelCount) + : FlowGraphFilter(channelCount) { +} + +int32_t ClipToRange::onProcess(int32_t numFrames) { + const float *inputBuffer = input.getBuffer(); + float *outputBuffer = output.getBuffer(); + + int32_t numSamples = numFrames * output.getSamplesPerFrame(); + for (int32_t i = 0; i < numSamples; i++) { + *outputBuffer++ = std::min(mMaximum, std::max(mMinimum, *inputBuffer++)); + } + + return numFrames; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ClipToRange_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ClipToRange_android.h new file mode 100644 index 0000000..eeadd49 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ClipToRange_android.h @@ -0,0 +1,68 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_CLIP_TO_RANGE_H +#define FLOWGRAPH_CLIP_TO_RANGE_H + +#include +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +// This is 3 dB, (10^(3/20)), to match the maximum headroom in AudioTrack for float data. +// It is designed to allow occasional transient peaks. +constexpr float kDefaultMaxHeadroom = 1.41253754f; +constexpr float kDefaultMinHeadroom = -kDefaultMaxHeadroom; + +class ClipToRange : public FlowGraphFilter { +public: + explicit ClipToRange(int32_t channelCount); + + virtual ~ClipToRange() = default; + + int32_t onProcess(int32_t numFrames) override; + + void setMinimum(float min) { + mMinimum = min; + } + + float getMinimum() const { + return mMinimum; + } + + void setMaximum(float min) { + mMaximum = min; + } + + float getMaximum() const { + return mMaximum; + } + + const char *getName() override { + return "ClipToRange"; + } + +private: + float mMinimum = kDefaultMinHeadroom; + float mMaximum = kDefaultMaxHeadroom; +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_CLIP_TO_RANGE_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_FlowGraphNode_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_FlowGraphNode_android.cpp new file mode 100644 index 0000000..48b739e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_FlowGraphNode_android.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdio.h" +#include +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +/***************************************************************************/ +int32_t FlowGraphNode::pullData(int32_t numFrames, int64_t callCount) { + int32_t frameCount = numFrames; + // Prevent recursion and multiple execution of nodes. + if (callCount > mLastCallCount) { + mLastCallCount = callCount; + if (mDataPulledAutomatically) { + // Pull from all the upstream nodes. + for (auto &port : mInputPorts) { + // TODO fix bug of leaving unused data in some ports if using multiple AudioSource + frameCount = port.get().pullData(callCount, frameCount); + } + } + if (frameCount > 0) { + frameCount = onProcess(frameCount); + } + mLastFrameCount = frameCount; + } else { + frameCount = mLastFrameCount; + } + return frameCount; +} + +void FlowGraphNode::pullReset() { + if (!mBlockRecursion) { + mBlockRecursion = true; // for cyclic graphs + // Pull reset from all the upstream nodes. + for (auto &port : mInputPorts) { + port.get().pullReset(); + } + mBlockRecursion = false; + reset(); + } +} + +void FlowGraphNode::reset() { + mLastFrameCount = 0; + mLastCallCount = kInitialCallCount; +} + +/***************************************************************************/ +FlowGraphPortFloat::FlowGraphPortFloat(FlowGraphNode &parent, + int32_t samplesPerFrame, + int32_t framesPerBuffer) + : FlowGraphPort(parent, samplesPerFrame) + , mFramesPerBuffer(framesPerBuffer) + , mBuffer(nullptr) { + size_t numFloats = static_cast(framesPerBuffer) * getSamplesPerFrame(); + mBuffer = std::make_unique(numFloats); +} + +/***************************************************************************/ +int32_t FlowGraphPortFloatOutput::pullData(int64_t callCount, int32_t numFrames) { + numFrames = std::min(getFramesPerBuffer(), numFrames); + return mContainingNode.pullData(numFrames, callCount); +} + +void FlowGraphPortFloatOutput::pullReset() { + mContainingNode.pullReset(); +} + +// These need to be in the .cpp file because of forward cross references. +void FlowGraphPortFloatOutput::connect(FlowGraphPortFloatInput *port) { + port->connect(this); +} + +void FlowGraphPortFloatOutput::disconnect(FlowGraphPortFloatInput *port) { + port->disconnect(this); +} + +/***************************************************************************/ +int32_t FlowGraphPortFloatInput::pullData(int64_t callCount, int32_t numFrames) { + return (mConnected == nullptr) + ? std::min(getFramesPerBuffer(), numFrames) + : mConnected->pullData(callCount, numFrames); +} +void FlowGraphPortFloatInput::pullReset() { + if (mConnected != nullptr) mConnected->pullReset(); +} + +float *FlowGraphPortFloatInput::getBuffer() { + if (mConnected == nullptr) { + return FlowGraphPortFloat::getBuffer(); // loaded using setValue() + } else { + return mConnected->getBuffer(); + } +} + +int32_t FlowGraphSink::pullData(int32_t numFrames) { + return FlowGraphNode::pullData(numFrames, getLastCallCount() + 1); +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_FlowGraphNode_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_FlowGraphNode_android.h new file mode 100644 index 0000000..2884c08 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_FlowGraphNode_android.h @@ -0,0 +1,450 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * FlowGraph.h + * + * Processing node and ports that can be used in a simple data flow graph. + * This was designed to work with audio but could be used for other + * types of data. + */ + +#ifndef FLOWGRAPH_FLOW_GRAPH_NODE_H +#define FLOWGRAPH_FLOW_GRAPH_NODE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO Move these classes into separate files. +// TODO Review use of raw pointers for connect(). Maybe use smart pointers but need to avoid +// run-time deallocation in audio thread. + +// Set flags FLOWGRAPH_ANDROID_INTERNAL and FLOWGRAPH_OUTER_NAMESPACE based on whether compiler +// flag __ANDROID_NDK__ is defined. __ANDROID_NDK__ should be defined in oboe and not aaudio. + +#ifndef FLOWGRAPH_ANDROID_INTERNAL +#ifdef __ANDROID_NDK__ +#define FLOWGRAPH_ANDROID_INTERNAL 0 +#else +#define FLOWGRAPH_ANDROID_INTERNAL 1 +#endif // __ANDROID_NDK__ +#endif // FLOWGRAPH_ANDROID_INTERNAL + +#ifndef FLOWGRAPH_OUTER_NAMESPACE +#ifdef __ANDROID_NDK__ +#define FLOWGRAPH_OUTER_NAMESPACE oboe +#else +#define FLOWGRAPH_OUTER_NAMESPACE aaudio +#endif // __ANDROID_NDK__ +#endif // FLOWGRAPH_OUTER_NAMESPACE + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +// Default block size that can be overridden when the FlowGraphPortFloat is created. +// If it is too small then we will have too much overhead from switching between nodes. +// If it is too high then we will thrash the caches. +constexpr int kDefaultBufferSize = 8; // arbitrary + +class FlowGraphPort; +class FlowGraphPortFloatInput; + +/***************************************************************************/ +/** + * Base class for all nodes in the flowgraph. + */ +class FlowGraphNode { +public: + FlowGraphNode() = default; + virtual ~FlowGraphNode() = default; + + /** + * Read from the input ports, + * generate multiple frames of data then write the results to the output ports. + * + * @param numFrames maximum number of frames requested for processing + * @return number of frames actually processed + */ + virtual int32_t onProcess(int32_t numFrames) = 0; + + /** + * If the callCount is at or after the previous callCount then call + * pullData on all of the upstreamNodes. + * Then call onProcess(). + * This prevents infinite recursion in case of cyclic graphs. + * It also prevents nodes upstream from a branch from being executed twice. + * + * @param callCount + * @param numFrames + * @return number of frames valid + */ + int32_t pullData(int32_t numFrames, int64_t callCount); + + /** + * Recursively reset all the nodes in the graph, starting from a Sink. + * + * This must not be called at the same time as pullData! + */ + void pullReset(); + + /** + * Reset framePosition counters. + */ + virtual void reset(); + + void addInputPort(FlowGraphPort &port) { + mInputPorts.emplace_back(port); + } + + bool isDataPulledAutomatically() const { + return mDataPulledAutomatically; + } + + /** + * Set true if you want the data pulled through the graph automatically. + * This is the default. + * + * Set false if you want to pull the data from the input ports in the onProcess() method. + * You might do this, for example, in a sample rate converting node. + * + * @param automatic + */ + void setDataPulledAutomatically(bool automatic) { + mDataPulledAutomatically = automatic; + } + + virtual const char *getName() { + return "FlowGraph"; + } + + int64_t getLastCallCount() { + return mLastCallCount; + } + +protected: + + static constexpr int64_t kInitialCallCount = -1; + int64_t mLastCallCount = kInitialCallCount; + + std::vector> mInputPorts; + +private: + bool mDataPulledAutomatically = true; + bool mBlockRecursion = false; + int32_t mLastFrameCount = 0; + +}; + +/***************************************************************************/ +/** + * This is a connector that allows data to flow between modules. + * + * The ports are the primary means of interacting with a module. + * So they are generally declared as public. + * + */ +class FlowGraphPort { +public: + FlowGraphPort(FlowGraphNode &parent, int32_t samplesPerFrame) + : mContainingNode(parent) + , mSamplesPerFrame(samplesPerFrame) { + } + + virtual ~FlowGraphPort() = default; + + // Ports are often declared public. So let's make them non-copyable. + FlowGraphPort(const FlowGraphPort&) = delete; + FlowGraphPort& operator=(const FlowGraphPort&) = delete; + + int32_t getSamplesPerFrame() const { + return mSamplesPerFrame; + } + + virtual int32_t pullData(int64_t framePosition, int32_t numFrames) = 0; + + virtual void pullReset() {} + +protected: + FlowGraphNode &mContainingNode; + +private: + const int32_t mSamplesPerFrame = 1; +}; + +/***************************************************************************/ +/** + * This port contains a 32-bit float buffer that can contain several frames of data. + * Processing the data in a block improves performance. + * + * The size is framesPerBuffer * samplesPerFrame). + */ +class FlowGraphPortFloat : public FlowGraphPort { +public: + FlowGraphPortFloat(FlowGraphNode &parent, + int32_t samplesPerFrame, + int32_t framesPerBuffer = kDefaultBufferSize + ); + + virtual ~FlowGraphPortFloat() = default; + + int32_t getFramesPerBuffer() const { + return mFramesPerBuffer; + } + +protected: + + /** + * @return buffer internal to the port or from a connected port + */ + virtual float *getBuffer() { + return mBuffer.get(); + } + +private: + const int32_t mFramesPerBuffer = 1; + std::unique_ptr mBuffer; // allocated in constructor +}; + +/***************************************************************************/ +/** + * The results of a node's processing are stored in the buffers of the output ports. + */ +class FlowGraphPortFloatOutput : public FlowGraphPortFloat { +public: + FlowGraphPortFloatOutput(FlowGraphNode &parent, int32_t samplesPerFrame) + : FlowGraphPortFloat(parent, samplesPerFrame) { + } + + virtual ~FlowGraphPortFloatOutput() = default; + + using FlowGraphPortFloat::getBuffer; + + /** + * Connect to the input of another module. + * An input port can only have one connection. + * An output port can have multiple connections. + * If you connect a second output port to an input port + * then it overwrites the previous connection. + * + * This not thread safe. Do not modify the graph topology from another thread while running. + * Also do not delete a module while it is connected to another port if the graph is running. + */ + void connect(FlowGraphPortFloatInput *port); + + /** + * Disconnect from the input of another module. + * This not thread safe. + */ + void disconnect(FlowGraphPortFloatInput *port); + + /** + * Call the parent module's onProcess() method. + * That may pull data from its inputs and recursively + * process the entire graph. + * @return number of frames actually pulled + */ + int32_t pullData(int64_t framePosition, int32_t numFrames) override; + + + void pullReset() override; + +}; + +/***************************************************************************/ + +/** + * An input port for streaming audio data. + * You can set a value that will be used for processing. + * If you connect an output port to this port then its value will be used instead. + */ +class FlowGraphPortFloatInput : public FlowGraphPortFloat { +public: + FlowGraphPortFloatInput(FlowGraphNode &parent, int32_t samplesPerFrame) + : FlowGraphPortFloat(parent, samplesPerFrame) { + // Add to parent so it can pull data from each input. + parent.addInputPort(*this); + } + + virtual ~FlowGraphPortFloatInput() = default; + + /** + * If connected to an output port then this will return + * that output ports buffers. + * If not connected then it returns the input ports own buffer + * which can be loaded using setValue(). + */ + float *getBuffer() override; + + /** + * Write every value of the float buffer. + * This value will be ignored if an output port is connected + * to this port. + */ + void setValue(float value) { + int numFloats = kDefaultBufferSize * getSamplesPerFrame(); + float *buffer = getBuffer(); + for (int i = 0; i < numFloats; i++) { + *buffer++ = value; + } + } + + /** + * Connect to the output of another module. + * An input port can only have one connection. + * An output port can have multiple connections. + * This not thread safe. + */ + void connect(FlowGraphPortFloatOutput *port) { + assert(getSamplesPerFrame() == port->getSamplesPerFrame()); + mConnected = port; + } + + void disconnect(FlowGraphPortFloatOutput *port) { + assert(mConnected == port); + (void) port; + mConnected = nullptr; + } + + void disconnect() { + mConnected = nullptr; + } + + /** + * Pull data from any output port that is connected. + */ + int32_t pullData(int64_t framePosition, int32_t numFrames) override; + + void pullReset() override; + +private: + FlowGraphPortFloatOutput *mConnected = nullptr; +}; + +/***************************************************************************/ + +/** + * Base class for an edge node in a graph that has no upstream nodes. + * It outputs data but does not consume data. + * By default, it will read its data from an external buffer. + */ +class FlowGraphSource : public FlowGraphNode { +public: + explicit FlowGraphSource(int32_t channelCount) + : output(*this, channelCount) { + } + + virtual ~FlowGraphSource() = default; + + FlowGraphPortFloatOutput output; +}; + +/***************************************************************************/ + +/** + * Base class for an edge node in a graph that has no upstream nodes. + * It outputs data but does not consume data. + * By default, it will read its data from an external buffer. + */ +class FlowGraphSourceBuffered : public FlowGraphSource { +public: + explicit FlowGraphSourceBuffered(int32_t channelCount) + : FlowGraphSource(channelCount) {} + + virtual ~FlowGraphSourceBuffered() = default; + + /** + * Specify buffer that the node will read from. + * + * @param data TODO Consider using std::shared_ptr. + * @param numFrames + */ + void setData(const void *data, int32_t numFrames) { + mData = data; + mSizeInFrames = numFrames; + mFrameIndex = 0; + } + +protected: + const void *mData = nullptr; + int32_t mSizeInFrames = 0; // number of frames in mData + int32_t mFrameIndex = 0; // index of next frame to be processed +}; + +/***************************************************************************/ +/** + * Base class for an edge node in a graph that has no downstream nodes. + * It consumes data but does not output data. + * This graph will be executed when data is read() from this node + * by pulling data from upstream nodes. + */ +class FlowGraphSink : public FlowGraphNode { +public: + explicit FlowGraphSink(int32_t channelCount) + : input(*this, channelCount) { + } + + virtual ~FlowGraphSink() = default; + + FlowGraphPortFloatInput input; + + /** + * Do nothing. The work happens in the read() method. + * + * @param numFrames + * @return number of frames actually processed + */ + int32_t onProcess(int32_t numFrames) override { + return numFrames; + } + + virtual int32_t read(void *data, int32_t numFrames) = 0; + +protected: + /** + * Pull data through the graph using this nodes last callCount. + * @param numFrames + * @return + */ + int32_t pullData(int32_t numFrames); +}; + +/***************************************************************************/ +/** + * Base class for a node that has an input and an output with the same number of channels. + * This may include traditional filters, eg. FIR, but also include + * any processing node that converts input to output. + */ +class FlowGraphFilter : public FlowGraphNode { +public: + explicit FlowGraphFilter(int32_t channelCount) + : input(*this, channelCount) + , output(*this, channelCount) { + } + + virtual ~FlowGraphFilter() = default; + + FlowGraphPortFloatInput input; + FlowGraphPortFloatOutput output; +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif /* FLOWGRAPH_FLOW_GRAPH_NODE_H */ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_FlowgraphUtilities_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_FlowgraphUtilities_android.h new file mode 100644 index 0000000..5e90588 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_FlowgraphUtilities_android.h @@ -0,0 +1,55 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_UTILITIES_H +#define FLOWGRAPH_UTILITIES_H + +#include + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +class FlowgraphUtilities { +public: +// This was copied from audio_utils/primitives.h +/** + * Convert a single-precision floating point value to a Q0.31 integer value. + * Rounds to nearest, ties away from 0. + * + * Values outside the range [-1.0, 1.0) are properly clamped to -2147483648 and 2147483647, + * including -Inf and +Inf. NaN values are considered undefined, and behavior may change + * depending on hardware and future implementation of this function. + */ +static int32_t clamp32FromFloat(float f) +{ + static const float scale = (float)(1UL << 31); + static const float limpos = 1.; + static const float limneg = -1.; + + if (f <= limneg) { + return INT32_MIN; + } else if (f >= limpos) { + return INT32_MAX; + } + f *= scale; + /* integer conversion is through truncation (though int to float is not). + * ensure that we round to nearest, ties away from 0. + */ + return f > 0 ? f + 0.5 : f - 0.5; +} + +}; + +#endif // FLOWGRAPH_UTILITIES_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ManyToMultiConverter_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ManyToMultiConverter_android.cpp new file mode 100644 index 0000000..23dcd4f --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ManyToMultiConverter_android.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "oboe_flowgraph_ManyToMultiConverter_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +ManyToMultiConverter::ManyToMultiConverter(int32_t channelCount) + : inputs(channelCount) + , output(*this, channelCount) { + for (int i = 0; i < channelCount; i++) { + inputs[i] = std::make_unique(*this, 1); + } +} + +int32_t ManyToMultiConverter::onProcess(int32_t numFrames) { + int32_t channelCount = output.getSamplesPerFrame(); + + for (int ch = 0; ch < channelCount; ch++) { + const float *inputBuffer = inputs[ch]->getBuffer(); + float *outputBuffer = output.getBuffer() + ch; + + for (int i = 0; i < numFrames; i++) { + // read one, write into the proper interleaved output channel + float sample = *inputBuffer++; + *outputBuffer = sample; + outputBuffer += channelCount; // advance to next multichannel frame + } + } + return numFrames; +} + diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ManyToMultiConverter_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ManyToMultiConverter_android.h new file mode 100644 index 0000000..9199ee7 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_ManyToMultiConverter_android.h @@ -0,0 +1,53 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H +#define FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H + +#include +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * Combine multiple mono inputs into one interleaved multi-channel output. + */ +class ManyToMultiConverter : public flowgraph::FlowGraphNode { +public: + explicit ManyToMultiConverter(int32_t channelCount); + + virtual ~ManyToMultiConverter() = default; + + int32_t onProcess(int numFrames) override; + + void setEnabled(bool /*enabled*/) {} + + std::vector> inputs; + flowgraph::FlowGraphPortFloatOutput output; + + const char *getName() override { + return "ManyToMultiConverter"; + } + +private: +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoBlend_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoBlend_android.cpp new file mode 100644 index 0000000..da4bbd0 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoBlend_android.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "oboe_flowgraph_MonoBlend_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +MonoBlend::MonoBlend(int32_t channelCount) + : FlowGraphFilter(channelCount) + , mInvChannelCount(1. / channelCount) +{ +} + +int32_t MonoBlend::onProcess(int32_t numFrames) { + int32_t channelCount = output.getSamplesPerFrame(); + const float *inputBuffer = input.getBuffer(); + float *outputBuffer = output.getBuffer(); + + for (size_t i = 0; i < numFrames; ++i) { + float accum = 0; + for (size_t j = 0; j < channelCount; ++j) { + accum += *inputBuffer++; + } + accum *= mInvChannelCount; + for (size_t j = 0; j < channelCount; ++j) { + *outputBuffer++ = accum; + } + } + + return numFrames; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoBlend_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoBlend_android.h new file mode 100644 index 0000000..223275e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoBlend_android.h @@ -0,0 +1,48 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_MONO_BLEND_H +#define FLOWGRAPH_MONO_BLEND_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * Combine data between multiple channels so each channel is an average + * of all channels. + */ +class MonoBlend : public FlowGraphFilter { +public: + explicit MonoBlend(int32_t channelCount); + + virtual ~MonoBlend() = default; + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "MonoBlend"; + } +private: + const float mInvChannelCount; +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_MONO_BLEND diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoToMultiConverter_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoToMultiConverter_android.cpp new file mode 100644 index 0000000..b70c780 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoToMultiConverter_android.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_MonoToMultiConverter_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +MonoToMultiConverter::MonoToMultiConverter(int32_t outputChannelCount) + : input(*this, 1) + , output(*this, outputChannelCount) { +} + +int32_t MonoToMultiConverter::onProcess(int32_t numFrames) { + const float *inputBuffer = input.getBuffer(); + float *outputBuffer = output.getBuffer(); + int32_t channelCount = output.getSamplesPerFrame(); + for (int i = 0; i < numFrames; i++) { + // read one, write many + float sample = *inputBuffer++; + for (int channel = 0; channel < channelCount; channel++) { + *outputBuffer++ = sample; + } + } + return numFrames; +} + diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoToMultiConverter_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoToMultiConverter_android.h new file mode 100644 index 0000000..a7a1453 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MonoToMultiConverter_android.h @@ -0,0 +1,49 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H +#define FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * Convert a monophonic stream to a multi-channel interleaved stream + * with the same signal on each channel. + */ +class MonoToMultiConverter : public FlowGraphNode { +public: + explicit MonoToMultiConverter(int32_t outputChannelCount); + + virtual ~MonoToMultiConverter() = default; + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "MonoToMultiConverter"; + } + + FlowGraphPortFloatInput input; + FlowGraphPortFloatOutput output; +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToManyConverter_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToManyConverter_android.cpp new file mode 100644 index 0000000..ab61645 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToManyConverter_android.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_MultiToManyConverter_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +MultiToManyConverter::MultiToManyConverter(int32_t channelCount) + : outputs(channelCount) + , input(*this, channelCount) { + for (int i = 0; i < channelCount; i++) { + outputs[i] = std::make_unique(*this, 1); + } +} + +MultiToManyConverter::~MultiToManyConverter() = default; + +int32_t MultiToManyConverter::onProcess(int32_t numFrames) { + int32_t channelCount = input.getSamplesPerFrame(); + + for (int ch = 0; ch < channelCount; ch++) { + const float *inputBuffer = input.getBuffer() + ch; + float *outputBuffer = outputs[ch]->getBuffer(); + + for (int i = 0; i < numFrames; i++) { + *outputBuffer++ = *inputBuffer; + inputBuffer += channelCount; + } + } + + return numFrames; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToManyConverter_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToManyConverter_android.h new file mode 100644 index 0000000..47b6426 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToManyConverter_android.h @@ -0,0 +1,49 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H +#define FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * Convert a multi-channel interleaved stream to multiple mono-channel + * outputs + */ + class MultiToManyConverter : public FlowGraphNode { + public: + explicit MultiToManyConverter(int32_t channelCount); + + virtual ~MultiToManyConverter(); + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "MultiToManyConverter"; + } + + std::vector> outputs; + flowgraph::FlowGraphPortFloatInput input; + }; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H \ No newline at end of file diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToMonoConverter_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToMonoConverter_android.cpp new file mode 100644 index 0000000..3b7888d --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToMonoConverter_android.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_MultiToMonoConverter_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +MultiToMonoConverter::MultiToMonoConverter(int32_t inputChannelCount) + : input(*this, inputChannelCount) + , output(*this, 1) { +} + +MultiToMonoConverter::~MultiToMonoConverter() = default; + +int32_t MultiToMonoConverter::onProcess(int32_t numFrames) { + const float *inputBuffer = input.getBuffer(); + float *outputBuffer = output.getBuffer(); + int32_t channelCount = input.getSamplesPerFrame(); + for (int i = 0; i < numFrames; i++) { + // read first channel of multi stream, write many + *outputBuffer++ = *inputBuffer; + inputBuffer += channelCount; + } + return numFrames; +} + diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToMonoConverter_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToMonoConverter_android.h new file mode 100644 index 0000000..1d14de7 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_MultiToMonoConverter_android.h @@ -0,0 +1,49 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H +#define FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * Convert a multi-channel interleaved stream to a monophonic stream + * by extracting channel[0]. + */ + class MultiToMonoConverter : public FlowGraphNode { + public: + explicit MultiToMonoConverter(int32_t inputChannelCount); + + virtual ~MultiToMonoConverter(); + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "MultiToMonoConverter"; + } + + FlowGraphPortFloatInput input; + FlowGraphPortFloatOutput output; + }; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_RampLinear_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_RampLinear_android.cpp new file mode 100644 index 0000000..1bb72bf --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_RampLinear_android.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_RampLinear_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +RampLinear::RampLinear(int32_t channelCount) + : FlowGraphFilter(channelCount) { + mTarget.store(1.0f); +} + +void RampLinear::setLengthInFrames(int32_t frames) { + mLengthInFrames = frames; +} + +void RampLinear::setTarget(float target) { + mTarget.store(target); + // If the ramp has not been used then start immediately at this level. + if (mLastCallCount == kInitialCallCount) { + forceCurrent(target); + } +} + +float RampLinear::interpolateCurrent() { + return mLevelTo - (mRemaining * mScaler); +} + +int32_t RampLinear::onProcess(int32_t numFrames) { + const float *inputBuffer = input.getBuffer(); + float *outputBuffer = output.getBuffer(); + int32_t channelCount = output.getSamplesPerFrame(); + + float target = getTarget(); + if (target != mLevelTo) { + // Start new ramp. Continue from previous level. + mLevelFrom = interpolateCurrent(); + mLevelTo = target; + mRemaining = mLengthInFrames; + mScaler = (mLevelTo - mLevelFrom) / mLengthInFrames; // for interpolation + } + + int32_t framesLeft = numFrames; + + if (mRemaining > 0) { // Ramping? This doesn't happen very often. + int32_t framesToRamp = std::min(framesLeft, mRemaining); + framesLeft -= framesToRamp; + while (framesToRamp > 0) { + float currentLevel = interpolateCurrent(); + for (int ch = 0; ch < channelCount; ch++) { + *outputBuffer++ = *inputBuffer++ * currentLevel; + } + mRemaining--; + framesToRamp--; + } + } + + // Process any frames after the ramp. + int32_t samplesLeft = framesLeft * channelCount; + for (int i = 0; i < samplesLeft; i++) { + *outputBuffer++ = *inputBuffer++ * mLevelTo; + } + + return numFrames; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_RampLinear_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_RampLinear_android.h new file mode 100644 index 0000000..2a67199 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_RampLinear_android.h @@ -0,0 +1,96 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_RAMP_LINEAR_H +#define FLOWGRAPH_RAMP_LINEAR_H + +#include +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * When the target is modified then the output will ramp smoothly + * between the original and the new target value. + * This can be used to smooth out control values and reduce pops. + * + * The target may be updated while a ramp is in progress, which will trigger + * a new ramp from the current value. + */ +class RampLinear : public FlowGraphFilter { +public: + explicit RampLinear(int32_t channelCount); + + virtual ~RampLinear() = default; + + int32_t onProcess(int32_t numFrames) override; + + /** + * This is used for the next ramp. + * Calling this does not affect a ramp that is in progress. + */ + void setLengthInFrames(int32_t frames); + + int32_t getLengthInFrames() const { + return mLengthInFrames; + } + + /** + * This may be safely called by another thread. + * @param target + */ + void setTarget(float target); + + float getTarget() const { + return mTarget.load(); + } + + /** + * Force the nextSegment to start from this level. + * + * WARNING: this can cause a discontinuity if called while the ramp is being used. + * Only call this when setting the initial ramp. + * + * @param level + */ + void forceCurrent(float level) { + mLevelFrom = level; + mLevelTo = level; + } + + const char *getName() override { + return "RampLinear"; + } + +private: + + float interpolateCurrent(); + + std::atomic mTarget; + + int32_t mLengthInFrames = 48000.0f / 100.0f ; // 10 msec at 48000 Hz; + int32_t mRemaining = 0; + float mScaler = 0.0f; + float mLevelFrom = 0.0f; + float mLevelTo = 0.0f; +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_RAMP_LINEAR_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SampleRateConverter_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SampleRateConverter_android.cpp new file mode 100644 index 0000000..f86eca7 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SampleRateConverter_android.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "oboe_flowgraph_SampleRateConverter_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; +using namespace RESAMPLER_OUTER_NAMESPACE::resampler; + +SampleRateConverter::SampleRateConverter(int32_t channelCount, + MultiChannelResampler &resampler) + : FlowGraphFilter(channelCount) + , mResampler(resampler) { + setDataPulledAutomatically(false); +} + +void SampleRateConverter::reset() { + FlowGraphNode::reset(); + mInputCursor = kInitialCallCount; +} + +// Return true if there is a sample available. +bool SampleRateConverter::isInputAvailable() { + // If we have consumed all of the input data then go out and get some more. + if (mInputCursor >= mNumValidInputFrames) { + mInputCallCount++; + mNumValidInputFrames = input.pullData(mInputCallCount, input.getFramesPerBuffer()); + mInputCursor = 0; + } + return (mInputCursor < mNumValidInputFrames); +} + +const float *SampleRateConverter::getNextInputFrame() { + const float *inputBuffer = input.getBuffer(); + return &inputBuffer[mInputCursor++ * input.getSamplesPerFrame()]; +} + +int32_t SampleRateConverter::onProcess(int32_t numFrames) { + float *outputBuffer = output.getBuffer(); + int32_t channelCount = output.getSamplesPerFrame(); + int framesLeft = numFrames; + while (framesLeft > 0) { + // Gather input samples as needed. + if(mResampler.isWriteNeeded()) { + if (isInputAvailable()) { + const float *frame = getNextInputFrame(); + mResampler.writeNextFrame(frame); + } else { + break; + } + } else { + // Output frame is interpolated from input samples. + mResampler.readNextFrame(outputBuffer); + outputBuffer += channelCount; + framesLeft--; + } + } + return numFrames - framesLeft; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SampleRateConverter_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SampleRateConverter_android.h new file mode 100644 index 0000000..6ec8ae3 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SampleRateConverter_android.h @@ -0,0 +1,63 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_SAMPLE_RATE_CONVERTER_H +#define FLOWGRAPH_SAMPLE_RATE_CONVERTER_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_resampler_MultiChannelResampler_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +class SampleRateConverter : public FlowGraphFilter { +public: + explicit SampleRateConverter(int32_t channelCount, + resampler::MultiChannelResampler &mResampler); + + virtual ~SampleRateConverter() = default; + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SampleRateConverter"; + } + + void reset() override; + +private: + + // Return true if there is a sample available. + bool isInputAvailable(); + + // This assumes data is available. Only call after calling isInputAvailable(). + const float *getNextInputFrame(); + + resampler::MultiChannelResampler &mResampler; + + int32_t mInputCursor = 0; // offset into the input port buffer + int32_t mNumValidInputFrames = 0; // number of valid frames currently in the input port buffer + // We need our own callCount for upstream calls because calls occur at a different rate. + // This means we cannot have cyclic graphs or merges that contain an SRC. + int64_t mInputCallCount = 0; + +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_SAMPLE_RATE_CONVERTER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkFloat_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkFloat_android.cpp new file mode 100644 index 0000000..e89e85f --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkFloat_android.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_SinkFloat_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +SinkFloat::SinkFloat(int32_t channelCount) + : FlowGraphSink(channelCount) { +} + +int32_t SinkFloat::read(void *data, int32_t numFrames) { + float *floatData = (float *) data; + const int32_t channelCount = input.getSamplesPerFrame(); + + int32_t framesLeft = numFrames; + while (framesLeft > 0) { + // Run the graph and pull data through the input port. + int32_t framesPulled = pullData(framesLeft); + if (framesPulled <= 0) { + break; + } + const float *signal = input.getBuffer(); + int32_t numSamples = framesPulled * channelCount; + memcpy(floatData, signal, numSamples * sizeof(float)); + floatData += numSamples; + framesLeft -= framesPulled; + } + return numFrames - framesLeft; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkFloat_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkFloat_android.h new file mode 100644 index 0000000..adbce83 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkFloat_android.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef FLOWGRAPH_SINK_FLOAT_H +#define FLOWGRAPH_SINK_FLOAT_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * AudioSink that lets you read data as 32-bit floats. + */ +class SinkFloat : public FlowGraphSink { +public: + explicit SinkFloat(int32_t channelCount); + ~SinkFloat() override = default; + + int32_t read(void *data, int32_t numFrames) override; + + const char *getName() override { + return "SinkFloat"; + } +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_SINK_FLOAT_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI16_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI16_android.cpp new file mode 100644 index 0000000..41142ad --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI16_android.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "oboe_flowgraph_SinkI16_android.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +SinkI16::SinkI16(int32_t channelCount) + : FlowGraphSink(channelCount) {} + +int32_t SinkI16::read(void *data, int32_t numFrames) { + int16_t *shortData = (int16_t *) data; + const int32_t channelCount = input.getSamplesPerFrame(); + + int32_t framesLeft = numFrames; + while (framesLeft > 0) { + // Run the graph and pull data through the input port. + int32_t framesRead = pullData(framesLeft); + if (framesRead <= 0) { + break; + } + const float *signal = input.getBuffer(); + int32_t numSamples = framesRead * channelCount; +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_i16_from_float(shortData, signal, numSamples); + shortData += numSamples; + signal += numSamples; +#else + for (int i = 0; i < numSamples; i++) { + int32_t n = (int32_t) (*signal++ * 32768.0f); + *shortData++ = std::min(INT16_MAX, std::max(INT16_MIN, n)); // clip + } +#endif + framesLeft -= framesRead; + } + return numFrames - framesLeft; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI16_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI16_android.h new file mode 100644 index 0000000..5ac36a0 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI16_android.h @@ -0,0 +1,43 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_SINK_I16_H +#define FLOWGRAPH_SINK_I16_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * AudioSink that lets you read data as 16-bit signed integers. + */ +class SinkI16 : public FlowGraphSink { +public: + explicit SinkI16(int32_t channelCount); + + int32_t read(void *data, int32_t numFrames) override; + + const char *getName() override { + return "SinkI16"; + } +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_SINK_I16_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI24_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI24_android.cpp new file mode 100644 index 0000000..4926d0d --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI24_android.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_SinkI24_android.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +SinkI24::SinkI24(int32_t channelCount) + : FlowGraphSink(channelCount) {} + +int32_t SinkI24::read(void *data, int32_t numFrames) { + uint8_t *byteData = (uint8_t *) data; + const int32_t channelCount = input.getSamplesPerFrame(); + + int32_t framesLeft = numFrames; + while (framesLeft > 0) { + // Run the graph and pull data through the input port. + int32_t framesRead = pullData(framesLeft); + if (framesRead <= 0) { + break; + } + const float *floatData = input.getBuffer(); + int32_t numSamples = framesRead * channelCount; +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_p24_from_float(byteData, floatData, numSamples); + static const int kBytesPerI24Packed = 3; + byteData += numSamples * kBytesPerI24Packed; + floatData += numSamples; +#else + const int32_t kI24PackedMax = 0x007FFFFF; + const int32_t kI24PackedMin = 0xFF800000; + for (int i = 0; i < numSamples; i++) { + int32_t n = (int32_t) (*floatData++ * 0x00800000); + n = std::min(kI24PackedMax, std::max(kI24PackedMin, n)); // clip + // Write as a packed 24-bit integer in Little Endian format. + *byteData++ = (uint8_t) n; + *byteData++ = (uint8_t) (n >> 8); + *byteData++ = (uint8_t) (n >> 16); + } +#endif + framesLeft -= framesRead; + } + return numFrames - framesLeft; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI24_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI24_android.h new file mode 100644 index 0000000..74d1ec9 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI24_android.h @@ -0,0 +1,44 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_SINK_I24_H +#define FLOWGRAPH_SINK_I24_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * AudioSink that lets you read data as packed 24-bit signed integers. + * The sample size is 3 bytes. + */ +class SinkI24 : public FlowGraphSink { +public: + explicit SinkI24(int32_t channelCount); + + int32_t read(void *data, int32_t numFrames) override; + + const char *getName() override { + return "SinkI24"; + } +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_SINK_I24_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI32_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI32_android.cpp new file mode 100644 index 0000000..8f1adcb --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI32_android.cpp @@ -0,0 +1,55 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_FlowgraphUtilities_android.h" +#include "oboe_flowgraph_SinkI32_android.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +SinkI32::SinkI32(int32_t channelCount) + : FlowGraphSink(channelCount) {} + +int32_t SinkI32::read(void *data, int32_t numFrames) { + int32_t *intData = (int32_t *) data; + const int32_t channelCount = input.getSamplesPerFrame(); + + int32_t framesLeft = numFrames; + while (framesLeft > 0) { + // Run the graph and pull data through the input port. + int32_t framesRead = pullData(framesLeft); + if (framesRead <= 0) { + break; + } + const float *signal = input.getBuffer(); + int32_t numSamples = framesRead * channelCount; +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_i32_from_float(intData, signal, numSamples); + intData += numSamples; + signal += numSamples; +#else + for (int i = 0; i < numSamples; i++) { + *intData++ = FlowgraphUtilities::clamp32FromFloat(*signal++); + } +#endif + framesLeft -= framesRead; + } + return numFrames - framesLeft; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI32_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI32_android.h new file mode 100644 index 0000000..221692b --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SinkI32_android.h @@ -0,0 +1,40 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_SINK_I32_H +#define FLOWGRAPH_SINK_I32_H + +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +class SinkI32 : public FlowGraphSink { +public: + explicit SinkI32(int32_t channelCount); + ~SinkI32() override = default; + + int32_t read(void *data, int32_t numFrames) override; + + const char *getName() override { + return "SinkI32"; + } +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_SINK_I32_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceFloat_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceFloat_android.cpp new file mode 100644 index 0000000..5780240 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceFloat_android.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_SourceFloat_android.h" + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +SourceFloat::SourceFloat(int32_t channelCount) + : FlowGraphSourceBuffered(channelCount) { +} + +int32_t SourceFloat::onProcess(int32_t numFrames) { + float *outputBuffer = output.getBuffer(); + const int32_t channelCount = output.getSamplesPerFrame(); + + const int32_t framesLeft = mSizeInFrames - mFrameIndex; + const int32_t framesToProcess = std::min(numFrames, framesLeft); + const int32_t numSamples = framesToProcess * channelCount; + + const float *floatBase = (float *) mData; + const float *floatData = &floatBase[mFrameIndex * channelCount]; + memcpy(outputBuffer, floatData, numSamples * sizeof(float)); + mFrameIndex += framesToProcess; + return framesToProcess; +} + diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceFloat_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceFloat_android.h new file mode 100644 index 0000000..461b202 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceFloat_android.h @@ -0,0 +1,44 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_SOURCE_FLOAT_H +#define FLOWGRAPH_SOURCE_FLOAT_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * AudioSource that reads a block of pre-defined float data. + */ +class SourceFloat : public FlowGraphSourceBuffered { +public: + explicit SourceFloat(int32_t channelCount); + ~SourceFloat() override = default; + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceFloat"; + } +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_SOURCE_FLOAT_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI16_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI16_android.cpp new file mode 100644 index 0000000..cf0696a --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI16_android.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_SourceI16_android.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +SourceI16::SourceI16(int32_t channelCount) + : FlowGraphSourceBuffered(channelCount) { +} + +int32_t SourceI16::onProcess(int32_t numFrames) { + float *floatData = output.getBuffer(); + int32_t channelCount = output.getSamplesPerFrame(); + + int32_t framesLeft = mSizeInFrames - mFrameIndex; + int32_t framesToProcess = std::min(numFrames, framesLeft); + int32_t numSamples = framesToProcess * channelCount; + + const int16_t *shortBase = static_cast(mData); + const int16_t *shortData = &shortBase[mFrameIndex * channelCount]; + +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_float_from_i16(floatData, shortData, numSamples); +#else + for (int i = 0; i < numSamples; i++) { + *floatData++ = *shortData++ * (1.0f / 32768); + } +#endif + + mFrameIndex += framesToProcess; + return framesToProcess; +} \ No newline at end of file diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI16_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI16_android.h new file mode 100644 index 0000000..edcf2c5 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI16_android.h @@ -0,0 +1,42 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_SOURCE_I16_H +#define FLOWGRAPH_SOURCE_I16_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { +/** + * AudioSource that reads a block of pre-defined 16-bit integer data. + */ +class SourceI16 : public FlowGraphSourceBuffered { +public: + explicit SourceI16(int32_t channelCount); + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceI16"; + } +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_SOURCE_I16_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI24_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI24_android.cpp new file mode 100644 index 0000000..cb61cb7 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI24_android.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_SourceI24_android.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +constexpr int kBytesPerI24Packed = 3; + +SourceI24::SourceI24(int32_t channelCount) + : FlowGraphSourceBuffered(channelCount) { +} + +int32_t SourceI24::onProcess(int32_t numFrames) { + float *floatData = output.getBuffer(); + int32_t channelCount = output.getSamplesPerFrame(); + + int32_t framesLeft = mSizeInFrames - mFrameIndex; + int32_t framesToProcess = std::min(numFrames, framesLeft); + int32_t numSamples = framesToProcess * channelCount; + + const uint8_t *byteBase = (uint8_t *) mData; + const uint8_t *byteData = &byteBase[mFrameIndex * channelCount * kBytesPerI24Packed]; + +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_float_from_p24(floatData, byteData, numSamples); +#else + static const float scale = 1. / (float)(1UL << 31); + for (int i = 0; i < numSamples; i++) { + // Assemble the data assuming Little Endian format. + int32_t pad = byteData[2]; + pad <<= 8; + pad |= byteData[1]; + pad <<= 8; + pad |= byteData[0]; + pad <<= 8; // Shift to 32 bit data so the sign is correct. + byteData += kBytesPerI24Packed; + *floatData++ = pad * scale; // scale to range -1.0 to 1.0 + } +#endif + + mFrameIndex += framesToProcess; + return framesToProcess; +} \ No newline at end of file diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI24_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI24_android.h new file mode 100644 index 0000000..9d98296 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI24_android.h @@ -0,0 +1,43 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_SOURCE_I24_H +#define FLOWGRAPH_SOURCE_I24_H + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +/** + * AudioSource that reads a block of pre-defined 24-bit packed integer data. + */ +class SourceI24 : public FlowGraphSourceBuffered { +public: + explicit SourceI24(int32_t channelCount); + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceI24"; + } +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_SOURCE_I24_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI32_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI32_android.cpp new file mode 100644 index 0000000..26f0bb3 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI32_android.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" +#include "oboe_flowgraph_SourceI32_android.h" + +#if FLOWGRAPH_ANDROID_INTERNAL +#include +#endif + +using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph; + +SourceI32::SourceI32(int32_t channelCount) + : FlowGraphSourceBuffered(channelCount) { +} + +int32_t SourceI32::onProcess(int32_t numFrames) { + float *floatData = output.getBuffer(); + const int32_t channelCount = output.getSamplesPerFrame(); + + const int32_t framesLeft = mSizeInFrames - mFrameIndex; + const int32_t framesToProcess = std::min(numFrames, framesLeft); + const int32_t numSamples = framesToProcess * channelCount; + + const int32_t *intBase = static_cast(mData); + const int32_t *intData = &intBase[mFrameIndex * channelCount]; + +#if FLOWGRAPH_ANDROID_INTERNAL + memcpy_to_float_from_i32(floatData, intData, numSamples); +#else + for (int i = 0; i < numSamples; i++) { + *floatData++ = *intData++ * kScale; + } +#endif + + mFrameIndex += framesToProcess; + return framesToProcess; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI32_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI32_android.h new file mode 100644 index 0000000..ed82a08 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_SourceI32_android.h @@ -0,0 +1,42 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOWGRAPH_SOURCE_I32_H +#define FLOWGRAPH_SOURCE_I32_H + +#include + +#include "oboe_flowgraph_FlowGraphNode_android.h" + +namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph { + +class SourceI32 : public FlowGraphSourceBuffered { +public: + explicit SourceI32(int32_t channelCount); + ~SourceI32() override = default; + + int32_t onProcess(int32_t numFrames) override; + + const char *getName() override { + return "SourceI32"; + } +private: + static constexpr float kScale = 1.0 / (1UL << 31); +}; + +} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */ + +#endif //FLOWGRAPH_SOURCE_I32_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_HyperbolicCosineWindow_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_HyperbolicCosineWindow_android.h new file mode 100644 index 0000000..f2f94c3 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_HyperbolicCosineWindow_android.h @@ -0,0 +1,71 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H +#define RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H + +#include + +#include "oboe_flowgraph_resampler_ResamplerDefinitions_android.h" + +namespace RESAMPLER_OUTER_NAMESPACE::resampler { + +/** + * Calculate a HyperbolicCosineWindow window centered at 0. + * This can be used in place of a Kaiser window. + * + * The code is based on an anonymous contribution by "a concerned citizen": + * https://dsp.stackexchange.com/questions/37714/kaiser-window-approximation + */ +class HyperbolicCosineWindow { +public: + HyperbolicCosineWindow() { + setStopBandAttenuation(60); + } + + /** + * @param attenuation typical values range from 30 to 90 dB + * @return beta + */ + double setStopBandAttenuation(double attenuation) { + double alpha = ((-325.1e-6 * attenuation + 0.1677) * attenuation) - 3.149; + setAlpha(alpha); + return alpha; + } + + void setAlpha(double alpha) { + mAlpha = alpha; + mInverseCoshAlpha = 1.0 / cosh(alpha); + } + + /** + * @param x ranges from -1.0 to +1.0 + */ + double operator()(double x) { + double x2 = x * x; + if (x2 >= 1.0) return 0.0; + double w = mAlpha * sqrt(1.0 - x2); + return cosh(w) * mInverseCoshAlpha; + } + +private: + double mAlpha = 0.0; + double mInverseCoshAlpha = 1.0; +}; + +} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ + +#endif //RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_IntegerRatio_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_IntegerRatio_android.cpp new file mode 100644 index 0000000..e16307a --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_IntegerRatio_android.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "oboe_flowgraph_resampler_IntegerRatio_android.h" + +using namespace RESAMPLER_OUTER_NAMESPACE::resampler; + +// Enough primes to cover the common sample rates. +static const int kPrimes[] = { + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, + 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, + 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, + 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199}; + +void IntegerRatio::reduce() { + for (int prime : kPrimes) { + if (mNumerator < prime || mDenominator < prime) { + break; + } + + // Find biggest prime factor for numerator. + while (true) { + int top = mNumerator / prime; + int bottom = mDenominator / prime; + if ((top >= 1) + && (bottom >= 1) + && (top * prime == mNumerator) // divided evenly? + && (bottom * prime == mDenominator)) { + mNumerator = top; + mDenominator = bottom; + } else { + break; + } + } + + } +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_IntegerRatio_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_IntegerRatio_android.h new file mode 100644 index 0000000..ca10d24 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_IntegerRatio_android.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RESAMPLER_INTEGER_RATIO_H +#define RESAMPLER_INTEGER_RATIO_H + +#include + +#include "oboe_flowgraph_resampler_ResamplerDefinitions_android.h" + +namespace RESAMPLER_OUTER_NAMESPACE::resampler { + +/** + * Represent the ratio of two integers. + */ +class IntegerRatio { +public: + IntegerRatio(int32_t numerator, int32_t denominator) + : mNumerator(numerator), mDenominator(denominator) {} + + /** + * Reduce by removing common prime factors. + */ + void reduce(); + + int32_t getNumerator() { + return mNumerator; + } + + int32_t getDenominator() { + return mDenominator; + } + +private: + int32_t mNumerator; + int32_t mDenominator; +}; + +} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ + +#endif //RESAMPLER_INTEGER_RATIO_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_KaiserWindow_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_KaiserWindow_android.h new file mode 100644 index 0000000..059c8fc --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_KaiserWindow_android.h @@ -0,0 +1,90 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RESAMPLER_KAISER_WINDOW_H +#define RESAMPLER_KAISER_WINDOW_H + +#include + +#include "oboe_flowgraph_resampler_ResamplerDefinitions_android.h" + +namespace RESAMPLER_OUTER_NAMESPACE::resampler { + +/** + * Calculate a Kaiser window centered at 0. + */ +class KaiserWindow { +public: + KaiserWindow() { + setStopBandAttenuation(60); + } + + /** + * @param attenuation typical values range from 30 to 90 dB + * @return beta + */ + double setStopBandAttenuation(double attenuation) { + double beta = 0.0; + if (attenuation > 50) { + beta = 0.1102 * (attenuation - 8.7); + } else if (attenuation >= 21) { + double a21 = attenuation - 21; + beta = 0.5842 * pow(a21, 0.4) + (0.07886 * a21); + } + setBeta(beta); + return beta; + } + + void setBeta(double beta) { + mBeta = beta; + mInverseBesselBeta = 1.0 / bessel(beta); + } + + /** + * @param x ranges from -1.0 to +1.0 + */ + double operator()(double x) { + double x2 = x * x; + if (x2 >= 1.0) return 0.0; + double w = mBeta * sqrt(1.0 - x2); + return bessel(w) * mInverseBesselBeta; + } + + // Approximation of a + // modified zero order Bessel function of the first kind. + // Based on a discussion at: + // https://dsp.stackexchange.com/questions/37714/kaiser-window-approximation + static double bessel(double x) { + double y = cosh(0.970941817426052 * x); + y += cosh(0.8854560256532099 * x); + y += cosh(0.7485107481711011 * x); + y += cosh(0.5680647467311558 * x); + y += cosh(0.3546048870425356 * x); + y += cosh(0.120536680255323 * x); + y *= 2; + y += cosh(x); + y /= 13; + return y; + } + +private: + double mBeta = 0.0; + double mInverseBesselBeta = 1.0; +}; + +} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ + +#endif //RESAMPLER_KAISER_WINDOW_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_LinearResampler_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_LinearResampler_android.cpp new file mode 100644 index 0000000..616a762 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_LinearResampler_android.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "oboe_flowgraph_resampler_LinearResampler_android.h" + +using namespace RESAMPLER_OUTER_NAMESPACE::resampler; + +LinearResampler::LinearResampler(const MultiChannelResampler::Builder &builder) + : MultiChannelResampler(builder) { + mPreviousFrame = std::make_unique(getChannelCount()); + mCurrentFrame = std::make_unique(getChannelCount()); +} + +void LinearResampler::writeFrame(const float *frame) { + memcpy(mPreviousFrame.get(), mCurrentFrame.get(), sizeof(float) * getChannelCount()); + memcpy(mCurrentFrame.get(), frame, sizeof(float) * getChannelCount()); +} + +void LinearResampler::readFrame(float *frame) { + float *previous = mPreviousFrame.get(); + float *current = mCurrentFrame.get(); + float phase = (float) getIntegerPhase() / mDenominator; + // iterate across samples in the frame + for (int channel = 0; channel < getChannelCount(); channel++) { + float f0 = *previous++; + float f1 = *current++; + *frame++ = f0 + (phase * (f1 - f0)); + } +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_LinearResampler_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_LinearResampler_android.h new file mode 100644 index 0000000..51a7cce --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_LinearResampler_android.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RESAMPLER_LINEAR_RESAMPLER_H +#define RESAMPLER_LINEAR_RESAMPLER_H + +#include +#include +#include + +#include "oboe_flowgraph_resampler_MultiChannelResampler_android.h" +#include "oboe_flowgraph_resampler_ResamplerDefinitions_android.h" + +namespace RESAMPLER_OUTER_NAMESPACE::resampler { + +/** + * Simple resampler that uses bi-linear interpolation. + */ +class LinearResampler : public MultiChannelResampler { +public: + explicit LinearResampler(const MultiChannelResampler::Builder &builder); + + void writeFrame(const float *frame) override; + + void readFrame(float *frame) override; + +private: + std::unique_ptr mPreviousFrame; + std::unique_ptr mCurrentFrame; +}; + +} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ + +#endif //RESAMPLER_LINEAR_RESAMPLER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_MultiChannelResampler_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_MultiChannelResampler_android.cpp new file mode 100644 index 0000000..830d4e3 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_MultiChannelResampler_android.cpp @@ -0,0 +1,172 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "oboe_flowgraph_resampler_IntegerRatio_android.h" +#include "oboe_flowgraph_resampler_LinearResampler_android.h" +#include "oboe_flowgraph_resampler_MultiChannelResampler_android.h" +#include "oboe_flowgraph_resampler_PolyphaseResampler_android.h" +#include "oboe_flowgraph_resampler_PolyphaseResamplerMono_android.h" +#include "oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.h" +#include "oboe_flowgraph_resampler_SincResampler_android.h" +#include "oboe_flowgraph_resampler_SincResamplerStereo_android.h" + +using namespace RESAMPLER_OUTER_NAMESPACE::resampler; + +MultiChannelResampler::MultiChannelResampler(const MultiChannelResampler::Builder &builder) + : mNumTaps(builder.getNumTaps()) + , mX(static_cast(builder.getChannelCount()) + * static_cast(builder.getNumTaps()) * 2) + , mSingleFrame(builder.getChannelCount()) + , mChannelCount(builder.getChannelCount()) + { + // Reduce sample rates to the smallest ratio. + // For example 44100/48000 would become 147/160. + IntegerRatio ratio(builder.getInputRate(), builder.getOutputRate()); + ratio.reduce(); + mNumerator = ratio.getNumerator(); + mDenominator = ratio.getDenominator(); + mIntegerPhase = mDenominator; // so we start with a write needed +} + +// static factory method +MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount, + int32_t inputRate, + int32_t outputRate, + Quality quality) { + Builder builder; + builder.setInputRate(inputRate); + builder.setOutputRate(outputRate); + builder.setChannelCount(channelCount); + + switch (quality) { + case Quality::Fastest: + builder.setNumTaps(2); + break; + case Quality::Low: + builder.setNumTaps(4); + break; + case Quality::Medium: + default: + builder.setNumTaps(8); + break; + case Quality::High: + builder.setNumTaps(16); + break; + case Quality::Best: + builder.setNumTaps(32); + break; + } + + // Set the cutoff frequency so that we do not get aliasing when down-sampling. + if (inputRate > outputRate) { + builder.setNormalizedCutoff(kDefaultNormalizedCutoff); + } + return builder.build(); +} + +MultiChannelResampler *MultiChannelResampler::Builder::build() { + if (getNumTaps() == 2) { + // Note that this does not do low pass filteringh. + return new LinearResampler(*this); + } + IntegerRatio ratio(getInputRate(), getOutputRate()); + ratio.reduce(); + bool usePolyphase = (getNumTaps() * ratio.getDenominator()) <= kMaxCoefficients; + if (usePolyphase) { + if (getChannelCount() == 1) { + return new PolyphaseResamplerMono(*this); + } else if (getChannelCount() == 2) { + return new PolyphaseResamplerStereo(*this); + } else { + return new PolyphaseResampler(*this); + } + } else { + // Use less optimized resampler that uses a float phaseIncrement. + // TODO mono resampler + if (getChannelCount() == 2) { + return new SincResamplerStereo(*this); + } else { + return new SincResampler(*this); + } + } +} + +void MultiChannelResampler::writeFrame(const float *frame) { + // Move cursor before write so that cursor points to last written frame in read. + if (--mCursor < 0) { + mCursor = getNumTaps() - 1; + } + float *dest = &mX[static_cast(mCursor) * static_cast(getChannelCount())]; + int offset = getNumTaps() * getChannelCount(); + for (int channel = 0; channel < getChannelCount(); channel++) { + // Write twice so we avoid having to wrap when reading. + dest[channel] = dest[channel + offset] = frame[channel]; + } +} + +float MultiChannelResampler::sinc(float radians) { + if (abs(radians) < 1.0e-9) return 1.0f; // avoid divide by zero + return sinf(radians) / radians; // Sinc function +} + +// Generate coefficients in the order they will be used by readFrame(). +// This is more complicated but readFrame() is called repeatedly and should be optimized. +void MultiChannelResampler::generateCoefficients(int32_t inputRate, + int32_t outputRate, + int32_t numRows, + double phaseIncrement, + float normalizedCutoff) { + mCoefficients.resize(static_cast(getNumTaps()) * static_cast(numRows)); + int coefficientIndex = 0; + double phase = 0.0; // ranges from 0.0 to 1.0, fraction between samples + // Stretch the sinc function for low pass filtering. + const float cutoffScaler = normalizedCutoff * + ((outputRate < inputRate) + ? ((float)outputRate / inputRate) + : ((float)inputRate / outputRate)); + const int numTapsHalf = getNumTaps() / 2; // numTaps must be even. + const float numTapsHalfInverse = 1.0f / numTapsHalf; + for (int i = 0; i < numRows; i++) { + float tapPhase = phase - numTapsHalf; + float gain = 0.0; // sum of raw coefficients + int gainCursor = coefficientIndex; + for (int tap = 0; tap < getNumTaps(); tap++) { + float radians = tapPhase * M_PI; + +#if MCR_USE_KAISER + float window = mKaiserWindow(tapPhase * numTapsHalfInverse); +#else + float window = mCoshWindow(static_cast(tapPhase) * numTapsHalfInverse); +#endif + float coefficient = sinc(radians * cutoffScaler) * window; + mCoefficients.at(coefficientIndex++) = coefficient; + gain += coefficient; + tapPhase += 1.0; + } + phase += phaseIncrement; + while (phase >= 1.0) { + phase -= 1.0; + } + + // Correct for gain variations. + float gainCorrection = 1.0 / gain; // normalize the gain + for (int tap = 0; tap < getNumTaps(); tap++) { + mCoefficients.at(gainCursor + tap) *= gainCorrection; + } + } +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_MultiChannelResampler_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_MultiChannelResampler_android.h new file mode 100644 index 0000000..0dde44e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_MultiChannelResampler_android.h @@ -0,0 +1,274 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RESAMPLER_MULTICHANNEL_RESAMPLER_H +#define RESAMPLER_MULTICHANNEL_RESAMPLER_H + +#include +#include +#include +#include + +#ifndef MCR_USE_KAISER +// It appears from the spectrogram that the HyperbolicCosine window leads to fewer artifacts. +// And it is faster to calculate. +#define MCR_USE_KAISER 0 +#endif + +#if MCR_USE_KAISER +#include "oboe_flowgraph_resampler_KaiserWindow_android.h" +#else +#include "oboe_flowgraph_resampler_HyperbolicCosineWindow_android.h" +#endif + +#include "oboe_flowgraph_resampler_ResamplerDefinitions_android.h" + +namespace RESAMPLER_OUTER_NAMESPACE::resampler { + +class MultiChannelResampler { + +public: + + enum class Quality : int32_t { + Fastest, + Low, + Medium, + High, + Best, + }; + + class Builder { + public: + /** + * Construct an optimal resampler based on the specified parameters. + * @return address of a resampler + */ + MultiChannelResampler *build(); + + /** + * The number of taps in the resampling filter. + * More taps gives better quality but uses more CPU time. + * This typically ranges from 4 to 64. Default is 16. + * + * For polyphase filters, numTaps must be a multiple of four for loop unrolling. + * @param numTaps number of taps for the filter + * @return address of this builder for chaining calls + */ + Builder *setNumTaps(int32_t numTaps) { + mNumTaps = numTaps; + return this; + } + + /** + * Use 1 for mono, 2 for stereo, etc. Default is 1. + * + * @param channelCount number of channels + * @return address of this builder for chaining calls + */ + Builder *setChannelCount(int32_t channelCount) { + mChannelCount = channelCount; + return this; + } + + /** + * Default is 48000. + * + * @param inputRate sample rate of the input stream + * @return address of this builder for chaining calls + */ + Builder *setInputRate(int32_t inputRate) { + mInputRate = inputRate; + return this; + } + + /** + * Default is 48000. + * + * @param outputRate sample rate of the output stream + * @return address of this builder for chaining calls + */ + Builder *setOutputRate(int32_t outputRate) { + mOutputRate = outputRate; + return this; + } + + /** + * Set cutoff frequency relative to the Nyquist rate of the output sample rate. + * Set to 1.0 to match the Nyquist frequency. + * Set lower to reduce aliasing. + * Default is 0.70. + * + * @param normalizedCutoff anti-aliasing filter cutoff + * @return address of this builder for chaining calls + */ + Builder *setNormalizedCutoff(float normalizedCutoff) { + mNormalizedCutoff = normalizedCutoff; + return this; + } + + int32_t getNumTaps() const { + return mNumTaps; + } + + int32_t getChannelCount() const { + return mChannelCount; + } + + int32_t getInputRate() const { + return mInputRate; + } + + int32_t getOutputRate() const { + return mOutputRate; + } + + float getNormalizedCutoff() const { + return mNormalizedCutoff; + } + + protected: + int32_t mChannelCount = 1; + int32_t mNumTaps = 16; + int32_t mInputRate = 48000; + int32_t mOutputRate = 48000; + float mNormalizedCutoff = kDefaultNormalizedCutoff; + }; + + virtual ~MultiChannelResampler() = default; + + /** + * Factory method for making a resampler that is optimal for the given inputs. + * + * @param channelCount number of channels, 2 for stereo + * @param inputRate sample rate of the input stream + * @param outputRate sample rate of the output stream + * @param quality higher quality sounds better but uses more CPU + * @return an optimal resampler + */ + static MultiChannelResampler *make(int32_t channelCount, + int32_t inputRate, + int32_t outputRate, + Quality quality); + + bool isWriteNeeded() const { + return mIntegerPhase >= mDenominator; + } + + /** + * Write a frame containing N samples. + * + * @param frame pointer to the first sample in a frame + */ + void writeNextFrame(const float *frame) { + writeFrame(frame); + advanceWrite(); + } + + /** + * Read a frame containing N samples. + * + * @param frame pointer to the first sample in a frame + */ + void readNextFrame(float *frame) { + readFrame(frame); + advanceRead(); + } + + int getNumTaps() const { + return mNumTaps; + } + + int getChannelCount() const { + return mChannelCount; + } + + static float hammingWindow(float radians, float spread); + + static float sinc(float radians); + +protected: + + explicit MultiChannelResampler(const MultiChannelResampler::Builder &builder); + + /** + * Write a frame containing N samples. + * Call advanceWrite() after calling this. + * @param frame pointer to the first sample in a frame + */ + virtual void writeFrame(const float *frame); + + /** + * Read a frame containing N samples using interpolation. + * Call advanceRead() after calling this. + * @param frame pointer to the first sample in a frame + */ + virtual void readFrame(float *frame) = 0; + + void advanceWrite() { + mIntegerPhase -= mDenominator; + } + + void advanceRead() { + mIntegerPhase += mNumerator; + } + + /** + * Generate the filter coefficients in optimal order. + * @param inputRate sample rate of the input stream + * @param outputRate sample rate of the output stream + * @param numRows number of rows in the array that contain a set of tap coefficients + * @param phaseIncrement how much to increment the phase between rows + * @param normalizedCutoff filter cutoff frequency normalized to Nyquist rate of output + */ + void generateCoefficients(int32_t inputRate, + int32_t outputRate, + int32_t numRows, + double phaseIncrement, + float normalizedCutoff); + + + int32_t getIntegerPhase() { + return mIntegerPhase; + } + + static constexpr int kMaxCoefficients = 8 * 1024; + std::vector mCoefficients; + + const int mNumTaps; + int mCursor = 0; + std::vector mX; // delayed input values for the FIR + std::vector mSingleFrame; // one frame for temporary use + int32_t mIntegerPhase = 0; + int32_t mNumerator = 0; + int32_t mDenominator = 0; + + +private: + +#if MCR_USE_KAISER + KaiserWindow mKaiserWindow; +#else + HyperbolicCosineWindow mCoshWindow; +#endif + + static constexpr float kDefaultNormalizedCutoff = 0.70f; + + const int mChannelCount; +}; + +} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ + +#endif //RESAMPLER_MULTICHANNEL_RESAMPLER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerMono_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerMono_android.cpp new file mode 100644 index 0000000..6e149ec --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerMono_android.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "oboe_flowgraph_resampler_PolyphaseResamplerMono_android.h" + +using namespace RESAMPLER_OUTER_NAMESPACE::resampler; + +#define MONO 1 + +PolyphaseResamplerMono::PolyphaseResamplerMono(const MultiChannelResampler::Builder &builder) + : PolyphaseResampler(builder) { + assert(builder.getChannelCount() == MONO); +} + +void PolyphaseResamplerMono::writeFrame(const float *frame) { + // Move cursor before write so that cursor points to last written frame in read. + if (--mCursor < 0) { + mCursor = getNumTaps() - 1; + } + float *dest = &mX[mCursor * MONO]; + const int offset = mNumTaps * MONO; + // Write each channel twice so we avoid having to wrap when running the FIR. + const float sample = frame[0]; + // Put ordered writes together. + dest[0] = sample; + dest[offset] = sample; +} + +void PolyphaseResamplerMono::readFrame(float *frame) { + // Clear accumulator. + float sum = 0.0; + + // Multiply input times precomputed windowed sinc function. + const float *coefficients = &mCoefficients[mCoefficientCursor]; + float *xFrame = &mX[mCursor * MONO]; + const int numLoops = mNumTaps >> 2; // n/4 + for (int i = 0; i < numLoops; i++) { + // Manual loop unrolling, might get converted to SIMD. + sum += *xFrame++ * *coefficients++; + sum += *xFrame++ * *coefficients++; + sum += *xFrame++ * *coefficients++; + sum += *xFrame++ * *coefficients++; + } + + mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size(); + + // Copy accumulator to output. + frame[0] = sum; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerMono_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerMono_android.h new file mode 100644 index 0000000..d3195bd --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerMono_android.h @@ -0,0 +1,41 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RESAMPLER_POLYPHASE_RESAMPLER_MONO_H +#define RESAMPLER_POLYPHASE_RESAMPLER_MONO_H + +#include +#include + +#include "oboe_flowgraph_resampler_PolyphaseResampler_android.h" +#include "oboe_flowgraph_resampler_ResamplerDefinitions_android.h" + +namespace RESAMPLER_OUTER_NAMESPACE::resampler { + +class PolyphaseResamplerMono : public PolyphaseResampler { +public: + explicit PolyphaseResamplerMono(const MultiChannelResampler::Builder &builder); + + virtual ~PolyphaseResamplerMono() = default; + + void writeFrame(const float *frame) override; + + void readFrame(float *frame) override; +}; + +} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ + +#endif //RESAMPLER_POLYPHASE_RESAMPLER_MONO_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.cpp new file mode 100644 index 0000000..e02aa66 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.h" + +using namespace RESAMPLER_OUTER_NAMESPACE::resampler; + +#define STEREO 2 + +PolyphaseResamplerStereo::PolyphaseResamplerStereo(const MultiChannelResampler::Builder &builder) + : PolyphaseResampler(builder) { + assert(builder.getChannelCount() == STEREO); +} + +void PolyphaseResamplerStereo::writeFrame(const float *frame) { + // Move cursor before write so that cursor points to last written frame in read. + if (--mCursor < 0) { + mCursor = getNumTaps() - 1; + } + float *dest = &mX[mCursor * STEREO]; + const int offset = mNumTaps * STEREO; + // Write each channel twice so we avoid having to wrap when running the FIR. + const float left = frame[0]; + const float right = frame[1]; + // Put ordered writes together. + dest[0] = left; + dest[1] = right; + dest[offset] = left; + dest[1 + offset] = right; +} + +void PolyphaseResamplerStereo::readFrame(float *frame) { + // Clear accumulators. + float left = 0.0; + float right = 0.0; + + // Multiply input times precomputed windowed sinc function. + const float *coefficients = &mCoefficients[mCoefficientCursor]; + float *xFrame = &mX[mCursor * STEREO]; + const int numLoops = mNumTaps >> 2; // n/4 + for (int i = 0; i < numLoops; i++) { + // Manual loop unrolling, might get converted to SIMD. + float coefficient = *coefficients++; + left += *xFrame++ * coefficient; + right += *xFrame++ * coefficient; + + coefficient = *coefficients++; // next tap + left += *xFrame++ * coefficient; + right += *xFrame++ * coefficient; + + coefficient = *coefficients++; // next tap + left += *xFrame++ * coefficient; + right += *xFrame++ * coefficient; + + coefficient = *coefficients++; // next tap + left += *xFrame++ * coefficient; + right += *xFrame++ * coefficient; + } + + mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size(); + + // Copy accumulators to output. + frame[0] = left; + frame[1] = right; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.h new file mode 100644 index 0000000..a8d0101 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.h @@ -0,0 +1,41 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RESAMPLER_POLYPHASE_RESAMPLER_STEREO_H +#define RESAMPLER_POLYPHASE_RESAMPLER_STEREO_H + +#include +#include + +#include "oboe_flowgraph_resampler_PolyphaseResampler_android.h" +#include "oboe_flowgraph_resampler_ResamplerDefinitions_android.h" + +namespace RESAMPLER_OUTER_NAMESPACE::resampler { + +class PolyphaseResamplerStereo : public PolyphaseResampler { +public: + explicit PolyphaseResamplerStereo(const MultiChannelResampler::Builder &builder); + + virtual ~PolyphaseResamplerStereo() = default; + + void writeFrame(const float *frame) override; + + void readFrame(float *frame) override; +}; + +} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ + +#endif //RESAMPLER_POLYPHASE_RESAMPLER_STEREO_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResampler_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResampler_android.cpp new file mode 100644 index 0000000..70a70d2 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResampler_android.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_flowgraph_resampler_IntegerRatio_android.h" +#include "oboe_flowgraph_resampler_PolyphaseResampler_android.h" + +using namespace RESAMPLER_OUTER_NAMESPACE::resampler; + +PolyphaseResampler::PolyphaseResampler(const MultiChannelResampler::Builder &builder) + : MultiChannelResampler(builder) + { + assert((getNumTaps() % 4) == 0); // Required for loop unrolling. + + int32_t inputRate = builder.getInputRate(); + int32_t outputRate = builder.getOutputRate(); + + int32_t numRows = mDenominator; + double phaseIncrement = (double) inputRate / (double) outputRate; + generateCoefficients(inputRate, outputRate, + numRows, phaseIncrement, + builder.getNormalizedCutoff()); +} + +void PolyphaseResampler::readFrame(float *frame) { + // Clear accumulator for mixing. + std::fill(mSingleFrame.begin(), mSingleFrame.end(), 0.0); + + // Multiply input times windowed sinc function. + float *coefficients = &mCoefficients[mCoefficientCursor]; + float *xFrame = &mX[static_cast(mCursor) * static_cast(getChannelCount())]; + for (int i = 0; i < mNumTaps; i++) { + float coefficient = *coefficients++; + for (int channel = 0; channel < getChannelCount(); channel++) { + mSingleFrame[channel] += *xFrame++ * coefficient; + } + } + + // Advance and wrap through coefficients. + mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size(); + + // Copy accumulator to output. + for (int channel = 0; channel < getChannelCount(); channel++) { + frame[channel] = mSingleFrame[channel]; + } +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResampler_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResampler_android.h new file mode 100644 index 0000000..74cdd7d --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_PolyphaseResampler_android.h @@ -0,0 +1,53 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RESAMPLER_POLYPHASE_RESAMPLER_H +#define RESAMPLER_POLYPHASE_RESAMPLER_H + +#include +#include +#include +#include + +#include "oboe_flowgraph_resampler_MultiChannelResampler_android.h" +#include "oboe_flowgraph_resampler_ResamplerDefinitions_android.h" + +namespace RESAMPLER_OUTER_NAMESPACE::resampler { +/** + * Resampler that is optimized for a reduced ratio of sample rates. + * All of the coefficients for each possible phase value are pre-calculated. + */ +class PolyphaseResampler : public MultiChannelResampler { +public: + /** + * + * @param builder containing lots of parameters + */ + explicit PolyphaseResampler(const MultiChannelResampler::Builder &builder); + + virtual ~PolyphaseResampler() = default; + + void readFrame(float *frame) override; + +protected: + + int32_t mCoefficientCursor = 0; + +}; + +} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ + +#endif //RESAMPLER_POLYPHASE_RESAMPLER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_ResamplerDefinitions_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_ResamplerDefinitions_android.h new file mode 100644 index 0000000..c6791ec --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_ResamplerDefinitions_android.h @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Set flag RESAMPLER_OUTER_NAMESPACE based on whether compiler flag +// __ANDROID_NDK__ is defined. __ANDROID_NDK__ should be defined in oboe +// but not in android. + +#ifndef RESAMPLER_OUTER_NAMESPACE +#ifdef __ANDROID_NDK__ +#define RESAMPLER_OUTER_NAMESPACE oboe +#else +#define RESAMPLER_OUTER_NAMESPACE aaudio +#endif // __ANDROID_NDK__ +#endif // RESAMPLER_OUTER_NAMESPACE diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResamplerStereo_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResamplerStereo_android.cpp new file mode 100644 index 0000000..cd31299 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResamplerStereo_android.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "oboe_flowgraph_resampler_SincResamplerStereo_android.h" + +using namespace RESAMPLER_OUTER_NAMESPACE::resampler; + +#define STEREO 2 + +SincResamplerStereo::SincResamplerStereo(const MultiChannelResampler::Builder &builder) + : SincResampler(builder) { + assert(builder.getChannelCount() == STEREO); +} + +void SincResamplerStereo::writeFrame(const float *frame) { + // Move cursor before write so that cursor points to last written frame in read. + if (--mCursor < 0) { + mCursor = getNumTaps() - 1; + } + float *dest = &mX[mCursor * STEREO]; + const int offset = mNumTaps * STEREO; + // Write each channel twice so we avoid having to wrap when running the FIR. + const float left = frame[0]; + const float right = frame[1]; + // Put ordered writes together. + dest[0] = left; + dest[1] = right; + dest[offset] = left; + dest[1 + offset] = right; +} + +// Multiply input times windowed sinc function. +void SincResamplerStereo::readFrame(float *frame) { + // Clear accumulator for mixing. + std::fill(mSingleFrame.begin(), mSingleFrame.end(), 0.0); + std::fill(mSingleFrame2.begin(), mSingleFrame2.end(), 0.0); + + // Determine indices into coefficients table. + double tablePhase = getIntegerPhase() * mPhaseScaler; + int index1 = static_cast(floor(tablePhase)); + float *coefficients1 = &mCoefficients[static_cast(index1) + * static_cast(getNumTaps())]; + int index2 = (index1 + 1); + assert (index2 < mNumRows); + float *coefficients2 = &mCoefficients[static_cast(index2) + * static_cast(getNumTaps())]; + float *xFrame = &mX[static_cast(mCursor) * static_cast(getChannelCount())]; + for (int i = 0; i < mNumTaps; i++) { + float coefficient1 = *coefficients1++; + float coefficient2 = *coefficients2++; + for (int channel = 0; channel < getChannelCount(); channel++) { + float sample = *xFrame++; + mSingleFrame[channel] += sample * coefficient1; + mSingleFrame2[channel] += sample * coefficient2; + } + } + + // Interpolate and copy to output. + float fraction = tablePhase - index1; + for (int channel = 0; channel < getChannelCount(); channel++) { + float low = mSingleFrame[channel]; + float high = mSingleFrame2[channel]; + frame[channel] = low + (fraction * (high - low)); + } +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResamplerStereo_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResamplerStereo_android.h new file mode 100644 index 0000000..16d3d37 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResamplerStereo_android.h @@ -0,0 +1,42 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RESAMPLER_SINC_RESAMPLER_STEREO_H +#define RESAMPLER_SINC_RESAMPLER_STEREO_H + +#include +#include + +#include "oboe_flowgraph_resampler_SincResampler_android.h" +#include "oboe_flowgraph_resampler_ResamplerDefinitions_android.h" + +namespace RESAMPLER_OUTER_NAMESPACE::resampler { + +class SincResamplerStereo : public SincResampler { +public: + explicit SincResamplerStereo(const MultiChannelResampler::Builder &builder); + + virtual ~SincResamplerStereo() = default; + + void writeFrame(const float *frame) override; + + void readFrame(float *frame) override; + +}; + +} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ + +#endif //RESAMPLER_SINC_RESAMPLER_STEREO_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResampler_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResampler_android.cpp new file mode 100644 index 0000000..7c86f3d --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResampler_android.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "oboe_flowgraph_resampler_SincResampler_android.h" + +using namespace RESAMPLER_OUTER_NAMESPACE::resampler; + +SincResampler::SincResampler(const MultiChannelResampler::Builder &builder) + : MultiChannelResampler(builder) + , mSingleFrame2(builder.getChannelCount()) { + assert((getNumTaps() % 4) == 0); // Required for loop unrolling. + mNumRows = kMaxCoefficients / getNumTaps(); // includes guard row + int32_t numRowsNoGuard = mNumRows - 1; + mPhaseScaler = (double) numRowsNoGuard / mDenominator; + double phaseIncrement = 1.0 / numRowsNoGuard; + generateCoefficients(builder.getInputRate(), + builder.getOutputRate(), + mNumRows, + phaseIncrement, + builder.getNormalizedCutoff()); +} + +void SincResampler::readFrame(float *frame) { + // Clear accumulator for mixing. + std::fill(mSingleFrame.begin(), mSingleFrame.end(), 0.0); + std::fill(mSingleFrame2.begin(), mSingleFrame2.end(), 0.0); + + // Determine indices into coefficients table. + double tablePhase = getIntegerPhase() * mPhaseScaler; + int indexLow = static_cast(floor(tablePhase)); + int indexHigh = indexLow + 1; // OK because using a guard row. + assert (indexHigh < mNumRows); + float *coefficientsLow = &mCoefficients[static_cast(indexLow) + * static_cast(getNumTaps())]; + float *coefficientsHigh = &mCoefficients[static_cast(indexHigh) + * static_cast(getNumTaps())]; + + float *xFrame = &mX[static_cast(mCursor) * static_cast(getChannelCount())]; + for (int tap = 0; tap < mNumTaps; tap++) { + float coefficientLow = *coefficientsLow++; + float coefficientHigh = *coefficientsHigh++; + for (int channel = 0; channel < getChannelCount(); channel++) { + float sample = *xFrame++; + mSingleFrame[channel] += sample * coefficientLow; + mSingleFrame2[channel] += sample * coefficientHigh; + } + } + + // Interpolate and copy to output. + float fraction = tablePhase - indexLow; + for (int channel = 0; channel < getChannelCount(); channel++) { + float low = mSingleFrame[channel]; + float high = mSingleFrame2[channel]; + frame[channel] = low + (fraction * (high - low)); + } +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResampler_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResampler_android.h new file mode 100644 index 0000000..c8a9a2c --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_flowgraph_resampler_SincResampler_android.h @@ -0,0 +1,50 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RESAMPLER_SINC_RESAMPLER_H +#define RESAMPLER_SINC_RESAMPLER_H + +#include +#include +#include + +#include "oboe_flowgraph_resampler_MultiChannelResampler_android.h" +#include "oboe_flowgraph_resampler_ResamplerDefinitions_android.h" + +namespace RESAMPLER_OUTER_NAMESPACE::resampler { + +/** + * Resampler that can interpolate between coefficients. + * This can be used to support arbitrary ratios. + */ +class SincResampler : public MultiChannelResampler { +public: + explicit SincResampler(const MultiChannelResampler::Builder &builder); + + virtual ~SincResampler() = default; + + void readFrame(float *frame) override; + +protected: + + std::vector mSingleFrame2; // for interpolation + int32_t mNumRows = 0; + double mPhaseScaler = 1.0; +}; + +} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */ + +#endif //RESAMPLER_SINC_RESAMPLER_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStreamBase_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStreamBase_android.h new file mode 100644 index 0000000..650fbc3 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStreamBase_android.h @@ -0,0 +1,274 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_STREAM_BASE_H_ +#define OBOE_STREAM_BASE_H_ + +#include +#include +#include "oboe_oboe_AudioStreamCallback_android.h" +#include "oboe_oboe_Definitions_android.h" + +namespace oboe { + +/** + * Base class containing parameters for audio streams and builders. + **/ +class AudioStreamBase { + +public: + + AudioStreamBase() {} + + virtual ~AudioStreamBase() = default; + + // This class only contains primitives so we can use default constructor and copy methods. + + /** + * Default copy constructor + */ + AudioStreamBase(const AudioStreamBase&) = default; + + /** + * Default assignment operator + */ + AudioStreamBase& operator=(const AudioStreamBase&) = default; + + /** + * @return number of channels, for example 2 for stereo, or kUnspecified + */ + int32_t getChannelCount() const { return mChannelCount; } + + /** + * @return Direction::Input or Direction::Output + */ + Direction getDirection() const { return mDirection; } + + /** + * @return sample rate for the stream or kUnspecified + */ + int32_t getSampleRate() const { return mSampleRate; } + + /** + * @deprecated use `getFramesPerDataCallback` instead. + */ + int32_t getFramesPerCallback() const { return getFramesPerDataCallback(); } + + /** + * @return the number of frames in each data callback or kUnspecified. + */ + int32_t getFramesPerDataCallback() const { return mFramesPerCallback; } + + /** + * @return the audio sample format (e.g. Float or I16) + */ + AudioFormat getFormat() const { return mFormat; } + + /** + * Query the maximum number of frames that can be filled without blocking. + * If the stream has been closed the last known value will be returned. + * + * @return buffer size + */ + virtual int32_t getBufferSizeInFrames() { return mBufferSizeInFrames; } + + /** + * @return capacityInFrames or kUnspecified + */ + virtual int32_t getBufferCapacityInFrames() const { return mBufferCapacityInFrames; } + + /** + * @return the sharing mode of the stream. + */ + SharingMode getSharingMode() const { return mSharingMode; } + + /** + * @return the performance mode of the stream. + */ + PerformanceMode getPerformanceMode() const { return mPerformanceMode; } + + /** + * @return the device ID of the stream. + */ + int32_t getDeviceId() const { return mDeviceId; } + + /** + * For internal use only. + * @return the data callback object for this stream, if set. + */ + AudioStreamDataCallback *getDataCallback() const { + return mDataCallback; + } + + /** + * For internal use only. + * @return the error callback object for this stream, if set. + */ + AudioStreamErrorCallback *getErrorCallback() const { + return mErrorCallback; + } + + /** + * @return true if a data callback was set for this stream + */ + bool isDataCallbackSpecified() const { + return mDataCallback != nullptr; + } + + /** + * Note that if the app does not set an error callback then a + * default one may be provided. + * @return true if an error callback was set for this stream + */ + bool isErrorCallbackSpecified() const { + return mErrorCallback != nullptr; + } + + /** + * @return the usage for this stream. + */ + Usage getUsage() const { return mUsage; } + + /** + * @return the stream's content type. + */ + ContentType getContentType() const { return mContentType; } + + /** + * @return the stream's input preset. + */ + InputPreset getInputPreset() const { return mInputPreset; } + + /** + * @return the stream's session ID allocation strategy (None or Allocate). + */ + SessionId getSessionId() const { return mSessionId; } + + /** + * @return true if Oboe can convert channel counts to achieve optimal results. + */ + bool isChannelConversionAllowed() const { + return mChannelConversionAllowed; + } + + /** + * @return true if Oboe can convert data formats to achieve optimal results. + */ + bool isFormatConversionAllowed() const { + return mFormatConversionAllowed; + } + + /** + * @return whether and how Oboe can convert sample rates to achieve optimal results. + */ + SampleRateConversionQuality getSampleRateConversionQuality() const { + return mSampleRateConversionQuality; + } + + /** + * @return the stream's channel mask. + */ + ChannelMask getChannelMask() const { + return mChannelMask; + } + +protected: + /** The callback which will be fired when new data is ready to be read/written. **/ + AudioStreamDataCallback *mDataCallback = nullptr; + std::shared_ptr mSharedDataCallback; + + /** The callback which will be fired when an error or a disconnect occurs. **/ + AudioStreamErrorCallback *mErrorCallback = nullptr; + std::shared_ptr mSharedErrorCallback; + + /** Number of audio frames which will be requested in each callback */ + int32_t mFramesPerCallback = kUnspecified; + /** Stream channel count */ + int32_t mChannelCount = kUnspecified; + /** Stream sample rate */ + int32_t mSampleRate = kUnspecified; + /** Stream audio device ID */ + int32_t mDeviceId = kUnspecified; + /** Stream buffer capacity specified as a number of audio frames */ + int32_t mBufferCapacityInFrames = kUnspecified; + /** Stream buffer size specified as a number of audio frames */ + int32_t mBufferSizeInFrames = kUnspecified; + /** Stream channel mask. Only active on Android 32+ */ + ChannelMask mChannelMask = ChannelMask::Unspecified; + + /** Stream sharing mode */ + SharingMode mSharingMode = SharingMode::Shared; + /** Format of audio frames */ + AudioFormat mFormat = AudioFormat::Unspecified; + /** Stream direction */ + Direction mDirection = Direction::Output; + /** Stream performance mode */ + PerformanceMode mPerformanceMode = PerformanceMode::None; + + /** Stream usage. Only active on Android 28+ */ + Usage mUsage = Usage::Media; + /** Stream content type. Only active on Android 28+ */ + ContentType mContentType = ContentType::Music; + /** Stream input preset. Only active on Android 28+ + * TODO InputPreset::Unspecified should be considered as a possible default alternative. + */ + InputPreset mInputPreset = InputPreset::VoiceRecognition; + /** Stream session ID allocation strategy. Only active on Android 28+ */ + SessionId mSessionId = SessionId::None; + + /** Control the name of the package creating the stream. Only active on Android 31+ */ + std::string mPackageName; + /** Control the attribution tag of the context creating the stream. Only active on Android 31+ */ + std::string mAttributionTag; + + // Control whether Oboe can convert channel counts to achieve optimal results. + bool mChannelConversionAllowed = false; + // Control whether Oboe can convert data formats to achieve optimal results. + bool mFormatConversionAllowed = false; + // Control whether and how Oboe can convert sample rates to achieve optimal results. + SampleRateConversionQuality mSampleRateConversionQuality = SampleRateConversionQuality::None; + + /** Validate stream parameters that might not be checked in lower layers */ + virtual Result isValidConfig() { + switch (mFormat) { + case AudioFormat::Unspecified: + case AudioFormat::I16: + case AudioFormat::Float: + case AudioFormat::I24: + case AudioFormat::I32: + break; + + default: + return Result::ErrorInvalidFormat; + } + + switch (mSampleRateConversionQuality) { + case SampleRateConversionQuality::None: + case SampleRateConversionQuality::Fastest: + case SampleRateConversionQuality::Low: + case SampleRateConversionQuality::Medium: + case SampleRateConversionQuality::High: + case SampleRateConversionQuality::Best: + return Result::OK; + default: + return Result::ErrorIllegalArgument; + } + } +}; + +} // namespace oboe + +#endif /* OBOE_STREAM_BASE_H_ */ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStreamBuilder_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStreamBuilder_android.h new file mode 100644 index 0000000..773686b --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStreamBuilder_android.h @@ -0,0 +1,602 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_STREAM_BUILDER_H_ +#define OBOE_STREAM_BUILDER_H_ + +#include "oboe_oboe_Definitions_android.h" +#include "oboe_oboe_AudioStreamBase_android.h" +#include "oboe_oboe_Utilities_android.h" +#include "oboe_oboe_ResultWithValue_android.h" + +namespace oboe { + + // This depends on AudioStream, so we use forward declaration, it will close and delete the stream + struct StreamDeleterFunctor; + using ManagedStream = std::unique_ptr; + +/** + * Factory class for an audio Stream. + */ +class AudioStreamBuilder : public AudioStreamBase { +public: + + AudioStreamBuilder() : AudioStreamBase() {} + + AudioStreamBuilder(const AudioStreamBase &audioStreamBase): AudioStreamBase(audioStreamBase) {} + + /** + * Request a specific number of channels. + * + * Default is kUnspecified. If the value is unspecified then + * the application should query for the actual value after the stream is opened. + * + * As the channel count here may be different from the corresponding channel count of + * provided channel mask used in setChannelMask(). The last called will be respected + * if this function and setChannelMask() are called. + */ + AudioStreamBuilder *setChannelCount(int channelCount) { + mChannelCount = channelCount; + mChannelMask = ChannelMask::Unspecified; + return this; + } + + /** + * Request a specific channel mask. + * + * Default is kUnspecified. If the value is unspecified then the application + * should query for the actual value after the stream is opened. + * + * As the corresponding channel count of provided channel mask here may be different + * from the channel count used in setChannelCount(). The last called will be respected + * if this function and setChannelCount() are called. + * + * As the setChannelMask API is available on Android 32+, this call will only take effects + * on Android 32+. + */ + AudioStreamBuilder *setChannelMask(ChannelMask channelMask) { + mChannelMask = channelMask; + mChannelCount = getChannelCountFromChannelMask(channelMask); + return this; + } + + /** + * Request the direction for a stream. The default is Direction::Output. + * + * @param direction Direction::Output or Direction::Input + */ + AudioStreamBuilder *setDirection(Direction direction) { + mDirection = direction; + return this; + } + + /** + * Request a specific sample rate in Hz. + * + * Default is kUnspecified. If the value is unspecified then + * the application should query for the actual value after the stream is opened. + * + * Technically, this should be called the "frame rate" or "frames per second", + * because it refers to the number of complete frames transferred per second. + * But it is traditionally called "sample rate". Se we use that term. + * + */ + AudioStreamBuilder *setSampleRate(int32_t sampleRate) { + mSampleRate = sampleRate; + return this; + } + + /** + * @deprecated use `setFramesPerDataCallback` instead. + */ + AudioStreamBuilder *setFramesPerCallback(int framesPerCallback) { + return setFramesPerDataCallback(framesPerCallback); + } + + /** + * Request a specific number of frames for the data callback. + * + * Default is kUnspecified. If the value is unspecified then + * the actual number may vary from callback to callback. + * + * If an application can handle a varying number of frames then we recommend + * leaving this unspecified. This allow the underlying API to optimize + * the callbacks. But if your application is, for example, doing FFTs or other block + * oriented operations, then call this function to get the sizes you need. + * + * Calling setFramesPerDataCallback() does not guarantee anything about timing. + * This just collects the data into a the number of frames that your app requires. + * We encourage leaving this unspecified in most cases. + * + * If this number is larger than the burst size, some bursts will not receive a callback. + * If this number is smaller than the burst size, there may be multiple callbacks in a single + * burst. + * + * @param framesPerCallback + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setFramesPerDataCallback(int framesPerCallback) { + mFramesPerCallback = framesPerCallback; + return this; + } + + /** + * Request a sample data format, for example Format::Float. + * + * Default is Format::Unspecified. If the value is unspecified then + * the application should query for the actual value after the stream is opened. + */ + AudioStreamBuilder *setFormat(AudioFormat format) { + mFormat = format; + return this; + } + + /** + * Set the requested buffer capacity in frames. + * BufferCapacityInFrames is the maximum possible BufferSizeInFrames. + * + * The final stream capacity may differ. For AAudio it should be at least this big. + * For OpenSL ES, it could be smaller. + * + * Default is kUnspecified. + * + * @param bufferCapacityInFrames the desired buffer capacity in frames or kUnspecified + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setBufferCapacityInFrames(int32_t bufferCapacityInFrames) { + mBufferCapacityInFrames = bufferCapacityInFrames; + return this; + } + + /** + * Get the audio API which will be requested when opening the stream. No guarantees that this is + * the API which will actually be used. Query the stream itself to find out the API which is + * being used. + * + * If you do not specify the API, then AAudio will be used if isAAudioRecommended() + * returns true. Otherwise OpenSL ES will be used. + * + * @return the requested audio API + */ + AudioApi getAudioApi() const { return mAudioApi; } + + /** + * If you leave this unspecified then Oboe will choose the best API + * for the device and SDK version at runtime. + * + * This should almost always be left unspecified, except for debugging purposes. + * Specifying AAudio will force Oboe to use AAudio on 8.0, which is extremely risky. + * Specifying OpenSLES should mainly be used to test legacy performance/functionality. + * + * If the caller requests AAudio and it is supported then AAudio will be used. + * + * @param audioApi Must be AudioApi::Unspecified, AudioApi::OpenSLES or AudioApi::AAudio. + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setAudioApi(AudioApi audioApi) { + mAudioApi = audioApi; + return this; + } + + /** + * Is the AAudio API supported on this device? + * + * AAudio was introduced in the Oreo 8.0 release. + * + * @return true if supported + */ + static bool isAAudioSupported(); + + /** + * Is the AAudio API recommended this device? + * + * AAudio may be supported but not recommended because of version specific issues. + * AAudio is not recommended for Android 8.0 or earlier versions. + * + * @return true if recommended + */ + static bool isAAudioRecommended(); + + /** + * Request a mode for sharing the device. + * The requested sharing mode may not be available. + * So the application should query for the actual mode after the stream is opened. + * + * @param sharingMode SharingMode::Shared or SharingMode::Exclusive + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setSharingMode(SharingMode sharingMode) { + mSharingMode = sharingMode; + return this; + } + + /** + * Request a performance level for the stream. + * This will determine the latency, the power consumption, and the level of + * protection from glitches. + * + * @param performanceMode for example, PerformanceMode::LowLatency + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setPerformanceMode(PerformanceMode performanceMode) { + mPerformanceMode = performanceMode; + return this; + } + + + /** + * Set the intended use case for an output stream. + * + * The system will use this information to optimize the behavior of the stream. + * This could, for example, affect how volume and focus is handled for the stream. + * The usage is ignored for input streams. + * + * The default, if you do not call this function, is Usage::Media. + * + * Added in API level 28. + * + * @param usage the desired usage, eg. Usage::Game + */ + AudioStreamBuilder *setUsage(Usage usage) { + mUsage = usage; + return this; + } + + /** + * Set the type of audio data that an output stream will carry. + * + * The system will use this information to optimize the behavior of the stream. + * This could, for example, affect whether a stream is paused when a notification occurs. + * The contentType is ignored for input streams. + * + * The default, if you do not call this function, is ContentType::Music. + * + * Added in API level 28. + * + * @param contentType the type of audio data, eg. ContentType::Speech + */ + AudioStreamBuilder *setContentType(ContentType contentType) { + mContentType = contentType; + return this; + } + + /** + * Set the input (capture) preset for the stream. + * + * The system will use this information to optimize the behavior of the stream. + * This could, for example, affect which microphones are used and how the + * recorded data is processed. + * + * The default, if you do not call this function, is InputPreset::VoiceRecognition. + * That is because VoiceRecognition is the preset with the lowest latency + * on many platforms. + * + * Added in API level 28. + * + * @param inputPreset the desired configuration for recording + */ + AudioStreamBuilder *setInputPreset(InputPreset inputPreset) { + mInputPreset = inputPreset; + return this; + } + + /** Set the requested session ID. + * + * The session ID can be used to associate a stream with effects processors. + * The effects are controlled using the Android AudioEffect Java API. + * + * The default, if you do not call this function, is SessionId::None. + * + * If set to SessionId::Allocate then a session ID will be allocated + * when the stream is opened. + * + * The allocated session ID can be obtained by calling AudioStream::getSessionId() + * and then used with this function when opening another stream. + * This allows effects to be shared between streams. + * + * Session IDs from Oboe can be used the Android Java APIs and vice versa. + * So a session ID from an Oboe stream can be passed to Java + * and effects applied using the Java AudioEffect API. + * + * Allocated session IDs will always be positive and nonzero. + * + * Added in API level 28. + * + * @param sessionId an allocated sessionID or SessionId::Allocate + */ + AudioStreamBuilder *setSessionId(SessionId sessionId) { + mSessionId = sessionId; + return this; + } + + /** + * Request a stream to a specific audio input/output device given an audio device ID. + * + * In most cases, the primary device will be the appropriate device to use, and the + * deviceId can be left kUnspecified. + * + * The ID could be obtained from the Java AudioManager. + * AudioManager.getDevices() returns an array of AudioDeviceInfo, + * which contains a getId() method. That ID can be passed to this function. + * + * It is possible that you may not get the device that you requested. + * So if it is important to you, you should call + * stream->getDeviceId() after the stream is opened to + * verify the actual ID. + * + * Note that when using OpenSL ES, this will be ignored and the created + * stream will have deviceId kUnspecified. + * + * @param deviceId device identifier or kUnspecified + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setDeviceId(int32_t deviceId) { + mDeviceId = deviceId; + return this; + } + + /** + * Specifies an object to handle data related callbacks from the underlying API. + * + * Important: See AudioStreamCallback for restrictions on what may be called + * from the callback methods. + * + * We pass a shared_ptr so that the sharedDataCallback object cannot be deleted + * before the stream is deleted. + * + * @param dataCallback + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setDataCallback(std::shared_ptr sharedDataCallback) { + // Use this raw pointer in the rest of the code to retain backwards compatibility. + mDataCallback = sharedDataCallback.get(); + // Hold a shared_ptr to protect the raw pointer for the lifetime of the stream. + mSharedDataCallback = sharedDataCallback; + return this; + } + + /** + * Pass a raw pointer to a data callback. This is not recommended because the dataCallback + * object might get deleted by the app while it is being used. + * + * @deprecated Call setDataCallback(std::shared_ptr) instead. + * @param dataCallback + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setDataCallback(AudioStreamDataCallback *dataCallback) { + mDataCallback = dataCallback; + mSharedDataCallback = nullptr; + return this; + } + + /** + * Specifies an object to handle error related callbacks from the underlying API. + * This can occur when a stream is disconnected because a headset is plugged in or unplugged. + * It can also occur if the audio service fails or if an exclusive stream is stolen by + * another stream. + * + * Important: See AudioStreamCallback for restrictions on what may be called + * from the callback methods. + * + * When an error callback occurs, the associated stream must be stopped and closed + * in a separate thread. + * + * We pass a shared_ptr so that the errorCallback object cannot be deleted before the stream is deleted. + * If the stream was created using a shared_ptr then the stream cannot be deleted before the + * error callback has finished running. + * + * @param sharedErrorCallback + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setErrorCallback(std::shared_ptr sharedErrorCallback) { + // Use this raw pointer in the rest of the code to retain backwards compatibility. + mErrorCallback = sharedErrorCallback.get(); + // Hold a shared_ptr to protect the raw pointer for the lifetime of the stream. + mSharedErrorCallback = sharedErrorCallback; + return this; + } + + /** + * Pass a raw pointer to an error callback. This is not recommended because the errorCallback + * object might get deleted by the app while it is being used. + * + * @deprecated Call setErrorCallback(std::shared_ptr) instead. + * @param errorCallback + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setErrorCallback(AudioStreamErrorCallback *errorCallback) { + mErrorCallback = errorCallback; + mSharedErrorCallback = nullptr; + return this; + } + + /** + * Specifies an object to handle data or error related callbacks from the underlying API. + * + * This is the equivalent of calling both setDataCallback() and setErrorCallback(). + * + * Important: See AudioStreamCallback for restrictions on what may be called + * from the callback methods. + * + * When an error callback occurs, the associated stream will be stopped and closed in a separate thread. + * + * A note on why the streamCallback parameter is a raw pointer rather than a smart pointer: + * + * The caller should retain ownership of the object streamCallback points to. At first glance weak_ptr may seem like + * a good candidate for streamCallback as this implies temporary ownership. However, a weak_ptr can only be created + * from a shared_ptr. A shared_ptr incurs some performance overhead. The callback object is likely to be accessed + * every few milliseconds when the stream requires new data so this overhead is something we want to avoid. + * + * This leaves a raw pointer as the logical type choice. The only caveat being that the caller must not destroy + * the callback before the stream has been closed. + * + * @param streamCallback + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setCallback(AudioStreamCallback *streamCallback) { + // Use the same callback object for both, dual inheritance. + mDataCallback = streamCallback; + mErrorCallback = streamCallback; + return this; + } + + /** + * If true then Oboe might convert channel counts to achieve optimal results. + * On some versions of Android for example, stereo streams could not use a FAST track. + * So a mono stream might be used instead and duplicated to two channels. + * On some devices, mono streams might be broken, so a stereo stream might be opened + * and converted to mono. + * + * Default is false. + */ + AudioStreamBuilder *setChannelConversionAllowed(bool allowed) { + mChannelConversionAllowed = allowed; + return this; + } + + /** + * If true then Oboe might convert data formats to achieve optimal results. + * On some versions of Android, for example, a float stream could not get a + * low latency data path. So an I16 stream might be opened and converted to float. + * + * Default is false. + */ + AudioStreamBuilder *setFormatConversionAllowed(bool allowed) { + mFormatConversionAllowed = allowed; + return this; + } + + /** + * Specify the quality of the sample rate converter in Oboe. + * + * If set to None then Oboe will not do sample rate conversion. But the underlying APIs might + * still do sample rate conversion if you specify a sample rate. + * That can prevent you from getting a low latency stream. + * + * If you do the conversion in Oboe then you might still get a low latency stream. + * + * Default is SampleRateConversionQuality::None + */ + AudioStreamBuilder *setSampleRateConversionQuality(SampleRateConversionQuality quality) { + mSampleRateConversionQuality = quality; + return this; + } + + /** + * Declare the name of the package creating the stream. + * + * This is usually {@code Context#getPackageName()}. + * + * The default, if you do not call this function, is a random package in the calling uid. + * The vast majority of apps have only one package per calling UID. + * If an invalid package name is set, input streams may not be given permission to + * record when started. + * + * The package name is usually the applicationId in your app's build.gradle file. + * + * Available since API level 31. + * + * @param packageName packageName of the calling app. + */ + AudioStreamBuilder *setPackageName(std::string packageName) { + mPackageName = packageName; + return this; + } + + /** + * Declare the attribution tag of the context creating the stream. + * + * This is usually {@code Context#getAttributionTag()}. + * + * The default, if you do not call this function, is null. + * + * Available since API level 31. + * + * @param attributionTag attributionTag of the calling context. + */ + AudioStreamBuilder *setAttributionTag(std::string attributionTag) { + mAttributionTag = attributionTag; + return this; + } + + /** + * @return true if AAudio will be used based on the current settings. + */ + bool willUseAAudio() const { + return (mAudioApi == AudioApi::AAudio && isAAudioSupported()) + || (mAudioApi == AudioApi::Unspecified && isAAudioRecommended()); + } + + /** + * Create and open a stream object based on the current settings. + * + * The caller owns the pointer to the AudioStream object + * and must delete it when finished. + * + * @deprecated Use openStream(std::shared_ptr &stream) instead. + * @param stream pointer to a variable to receive the stream address + * @return OBOE_OK if successful or a negative error code + */ + Result openStream(AudioStream **stream); + + /** + * Create and open a stream object based on the current settings. + * + * The caller shares the pointer to the AudioStream object. + * The shared_ptr is used internally by Oboe to prevent the stream from being + * deleted while it is being used by callbacks. + * + * @param stream reference to a shared_ptr to receive the stream address + * @return OBOE_OK if successful or a negative error code + */ + Result openStream(std::shared_ptr &stream); + + /** + * Create and open a ManagedStream object based on the current builder state. + * + * The caller must create a unique ptr, and pass by reference so it can be + * modified to point to an opened stream. The caller owns the unique ptr, + * and it will be automatically closed and deleted when going out of scope. + * + * @deprecated Use openStream(std::shared_ptr &stream) instead. + * @param stream Reference to the ManagedStream (uniqueptr) used to keep track of stream + * @return OBOE_OK if successful or a negative error code. + */ + Result openManagedStream(ManagedStream &stream); + +private: + + /** + * @param other + * @return true if channels, format and sample rate match + */ + bool isCompatible(AudioStreamBase &other); + + /** + * Create an AudioStream object. The AudioStream must be opened before use. + * + * The caller owns the pointer. + * + * @return pointer to an AudioStream object or nullptr. + */ + oboe::AudioStream *build(); + + AudioApi mAudioApi = AudioApi::Unspecified; +}; + +} // namespace oboe + +#endif /* OBOE_STREAM_BUILDER_H_ */ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStreamCallback_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStreamCallback_android.h new file mode 100644 index 0000000..35e544a --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStreamCallback_android.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_STREAM_CALLBACK_H +#define OBOE_STREAM_CALLBACK_H + +#include "oboe_oboe_Definitions_android.h" + +namespace oboe { + +class AudioStream; + +/** + * AudioStreamDataCallback defines a callback interface for + * moving data to/from an audio stream using `onAudioReady` + * 2) being alerted when a stream has an error using `onError*` methods + * + * It is used with AudioStreamBuilder::setDataCallback(). + */ + +class AudioStreamDataCallback { +public: + virtual ~AudioStreamDataCallback() = default; + + /** + * A buffer is ready for processing. + * + * For an output stream, this function should render and write numFrames of data + * in the stream's current data format to the audioData buffer. + * + * For an input stream, this function should read and process numFrames of data + * from the audioData buffer. + * + * The audio data is passed through the buffer. So do NOT call read() or + * write() on the stream that is making the callback. + * + * Note that numFrames can vary unless AudioStreamBuilder::setFramesPerCallback() + * is called. + * + * Also note that this callback function should be considered a "real-time" function. + * It must not do anything that could cause an unbounded delay because that can cause the + * audio to glitch or pop. + * + * These are things the function should NOT do: + *
    + *
  • allocate memory using, for example, malloc() or new
  • + *
  • any file operations such as opening, closing, reading or writing
  • + *
  • any network operations such as streaming
  • + *
  • use any mutexes or other synchronization primitives
  • + *
  • sleep
  • + *
  • oboeStream->stop(), pause(), flush() or close()
  • + *
  • oboeStream->read()
  • + *
  • oboeStream->write()
  • + *
+ * + * The following are OK to call from the data callback: + *
    + *
  • oboeStream->get*()
  • + *
  • oboe::convertToText()
  • + *
  • oboeStream->setBufferSizeInFrames()
  • + *
+ * + * If you need to move data, eg. MIDI commands, in or out of the callback function then + * we recommend the use of non-blocking techniques such as an atomic FIFO. + * + * @param audioStream pointer to the associated stream + * @param audioData buffer containing input data or a place to put output data + * @param numFrames number of frames to be processed + * @return DataCallbackResult::Continue or DataCallbackResult::Stop + */ + virtual DataCallbackResult onAudioReady( + AudioStream *audioStream, + void *audioData, + int32_t numFrames) = 0; +}; + +/** + * AudioStreamErrorCallback defines a callback interface for + * being alerted when a stream has an error or is disconnected + * using `onError*` methods. + * + * Note: This callback is only fired when an AudioStreamCallback is set. + * If you use AudioStream::write() you have to evaluate the return codes of + * AudioStream::write() to notice errors in the stream. + * + * It is used with AudioStreamBuilder::setErrorCallback(). + */ +class AudioStreamErrorCallback { +public: + virtual ~AudioStreamErrorCallback() = default; + + /** + * This will be called before other `onError` methods when an error occurs on a stream, + * such as when the stream is disconnected. + * + * It can be used to override and customize the normal error processing. + * Use of this method is considered an advanced technique. + * It might, for example, be used if an app want to use a high level lock when + * closing and reopening a stream. + * Or it might be used when an app want to signal a management thread that handles + * all of the stream state. + * + * If this method returns false it indicates that the stream has *not been stopped and closed + * by the application. In this case it will be stopped by Oboe in the following way: + * onErrorBeforeClose() will be called, then the stream will be closed and onErrorAfterClose() + * will be closed. + * + * If this method returns true it indicates that the stream *has* been stopped and closed + * by the application and Oboe will not do this. + * In that case, the app MUST stop() and close() the stream. + * + * This method will be called on a thread created by Oboe. + * + * @param audioStream pointer to the associated stream + * @param error + * @return true if the stream has been stopped and closed, false if not + */ + virtual bool onError(AudioStream* /* audioStream */, Result /* error */) { + return false; + } + + /** + * This will be called when an error occurs on a stream, + * such as when the stream is disconnected, + * and if onError() returns false (indicating that the error has not already been handled). + * + * Note that this will be called on a thread created by Oboe. + * + * The underlying stream will already be stopped by Oboe but not yet closed. + * So the stream can be queried. + * + * Do not close or delete the stream in this method because it will be + * closed after this method returns. + * + * @param audioStream pointer to the associated stream + * @param error + */ + virtual void onErrorBeforeClose(AudioStream* /* audioStream */, Result /* error */) {} + + /** + * This will be called when an error occurs on a stream, + * such as when the stream is disconnected, + * and if onError() returns false (indicating that the error has not already been handled). + * + * The underlying AAudio or OpenSL ES stream will already be stopped AND closed by Oboe. + * So the underlying stream cannot be referenced. + * But you can still query most parameters. + * + * This callback could be used to reopen a new stream on another device. + * + * @param audioStream pointer to the associated stream + * @param error + */ + virtual void onErrorAfterClose(AudioStream* /* audioStream */, Result /* error */) {} + +}; + +/** + * AudioStreamCallback defines a callback interface for: + * + * 1) moving data to/from an audio stream using `onAudioReady` + * 2) being alerted when a stream has an error using `onError*` methods + * + * It is used with AudioStreamBuilder::setCallback(). + * + * It combines the interfaces defined by AudioStreamDataCallback and AudioStreamErrorCallback. + * This was the original callback object. We now recommend using the individual interfaces + * and using setDataCallback() and setErrorCallback(). + * + * @deprecated Use `AudioStreamDataCallback` and `AudioStreamErrorCallback` instead + */ +class AudioStreamCallback : public AudioStreamDataCallback, + public AudioStreamErrorCallback { +public: + virtual ~AudioStreamCallback() = default; +}; + +} // namespace oboe + +#endif //OBOE_STREAM_CALLBACK_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStream_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStream_android.h new file mode 100644 index 0000000..9f13180 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_AudioStream_android.h @@ -0,0 +1,623 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_STREAM_H_ +#define OBOE_STREAM_H_ + +#include +#include +#include +#include +#include "oboe_oboe_Definitions_android.h" +#include "oboe_oboe_ResultWithValue_android.h" +#include "oboe_oboe_AudioStreamBuilder_android.h" +#include "oboe_oboe_AudioStreamBase_android.h" + +/** WARNING - UNDER CONSTRUCTION - THIS API WILL CHANGE. */ + +namespace oboe { + +/** + * The default number of nanoseconds to wait for when performing state change operations on the + * stream, such as `start` and `stop`. + * + * @see oboe::AudioStream::start + */ +constexpr int64_t kDefaultTimeoutNanos = (2000 * kNanosPerMillisecond); + +/** + * Base class for Oboe C++ audio stream. + */ +class AudioStream : public AudioStreamBase { + friend class AudioStreamBuilder; // allow access to setWeakThis() and lockWeakThis() +public: + + AudioStream() {} + + /** + * Construct an `AudioStream` using the given `AudioStreamBuilder` + * + * @param builder containing all the stream's attributes + */ + explicit AudioStream(const AudioStreamBuilder &builder); + + virtual ~AudioStream() = default; + + /** + * Open a stream based on the current settings. + * + * Note that we do not recommend re-opening a stream that has been closed. + * TODO Should we prevent re-opening? + * + * @return + */ + virtual Result open() { + return Result::OK; // Called by subclasses. Might do more in the future. + } + + /** + * Close the stream and deallocate any resources from the open() call. + */ + virtual Result close(); + + /** + * Start the stream. This will block until the stream has been started, an error occurs + * or `timeoutNanoseconds` has been reached. + */ + virtual Result start(int64_t timeoutNanoseconds = kDefaultTimeoutNanos); + + /** + * Pause the stream. This will block until the stream has been paused, an error occurs + * or `timeoutNanoseconds` has been reached. + */ + virtual Result pause(int64_t timeoutNanoseconds = kDefaultTimeoutNanos); + + /** + * Flush the stream. This will block until the stream has been flushed, an error occurs + * or `timeoutNanoseconds` has been reached. + */ + virtual Result flush(int64_t timeoutNanoseconds = kDefaultTimeoutNanos); + + /** + * Stop the stream. This will block until the stream has been stopped, an error occurs + * or `timeoutNanoseconds` has been reached. + */ + virtual Result stop(int64_t timeoutNanoseconds = kDefaultTimeoutNanos); + + /* Asynchronous requests. + * Use waitForStateChange() if you need to wait for completion. + */ + + /** + * Start the stream asynchronously. Returns immediately (does not block). Equivalent to calling + * `start(0)`. + */ + virtual Result requestStart() = 0; + + /** + * Pause the stream asynchronously. Returns immediately (does not block). Equivalent to calling + * `pause(0)`. + */ + virtual Result requestPause() = 0; + + /** + * Flush the stream asynchronously. Returns immediately (does not block). Equivalent to calling + * `flush(0)`. + */ + virtual Result requestFlush() = 0; + + /** + * Stop the stream asynchronously. Returns immediately (does not block). Equivalent to calling + * `stop(0)`. + */ + virtual Result requestStop() = 0; + + /** + * Query the current state, eg. StreamState::Pausing + * + * @return state or a negative error. + */ + virtual StreamState getState() = 0; + + /** + * Wait until the stream's current state no longer matches the input state. + * The input state is passed to avoid race conditions caused by the state + * changing between calls. + * + * Note that generally applications do not need to call this. It is considered + * an advanced technique and is mostly used for testing. + * + *

+     * int64_t timeoutNanos = 500 * kNanosPerMillisecond; // arbitrary 1/2 second
+     * StreamState currentState = stream->getState();
+     * StreamState nextState = StreamState::Unknown;
+     * while (result == Result::OK && currentState != StreamState::Paused) {
+     *     result = stream->waitForStateChange(
+     *                                   currentState, &nextState, timeoutNanos);
+     *     currentState = nextState;
+     * }
+     * 
+ * + * If the state does not change within the timeout period then it will + * return ErrorTimeout. This is true even if timeoutNanoseconds is zero. + * + * @param inputState The state we want to change away from. + * @param nextState Pointer to a variable that will be set to the new state. + * @param timeoutNanoseconds The maximum time to wait in nanoseconds. + * @return Result::OK or a Result::Error. + */ + virtual Result waitForStateChange(StreamState inputState, + StreamState *nextState, + int64_t timeoutNanoseconds) = 0; + + /** + * This can be used to adjust the latency of the buffer by changing + * the threshold where blocking will occur. + * By combining this with getXRunCount(), the latency can be tuned + * at run-time for each device. + * + * This cannot be set higher than getBufferCapacity(). + * + * @param requestedFrames requested number of frames that can be filled without blocking + * @return the resulting buffer size in frames (obtained using value()) or an error (obtained + * using error()) + */ + virtual ResultWithValue setBufferSizeInFrames(int32_t /* requestedFrames */) { + return Result::ErrorUnimplemented; + } + + /** + * An XRun is an Underrun or an Overrun. + * During playing, an underrun will occur if the stream is not written in time + * and the system runs out of valid data. + * During recording, an overrun will occur if the stream is not read in time + * and there is no place to put the incoming data so it is discarded. + * + * An underrun or overrun can cause an audible "pop" or "glitch". + * + * @return a result which is either Result::OK with the xRun count as the value, or a + * Result::Error* code + */ + virtual ResultWithValue getXRunCount() { + return ResultWithValue(Result::ErrorUnimplemented); + } + + /** + * @return true if XRun counts are supported on the stream + */ + virtual bool isXRunCountSupported() const = 0; + + /** + * Query the number of frames that are read or written by the endpoint at one time. + * + * @return burst size + */ + int32_t getFramesPerBurst() const { + return mFramesPerBurst; + } + + /** + * Get the number of bytes in each audio frame. This is calculated using the channel count + * and the sample format. For example, a 2 channel floating point stream will have + * 2 * 4 = 8 bytes per frame. + * + * @return number of bytes in each audio frame. + */ + int32_t getBytesPerFrame() const { return mChannelCount * getBytesPerSample(); } + + /** + * Get the number of bytes per sample. This is calculated using the sample format. For example, + * a stream using 16-bit integer samples will have 2 bytes per sample. + * + * @return the number of bytes per sample. + */ + int32_t getBytesPerSample() const; + + /** + * The number of audio frames written into the stream. + * This monotonic counter will never get reset. + * + * @return the number of frames written so far + */ + virtual int64_t getFramesWritten(); + + /** + * The number of audio frames read from the stream. + * This monotonic counter will never get reset. + * + * @return the number of frames read so far + */ + virtual int64_t getFramesRead(); + + /** + * Calculate the latency of a stream based on getTimestamp(). + * + * Output latency is the time it takes for a given frame to travel from the + * app to some type of digital-to-analog converter. If the DAC is external, for example + * in a USB interface or a TV connected by HDMI, then there may be additional latency + * that the Android device is unaware of. + * + * Input latency is the time it takes to a given frame to travel from an analog-to-digital + * converter (ADC) to the app. + * + * Note that the latency of an OUTPUT stream will increase abruptly when you write data to it + * and then decrease slowly over time as the data is consumed. + * + * The latency of an INPUT stream will decrease abruptly when you read data from it + * and then increase slowly over time as more data arrives. + * + * The latency of an OUTPUT stream is generally higher than the INPUT latency + * because an app generally tries to keep the OUTPUT buffer full and the INPUT buffer empty. + * + * Note that due to issues in Android before R, we recommend NOT calling + * this method from a data callback. See this tech note for more details. + * https://github.com/google/oboe/blob/main/docs/notes/rlsbuffer.md + * + * @return a ResultWithValue which has a result of Result::OK and a value containing the latency + * in milliseconds, or a result of Result::Error*. + */ + virtual ResultWithValue calculateLatencyMillis() { + return ResultWithValue(Result::ErrorUnimplemented); + } + + /** + * Get the estimated time that the frame at `framePosition` entered or left the audio processing + * pipeline. + * + * This can be used to coordinate events and interactions with the external environment, and to + * estimate the latency of an audio stream. An example of usage can be found in the hello-oboe + * sample (search for "calculateCurrentOutputLatencyMillis"). + * + * The time is based on the implementation's best effort, using whatever knowledge is available + * to the system, but cannot account for any delay unknown to the implementation. + * + * Note that due to issues in Android before R, we recommend NOT calling + * this method from a data callback. See this tech note for more details. + * https://github.com/google/oboe/blob/main/docs/notes/rlsbuffer.md + * + * @deprecated since 1.0, use AudioStream::getTimestamp(clockid_t clockId) instead, which + * returns ResultWithValue + * @param clockId the type of clock to use e.g. CLOCK_MONOTONIC + * @param framePosition the frame number to query + * @param timeNanoseconds an output parameter which will contain the presentation timestamp + */ + virtual Result getTimestamp(clockid_t /* clockId */, + int64_t* /* framePosition */, + int64_t* /* timeNanoseconds */) { + return Result::ErrorUnimplemented; + } + + /** + * Get the estimated time that the frame at `framePosition` entered or left the audio processing + * pipeline. + * + * This can be used to coordinate events and interactions with the external environment, and to + * estimate the latency of an audio stream. An example of usage can be found in the hello-oboe + * sample (search for "calculateCurrentOutputLatencyMillis"). + * + * The time is based on the implementation's best effort, using whatever knowledge is available + * to the system, but cannot account for any delay unknown to the implementation. + * + * Note that due to issues in Android before R, we recommend NOT calling + * this method from a data callback. See this tech note for more details. + * https://github.com/google/oboe/blob/main/docs/notes/rlsbuffer.md + * + * See + * @param clockId the type of clock to use e.g. CLOCK_MONOTONIC + * @return a FrameTimestamp containing the position and time at which a particular audio frame + * entered or left the audio processing pipeline, or an error if the operation failed. + */ + virtual ResultWithValue getTimestamp(clockid_t /* clockId */); + + // ============== I/O =========================== + /** + * Write data from the supplied buffer into the stream. This method will block until the write + * is complete or it runs out of time. + * + * If `timeoutNanoseconds` is zero then this call will not wait. + * + * @param buffer The address of the first sample. + * @param numFrames Number of frames to write. Only complete frames will be written. + * @param timeoutNanoseconds Maximum number of nanoseconds to wait for completion. + * @return a ResultWithValue which has a result of Result::OK and a value containing the number + * of frames actually written, or result of Result::Error*. + */ + virtual ResultWithValue write(const void* /* buffer */, + int32_t /* numFrames */, + int64_t /* timeoutNanoseconds */ ) { + return ResultWithValue(Result::ErrorUnimplemented); + } + + /** + * Read data into the supplied buffer from the stream. This method will block until the read + * is complete or it runs out of time. + * + * If `timeoutNanoseconds` is zero then this call will not wait. + * + * @param buffer The address of the first sample. + * @param numFrames Number of frames to read. Only complete frames will be read. + * @param timeoutNanoseconds Maximum number of nanoseconds to wait for completion. + * @return a ResultWithValue which has a result of Result::OK and a value containing the number + * of frames actually read, or result of Result::Error*. + */ + virtual ResultWithValue read(void* /* buffer */, + int32_t /* numFrames */, + int64_t /* timeoutNanoseconds */) { + return ResultWithValue(Result::ErrorUnimplemented); + } + + /** + * Get the underlying audio API which the stream uses. + * + * @return the API that this stream uses. + */ + virtual AudioApi getAudioApi() const = 0; + + /** + * Returns true if the underlying audio API is AAudio. + * + * @return true if this stream is implemented using the AAudio API. + */ + bool usesAAudio() const { + return getAudioApi() == AudioApi::AAudio; + } + + /** + * Only for debugging. Do not use in production. + * If you need to call this method something is wrong. + * If you think you need it for production then please let us know + * so we can modify Oboe so that you don't need this. + * + * @return nullptr or a pointer to a stream from the system API + */ + virtual void *getUnderlyingStream() const { + return nullptr; + } + + /** + * Update mFramesWritten. + * For internal use only. + */ + virtual void updateFramesWritten() = 0; + + /** + * Update mFramesRead. + * For internal use only. + */ + virtual void updateFramesRead() = 0; + + /* + * Swap old callback for new callback. + * This not atomic. + * This should only be used internally. + * @param dataCallback + * @return previous dataCallback + */ + AudioStreamDataCallback *swapDataCallback(AudioStreamDataCallback *dataCallback) { + AudioStreamDataCallback *previousCallback = mDataCallback; + mDataCallback = dataCallback; + return previousCallback; + } + + /* + * Swap old callback for new callback. + * This not atomic. + * This should only be used internally. + * @param errorCallback + * @return previous errorCallback + */ + AudioStreamErrorCallback *swapErrorCallback(AudioStreamErrorCallback *errorCallback) { + AudioStreamErrorCallback *previousCallback = mErrorCallback; + mErrorCallback = errorCallback; + return previousCallback; + } + + /** + * @return number of frames of data currently in the buffer + */ + ResultWithValue getAvailableFrames(); + + /** + * Wait until the stream has a minimum amount of data available in its buffer. + * This can be used with an EXCLUSIVE MMAP input stream to avoid reading data too close to + * the DSP write position, which may cause glitches. + * + * @param numFrames minimum frames available + * @param timeoutNanoseconds + * @return number of frames available, ErrorTimeout + */ + ResultWithValue waitForAvailableFrames(int32_t numFrames, + int64_t timeoutNanoseconds); + + /** + * @return last result passed from an error callback + */ + virtual oboe::Result getLastErrorCallbackResult() const { + return mErrorCallbackResult; + } + + + int32_t getDelayBeforeCloseMillis() const { + return mDelayBeforeCloseMillis; + } + + /** + * Set the time to sleep before closing the internal stream. + * + * Sometimes a callback can occur shortly after a stream has been stopped and + * even after a close! If the stream has been closed then the callback + * might access memory that has been freed, which could cause a crash. + * This seems to be more likely in Android P or earlier. + * But it can also occur in later versions. By sleeping, we give time for + * the callback threads to finish. + * + * Note that this only has an effect when OboeGlobals::areWorkaroundsEnabled() is true. + * + * @param delayBeforeCloseMillis time to sleep before close. + */ + void setDelayBeforeCloseMillis(int32_t delayBeforeCloseMillis) { + mDelayBeforeCloseMillis = delayBeforeCloseMillis; + } + +protected: + + /** + * This is used to detect more than one error callback from a stream. + * These were bugs in some versions of Android that caused multiple error callbacks. + * Internal bug b/63087953 + * + * Calling this sets an atomic true and returns the previous value. + * + * @return false on first call, true on subsequent calls + */ + bool wasErrorCallbackCalled() { + return mErrorCallbackCalled.exchange(true); + } + + /** + * Wait for a transition from one state to another. + * @return OK if the endingState was observed, or ErrorUnexpectedState + * if any state that was not the startingState or endingState was observed + * or ErrorTimeout. + */ + virtual Result waitForStateTransition(StreamState startingState, + StreamState endingState, + int64_t timeoutNanoseconds); + + /** + * Override this to provide a default for when the application did not specify a callback. + * + * @param audioData + * @param numFrames + * @return result + */ + virtual DataCallbackResult onDefaultCallback(void* /* audioData */, int /* numFrames */) { + return DataCallbackResult::Stop; + } + + /** + * Override this to provide your own behaviour for the audio callback + * + * @param audioData container array which audio frames will be written into or read from + * @param numFrames number of frames which were read/written + * @return the result of the callback: stop or continue + * + */ + DataCallbackResult fireDataCallback(void *audioData, int numFrames); + + /** + * @return true if callbacks may be called + */ + bool isDataCallbackEnabled() { + return mDataCallbackEnabled; + } + + /** + * This can be set false internally to prevent callbacks + * after DataCallbackResult::Stop has been returned. + */ + void setDataCallbackEnabled(bool enabled) { + mDataCallbackEnabled = enabled; + } + + /** + * This should only be called as a stream is being opened. + * Otherwise we might override setDelayBeforeCloseMillis(). + */ + void calculateDefaultDelayBeforeCloseMillis(); + + /** + * Try to avoid a race condition when closing. + */ + void sleepBeforeClose() { + if (mDelayBeforeCloseMillis > 0) { + usleep(mDelayBeforeCloseMillis * 1000); + } + } + + /* + * Set a weak_ptr to this stream from the shared_ptr so that we can + * later use a shared_ptr in the error callback. + */ + void setWeakThis(std::shared_ptr &sharedStream) { + mWeakThis = sharedStream; + } + + /* + * Make a shared_ptr that will prevent this stream from being deleted. + */ + std::shared_ptr lockWeakThis() { + return mWeakThis.lock(); + } + + std::weak_ptr mWeakThis; // weak pointer to this object + + /** + * Number of frames which have been written into the stream + * + * This is signed integer to match the counters in AAudio. + * At audio rates, the counter will overflow in about six million years. + */ + std::atomic mFramesWritten{}; + + /** + * Number of frames which have been read from the stream. + * + * This is signed integer to match the counters in AAudio. + * At audio rates, the counter will overflow in about six million years. + */ + std::atomic mFramesRead{}; + + std::mutex mLock; // for synchronizing start/stop/close + + oboe::Result mErrorCallbackResult = oboe::Result::OK; + + /** + * Number of frames which will be copied to/from the audio device in a single read/write + * operation + */ + int32_t mFramesPerBurst = kUnspecified; + + // Time to sleep in order to prevent a race condition with a callback after a close(). + // Two milliseconds may be enough but 10 msec is even safer. + static constexpr int kMinDelayBeforeCloseMillis = 10; + int32_t mDelayBeforeCloseMillis = kMinDelayBeforeCloseMillis; + +private: + + // Log the scheduler if it changes. + void checkScheduler(); + int mPreviousScheduler = -1; + + std::atomic mDataCallbackEnabled{false}; + std::atomic mErrorCallbackCalled{false}; +}; + +/** + * This struct is a stateless functor which closes an AudioStream prior to its deletion. + * This means it can be used to safely delete a smart pointer referring to an open stream. + */ + struct StreamDeleterFunctor { + void operator()(AudioStream *audioStream) { + if (audioStream) { + audioStream->close(); + } + delete audioStream; + } + }; +} // namespace oboe + +#endif /* OBOE_STREAM_H_ */ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Definitions_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Definitions_android.h new file mode 100644 index 0000000..be65c84 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Definitions_android.h @@ -0,0 +1,711 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_DEFINITIONS_H +#define OBOE_DEFINITIONS_H + +#include +#include + +// Oboe needs to be able to build on old NDKs so we use hard coded constants. +// The correctness of these constants is verified in "aaudio/AAudioLoader.cpp". + +namespace oboe { + + /** + * Represents any attribute, property or value which hasn't been specified. + */ + constexpr int32_t kUnspecified = 0; + + // TODO: Investigate using std::chrono + /** + * The number of nanoseconds in a microsecond. 1,000. + */ + constexpr int64_t kNanosPerMicrosecond = 1000; + + /** + * The number of nanoseconds in a millisecond. 1,000,000. + */ + constexpr int64_t kNanosPerMillisecond = kNanosPerMicrosecond * 1000; + + /** + * The number of milliseconds in a second. 1,000. + */ + constexpr int64_t kMillisPerSecond = 1000; + + /** + * The number of nanoseconds in a second. 1,000,000,000. + */ + constexpr int64_t kNanosPerSecond = kNanosPerMillisecond * kMillisPerSecond; + + /** + * The state of the audio stream. + */ + enum class StreamState : int32_t { // aaudio_stream_state_t + Uninitialized = 0, // AAUDIO_STREAM_STATE_UNINITIALIZED, + Unknown = 1, // AAUDIO_STREAM_STATE_UNKNOWN, + Open = 2, // AAUDIO_STREAM_STATE_OPEN, + Starting = 3, // AAUDIO_STREAM_STATE_STARTING, + Started = 4, // AAUDIO_STREAM_STATE_STARTED, + Pausing = 5, // AAUDIO_STREAM_STATE_PAUSING, + Paused = 6, // AAUDIO_STREAM_STATE_PAUSED, + Flushing = 7, // AAUDIO_STREAM_STATE_FLUSHING, + Flushed = 8, // AAUDIO_STREAM_STATE_FLUSHED, + Stopping = 9, // AAUDIO_STREAM_STATE_STOPPING, + Stopped = 10, // AAUDIO_STREAM_STATE_STOPPED, + Closing = 11, // AAUDIO_STREAM_STATE_CLOSING, + Closed = 12, // AAUDIO_STREAM_STATE_CLOSED, + Disconnected = 13, // AAUDIO_STREAM_STATE_DISCONNECTED, + }; + + /** + * The direction of the stream. + */ + enum class Direction : int32_t { // aaudio_direction_t + + /** + * Used for playback. + */ + Output = 0, // AAUDIO_DIRECTION_OUTPUT, + + /** + * Used for recording. + */ + Input = 1, // AAUDIO_DIRECTION_INPUT, + }; + + /** + * The format of audio samples. + */ + enum class AudioFormat : int32_t { // aaudio_format_t + /** + * Invalid format. + */ + Invalid = -1, // AAUDIO_FORMAT_INVALID, + + /** + * Unspecified format. Format will be decided by Oboe. + */ + Unspecified = 0, // AAUDIO_FORMAT_UNSPECIFIED, + + /** + * Signed 16-bit integers. + */ + I16 = 1, // AAUDIO_FORMAT_PCM_I16, + + /** + * Single precision floating point. + * + * This is the recommended format for most applications. + * But note that the use of Float may prevent the opening of + * a low-latency input path on OpenSL ES or Legacy AAudio streams. + */ + Float = 2, // AAUDIO_FORMAT_PCM_FLOAT, + + /** + * Signed 24-bit integers, packed into 3 bytes. + * + * Note that the use of this format does not guarantee that + * the full precision will be provided. The underlying device may + * be using I16 format. + * + * Added in API 31 (S). + */ + I24 = 3, // AAUDIO_FORMAT_PCM_I24_PACKED + + /** + * Signed 32-bit integers. + * + * Note that the use of this format does not guarantee that + * the full precision will be provided. The underlying device may + * be using I16 format. + * + * Added in API 31 (S). + */ + I32 = 4, // AAUDIO_FORMAT_PCM_I32 + + }; + + /** + * The result of an audio callback. + */ + enum class DataCallbackResult : int32_t { // aaudio_data_callback_result_t + // Indicates to the caller that the callbacks should continue. + Continue = 0, // AAUDIO_CALLBACK_RESULT_CONTINUE, + + // Indicates to the caller that the callbacks should stop immediately. + Stop = 1, // AAUDIO_CALLBACK_RESULT_STOP, + }; + + /** + * The result of an operation. All except the `OK` result indicates that an error occurred. + * The `Result` can be converted into a human readable string using `convertToText`. + */ + enum class Result : int32_t { // aaudio_result_t + OK = 0, // AAUDIO_OK + ErrorBase = -900, // AAUDIO_ERROR_BASE, + ErrorDisconnected = -899, // AAUDIO_ERROR_DISCONNECTED, + ErrorIllegalArgument = -898, // AAUDIO_ERROR_ILLEGAL_ARGUMENT, + ErrorInternal = -896, // AAUDIO_ERROR_INTERNAL, + ErrorInvalidState = -895, // AAUDIO_ERROR_INVALID_STATE, + ErrorInvalidHandle = -892, // AAUDIO_ERROR_INVALID_HANDLE, + ErrorUnimplemented = -890, // AAUDIO_ERROR_UNIMPLEMENTED, + ErrorUnavailable = -889, // AAUDIO_ERROR_UNAVAILABLE, + ErrorNoFreeHandles = -888, // AAUDIO_ERROR_NO_FREE_HANDLES, + ErrorNoMemory = -887, // AAUDIO_ERROR_NO_MEMORY, + ErrorNull = -886, // AAUDIO_ERROR_NULL, + ErrorTimeout = -885, // AAUDIO_ERROR_TIMEOUT, + ErrorWouldBlock = -884, // AAUDIO_ERROR_WOULD_BLOCK, + ErrorInvalidFormat = -883, // AAUDIO_ERROR_INVALID_FORMAT, + ErrorOutOfRange = -882, // AAUDIO_ERROR_OUT_OF_RANGE, + ErrorNoService = -881, // AAUDIO_ERROR_NO_SERVICE, + ErrorInvalidRate = -880, // AAUDIO_ERROR_INVALID_RATE, + // Reserved for future AAudio result types + Reserved1, + Reserved2, + Reserved3, + Reserved4, + Reserved5, + Reserved6, + Reserved7, + Reserved8, + Reserved9, + Reserved10, + ErrorClosed = -869, + }; + + /** + * The sharing mode of the audio stream. + */ + enum class SharingMode : int32_t { // aaudio_sharing_mode_t + + /** + * This will be the only stream using a particular source or sink. + * This mode will provide the lowest possible latency. + * You should close EXCLUSIVE streams immediately when you are not using them. + * + * If you do not need the lowest possible latency then we recommend using Shared, + * which is the default. + */ + Exclusive = 0, // AAUDIO_SHARING_MODE_EXCLUSIVE, + + /** + * Multiple applications can share the same device. + * The data from output streams will be mixed by the audio service. + * The data for input streams will be distributed by the audio service. + * + * This will have higher latency than the EXCLUSIVE mode. + */ + Shared = 1, // AAUDIO_SHARING_MODE_SHARED, + }; + + /** + * The performance mode of the audio stream. + */ + enum class PerformanceMode : int32_t { // aaudio_performance_mode_t + + /** + * No particular performance needs. Default. + */ + None = 10, // AAUDIO_PERFORMANCE_MODE_NONE, + + /** + * Extending battery life is most important. + */ + PowerSaving = 11, // AAUDIO_PERFORMANCE_MODE_POWER_SAVING, + + /** + * Reducing latency is most important. + */ + LowLatency = 12, // AAUDIO_PERFORMANCE_MODE_LOW_LATENCY + }; + + /** + * The underlying audio API used by the audio stream. + */ + enum class AudioApi : int32_t { + /** + * Try to use AAudio. If not available then use OpenSL ES. + */ + Unspecified = kUnspecified, + + /** + * Use OpenSL ES. + * Note that OpenSL ES is deprecated in Android 13, API 30 and above. + */ + OpenSLES, + + /** + * Try to use AAudio. Fail if unavailable. + * AAudio was first supported in Android 8, API 26 and above. + * It is only recommended for API 27 and above. + */ + AAudio + }; + + /** + * Specifies the quality of the sample rate conversion performed by Oboe. + * Higher quality will require more CPU load. + * Higher quality conversion will probably be implemented using a sinc based resampler. + */ + enum class SampleRateConversionQuality : int32_t { + /** + * No conversion by Oboe. Underlying APIs may still do conversion. + */ + None, + /** + * Fastest conversion but may not sound great. + * This may be implemented using bilinear interpolation. + */ + Fastest, + /** + * Low quality conversion with 8 taps. + */ + Low, + /** + * Medium quality conversion with 16 taps. + */ + Medium, + /** + * High quality conversion with 32 taps. + */ + High, + /** + * Highest quality conversion, which may be expensive in terms of CPU. + */ + Best, + }; + + /** + * The Usage attribute expresses *why* you are playing a sound, what is this sound used for. + * This information is used by certain platforms or routing policies + * to make more refined volume or routing decisions. + * + * Note that these match the equivalent values in AudioAttributes in the Android Java API. + * + * This attribute only has an effect on Android API 28+. + */ + enum class Usage : int32_t { // aaudio_usage_t + /** + * Use this for streaming media, music performance, video, podcasts, etcetera. + */ + Media = 1, // AAUDIO_USAGE_MEDIA + + /** + * Use this for voice over IP, telephony, etcetera. + */ + VoiceCommunication = 2, // AAUDIO_USAGE_VOICE_COMMUNICATION + + /** + * Use this for sounds associated with telephony such as busy tones, DTMF, etcetera. + */ + VoiceCommunicationSignalling = 3, // AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING + + /** + * Use this to demand the users attention. + */ + Alarm = 4, // AAUDIO_USAGE_ALARM + + /** + * Use this for notifying the user when a message has arrived or some + * other background event has occured. + */ + Notification = 5, // AAUDIO_USAGE_NOTIFICATION + + /** + * Use this when the phone rings. + */ + NotificationRingtone = 6, // AAUDIO_USAGE_NOTIFICATION_RINGTONE + + /** + * Use this to attract the users attention when, for example, the battery is low. + */ + NotificationEvent = 10, // AAUDIO_USAGE_NOTIFICATION_EVENT + + /** + * Use this for screen readers, etcetera. + */ + AssistanceAccessibility = 11, // AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY + + /** + * Use this for driving or navigation directions. + */ + AssistanceNavigationGuidance = 12, // AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE + + /** + * Use this for user interface sounds, beeps, etcetera. + */ + AssistanceSonification = 13, // AAUDIO_USAGE_ASSISTANCE_SONIFICATION + + /** + * Use this for game audio and sound effects. + */ + Game = 14, // AAUDIO_USAGE_GAME + + /** + * Use this for audio responses to user queries, audio instructions or help utterances. + */ + Assistant = 16, // AAUDIO_USAGE_ASSISTANT + }; + + + /** + * The ContentType attribute describes *what* you are playing. + * It expresses the general category of the content. This information is optional. + * But in case it is known (for instance {@link Movie} for a + * movie streaming service or {@link Speech} for + * an audio book application) this information might be used by the audio framework to + * enforce audio focus. + * + * Note that these match the equivalent values in AudioAttributes in the Android Java API. + * + * This attribute only has an effect on Android API 28+. + */ + enum ContentType : int32_t { // aaudio_content_type_t + + /** + * Use this for spoken voice, audio books, etcetera. + */ + Speech = 1, // AAUDIO_CONTENT_TYPE_SPEECH + + /** + * Use this for pre-recorded or live music. + */ + Music = 2, // AAUDIO_CONTENT_TYPE_MUSIC + + /** + * Use this for a movie or video soundtrack. + */ + Movie = 3, // AAUDIO_CONTENT_TYPE_MOVIE + + /** + * Use this for sound is designed to accompany a user action, + * such as a click or beep sound made when the user presses a button. + */ + Sonification = 4, // AAUDIO_CONTENT_TYPE_SONIFICATION + }; + + /** + * Defines the audio source. + * An audio source defines both a default physical source of audio signal, and a recording + * configuration. + * + * Note that these match the equivalent values in MediaRecorder.AudioSource in the Android Java API. + * + * This attribute only has an effect on Android API 28+. + */ + enum InputPreset : int32_t { // aaudio_input_preset_t + /** + * Use this preset when other presets do not apply. + */ + Generic = 1, // AAUDIO_INPUT_PRESET_GENERIC + + /** + * Use this preset when recording video. + */ + Camcorder = 5, // AAUDIO_INPUT_PRESET_CAMCORDER + + /** + * Use this preset when doing speech recognition. + */ + VoiceRecognition = 6, // AAUDIO_INPUT_PRESET_VOICE_RECOGNITION + + /** + * Use this preset when doing telephony or voice messaging. + */ + VoiceCommunication = 7, // AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION + + /** + * Use this preset to obtain an input with no effects. + * Note that this input will not have automatic gain control + * so the recorded volume may be very low. + */ + Unprocessed = 9, // AAUDIO_INPUT_PRESET_UNPROCESSED + + /** + * Use this preset for capturing audio meant to be processed in real time + * and played back for live performance (e.g karaoke). + * The capture path will minimize latency and coupling with playback path. + */ + VoicePerformance = 10, // AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE + + }; + + /** + * This attribute can be used to allocate a session ID to the audio stream. + * + * This attribute only has an effect on Android API 28+. + */ + enum SessionId { + /** + * Do not allocate a session ID. + * Effects cannot be used with this stream. + * Default. + */ + None = -1, // AAUDIO_SESSION_ID_NONE + + /** + * Allocate a session ID that can be used to attach and control + * effects using the Java AudioEffects API. + * Note that the use of this flag may result in higher latency. + * + * Note that this matches the value of AudioManager.AUDIO_SESSION_ID_GENERATE. + */ + Allocate = 0, // AAUDIO_SESSION_ID_ALLOCATE + }; + + /** + * The channel count of the audio stream. The underlying type is `int32_t`. + * Use of this enum is convenient to avoid "magic" + * numbers when specifying the channel count. + * + * For example, you can write + * `builder.setChannelCount(ChannelCount::Stereo)` + * rather than `builder.setChannelCount(2)` + * + */ + enum ChannelCount : int32_t { + /** + * Audio channel count definition, use Mono or Stereo + */ + Unspecified = kUnspecified, + + /** + * Use this for mono audio + */ + Mono = 1, + + /** + * Use this for stereo audio. + */ + Stereo = 2, + }; + + /** + * The channel mask of the audio stream. The underlying type is `uint32_t`. + * Use of this enum is convenient. + * + * ChannelMask::Unspecified means this is not specified. + * The rest of the enums are channel position masks. + * Use the combinations of the channel position masks defined below instead of + * using those values directly. + */ + enum class ChannelMask : uint32_t { // aaudio_channel_mask_t + Unspecified = kUnspecified, + FrontLeft = 1 << 0, + FrontRight = 1 << 1, + FrontCenter = 1 << 2, + LowFrequency = 1 << 3, + BackLeft = 1 << 4, + BackRight = 1 << 5, + FrontLeftOfCenter = 1 << 6, + FrontRightOfCenter = 1 << 7, + BackCenter = 1 << 8, + SideLeft = 1 << 9, + SideRight = 1 << 10, + TopCenter = 1 << 11, + TopFrontLeft = 1 << 12, + TopFrontCenter = 1 << 13, + TopFrontRight = 1 << 14, + TopBackLeft = 1 << 15, + TopBackCenter = 1 << 16, + TopBackRight = 1 << 17, + TopSideLeft = 1 << 18, + TopSideRight = 1 << 19, + BottomFrontLeft = 1 << 20, + BottomFrontCenter = 1 << 21, + BottomFrontRight = 1 << 22, + LowFrequency2 = 1 << 23, + FrontWideLeft = 1 << 24, + FrontWideRight = 1 << 25, + + Mono = FrontLeft, + + Stereo = FrontLeft | + FrontRight, + + CM2Point1 = FrontLeft | + FrontRight | + LowFrequency, + + Tri = FrontLeft | + FrontRight | + FrontCenter, + + TriBack = FrontLeft | + FrontRight | + BackCenter, + + CM3Point1 = FrontLeft | + FrontRight | + FrontCenter | + LowFrequency, + + CM2Point0Point2 = FrontLeft | + FrontRight | + TopSideLeft | + TopSideRight, + + CM2Point1Point2 = CM2Point0Point2 | + LowFrequency, + + CM3Point0Point2 = FrontLeft | + FrontRight | + FrontCenter | + TopSideLeft | + TopSideRight, + + CM3Point1Point2 = CM3Point0Point2 | + LowFrequency, + + Quad = FrontLeft | + FrontRight | + BackLeft | + BackRight, + + QuadSide = FrontLeft | + FrontRight | + SideLeft | + SideRight, + + Surround = FrontLeft | + FrontRight | + FrontCenter | + BackCenter, + + Penta = Quad | + FrontCenter, + + // aka 5Point1Back + CM5Point1 = FrontLeft | + FrontRight | + FrontCenter | + LowFrequency | + BackLeft | + BackRight, + + CM5Point1Side = FrontLeft | + FrontRight | + FrontCenter | + LowFrequency | + SideLeft | + SideRight, + + CM6Point1 = FrontLeft | + FrontRight | + FrontCenter | + LowFrequency | + BackLeft | + BackRight | + BackCenter, + + CM7Point1 = CM5Point1 | + SideLeft | + SideRight, + + CM5Point1Point2 = CM5Point1 | + TopSideLeft | + TopSideRight, + + CM5Point1Point4 = CM5Point1 | + TopFrontLeft | + TopFrontRight | + TopBackLeft | + TopBackRight, + + CM7Point1Point2 = CM7Point1 | + TopSideLeft | + TopSideRight, + + CM7Point1Point4 = CM7Point1 | + TopFrontLeft | + TopFrontRight | + TopBackLeft | + TopBackRight, + + CM9Point1Point4 = CM7Point1Point4 | + FrontWideLeft | + FrontWideRight, + + CM9Point1Point6 = CM9Point1Point4 | + TopSideLeft | + TopSideRight, + + FrontBack = FrontCenter | + BackCenter, + }; + + /** + * On API 16 to 26 OpenSL ES will be used. When using OpenSL ES the optimal values for sampleRate and + * framesPerBurst are not known by the native code. + * On API 17+ these values should be obtained from the AudioManager using this code: + * + *

+     * // Note that this technique only works for built-in speakers and headphones.
+     * AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+     * String sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
+     * int defaultSampleRate = Integer.parseInt(sampleRateStr);
+     * String framesPerBurstStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+     * int defaultFramesPerBurst = Integer.parseInt(framesPerBurstStr);
+     * 
+ * + * It can then be passed down to Oboe through JNI. + * + * AAudio will get the optimal framesPerBurst from the HAL and will ignore this value. + */ + class DefaultStreamValues { + + public: + + /** The default sample rate to use when opening new audio streams */ + static int32_t SampleRate; + /** The default frames per burst to use when opening new audio streams */ + static int32_t FramesPerBurst; + /** The default channel count to use when opening new audio streams */ + static int32_t ChannelCount; + + }; + + /** + * The time at which the frame at `position` was presented + */ + struct FrameTimestamp { + int64_t position; // in frames + int64_t timestamp; // in nanoseconds + }; + + class OboeGlobals { + public: + + static bool areWorkaroundsEnabled() { + return mWorkaroundsEnabled; + } + + /** + * Disable this when writing tests to reproduce bugs in AAudio or OpenSL ES + * that have workarounds in Oboe. + * @param enabled + */ + static void setWorkaroundsEnabled(bool enabled) { + mWorkaroundsEnabled = enabled; + } + + private: + static bool mWorkaroundsEnabled; + }; +} // namespace oboe + +#endif // OBOE_DEFINITIONS_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_FifoBuffer_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_FifoBuffer_android.h new file mode 100644 index 0000000..e7c389e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_FifoBuffer_android.h @@ -0,0 +1,164 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_FIFOPROCESSOR_H +#define OBOE_FIFOPROCESSOR_H + +#include +#include + +#include "oboe_oboe_Definitions_android.h" + +#include "oboe_oboe_FifoControllerBase_android.h" + +namespace oboe { + +class FifoBuffer { +public: + /** + * Construct a `FifoBuffer`. + * + * @param bytesPerFrame amount of bytes for one frame + * @param capacityInFrames the capacity of frames in fifo + */ + FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames); + + /** + * Construct a `FifoBuffer`. + * To be used if the storage allocation is done outside of FifoBuffer. + * + * @param bytesPerFrame amount of bytes for one frame + * @param capacityInFrames capacity of frames in fifo + * @param readCounterAddress address of read counter + * @param writeCounterAddress address of write counter + * @param dataStorageAddress address of storage + */ + FifoBuffer(uint32_t bytesPerFrame, + uint32_t capacityInFrames, + std::atomic *readCounterAddress, + std::atomic *writeCounterAddress, + uint8_t *dataStorageAddress); + + ~FifoBuffer(); + + /** + * Convert a number of frames in bytes. + * + * @return number of bytes + */ + int32_t convertFramesToBytes(int32_t frames); + + /** + * Read framesToRead or, if not enough, then read as many as are available. + * + * @param destination + * @param framesToRead number of frames requested + * @return number of frames actually read + */ + int32_t read(void *destination, int32_t framesToRead); + + /** + * Write framesToWrite or, if too enough, then write as many as the fifo are not empty. + * + * @param destination + * @param framesToWrite number of frames requested + * @return number of frames actually write + */ + int32_t write(const void *source, int32_t framesToWrite); + + /** + * Get the buffer capacity in frames. + * + * @return number of frames + */ + uint32_t getBufferCapacityInFrames() const; + + /** + * Calls read(). If all of the frames cannot be read then the remainder of the buffer + * is set to zero. + * + * @param destination + * @param framesToRead number of frames requested + * @return number of frames actually read + */ + int32_t readNow(void *destination, int32_t numFrames); + + /** + * Get the number of frames in the fifo. + * + * @return number of frames actually in the buffer + */ + uint32_t getFullFramesAvailable() { + return mFifo->getFullFramesAvailable(); + } + + /** + * Get the amount of bytes per frame. + * + * @return number of bytes per frame + */ + uint32_t getBytesPerFrame() const { + return mBytesPerFrame; + } + + /** + * Get the position of read counter. + * + * @return position of read counter + */ + uint64_t getReadCounter() const { + return mFifo->getReadCounter(); + } + + /** + * Set the position of read counter. + * + * @param n position of read counter + */ + void setReadCounter(uint64_t n) { + mFifo->setReadCounter(n); + } + + /** + * Get the position of write counter. + * + * @return position of write counter + */ + uint64_t getWriteCounter() { + return mFifo->getWriteCounter(); + } + + /** + * Set the position of write counter. + * + * @param n position of write counter + */ + void setWriteCounter(uint64_t n) { + mFifo->setWriteCounter(n); + } + +private: + uint32_t mBytesPerFrame; + uint8_t* mStorage; + bool mStorageOwned; // did this object allocate the storage? + std::unique_ptr mFifo; + uint64_t mFramesReadCount; + uint64_t mFramesUnderrunCount; +}; + +} // namespace oboe + +#endif //OBOE_FIFOPROCESSOR_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_FifoControllerBase_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_FifoControllerBase_android.h new file mode 100644 index 0000000..6c12b75 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_FifoControllerBase_android.h @@ -0,0 +1,112 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NATIVEOBOE_FIFOCONTROLLERBASE_H +#define NATIVEOBOE_FIFOCONTROLLERBASE_H + +#include + +namespace oboe { + +/** + * Manage the read/write indices of a circular buffer. + * + * The caller is responsible for reading and writing the actual data. + * Note that the span of available frames may not be contiguous. They + * may wrap around from the end to the beginning of the buffer. In that + * case the data must be read or written in at least two blocks of frames. + * + */ + +class FifoControllerBase { + +public: + /** + * Construct a `FifoControllerBase`. + * + * @param totalFrames capacity of the circular buffer in frames + */ + FifoControllerBase(uint32_t totalFrames); + + virtual ~FifoControllerBase() = default; + + /** + * The frames available to read will be calculated from the read and write counters. + * The result will be clipped to the capacity of the buffer. + * If the buffer has underflowed then this will return zero. + * + * @return number of valid frames available to read. + */ + uint32_t getFullFramesAvailable() const; + + /** + * The index in a circular buffer of the next frame to read. + * + * @return read index position + */ + uint32_t getReadIndex() const; + + /** + * Advance read index from a number of frames. + * Equivalent of incrementReadCounter(numFrames). + * + * @param numFrames number of frames to advance the read index + */ + void advanceReadIndex(uint32_t numFrames); + + /** + * Get the number of frame that are not written yet. + * + * @return maximum number of frames that can be written without exceeding the threshold + */ + uint32_t getEmptyFramesAvailable() const; + + /** + * The index in a circular buffer of the next frame to write. + * + * @return index of the next frame to write + */ + uint32_t getWriteIndex() const; + + /** + * Advance write index from a number of frames. + * Equivalent of incrementWriteCounter(numFrames). + * + * @param numFrames number of frames to advance the write index + */ + void advanceWriteIndex(uint32_t numFrames); + + /** + * Get the frame capacity of the fifo. + * + * @return frame capacity + */ + uint32_t getFrameCapacity() const { return mTotalFrames; } + + virtual uint64_t getReadCounter() const = 0; + virtual void setReadCounter(uint64_t n) = 0; + virtual void incrementReadCounter(uint64_t n) = 0; + virtual uint64_t getWriteCounter() const = 0; + virtual void setWriteCounter(uint64_t n) = 0; + virtual void incrementWriteCounter(uint64_t n) = 0; + +private: + uint32_t mTotalFrames; +}; + +} // namespace oboe + +#endif //NATIVEOBOE_FIFOCONTROLLERBASE_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_LatencyTuner_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_LatencyTuner_android.h new file mode 100644 index 0000000..efff393 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_LatencyTuner_android.h @@ -0,0 +1,150 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_LATENCY_TUNER_ +#define OBOE_LATENCY_TUNER_ + +#include +#include +#include "oboe_oboe_Definitions_android.h" +#include "oboe_oboe_AudioStream_android.h" + +namespace oboe { + +/** + * LatencyTuner can be used to dynamically tune the latency of an output stream. + * It adjusts the stream's bufferSize by monitoring the number of underruns. + * + * This only affects the latency associated with the first level of buffering that is closest + * to the application. It does not affect low latency in the HAL, or touch latency in the UI. + * + * Call tune() right before returning from your data callback function if using callbacks. + * Call tune() right before calling write() if using blocking writes. + * + * If you want to see the ongoing results of this tuning process then call + * stream->getBufferSize() periodically. + * + */ +class LatencyTuner { +public: + + /** + * Construct a new LatencyTuner object which will act on the given audio stream + * + * @param stream the stream who's latency will be tuned + */ + explicit LatencyTuner(AudioStream &stream); + + /** + * Construct a new LatencyTuner object which will act on the given audio stream. + * + * @param stream the stream who's latency will be tuned + * @param the maximum buffer size which the tune() operation will set the buffer size to + */ + explicit LatencyTuner(AudioStream &stream, int32_t maximumBufferSize); + + /** + * Adjust the bufferSizeInFrames to optimize latency. + * It will start with a low latency and then raise it if an underrun occurs. + * + * Latency tuning is only supported for AAudio. + * + * @return OK or negative error, ErrorUnimplemented for OpenSL ES + */ + Result tune(); + + /** + * This may be called from another thread. Then tune() will call reset(), + * which will lower the latency to the minimum and then allow it to rise back up + * if there are glitches. + * + * This is typically called in response to a user decision to minimize latency. In other words, + * call this from a button handler. + */ + void requestReset(); + + /** + * @return true if the audio stream's buffer size is at the maximum value. If no maximum value + * was specified when constructing the LatencyTuner then the value of + * stream->getBufferCapacityInFrames is used + */ + bool isAtMaximumBufferSize(); + + /** + * Set the minimum bufferSize in frames that is used when the tuner is reset. + * You may wish to call requestReset() after calling this. + * @param bufferSize + */ + void setMinimumBufferSize(int32_t bufferSize) { + mMinimumBufferSize = bufferSize; + } + + int32_t getMinimumBufferSize() const { + return mMinimumBufferSize; + } + + /** + * Set the amount the bufferSize will be incremented while tuning. + * By default, this will be one burst. + * + * Note that AAudio will quantize the buffer size to a multiple of the burstSize. + * So the final buffer sizes may not be a multiple of this increment. + * + * @param sizeIncrement + */ + void setBufferSizeIncrement(int32_t sizeIncrement) { + mBufferSizeIncrement = sizeIncrement; + } + + int32_t getBufferSizeIncrement() const { + return mBufferSizeIncrement; + } + +private: + + /** + * Drop the latency down to the minimum and then let it rise back up. + * This is useful if a glitch caused the latency to increase and it hasn't gone back down. + * + * This should only be called in the same thread as tune(). + */ + void reset(); + + enum class State { + Idle, + Active, + AtMax, + Unsupported + } ; + + // arbitrary number of calls to wait before bumping up the latency + static constexpr int32_t kIdleCount = 8; + static constexpr int32_t kDefaultNumBursts = 2; + + AudioStream &mStream; + State mState = State::Idle; + int32_t mMaxBufferSize = 0; + int32_t mPreviousXRuns = 0; + int32_t mIdleCountDown = 0; + int32_t mMinimumBufferSize; + int32_t mBufferSizeIncrement; + std::atomic mLatencyTriggerRequests{0}; // TODO user atomic requester from AAudio + std::atomic mLatencyTriggerResponses{0}; +}; + +} // namespace oboe + +#endif // OBOE_LATENCY_TUNER_ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_OboeExtensions_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_OboeExtensions_android.h new file mode 100644 index 0000000..b03dbbe --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_OboeExtensions_android.h @@ -0,0 +1,64 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_EXTENSIONS_ +#define OBOE_EXTENSIONS_ + +#include + +#include "oboe_oboe_Definitions_android.h" +#include "oboe_oboe_AudioStream_android.h" + +namespace oboe { + +/** + * The definitions below are only for testing. + * They are not recommended for use in an application. + * They may change or be removed at any time. + */ +class OboeExtensions { +public: + + /** + * @returns true if the device supports AAudio MMAP + */ + static bool isMMapSupported(); + + /** + * @returns true if the AAudio MMAP data path can be selected + */ + static bool isMMapEnabled(); + + /** + * Controls whether the AAudio MMAP data path can be selected when opening a stream. + * It has no effect after the stream has been opened. + * It only affects the application that calls it. Other apps are not affected. + * + * @param enabled + * @return 0 or a negative error code + */ + static int32_t setMMapEnabled(bool enabled); + + /** + * @param oboeStream + * @return true if the AAudio MMAP data path is used on the stream + */ + static bool isMMapUsed(oboe::AudioStream *oboeStream); +}; + +} // namespace oboe + +#endif // OBOE_LATENCY_TUNER_ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Oboe_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Oboe_android.h new file mode 100644 index 0000000..84bc1ac --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Oboe_android.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_OBOE_H +#define OBOE_OBOE_H + +/** + * \mainpage API reference + * + * All documentation is found in the oboe namespace section + * + */ + +#include "oboe_oboe_Definitions_android.h" +#include "oboe_oboe_ResultWithValue_android.h" +#include "oboe_oboe_LatencyTuner_android.h" +#include "oboe_oboe_AudioStream_android.h" +#include "oboe_oboe_AudioStreamBase_android.h" +#include "oboe_oboe_AudioStreamBuilder_android.h" +#include "oboe_oboe_Utilities_android.h" +#include "oboe_oboe_Version_android.h" +#include "oboe_oboe_StabilizedCallback_android.h" +#include "oboe_oboe_FifoBuffer_android.h" +#include "oboe_oboe_OboeExtensions_android.h" + +#endif //OBOE_OBOE_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_ResultWithValue_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_ResultWithValue_android.h new file mode 100644 index 0000000..5e5c952 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_ResultWithValue_android.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_RESULT_WITH_VALUE_H +#define OBOE_RESULT_WITH_VALUE_H + +#include "oboe_oboe_Definitions_android.h" +#include +#include + +namespace oboe { + +/** + * A ResultWithValue can store both the result of an operation (either OK or an error) and a value. + * + * It has been designed for cases where the caller needs to know whether an operation succeeded and, + * if it did, a value which was obtained during the operation. + * + * For example, when reading from a stream the caller needs to know the result of the read operation + * and, if it was successful, how many frames were read. Note that ResultWithValue can be evaluated + * as a boolean so it's simple to check whether the result is OK. + * + * + * ResultWithValue resultOfRead = myStream.read(&buffer, numFrames, timeoutNanoseconds); + * + * if (resultOfRead) { + * LOGD("Frames read: %d", resultOfRead.value()); + * } else { + * LOGD("Error reading from stream: %s", resultOfRead.error()); + * } + * + */ +template +class ResultWithValue { +public: + + /** + * Construct a ResultWithValue containing an error result. + * + * @param error The error + */ + ResultWithValue(oboe::Result error) + : mValue{} + , mError(error) {} + + /** + * Construct a ResultWithValue containing an OK result and a value. + * + * @param value the value to store + */ + explicit ResultWithValue(T value) + : mValue(value) + , mError(oboe::Result::OK) {} + + /** + * Get the result. + * + * @return the result + */ + oboe::Result error() const { + return mError; + } + + /** + * Get the value + * @return + */ + T value() const { + return mValue; + } + + /** + * @return true if OK + */ + explicit operator bool() const { return mError == oboe::Result::OK; } + + /** + * Quick way to check for an error. + * + * The caller could write something like this: + * + * if (!result) { printf("Got error %s\n", convertToText(result.error())); } + * + * + * @return true if an error occurred + */ + bool operator !() const { return mError != oboe::Result::OK; } + + /** + * Implicitly convert to a Result. This enables easy comparison with Result values. Example: + * + * + * ResultWithValue result = openStream(); + * if (result == Result::ErrorNoMemory){ // tell user they're out of memory } + * + */ + operator Result() const { + return mError; + } + + /** + * Create a ResultWithValue from a number. If the number is positive the ResultWithValue will + * have a result of Result::OK and the value will contain the number. If the number is negative + * the result will be obtained from the negative number (numeric error codes can be found in + * AAudio.h) and the value will be null. + * + */ + static ResultWithValue createBasedOnSign(T numericResult){ + + // Ensure that the type is either an integer or float + static_assert(std::is_arithmetic::value, + "createBasedOnSign can only be called for numeric types (int or float)"); + + if (numericResult >= 0){ + return ResultWithValue(numericResult); + } else { + return ResultWithValue(static_cast(numericResult)); + } + } + +private: + const T mValue; + const oboe::Result mError; +}; + +/** + * If the result is `OK` then return the value, otherwise return a human-readable error message. + */ +template +std::ostream& operator<<(std::ostream &strm, const ResultWithValue &result) { + if (!result) { + strm << convertToText(result.error()); + } else { + strm << result.value(); + } + return strm; +} + +} // namespace oboe + + +#endif //OBOE_RESULT_WITH_VALUE_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_StabilizedCallback_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_StabilizedCallback_android.h new file mode 100644 index 0000000..1e84343 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_StabilizedCallback_android.h @@ -0,0 +1,75 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_STABILIZEDCALLBACK_H +#define OBOE_STABILIZEDCALLBACK_H + +#include +#include "oboe_oboe_AudioStream_android.h" + +namespace oboe { + +class StabilizedCallback : public AudioStreamCallback { + +public: + explicit StabilizedCallback(AudioStreamCallback *callback); + + DataCallbackResult + onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override; + + void onErrorBeforeClose(AudioStream *oboeStream, Result error) override { + return mCallback->onErrorBeforeClose(oboeStream, error); + } + + void onErrorAfterClose(AudioStream *oboeStream, Result error) override { + + // Reset all fields now that the stream has been closed + mFrameCount = 0; + mEpochTimeNanos = 0; + mOpsPerNano = 1; + return mCallback->onErrorAfterClose(oboeStream, error); + } + +private: + + AudioStreamCallback *mCallback = nullptr; + int64_t mFrameCount = 0; + int64_t mEpochTimeNanos = 0; + double mOpsPerNano = 1; + + void generateLoad(int64_t durationNanos); +}; + +/** + * cpu_relax is an architecture specific method of telling the CPU that you don't want it to + * do much work. asm volatile keeps the compiler from optimising these instructions out. + */ +#if defined(__i386__) || defined(__x86_64__) +#define cpu_relax() asm volatile("rep; nop" ::: "memory"); + +#elif defined(__arm__) || defined(__mips__) || defined(__riscv) + #define cpu_relax() asm volatile("":::"memory") + +#elif defined(__aarch64__) +#define cpu_relax() asm volatile("yield" ::: "memory") + +#else +#error "cpu_relax is not defined for this architecture" +#endif + +} + +#endif //OBOE_STABILIZEDCALLBACK_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Utilities_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Utilities_android.h new file mode 100644 index 0000000..b32e8d9 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Utilities_android.h @@ -0,0 +1,89 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_UTILITIES_H +#define OBOE_UTILITIES_H + +#include +#include +#include +#include "oboe_oboe_Definitions_android.h" + +namespace oboe { + +/** + * Convert an array of floats to an array of 16-bit integers. + * + * @param source the input array. + * @param destination the output array. + * @param numSamples the number of values to convert. + */ +void convertFloatToPcm16(const float *source, int16_t *destination, int32_t numSamples); + +/** + * Convert an array of 16-bit integers to an array of floats. + * + * @param source the input array. + * @param destination the output array. + * @param numSamples the number of values to convert. + */ +void convertPcm16ToFloat(const int16_t *source, float *destination, int32_t numSamples); + +/** + * @return the size of a sample of the given format in bytes or 0 if format is invalid + */ +int32_t convertFormatToSizeInBytes(AudioFormat format); + +/** + * The text is the ASCII symbol corresponding to the supplied Oboe enum value, + * or an English message saying the value is unrecognized. + * This is intended for developers to use when debugging. + * It is not for displaying to users. + * + * @param input object to convert from. @see common/Utilities.cpp for concrete implementations + * @return text representation of an Oboe enum value. There is no need to call free on this. + */ +template +const char * convertToText(FromType input); + +/** + * @param name + * @return the value of a named system property in a string or empty string + */ +std::string getPropertyString(const char * name); + +/** + * @param name + * @param defaultValue + * @return integer value associated with a property or the default value + */ +int getPropertyInteger(const char * name, int defaultValue); + +/** + * Return the version of the SDK that is currently running. + * + * For example, on Android, this would return 27 for Oreo 8.1. + * If the version number cannot be determined then this will return -1. + * + * @return version number or -1 + */ +int getSdkVersion(); + +int getChannelCountFromChannelMask(ChannelMask channelMask); + +} // namespace oboe + +#endif //OBOE_UTILITIES_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Version_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Version_android.h new file mode 100644 index 0000000..68c3fe9 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_oboe_Version_android.h @@ -0,0 +1,92 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_VERSIONINFO_H +#define OBOE_VERSIONINFO_H + +#include + +/** + * A note on use of preprocessor defines: + * + * This is one of the few times when it's suitable to use preprocessor defines rather than constexpr + * Why? Because C++11 requires a lot of boilerplate code to convert integers into compile-time + * string literals. The preprocessor, despite it's lack of type checking, is more suited to the task + * + * See: https://stackoverflow.com/questions/6713420/c-convert-integer-to-string-at-compile-time/26824971#26824971 + * + */ + +// Type: 8-bit unsigned int. Min value: 0 Max value: 255. See below for description. +#define OBOE_VERSION_MAJOR 1 + +// Type: 8-bit unsigned int. Min value: 0 Max value: 255. See below for description. +#define OBOE_VERSION_MINOR 7 + +// Type: 16-bit unsigned int. Min value: 0 Max value: 65535. See below for description. +#define OBOE_VERSION_PATCH 0 + +#define OBOE_STRINGIFY(x) #x +#define OBOE_TOSTRING(x) OBOE_STRINGIFY(x) + +// Type: String literal. See below for description. +#define OBOE_VERSION_TEXT \ + OBOE_TOSTRING(OBOE_VERSION_MAJOR) "." \ + OBOE_TOSTRING(OBOE_VERSION_MINOR) "." \ + OBOE_TOSTRING(OBOE_VERSION_PATCH) + +// Type: 32-bit unsigned int. See below for description. +#define OBOE_VERSION_NUMBER ((OBOE_VERSION_MAJOR << 24) | (OBOE_VERSION_MINOR << 16) | OBOE_VERSION_PATCH) + +namespace oboe { + +const char * getVersionText(); + +/** + * Oboe versioning object + */ +struct Version { + /** + * This is incremented when we make breaking API changes. Based loosely on https://semver.org/. + */ + static constexpr uint8_t Major = OBOE_VERSION_MAJOR; + + /** + * This is incremented when we add backwards compatible functionality. Or set to zero when MAJOR is + * incremented. + */ + static constexpr uint8_t Minor = OBOE_VERSION_MINOR; + + /** + * This is incremented when we make backwards compatible bug fixes. Or set to zero when MINOR is + * incremented. + */ + static constexpr uint16_t Patch = OBOE_VERSION_PATCH; + + /** + * Version string in the form MAJOR.MINOR.PATCH. + */ + static constexpr const char * Text = OBOE_VERSION_TEXT; + + /** + * Integer representation of the current Oboe library version. This will always increase when the + * version number changes so can be compared using integer comparison. + */ + static constexpr uint32_t Number = OBOE_VERSION_NUMBER; +}; + +} // namespace oboe +#endif //OBOE_VERSIONINFO_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioInputStreamOpenSLES_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioInputStreamOpenSLES_android.cpp new file mode 100644 index 0000000..2aa734c --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioInputStreamOpenSLES_android.cpp @@ -0,0 +1,358 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include "oboe_common_OboeDebug_android.h" +#include "oboe_oboe_AudioStreamBuilder_android.h" +#include "oboe_opensles_AudioInputStreamOpenSLES_android.h" +#include "oboe_opensles_AudioStreamOpenSLES_android.h" +#include "oboe_opensles_OpenSLESUtilities_android.h" + +using namespace oboe; + +static SLuint32 OpenSLES_convertInputPreset(InputPreset oboePreset) { + SLuint32 openslPreset = SL_ANDROID_RECORDING_PRESET_NONE; + switch(oboePreset) { + case InputPreset::Generic: + openslPreset = SL_ANDROID_RECORDING_PRESET_GENERIC; + break; + case InputPreset::Camcorder: + openslPreset = SL_ANDROID_RECORDING_PRESET_CAMCORDER; + break; + case InputPreset::VoiceRecognition: + case InputPreset::VoicePerformance: + openslPreset = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + break; + case InputPreset::VoiceCommunication: + openslPreset = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION; + break; + case InputPreset::Unprocessed: + openslPreset = SL_ANDROID_RECORDING_PRESET_UNPROCESSED; + break; + default: + break; + } + return openslPreset; +} + +AudioInputStreamOpenSLES::AudioInputStreamOpenSLES(const AudioStreamBuilder &builder) + : AudioStreamOpenSLES(builder) { +} + +AudioInputStreamOpenSLES::~AudioInputStreamOpenSLES() { +} + +// Calculate masks specific to INPUT streams. +SLuint32 AudioInputStreamOpenSLES::channelCountToChannelMask(int channelCount) const { + // Derived from internal sles_channel_in_mask_from_count(chanCount); + // in "frameworks/wilhelm/src/android/channels.cpp". + // Yes, it seems strange to use SPEAKER constants to describe inputs. + // But that is how OpenSL ES does it internally. + switch (channelCount) { + case 1: + return SL_SPEAKER_FRONT_LEFT; + case 2: + return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + default: + return channelCountToChannelMaskDefault(channelCount); + } +} + +Result AudioInputStreamOpenSLES::open() { + logUnsupportedAttributes(); + + SLAndroidConfigurationItf configItf = nullptr; + + if (getSdkVersion() < __ANDROID_API_M__ && mFormat == AudioFormat::Float){ + // TODO: Allow floating point format on API <23 using float->int16 converter + return Result::ErrorInvalidFormat; + } + + // If audio format is unspecified then choose a suitable default. + // API 23+: FLOAT + // API <23: INT16 + if (mFormat == AudioFormat::Unspecified){ + mFormat = (getSdkVersion() < __ANDROID_API_M__) ? + AudioFormat::I16 : AudioFormat::Float; + } + + Result oboeResult = AudioStreamOpenSLES::open(); + if (Result::OK != oboeResult) return oboeResult; + + SLuint32 bitsPerSample = static_cast(getBytesPerSample() * kBitsPerByte); + + // configure audio sink + mBufferQueueLength = calculateOptimalBufferQueueLength(); + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locatorType + static_cast(mBufferQueueLength)}; // numBuffers + + // Define the audio data format. + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, // formatType + static_cast(mChannelCount), // numChannels + static_cast(mSampleRate * kMillisPerSecond), // milliSamplesPerSec + bitsPerSample, // bitsPerSample + bitsPerSample, // containerSize; + channelCountToChannelMask(mChannelCount), // channelMask + getDefaultByteOrder(), + }; + + SLDataSink audioSink = {&loc_bufq, &format_pcm}; + + /** + * API 23 (Marshmallow) introduced support for floating-point data representation and an + * extended data format type: SLAndroidDataFormat_PCM_EX for recording streams (playback streams + * got this in API 21). If running on API 23+ use this newer format type, creating it from our + * original format. + */ + SLAndroidDataFormat_PCM_EX format_pcm_ex; + if (getSdkVersion() >= __ANDROID_API_M__) { + SLuint32 representation = OpenSLES_ConvertFormatToRepresentation(getFormat()); + // Fill in the format structure. + format_pcm_ex = OpenSLES_createExtendedFormat(format_pcm, representation); + // Use in place of the previous format. + audioSink.pFormat = &format_pcm_ex; + } + + + // configure audio source + SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, + NULL}; + SLDataSource audioSrc = {&loc_dev, NULL}; + + SLresult result = EngineOpenSLES::getInstance().createAudioRecorder(&mObjectInterface, + &audioSrc, + &audioSink); + + if (SL_RESULT_SUCCESS != result) { + LOGE("createAudioRecorder() result:%s", getSLErrStr(result)); + goto error; + } + + // Configure the stream. + result = (*mObjectInterface)->GetInterface(mObjectInterface, + SL_IID_ANDROIDCONFIGURATION, + &configItf); + + if (SL_RESULT_SUCCESS != result) { + LOGW("%s() GetInterface(SL_IID_ANDROIDCONFIGURATION) failed with %s", + __func__, getSLErrStr(result)); + } else { + if (getInputPreset() == InputPreset::VoicePerformance) { + LOGD("OpenSL ES does not support InputPreset::VoicePerformance. Use VoiceRecognition."); + mInputPreset = InputPreset::VoiceRecognition; + } + SLuint32 presetValue = OpenSLES_convertInputPreset(getInputPreset()); + result = (*configItf)->SetConfiguration(configItf, + SL_ANDROID_KEY_RECORDING_PRESET, + &presetValue, + sizeof(SLuint32)); + if (SL_RESULT_SUCCESS != result + && presetValue != SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION) { + presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + LOGD("Setting InputPreset %d failed. Using VoiceRecognition instead.", getInputPreset()); + mInputPreset = InputPreset::VoiceRecognition; + (*configItf)->SetConfiguration(configItf, + SL_ANDROID_KEY_RECORDING_PRESET, + &presetValue, + sizeof(SLuint32)); + } + + result = configurePerformanceMode(configItf); + if (SL_RESULT_SUCCESS != result) { + goto error; + } + } + + result = (*mObjectInterface)->Realize(mObjectInterface, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + LOGE("Realize recorder object result:%s", getSLErrStr(result)); + goto error; + } + + result = (*mObjectInterface)->GetInterface(mObjectInterface, SL_IID_RECORD, &mRecordInterface); + if (SL_RESULT_SUCCESS != result) { + LOGE("GetInterface RECORD result:%s", getSLErrStr(result)); + goto error; + } + + result = finishCommonOpen(configItf); + if (SL_RESULT_SUCCESS != result) { + goto error; + } + + setState(StreamState::Open); + return Result::OK; + +error: + close(); // Clean up various OpenSL objects and prevent resource leaks. + return Result::ErrorInternal; // TODO convert error from SLES to OBOE +} + +Result AudioInputStreamOpenSLES::close() { + LOGD("AudioInputStreamOpenSLES::%s()", __func__); + std::lock_guard lock(mLock); + Result result = Result::OK; + if (getState() == StreamState::Closed){ + result = Result::ErrorClosed; + } else { + (void) requestStop_l(); + if (OboeGlobals::areWorkaroundsEnabled()) { + sleepBeforeClose(); + } + // invalidate any interfaces + mRecordInterface = nullptr; + result = AudioStreamOpenSLES::close_l(); + } + return result; +} + +Result AudioInputStreamOpenSLES::setRecordState_l(SLuint32 newState) { + LOGD("AudioInputStreamOpenSLES::%s(%u)", __func__, newState); + Result result = Result::OK; + + if (mRecordInterface == nullptr) { + LOGW("AudioInputStreamOpenSLES::%s() mRecordInterface is null", __func__); + return Result::ErrorInvalidState; + } + SLresult slResult = (*mRecordInterface)->SetRecordState(mRecordInterface, newState); + //LOGD("AudioInputStreamOpenSLES::%s(%u) returned %u", __func__, newState, slResult); + if (SL_RESULT_SUCCESS != slResult) { + LOGE("AudioInputStreamOpenSLES::%s(%u) returned error %s", + __func__, newState, getSLErrStr(slResult)); + result = Result::ErrorInternal; // TODO review + } + return result; +} + +Result AudioInputStreamOpenSLES::requestStart() { + LOGD("AudioInputStreamOpenSLES(): %s() called", __func__); + std::lock_guard lock(mLock); + StreamState initialState = getState(); + switch (initialState) { + case StreamState::Starting: + case StreamState::Started: + return Result::OK; + case StreamState::Closed: + return Result::ErrorClosed; + default: + break; + } + + // We use a callback if the user requests one + // OR if we have an internal callback to fill the blocking IO buffer. + setDataCallbackEnabled(true); + + setState(StreamState::Starting); + + if (getBufferDepth(mSimpleBufferQueueInterface) == 0) { + // Enqueue the first buffer to start the streaming. + // This does not call the callback function. + enqueueCallbackBuffer(mSimpleBufferQueueInterface); + } + + Result result = setRecordState_l(SL_RECORDSTATE_RECORDING); + if (result == Result::OK) { + setState(StreamState::Started); + } else { + setState(initialState); + } + return result; +} + + +Result AudioInputStreamOpenSLES::requestPause() { + LOGW("AudioInputStreamOpenSLES::%s() is intentionally not implemented for input " + "streams", __func__); + return Result::ErrorUnimplemented; // Matches AAudio behavior. +} + +Result AudioInputStreamOpenSLES::requestFlush() { + LOGW("AudioInputStreamOpenSLES::%s() is intentionally not implemented for input " + "streams", __func__); + return Result::ErrorUnimplemented; // Matches AAudio behavior. +} + +Result AudioInputStreamOpenSLES::requestStop() { + LOGD("AudioInputStreamOpenSLES(): %s() called", __func__); + std::lock_guard lock(mLock); + return requestStop_l(); +} + +// Call under mLock +Result AudioInputStreamOpenSLES::requestStop_l() { + StreamState initialState = getState(); + switch (initialState) { + case StreamState::Stopping: + case StreamState::Stopped: + return Result::OK; + case StreamState::Uninitialized: + case StreamState::Closed: + return Result::ErrorClosed; + default: + break; + } + + setState(StreamState::Stopping); + + Result result = setRecordState_l(SL_RECORDSTATE_STOPPED); + if (result == Result::OK) { + mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped. + setState(StreamState::Stopped); + } else { + setState(initialState); + } + return result; +} + +void AudioInputStreamOpenSLES::updateFramesWritten() { + if (usingFIFO()) { + AudioStreamBuffered::updateFramesWritten(); + } else { + mFramesWritten = getFramesProcessedByServer(); + } +} + +Result AudioInputStreamOpenSLES::updateServiceFrameCounter() { + Result result = Result::OK; + // Avoid deadlock if another thread is trying to stop or close this stream + // and this is being called from a callback. + if (mLock.try_lock()) { + + if (mRecordInterface == nullptr) { + mLock.unlock(); + return Result::ErrorNull; + } + SLmillisecond msec = 0; + SLresult slResult = (*mRecordInterface)->GetPosition(mRecordInterface, &msec); + if (SL_RESULT_SUCCESS != slResult) { + LOGW("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult)); + // set result based on SLresult + result = Result::ErrorInternal; + } else { + mPositionMillis.update32(msec); + } + mLock.unlock(); + } + return result; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioInputStreamOpenSLES_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioInputStreamOpenSLES_android.h new file mode 100644 index 0000000..8141b9b --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioInputStreamOpenSLES_android.h @@ -0,0 +1,66 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AUDIO_INPUT_STREAM_OPENSL_ES_H_ +#define AUDIO_INPUT_STREAM_OPENSL_ES_H_ + + +#include +#include + +#include "oboe_oboe_Oboe_android.h" +#include "oboe_opensles_AudioStreamOpenSLES_android.h" + +namespace oboe { + +/** + * INTERNAL USE ONLY + */ + +class AudioInputStreamOpenSLES : public AudioStreamOpenSLES { +public: + AudioInputStreamOpenSLES(); + explicit AudioInputStreamOpenSLES(const AudioStreamBuilder &builder); + + virtual ~AudioInputStreamOpenSLES(); + + Result open() override; + Result close() override; + + Result requestStart() override; + Result requestPause() override; + Result requestFlush() override; + Result requestStop() override; + +protected: + Result requestStop_l(); + + Result updateServiceFrameCounter() override; + + void updateFramesWritten() override; + +private: + + SLuint32 channelCountToChannelMask(int chanCount) const; + + Result setRecordState_l(SLuint32 newState); + + SLRecordItf mRecordInterface = nullptr; +}; + +} // namespace oboe + +#endif //AUDIO_INPUT_STREAM_OPENSL_ES_H_ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioOutputStreamOpenSLES_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioOutputStreamOpenSLES_android.cpp new file mode 100644 index 0000000..3b5ffdd --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioOutputStreamOpenSLES_android.cpp @@ -0,0 +1,461 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include "oboe_common_AudioClock_android.h" + +#include "oboe_common_OboeDebug_android.h" +#include "oboe_oboe_AudioStreamBuilder_android.h" +#include "oboe_opensles_AudioOutputStreamOpenSLES_android.h" +#include "oboe_opensles_AudioStreamOpenSLES_android.h" +#include "oboe_opensles_OpenSLESUtilities_android.h" +#include "oboe_opensles_OutputMixerOpenSLES_android.h" + +using namespace oboe; + +static SLuint32 OpenSLES_convertOutputUsage(Usage oboeUsage) { + SLuint32 openslStream = SL_ANDROID_STREAM_MEDIA; + switch(oboeUsage) { + case Usage::Media: + openslStream = SL_ANDROID_STREAM_MEDIA; + break; + case Usage::VoiceCommunication: + case Usage::VoiceCommunicationSignalling: + openslStream = SL_ANDROID_STREAM_VOICE; + break; + case Usage::Alarm: + openslStream = SL_ANDROID_STREAM_ALARM; + break; + case Usage::Notification: + case Usage::NotificationRingtone: + case Usage::NotificationEvent: + openslStream = SL_ANDROID_STREAM_NOTIFICATION; + break; + case Usage::AssistanceAccessibility: + case Usage::AssistanceNavigationGuidance: + case Usage::AssistanceSonification: + openslStream = SL_ANDROID_STREAM_SYSTEM; + break; + case Usage::Game: + openslStream = SL_ANDROID_STREAM_MEDIA; + break; + case Usage::Assistant: + default: + openslStream = SL_ANDROID_STREAM_SYSTEM; + break; + } + return openslStream; +} + +AudioOutputStreamOpenSLES::AudioOutputStreamOpenSLES(const AudioStreamBuilder &builder) + : AudioStreamOpenSLES(builder) { +} + +// These will wind up in +constexpr int SL_ANDROID_SPEAKER_STEREO = (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT); + +constexpr int SL_ANDROID_SPEAKER_QUAD = (SL_ANDROID_SPEAKER_STEREO + | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT); + +constexpr int SL_ANDROID_SPEAKER_5DOT1 = (SL_ANDROID_SPEAKER_QUAD + | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY); + +constexpr int SL_ANDROID_SPEAKER_7DOT1 = (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT + | SL_SPEAKER_SIDE_RIGHT); + +SLuint32 AudioOutputStreamOpenSLES::channelCountToChannelMask(int channelCount) const { + SLuint32 channelMask = 0; + + switch (channelCount) { + case 1: + channelMask = SL_SPEAKER_FRONT_CENTER; + break; + + case 2: + channelMask = SL_ANDROID_SPEAKER_STEREO; + break; + + case 4: // Quad + channelMask = SL_ANDROID_SPEAKER_QUAD; + break; + + case 6: // 5.1 + channelMask = SL_ANDROID_SPEAKER_5DOT1; + break; + + case 8: // 7.1 + channelMask = SL_ANDROID_SPEAKER_7DOT1; + break; + + default: + channelMask = channelCountToChannelMaskDefault(channelCount); + break; + } + return channelMask; +} + +Result AudioOutputStreamOpenSLES::open() { + logUnsupportedAttributes(); + + SLAndroidConfigurationItf configItf = nullptr; + + + if (getSdkVersion() < __ANDROID_API_L__ && mFormat == AudioFormat::Float){ + // TODO: Allow floating point format on API <21 using float->int16 converter + return Result::ErrorInvalidFormat; + } + + // If audio format is unspecified then choose a suitable default. + // API 21+: FLOAT + // API <21: INT16 + if (mFormat == AudioFormat::Unspecified){ + mFormat = (getSdkVersion() < __ANDROID_API_L__) ? + AudioFormat::I16 : AudioFormat::Float; + } + + Result oboeResult = AudioStreamOpenSLES::open(); + if (Result::OK != oboeResult) return oboeResult; + + SLresult result = OutputMixerOpenSL::getInstance().open(); + if (SL_RESULT_SUCCESS != result) { + AudioStreamOpenSLES::close(); + return Result::ErrorInternal; + } + + SLuint32 bitsPerSample = static_cast(getBytesPerSample() * kBitsPerByte); + + // configure audio source + mBufferQueueLength = calculateOptimalBufferQueueLength(); + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locatorType + static_cast(mBufferQueueLength)}; // numBuffers + + // Define the audio data format. + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, // formatType + static_cast(mChannelCount), // numChannels + static_cast(mSampleRate * kMillisPerSecond), // milliSamplesPerSec + bitsPerSample, // bitsPerSample + bitsPerSample, // containerSize; + channelCountToChannelMask(mChannelCount), // channelMask + getDefaultByteOrder(), + }; + + SLDataSource audioSrc = {&loc_bufq, &format_pcm}; + + /** + * API 21 (Lollipop) introduced support for floating-point data representation and an extended + * data format type: SLAndroidDataFormat_PCM_EX. If running on API 21+ use this newer format + * type, creating it from our original format. + */ + SLAndroidDataFormat_PCM_EX format_pcm_ex; + if (getSdkVersion() >= __ANDROID_API_L__) { + SLuint32 representation = OpenSLES_ConvertFormatToRepresentation(getFormat()); + // Fill in the format structure. + format_pcm_ex = OpenSLES_createExtendedFormat(format_pcm, representation); + // Use in place of the previous format. + audioSrc.pFormat = &format_pcm_ex; + } + + result = OutputMixerOpenSL::getInstance().createAudioPlayer(&mObjectInterface, + &audioSrc); + if (SL_RESULT_SUCCESS != result) { + LOGE("createAudioPlayer() result:%s", getSLErrStr(result)); + goto error; + } + + // Configure the stream. + result = (*mObjectInterface)->GetInterface(mObjectInterface, + SL_IID_ANDROIDCONFIGURATION, + (void *)&configItf); + if (SL_RESULT_SUCCESS != result) { + LOGW("%s() GetInterface(SL_IID_ANDROIDCONFIGURATION) failed with %s", + __func__, getSLErrStr(result)); + } else { + result = configurePerformanceMode(configItf); + if (SL_RESULT_SUCCESS != result) { + goto error; + } + + SLuint32 presetValue = OpenSLES_convertOutputUsage(getUsage()); + result = (*configItf)->SetConfiguration(configItf, + SL_ANDROID_KEY_STREAM_TYPE, + &presetValue, + sizeof(presetValue)); + if (SL_RESULT_SUCCESS != result) { + goto error; + } + } + + result = (*mObjectInterface)->Realize(mObjectInterface, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + LOGE("Realize player object result:%s", getSLErrStr(result)); + goto error; + } + + result = (*mObjectInterface)->GetInterface(mObjectInterface, SL_IID_PLAY, &mPlayInterface); + if (SL_RESULT_SUCCESS != result) { + LOGE("GetInterface PLAY result:%s", getSLErrStr(result)); + goto error; + } + + result = finishCommonOpen(configItf); + if (SL_RESULT_SUCCESS != result) { + goto error; + } + + setState(StreamState::Open); + return Result::OK; + +error: + close(); // Clean up various OpenSL objects and prevent resource leaks. + return Result::ErrorInternal; // TODO convert error from SLES to OBOE +} + +Result AudioOutputStreamOpenSLES::onAfterDestroy() { + OutputMixerOpenSL::getInstance().close(); + return Result::OK; +} + +Result AudioOutputStreamOpenSLES::close() { + LOGD("AudioOutputStreamOpenSLES::%s()", __func__); + std::lock_guard lock(mLock); + Result result = Result::OK; + if (getState() == StreamState::Closed){ + result = Result::ErrorClosed; + } else { + (void) requestPause_l(); + if (OboeGlobals::areWorkaroundsEnabled()) { + sleepBeforeClose(); + } + // invalidate any interfaces + mPlayInterface = nullptr; + result = AudioStreamOpenSLES::close_l(); + } + return result; +} + +Result AudioOutputStreamOpenSLES::setPlayState_l(SLuint32 newState) { + + LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__); + Result result = Result::OK; + + if (mPlayInterface == nullptr){ + LOGE("AudioOutputStreamOpenSLES::%s() mPlayInterface is null", __func__); + return Result::ErrorInvalidState; + } + + SLresult slResult = (*mPlayInterface)->SetPlayState(mPlayInterface, newState); + if (SL_RESULT_SUCCESS != slResult) { + LOGW("AudioOutputStreamOpenSLES(): %s() returned %s", __func__, getSLErrStr(slResult)); + result = Result::ErrorInternal; // TODO convert slResult to Result::Error + } + return result; +} + +Result AudioOutputStreamOpenSLES::requestStart() { + LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__); + + mLock.lock(); + StreamState initialState = getState(); + switch (initialState) { + case StreamState::Starting: + case StreamState::Started: + mLock.unlock(); + return Result::OK; + case StreamState::Closed: + mLock.unlock(); + return Result::ErrorClosed; + default: + break; + } + + // We use a callback if the user requests one + // OR if we have an internal callback to read the blocking IO buffer. + setDataCallbackEnabled(true); + + setState(StreamState::Starting); + + if (getBufferDepth(mSimpleBufferQueueInterface) == 0) { + // Enqueue the first buffer if needed to start the streaming. + // We may need to stop the current stream. + bool shouldStopStream = processBufferCallback(mSimpleBufferQueueInterface); + if (shouldStopStream) { + LOGD("Stopping the current stream."); + if (requestStop_l() != Result::OK) { + LOGW("Failed to flush the stream. Error %s", convertToText(flush())); + } + setState(initialState); + mLock.unlock(); + return Result::ErrorClosed; + } + } + + Result result = setPlayState_l(SL_PLAYSTATE_PLAYING); + if (result == Result::OK) { + setState(StreamState::Started); + mLock.unlock(); + } else { + setState(initialState); + mLock.unlock(); + } + return result; +} + +Result AudioOutputStreamOpenSLES::requestPause() { + LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__); + std::lock_guard lock(mLock); + return requestPause_l(); +} + +// Call under mLock +Result AudioOutputStreamOpenSLES::requestPause_l() { + StreamState initialState = getState(); + switch (initialState) { + case StreamState::Pausing: + case StreamState::Paused: + return Result::OK; + case StreamState::Uninitialized: + case StreamState::Closed: + return Result::ErrorClosed; + default: + break; + } + + setState(StreamState::Pausing); + Result result = setPlayState_l(SL_PLAYSTATE_PAUSED); + if (result == Result::OK) { + // Note that OpenSL ES does NOT reset its millisecond position when OUTPUT is paused. + int64_t framesWritten = getFramesWritten(); + if (framesWritten >= 0) { + setFramesRead(framesWritten); + } + setState(StreamState::Paused); + } else { + setState(initialState); + } + return result; +} + +/** + * Flush/clear the queue buffers + */ +Result AudioOutputStreamOpenSLES::requestFlush() { + std::lock_guard lock(mLock); + return requestFlush_l(); +} + +Result AudioOutputStreamOpenSLES::requestFlush_l() { + LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__); + if (getState() == StreamState::Closed) { + return Result::ErrorClosed; + } + + Result result = Result::OK; + if (mPlayInterface == nullptr || mSimpleBufferQueueInterface == nullptr) { + result = Result::ErrorInvalidState; + } else { + SLresult slResult = (*mSimpleBufferQueueInterface)->Clear(mSimpleBufferQueueInterface); + if (slResult != SL_RESULT_SUCCESS){ + LOGW("Failed to clear buffer queue. OpenSLES error: %d", result); + result = Result::ErrorInternal; + } + } + return result; +} + +Result AudioOutputStreamOpenSLES::requestStop() { + std::lock_guard lock(mLock); + return requestStop_l(); +} + +Result AudioOutputStreamOpenSLES::requestStop_l() { + LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__); + + StreamState initialState = getState(); + switch (initialState) { + case StreamState::Stopping: + case StreamState::Stopped: + return Result::OK; + case StreamState::Uninitialized: + case StreamState::Closed: + return Result::ErrorClosed; + default: + break; + } + + setState(StreamState::Stopping); + + Result result = setPlayState_l(SL_PLAYSTATE_STOPPED); + if (result == Result::OK) { + + // Also clear the buffer queue so the old data won't be played if the stream is restarted. + // Call the _l function that expects to already be under a lock. + if (requestFlush_l() != Result::OK) { + LOGW("Failed to flush the stream. Error %s", convertToText(flush())); + } + + mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped. + int64_t framesWritten = getFramesWritten(); + if (framesWritten >= 0) { + setFramesRead(framesWritten); + } + setState(StreamState::Stopped); + } else { + setState(initialState); + } + return result; +} + +void AudioOutputStreamOpenSLES::setFramesRead(int64_t framesRead) { + int64_t millisWritten = framesRead * kMillisPerSecond / getSampleRate(); + mPositionMillis.set(millisWritten); +} + +void AudioOutputStreamOpenSLES::updateFramesRead() { + if (usingFIFO()) { + AudioStreamBuffered::updateFramesRead(); + } else { + mFramesRead = getFramesProcessedByServer(); + } +} + +Result AudioOutputStreamOpenSLES::updateServiceFrameCounter() { + Result result = Result::OK; + // Avoid deadlock if another thread is trying to stop or close this stream + // and this is being called from a callback. + if (mLock.try_lock()) { + + if (mPlayInterface == nullptr) { + mLock.unlock(); + return Result::ErrorNull; + } + SLmillisecond msec = 0; + SLresult slResult = (*mPlayInterface)->GetPosition(mPlayInterface, &msec); + if (SL_RESULT_SUCCESS != slResult) { + LOGW("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult)); + // set result based on SLresult + result = Result::ErrorInternal; + } else { + mPositionMillis.update32(msec); + } + mLock.unlock(); + } + return result; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioOutputStreamOpenSLES_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioOutputStreamOpenSLES_android.h new file mode 100644 index 0000000..1faaefa --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioOutputStreamOpenSLES_android.h @@ -0,0 +1,80 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AUDIO_OUTPUT_STREAM_OPENSL_ES_H_ +#define AUDIO_OUTPUT_STREAM_OPENSL_ES_H_ + + +#include +#include + +#include "oboe_oboe_Oboe_android.h" +#include "oboe_opensles_AudioStreamOpenSLES_android.h" + +namespace oboe { + +/** + * INTERNAL USE ONLY + */ +class AudioOutputStreamOpenSLES : public AudioStreamOpenSLES { +public: + AudioOutputStreamOpenSLES(); + explicit AudioOutputStreamOpenSLES(const AudioStreamBuilder &builder); + + virtual ~AudioOutputStreamOpenSLES() = default; + + Result open() override; + Result close() override; + + Result requestStart() override; + Result requestPause() override; + Result requestFlush() override; + Result requestStop() override; + +protected: + Result requestPause_l(); + + void setFramesRead(int64_t framesRead); + + Result updateServiceFrameCounter() override; + + void updateFramesRead() override; + +private: + + SLuint32 channelCountToChannelMask(int chanCount) const; + + Result onAfterDestroy() override; + + Result requestFlush_l(); + + Result requestStop_l(); + + /** + * Set OpenSL ES PLAYSTATE. + * + * @param newState SL_PLAYSTATE_PAUSED, SL_PLAYSTATE_PLAYING, SL_PLAYSTATE_STOPPED + * @return + */ + Result setPlayState_l(SLuint32 newState); + + SLPlayItf mPlayInterface = nullptr; + +}; + +} // namespace oboe + +#endif //AUDIO_OUTPUT_STREAM_OPENSL_ES_H_ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamBuffered_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamBuffered_android.cpp new file mode 100644 index 0000000..f1a9f2e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamBuffered_android.cpp @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "oboe_oboe_Oboe_android.h" + +#include "oboe_common_OboeDebug_android.h" +#include "oboe_opensles_AudioStreamBuffered_android.h" +#include "oboe_common_AudioClock_android.h" + +namespace oboe { + +constexpr int kDefaultBurstsPerBuffer = 16; // arbitrary, allows dynamic latency tuning +constexpr int kMinBurstsPerBuffer = 4; // arbitrary, allows dynamic latency tuning +constexpr int kMinFramesPerBuffer = 48 * 32; // arbitrary + +/* + * AudioStream with a FifoBuffer + */ +AudioStreamBuffered::AudioStreamBuffered(const AudioStreamBuilder &builder) + : AudioStream(builder) { +} + +void AudioStreamBuffered::allocateFifo() { + // If the caller does not provide a callback use our own internal + // callback that reads data from the FIFO. + if (usingFIFO()) { + // FIFO is configured with the same format and channels as the stream. + int32_t capacityFrames = getBufferCapacityInFrames(); + if (capacityFrames == oboe::kUnspecified) { + capacityFrames = getFramesPerBurst() * kDefaultBurstsPerBuffer; + } else { + int32_t minFramesPerBufferByBursts = getFramesPerBurst() * kMinBurstsPerBuffer; + if (capacityFrames <= minFramesPerBufferByBursts) { + capacityFrames = minFramesPerBufferByBursts; + } else { + capacityFrames = std::max(kMinFramesPerBuffer, capacityFrames); + // round up to nearest burst + int32_t numBursts = (capacityFrames + getFramesPerBurst() - 1) + / getFramesPerBurst(); + capacityFrames = numBursts * getFramesPerBurst(); + } + } + + mFifoBuffer = std::make_unique(getBytesPerFrame(), capacityFrames); + mBufferCapacityInFrames = capacityFrames; + mBufferSizeInFrames = mBufferCapacityInFrames; + } +} + +void AudioStreamBuffered::updateFramesWritten() { + if (mFifoBuffer) { + mFramesWritten = static_cast(mFifoBuffer->getWriteCounter()); + } // or else it will get updated by processBufferCallback() +} + +void AudioStreamBuffered::updateFramesRead() { + if (mFifoBuffer) { + mFramesRead = static_cast(mFifoBuffer->getReadCounter()); + } // or else it will get updated by processBufferCallback() +} + +// This is called by the OpenSL ES callback to read or write the back end of the FIFO. +DataCallbackResult AudioStreamBuffered::onDefaultCallback(void *audioData, int numFrames) { + int32_t framesTransferred = 0; + + if (getDirection() == oboe::Direction::Output) { + // Read from the FIFO and write to audioData, clear part of buffer if not enough data. + framesTransferred = mFifoBuffer->readNow(audioData, numFrames); + } else { + // Read from audioData and write to the FIFO + framesTransferred = mFifoBuffer->write(audioData, numFrames); // There is no writeNow() + } + + if (framesTransferred < numFrames) { + LOGD("AudioStreamBuffered::%s(): xrun! framesTransferred = %d, numFrames = %d", + __func__, framesTransferred, numFrames); + // TODO If we do not allow FIFO to wrap then our timestamps will drift when there is an XRun! + incrementXRunCount(); + } + markCallbackTime(static_cast(numFrames)); // so foreground knows how long to wait. + return DataCallbackResult::Continue; +} + +void AudioStreamBuffered::markCallbackTime(int32_t numFrames) { + mLastBackgroundSize = numFrames; + mBackgroundRanAtNanoseconds = AudioClock::getNanoseconds(); +} + +int64_t AudioStreamBuffered::predictNextCallbackTime() { + if (mBackgroundRanAtNanoseconds == 0) { + return 0; + } + int64_t nanosPerBuffer = (kNanosPerSecond * mLastBackgroundSize) / getSampleRate(); + const int64_t margin = 200 * kNanosPerMicrosecond; // arbitrary delay so we wake up just after + return mBackgroundRanAtNanoseconds + nanosPerBuffer + margin; +} + +// Common code for read/write. +// @return Result::OK with frames read/written, or Result::Error* +ResultWithValue AudioStreamBuffered::transfer( + void *readBuffer, + const void *writeBuffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + // Validate arguments. + if (readBuffer != nullptr && writeBuffer != nullptr) { + LOGE("AudioStreamBuffered::%s(): both buffers are not NULL", __func__); + return ResultWithValue(Result::ErrorInternal); + } + if (getDirection() == Direction::Input && readBuffer == nullptr) { + LOGE("AudioStreamBuffered::%s(): readBuffer is NULL", __func__); + return ResultWithValue(Result::ErrorNull); + } + if (getDirection() == Direction::Output && writeBuffer == nullptr) { + LOGE("AudioStreamBuffered::%s(): writeBuffer is NULL", __func__); + return ResultWithValue(Result::ErrorNull); + } + if (numFrames < 0) { + LOGE("AudioStreamBuffered::%s(): numFrames is negative", __func__); + return ResultWithValue(Result::ErrorOutOfRange); + } else if (numFrames == 0) { + return ResultWithValue(numFrames); + } + if (timeoutNanoseconds < 0) { + LOGE("AudioStreamBuffered::%s(): timeoutNanoseconds is negative", __func__); + return ResultWithValue(Result::ErrorOutOfRange); + } + + int32_t result = 0; + uint8_t *readData = reinterpret_cast(readBuffer); + const uint8_t *writeData = reinterpret_cast(writeBuffer); + int32_t framesLeft = numFrames; + int64_t timeToQuit = 0; + bool repeat = true; + + // Calculate when to timeout. + if (timeoutNanoseconds > 0) { + timeToQuit = AudioClock::getNanoseconds() + timeoutNanoseconds; + } + + // Loop until we get the data, or we have an error, or we timeout. + do { + // read or write + if (getDirection() == Direction::Input) { + result = mFifoBuffer->read(readData, framesLeft); + if (result > 0) { + readData += mFifoBuffer->convertFramesToBytes(result); + framesLeft -= result; + } + } else { + // between zero and capacity + uint32_t fullFrames = mFifoBuffer->getFullFramesAvailable(); + // Do not write above threshold size. + int32_t emptyFrames = getBufferSizeInFrames() - static_cast(fullFrames); + int32_t framesToWrite = std::max(0, std::min(framesLeft, emptyFrames)); + result = mFifoBuffer->write(writeData, framesToWrite); + if (result > 0) { + writeData += mFifoBuffer->convertFramesToBytes(result); + framesLeft -= result; + } + } + + // If we need more data then sleep and try again. + if (framesLeft > 0 && result >= 0 && timeoutNanoseconds > 0) { + int64_t timeNow = AudioClock::getNanoseconds(); + if (timeNow >= timeToQuit) { + LOGE("AudioStreamBuffered::%s(): TIMEOUT", __func__); + repeat = false; // TIMEOUT + } else { + // Figure out how long to sleep. + int64_t sleepForNanos; + int64_t wakeTimeNanos = predictNextCallbackTime(); + if (wakeTimeNanos <= 0) { + // No estimate available. Sleep for one burst. + sleepForNanos = (getFramesPerBurst() * kNanosPerSecond) / getSampleRate(); + } else { + // Don't sleep past timeout. + if (wakeTimeNanos > timeToQuit) { + wakeTimeNanos = timeToQuit; + } + sleepForNanos = wakeTimeNanos - timeNow; + // Avoid rapid loop with no sleep. + const int64_t minSleepTime = kNanosPerMillisecond; // arbitrary + if (sleepForNanos < minSleepTime) { + sleepForNanos = minSleepTime; + } + } + + AudioClock::sleepForNanos(sleepForNanos); + } + + } else { + repeat = false; + } + } while(repeat); + + if (result < 0) { + return ResultWithValue(static_cast(result)); + } else { + int32_t framesWritten = numFrames - framesLeft; + return ResultWithValue(framesWritten); + } +} + +// Write to the FIFO so the callback can read from it. +ResultWithValue AudioStreamBuffered::write(const void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + if (getState() == StreamState::Closed){ + return ResultWithValue(Result::ErrorClosed); + } + + if (getDirection() == Direction::Input) { + return ResultWithValue(Result::ErrorUnavailable); // TODO review, better error code? + } + Result result = updateServiceFrameCounter(); + if (result != Result::OK) return ResultWithValue(static_cast(result)); + return transfer(nullptr, buffer, numFrames, timeoutNanoseconds); +} + +// Read data from the FIFO that was written by the callback. +ResultWithValue AudioStreamBuffered::read(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + if (getState() == StreamState::Closed){ + return ResultWithValue(Result::ErrorClosed); + } + + if (getDirection() == Direction::Output) { + return ResultWithValue(Result::ErrorUnavailable); // TODO review, better error code? + } + Result result = updateServiceFrameCounter(); + if (result != Result::OK) return ResultWithValue(static_cast(result)); + return transfer(buffer, nullptr, numFrames, timeoutNanoseconds); +} + +// Only supported when we are not using a callback. +ResultWithValue AudioStreamBuffered::setBufferSizeInFrames(int32_t requestedFrames) +{ + if (getState() == StreamState::Closed){ + return ResultWithValue(Result::ErrorClosed); + } + + if (!mFifoBuffer) { + return ResultWithValue(Result::ErrorUnimplemented); + } + + if (requestedFrames > mFifoBuffer->getBufferCapacityInFrames()) { + requestedFrames = mFifoBuffer->getBufferCapacityInFrames(); + } else if (requestedFrames < getFramesPerBurst()) { + requestedFrames = getFramesPerBurst(); + } + mBufferSizeInFrames = requestedFrames; + return ResultWithValue(requestedFrames); +} + +int32_t AudioStreamBuffered::getBufferCapacityInFrames() const { + if (mFifoBuffer) { + return mFifoBuffer->getBufferCapacityInFrames(); + } else { + return AudioStream::getBufferCapacityInFrames(); + } +} + +bool AudioStreamBuffered::isXRunCountSupported() const { + // XRun count is only supported if we're using blocking I/O (not callbacks) + return (!isDataCallbackSpecified()); +} + +} // namespace oboe \ No newline at end of file diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamBuffered_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamBuffered_android.h new file mode 100644 index 0000000..0fc9651 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamBuffered_android.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_STREAM_BUFFERED_H +#define OBOE_STREAM_BUFFERED_H + +#include +#include +#include "oboe_common_OboeDebug_android.h" +#include "oboe_oboe_AudioStream_android.h" +#include "oboe_oboe_AudioStreamCallback_android.h" +#include "oboe_oboe_FifoBuffer_android.h" + +namespace oboe { + +// A stream that contains a FIFO buffer. +// This is used to implement blocking reads and writes. +class AudioStreamBuffered : public AudioStream { +public: + + AudioStreamBuffered(); + explicit AudioStreamBuffered(const AudioStreamBuilder &builder); + + void allocateFifo(); + + + ResultWithValue write(const void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) override; + + ResultWithValue read(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) override; + + ResultWithValue setBufferSizeInFrames(int32_t requestedFrames) override; + + int32_t getBufferCapacityInFrames() const override; + + ResultWithValue getXRunCount() override { + return ResultWithValue(mXRunCount); + } + + bool isXRunCountSupported() const override; + +protected: + + DataCallbackResult onDefaultCallback(void *audioData, int numFrames) override; + + // If there is no callback then we need a FIFO between the App and OpenSL ES. + bool usingFIFO() const { return !isDataCallbackSpecified(); } + + virtual Result updateServiceFrameCounter() = 0; + + void updateFramesRead() override; + void updateFramesWritten() override; + +private: + + int64_t predictNextCallbackTime(); + + void markCallbackTime(int32_t numFrames); + + // Read or write to the FIFO. + // Only pass one pointer and set the other to nullptr. + ResultWithValue transfer(void *readBuffer, + const void *writeBuffer, + int32_t numFrames, + int64_t timeoutNanoseconds); + + void incrementXRunCount() { + ++mXRunCount; + } + + std::unique_ptr mFifoBuffer{}; + + int64_t mBackgroundRanAtNanoseconds = 0; + int32_t mLastBackgroundSize = 0; + int32_t mXRunCount = 0; +}; + +} // namespace oboe + +#endif //OBOE_STREAM_BUFFERED_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamOpenSLES_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamOpenSLES_android.cpp new file mode 100644 index 0000000..60adf8d --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamOpenSLES_android.cpp @@ -0,0 +1,473 @@ +/* Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include + +#include +#include +#include "oboe_oboe_AudioStream_android.h" +#include "oboe_common_AudioClock_android.h" + +#include "oboe_common_OboeDebug_android.h" +#include "oboe_oboe_AudioStreamBuilder_android.h" +#include "oboe_opensles_AudioStreamOpenSLES_android.h" +#include "oboe_opensles_OpenSLESUtilities_android.h" + +using namespace oboe; + +AudioStreamOpenSLES::AudioStreamOpenSLES(const AudioStreamBuilder &builder) + : AudioStreamBuffered(builder) { + // OpenSL ES does not support device IDs. So overwrite value from builder. + mDeviceId = kUnspecified; + // OpenSL ES does not support session IDs. So overwrite value from builder. + mSessionId = SessionId::None; +} + +static constexpr int32_t kHighLatencyBufferSizeMillis = 20; // typical Android period +static constexpr SLuint32 kAudioChannelCountMax = 30; // TODO Why 30? +static constexpr SLuint32 SL_ANDROID_UNKNOWN_CHANNELMASK = 0; // Matches name used internally. + +SLuint32 AudioStreamOpenSLES::channelCountToChannelMaskDefault(int channelCount) const { + if (channelCount > kAudioChannelCountMax) { + return SL_ANDROID_UNKNOWN_CHANNELMASK; + } + + SLuint32 bitfield = (1 << channelCount) - 1; + + // Check for OS at run-time. + if(getSdkVersion() >= __ANDROID_API_N__) { + return SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(bitfield); + } + + // Indexed channels masks were added in N. + // For before N, the best we can do is use a positional channel mask. + return bitfield; +} + +static bool s_isLittleEndian() { + static uint32_t value = 1; + return (*reinterpret_cast(&value) == 1); // Does address point to LSB? +} + +SLuint32 AudioStreamOpenSLES::getDefaultByteOrder() { + return s_isLittleEndian() ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; +} + +Result AudioStreamOpenSLES::open() { + + LOGI("AudioStreamOpenSLES::open() chans=%d, rate=%d", mChannelCount, mSampleRate); + + // OpenSL ES only supports I16 and Float + if (mFormat != AudioFormat::I16 && mFormat != AudioFormat::Float) { + LOGW("%s() Android's OpenSL ES implementation only supports I16 and Float. Format: %d", + __func__, mFormat); + return Result::ErrorInvalidFormat; + } + + SLresult result = EngineOpenSLES::getInstance().open(); + if (SL_RESULT_SUCCESS != result) { + return Result::ErrorInternal; + } + + Result oboeResult = AudioStreamBuffered::open(); + if (oboeResult != Result::OK) { + EngineOpenSLES::getInstance().close(); + return oboeResult; + } + // Convert to defaults if UNSPECIFIED + if (mSampleRate == kUnspecified) { + mSampleRate = DefaultStreamValues::SampleRate; + } + if (mChannelCount == kUnspecified) { + mChannelCount = DefaultStreamValues::ChannelCount; + } + if (mContentType == kUnspecified) { + mContentType = ContentType::Music; + } + if (static_cast(mUsage) == kUnspecified) { + mUsage = Usage::Media; + } + + mSharingMode = SharingMode::Shared; + + return Result::OK; +} + + +SLresult AudioStreamOpenSLES::finishCommonOpen(SLAndroidConfigurationItf configItf) { + SLresult result = registerBufferQueueCallback(); + if (SL_RESULT_SUCCESS != result) { + return result; + } + + result = updateStreamParameters(configItf); + if (SL_RESULT_SUCCESS != result) { + return result; + } + + Result oboeResult = configureBufferSizes(mSampleRate); + if (Result::OK != oboeResult) { + return (SLresult) oboeResult; + } + + allocateFifo(); + + calculateDefaultDelayBeforeCloseMillis(); + + return SL_RESULT_SUCCESS; +} + +static int32_t roundUpDivideByN(int32_t x, int32_t n) { + return (x + n - 1) / n; +} + +int32_t AudioStreamOpenSLES::calculateOptimalBufferQueueLength() { + int32_t queueLength = kBufferQueueLengthDefault; + int32_t likelyFramesPerBurst = estimateNativeFramesPerBurst(); + int32_t minCapacity = mBufferCapacityInFrames; // specified by app or zero + // The buffer capacity needs to be at least twice the size of the requested callbackSize + // so that we can have double buffering. + minCapacity = std::max(minCapacity, kDoubleBufferCount * mFramesPerCallback); + if (minCapacity > 0) { + int32_t queueLengthFromCapacity = roundUpDivideByN(minCapacity, likelyFramesPerBurst); + queueLength = std::max(queueLength, queueLengthFromCapacity); + } + queueLength = std::min(queueLength, kBufferQueueLengthMax); // clip to max + // TODO Investigate the effect of queueLength on latency for normal streams. (not low latency) + return queueLength; +} + +/** + * The best information we have is if DefaultStreamValues::FramesPerBurst + * was set by the app based on AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER. + * Without that we just have to guess. + * @return + */ +int32_t AudioStreamOpenSLES::estimateNativeFramesPerBurst() { + int32_t framesPerBurst = DefaultStreamValues::FramesPerBurst; + LOGD("AudioStreamOpenSLES:%s() DefaultStreamValues::FramesPerBurst = %d", + __func__, DefaultStreamValues::FramesPerBurst); + framesPerBurst = std::max(framesPerBurst, 16); + // Calculate the size of a fixed duration high latency buffer based on sample rate. + // Estimate sample based on default options in order of priority. + int32_t sampleRate = 48000; + sampleRate = (DefaultStreamValues::SampleRate > 0) + ? DefaultStreamValues::SampleRate : sampleRate; + sampleRate = (mSampleRate > 0) ? mSampleRate : sampleRate; + int32_t framesPerHighLatencyBuffer = + (kHighLatencyBufferSizeMillis * sampleRate) / kMillisPerSecond; + // For high latency streams, use a larger buffer size. + // Performance Mode support was added in N_MR1 (7.1) + if (getSdkVersion() >= __ANDROID_API_N_MR1__ + && mPerformanceMode != PerformanceMode::LowLatency + && framesPerBurst < framesPerHighLatencyBuffer) { + // Find a multiple of framesPerBurst >= framesPerHighLatencyBuffer. + int32_t numBursts = roundUpDivideByN(framesPerHighLatencyBuffer, framesPerBurst); + framesPerBurst *= numBursts; + LOGD("AudioStreamOpenSLES:%s() NOT low latency, numBursts = %d, mSampleRate = %d, set framesPerBurst = %d", + __func__, numBursts, mSampleRate, framesPerBurst); + } + return framesPerBurst; +} + +Result AudioStreamOpenSLES::configureBufferSizes(int32_t sampleRate) { + LOGD("AudioStreamOpenSLES:%s(%d) initial mFramesPerBurst = %d, mFramesPerCallback = %d", + __func__, mSampleRate, mFramesPerBurst, mFramesPerCallback); + mFramesPerBurst = estimateNativeFramesPerBurst(); + mFramesPerCallback = (mFramesPerCallback > 0) ? mFramesPerCallback : mFramesPerBurst; + LOGD("AudioStreamOpenSLES:%s(%d) final mFramesPerBurst = %d, mFramesPerCallback = %d", + __func__, mSampleRate, mFramesPerBurst, mFramesPerCallback); + + mBytesPerCallback = mFramesPerCallback * getBytesPerFrame(); + if (mBytesPerCallback <= 0) { + LOGE("AudioStreamOpenSLES::open() bytesPerCallback < 0 = %d, bad format?", + mBytesPerCallback); + return Result::ErrorInvalidFormat; // causing bytesPerFrame == 0 + } + + for (int i = 0; i < mBufferQueueLength; ++i) { + mCallbackBuffer[i] = std::make_unique(mBytesPerCallback); + } + + if (!usingFIFO()) { + mBufferCapacityInFrames = mFramesPerBurst * mBufferQueueLength; + // Check for overflow. + if (mBufferCapacityInFrames <= 0) { + mBufferCapacityInFrames = 0; + LOGE("AudioStreamOpenSLES::open() numeric overflow because mFramesPerBurst = %d", + mFramesPerBurst); + return Result::ErrorOutOfRange; + } + mBufferSizeInFrames = mBufferCapacityInFrames; + } + + return Result::OK; +} + +SLuint32 AudioStreamOpenSLES::convertPerformanceMode(PerformanceMode oboeMode) const { + SLuint32 openslMode = SL_ANDROID_PERFORMANCE_NONE; + switch(oboeMode) { + case PerformanceMode::None: + openslMode = SL_ANDROID_PERFORMANCE_NONE; + break; + case PerformanceMode::LowLatency: + openslMode = (getSessionId() == SessionId::None) ? SL_ANDROID_PERFORMANCE_LATENCY : SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS; + break; + case PerformanceMode::PowerSaving: + openslMode = SL_ANDROID_PERFORMANCE_POWER_SAVING; + break; + default: + break; + } + return openslMode; +} + +PerformanceMode AudioStreamOpenSLES::convertPerformanceMode(SLuint32 openslMode) const { + PerformanceMode oboeMode = PerformanceMode::None; + switch(openslMode) { + case SL_ANDROID_PERFORMANCE_NONE: + oboeMode = PerformanceMode::None; + break; + case SL_ANDROID_PERFORMANCE_LATENCY: + case SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: + oboeMode = PerformanceMode::LowLatency; + break; + case SL_ANDROID_PERFORMANCE_POWER_SAVING: + oboeMode = PerformanceMode::PowerSaving; + break; + default: + break; + } + return oboeMode; +} + +void AudioStreamOpenSLES::logUnsupportedAttributes() { + // Log unsupported attributes + // only report if changed from the default + + // Device ID + if (mDeviceId != kUnspecified) { + LOGW("Device ID [AudioStreamBuilder::setDeviceId()] " + "is not supported on OpenSLES streams."); + } + // Sharing Mode + if (mSharingMode != SharingMode::Shared) { + LOGW("SharingMode [AudioStreamBuilder::setSharingMode()] " + "is not supported on OpenSLES streams."); + } + // Performance Mode + int sdkVersion = getSdkVersion(); + if (mPerformanceMode != PerformanceMode::None && sdkVersion < __ANDROID_API_N_MR1__) { + LOGW("PerformanceMode [AudioStreamBuilder::setPerformanceMode()] " + "is not supported on OpenSLES streams running on pre-Android N-MR1 versions."); + } + // Content Type + if (mContentType != ContentType::Music) { + LOGW("ContentType [AudioStreamBuilder::setContentType()] " + "is not supported on OpenSLES streams."); + } + + // Session Id + if (mSessionId != SessionId::None) { + LOGW("SessionId [AudioStreamBuilder::setSessionId()] " + "is not supported on OpenSLES streams."); + } +} + +SLresult AudioStreamOpenSLES::configurePerformanceMode(SLAndroidConfigurationItf configItf) { + + if (configItf == nullptr) { + LOGW("%s() called with NULL configuration", __func__); + mPerformanceMode = PerformanceMode::None; + return SL_RESULT_INTERNAL_ERROR; + } + if (getSdkVersion() < __ANDROID_API_N_MR1__) { + LOGW("%s() not supported until N_MR1", __func__); + mPerformanceMode = PerformanceMode::None; + return SL_RESULT_SUCCESS; + } + + SLresult result = SL_RESULT_SUCCESS; + SLuint32 performanceMode = convertPerformanceMode(getPerformanceMode()); + result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE, + &performanceMode, sizeof(performanceMode)); + if (SL_RESULT_SUCCESS != result) { + LOGW("SetConfiguration(PERFORMANCE_MODE, SL %u) returned %s", + performanceMode, getSLErrStr(result)); + mPerformanceMode = PerformanceMode::None; + } + + return result; +} + +SLresult AudioStreamOpenSLES::updateStreamParameters(SLAndroidConfigurationItf configItf) { + SLresult result = SL_RESULT_SUCCESS; + if(getSdkVersion() >= __ANDROID_API_N_MR1__ && configItf != nullptr) { + SLuint32 performanceMode = 0; + SLuint32 performanceModeSize = sizeof(performanceMode); + result = (*configItf)->GetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE, + &performanceModeSize, &performanceMode); + // A bug in GetConfiguration() before P caused a wrong result code to be returned. + if (getSdkVersion() <= __ANDROID_API_O_MR1__) { + result = SL_RESULT_SUCCESS; // Ignore actual result before P. + } + + if (SL_RESULT_SUCCESS != result) { + LOGW("GetConfiguration(SL_ANDROID_KEY_PERFORMANCE_MODE) returned %d", result); + mPerformanceMode = PerformanceMode::None; // If we can't query it then assume None. + } else { + mPerformanceMode = convertPerformanceMode(performanceMode); // convert SL to Oboe mode + } + } else { + mPerformanceMode = PerformanceMode::None; // If we can't query it then assume None. + } + return result; +} + +// This is called under mLock. +Result AudioStreamOpenSLES::close_l() { + if (mState == StreamState::Closed) { + return Result::ErrorClosed; + } + + AudioStreamBuffered::close(); + + onBeforeDestroy(); + + if (mObjectInterface != nullptr) { + (*mObjectInterface)->Destroy(mObjectInterface); + mObjectInterface = nullptr; + } + + onAfterDestroy(); + + mSimpleBufferQueueInterface = nullptr; + EngineOpenSLES::getInstance().close(); + + setState(StreamState::Closed); + return Result::OK; +} + +SLresult AudioStreamOpenSLES::enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq) { + SLresult result = (*bq)->Enqueue( + bq, mCallbackBuffer[mCallbackBufferIndex].get(), mBytesPerCallback); + mCallbackBufferIndex = (mCallbackBufferIndex + 1) % mBufferQueueLength; + return result; +} + +int32_t AudioStreamOpenSLES::getBufferDepth(SLAndroidSimpleBufferQueueItf bq) { + SLAndroidSimpleBufferQueueState queueState; + SLresult result = (*bq)->GetState(bq, &queueState); + return (result == SL_RESULT_SUCCESS) ? queueState.count : -1; +} + +bool AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueItf bq) { + bool shouldStopStream = false; + // Ask the app callback to process the buffer. + DataCallbackResult result = + fireDataCallback(mCallbackBuffer[mCallbackBufferIndex].get(), mFramesPerCallback); + if (result == DataCallbackResult::Continue) { + // Pass the buffer to OpenSLES. + SLresult enqueueResult = enqueueCallbackBuffer(bq); + if (enqueueResult != SL_RESULT_SUCCESS) { + LOGE("%s() returned %d", __func__, enqueueResult); + shouldStopStream = true; + } + // Update Oboe client position with frames handled by the callback. + if (getDirection() == Direction::Input) { + mFramesRead += mFramesPerCallback; + } else { + mFramesWritten += mFramesPerCallback; + } + } else if (result == DataCallbackResult::Stop) { + LOGD("Oboe callback returned Stop"); + shouldStopStream = true; + } else { + LOGW("Oboe callback returned unexpected value = %d", result); + shouldStopStream = true; + } + if (shouldStopStream) { + mCallbackBufferIndex = 0; + } + return shouldStopStream; +} + +// This callback handler is called every time a buffer has been processed by OpenSL ES. +static void bqCallbackGlue(SLAndroidSimpleBufferQueueItf bq, void *context) { + bool shouldStopStream = (reinterpret_cast(context)) + ->processBufferCallback(bq); + if (shouldStopStream) { + (reinterpret_cast(context))->requestStop(); + } +} + +SLresult AudioStreamOpenSLES::registerBufferQueueCallback() { + // The BufferQueue + SLresult result = (*mObjectInterface)->GetInterface(mObjectInterface, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &mSimpleBufferQueueInterface); + if (SL_RESULT_SUCCESS != result) { + LOGE("get buffer queue interface:%p result:%s", + mSimpleBufferQueueInterface, + getSLErrStr(result)); + } else { + // Register the BufferQueue callback + result = (*mSimpleBufferQueueInterface)->RegisterCallback(mSimpleBufferQueueInterface, + bqCallbackGlue, this); + if (SL_RESULT_SUCCESS != result) { + LOGE("RegisterCallback result:%s", getSLErrStr(result)); + } + } + return result; +} + +int64_t AudioStreamOpenSLES::getFramesProcessedByServer() { + updateServiceFrameCounter(); + int64_t millis64 = mPositionMillis.get(); + int64_t framesProcessed = millis64 * getSampleRate() / kMillisPerSecond; + return framesProcessed; +} + +Result AudioStreamOpenSLES::waitForStateChange(StreamState currentState, + StreamState *nextState, + int64_t timeoutNanoseconds) { + Result oboeResult = Result::ErrorTimeout; + int64_t sleepTimeNanos = 20 * kNanosPerMillisecond; // arbitrary + int64_t timeLeftNanos = timeoutNanoseconds; + + while (true) { + const StreamState state = getState(); // this does not require a lock + if (nextState != nullptr) { + *nextState = state; + } + if (currentState != state) { // state changed? + oboeResult = Result::OK; + break; + } + + // Did we timeout or did user ask for non-blocking? + if (timeLeftNanos <= 0) { + break; + } + + if (sleepTimeNanos > timeLeftNanos){ + sleepTimeNanos = timeLeftNanos; + } + AudioClock::sleepForNanos(sleepTimeNanos); + timeLeftNanos -= sleepTimeNanos; + } + + return oboeResult; +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamOpenSLES_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamOpenSLES_android.h new file mode 100644 index 0000000..541d045 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_AudioStreamOpenSLES_android.h @@ -0,0 +1,148 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_AUDIO_STREAM_OPENSL_ES_H_ +#define OBOE_AUDIO_STREAM_OPENSL_ES_H_ + +#include + +#include +#include + +#include "oboe_oboe_Oboe_android.h" +#include "oboe_common_MonotonicCounter_android.h" +#include "oboe_opensles_AudioStreamBuffered_android.h" +#include "oboe_opensles_EngineOpenSLES_android.h" + +namespace oboe { + +constexpr int kBitsPerByte = 8; +constexpr int kBufferQueueLengthDefault = 2; // double buffered for callbacks +constexpr int kBufferQueueLengthMax = 8; // AudioFlinger won't use more than 8 + +/** + * INTERNAL USE ONLY + * + * A stream that wraps OpenSL ES. + * + * Do not instantiate this class directly. + * Use an OboeStreamBuilder to create one. + */ + +class AudioStreamOpenSLES : public AudioStreamBuffered { +public: + + AudioStreamOpenSLES(); + explicit AudioStreamOpenSLES(const AudioStreamBuilder &builder); + + virtual ~AudioStreamOpenSLES() = default; + + virtual Result open() override; + + /** + * Query the current state, eg. OBOE_STREAM_STATE_PAUSING + * + * @return state or a negative error. + */ + StreamState getState() override { return mState.load(); } + + AudioApi getAudioApi() const override { + return AudioApi::OpenSLES; + } + + /** + * Process next OpenSL ES buffer. + * Called by by OpenSL ES framework. + * + * This is public, but don't call it directly. + * + * @return whether the current stream should be stopped. + */ + bool processBufferCallback(SLAndroidSimpleBufferQueueItf bq); + + Result waitForStateChange(StreamState currentState, + StreamState *nextState, + int64_t timeoutNanoseconds) override; + +protected: + + /** + * Finish setting up the stream. Common for INPUT and OUTPUT. + * + * @param configItf + * @return SL_RESULT_SUCCESS if OK. + */ + SLresult finishCommonOpen(SLAndroidConfigurationItf configItf); + + // This must be called under mLock. + Result close_l(); + + SLuint32 channelCountToChannelMaskDefault(int channelCount) const; + + virtual Result onBeforeDestroy() { return Result::OK; } + virtual Result onAfterDestroy() { return Result::OK; } + + static SLuint32 getDefaultByteOrder(); + + int32_t getBufferDepth(SLAndroidSimpleBufferQueueItf bq); + + int32_t calculateOptimalBufferQueueLength(); + int32_t estimateNativeFramesPerBurst(); + + SLresult enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq); + + SLresult configurePerformanceMode(SLAndroidConfigurationItf configItf); + + PerformanceMode convertPerformanceMode(SLuint32 openslMode) const; + SLuint32 convertPerformanceMode(PerformanceMode oboeMode) const; + + void logUnsupportedAttributes(); + + /** + * Internal use only. + * Use this instead of directly setting the internal state variable. + */ + void setState(StreamState state) { + mState.store(state); + } + + int64_t getFramesProcessedByServer(); + + // OpenSLES stuff + SLObjectItf mObjectInterface = nullptr; + SLAndroidSimpleBufferQueueItf mSimpleBufferQueueInterface = nullptr; + int mBufferQueueLength = 0; + + int32_t mBytesPerCallback = oboe::kUnspecified; + MonotonicCounter mPositionMillis; // for tracking OpenSL ES service position + +private: + + constexpr static int kDoubleBufferCount = 2; + + SLresult registerBufferQueueCallback(); + SLresult updateStreamParameters(SLAndroidConfigurationItf configItf); + Result configureBufferSizes(int32_t sampleRate); + + std::unique_ptr mCallbackBuffer[kBufferQueueLengthMax]; + int mCallbackBufferIndex = 0; + std::atomic mState{StreamState::Uninitialized}; + +}; + +} // namespace oboe + +#endif // OBOE_AUDIO_STREAM_OPENSL_ES_H_ diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_EngineOpenSLES_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_EngineOpenSLES_android.cpp new file mode 100644 index 0000000..93a0e7e --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_EngineOpenSLES_android.cpp @@ -0,0 +1,141 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "oboe_common_OboeDebug_android.h" +#include "oboe_opensles_EngineOpenSLES_android.h" +#include "oboe_opensles_OpenSLESUtilities_android.h" + +using namespace oboe; + +// OpenSL ES is deprecated in SDK 30. +// So we use custom dynamic linking to access the library. +#define LIB_OPENSLES_NAME "libOpenSLES.so" +typedef SLresult (*prototype_slCreateEngine)( + SLObjectItf *pEngine, + SLuint32 numOptions, + const SLEngineOption *pEngineOptions, + SLuint32 numInterfaces, + const SLInterfaceID *pInterfaceIds, + const SLboolean *pInterfaceRequired +); +static prototype_slCreateEngine gFunction_slCreateEngine = nullptr; +static void *gLibOpenSlesLibraryHandle = nullptr; + +// Load the OpenSL ES library and the one primary entry point. +// @return true if linked OK +static bool linkOpenSLES() { + if (gLibOpenSlesLibraryHandle == nullptr && gFunction_slCreateEngine == nullptr) { + // Use RTLD_NOW to avoid the unpredictable behavior that RTLD_LAZY can cause. + // Also resolving all the links now will prevent a run-time penalty later. + gLibOpenSlesLibraryHandle = dlopen(LIB_OPENSLES_NAME, RTLD_NOW); + if (gLibOpenSlesLibraryHandle == nullptr) { + LOGE("linkOpenSLES() could not find " LIB_OPENSLES_NAME); + } else { + gFunction_slCreateEngine = (prototype_slCreateEngine) dlsym( + gLibOpenSlesLibraryHandle, + "slCreateEngine"); + LOGD("linkOpenSLES(): dlsym(%s) returned %p", "slCreateEngine", + gFunction_slCreateEngine); + } + } + return gFunction_slCreateEngine != nullptr; +} + +EngineOpenSLES &EngineOpenSLES::getInstance() { + static EngineOpenSLES sInstance; + return sInstance; +} + +SLresult EngineOpenSLES::open() { + std::lock_guard lock(mLock); + + SLresult result = SL_RESULT_SUCCESS; + if (mOpenCount++ == 0) { + // load the library and link to it + if (!linkOpenSLES()) { + result = SL_RESULT_FEATURE_UNSUPPORTED; + goto error; + }; + + // create engine + result = (*gFunction_slCreateEngine)(&mEngineObject, 0, NULL, 0, NULL, NULL); + if (SL_RESULT_SUCCESS != result) { + LOGE("EngineOpenSLES - slCreateEngine() result:%s", getSLErrStr(result)); + goto error; + } + + // realize the engine + result = (*mEngineObject)->Realize(mEngineObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + LOGE("EngineOpenSLES - Realize() engine result:%s", getSLErrStr(result)); + goto error; + } + + // get the engine interface, which is needed in order to create other objects + result = (*mEngineObject)->GetInterface(mEngineObject, SL_IID_ENGINE, &mEngineInterface); + if (SL_RESULT_SUCCESS != result) { + LOGE("EngineOpenSLES - GetInterface() engine result:%s", getSLErrStr(result)); + goto error; + } + } + + return result; + +error: + close(); + return result; +} + +void EngineOpenSLES::close() { + std::lock_guard lock(mLock); + if (--mOpenCount == 0) { + if (mEngineObject != nullptr) { + (*mEngineObject)->Destroy(mEngineObject); + mEngineObject = nullptr; + mEngineInterface = nullptr; + } + } +} + +SLresult EngineOpenSLES::createOutputMix(SLObjectItf *objectItf) { + return (*mEngineInterface)->CreateOutputMix(mEngineInterface, objectItf, 0, 0, 0); +} + +SLresult EngineOpenSLES::createAudioPlayer(SLObjectItf *objectItf, + SLDataSource *audioSource, + SLDataSink *audioSink) { + + const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION}; + const SLboolean reqs[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + + return (*mEngineInterface)->CreateAudioPlayer(mEngineInterface, objectItf, audioSource, + audioSink, + sizeof(ids) / sizeof(ids[0]), ids, reqs); +} + +SLresult EngineOpenSLES::createAudioRecorder(SLObjectItf *objectItf, + SLDataSource *audioSource, + SLDataSink *audioSink) { + + const SLInterfaceID ids[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }; + const SLboolean reqs[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + + return (*mEngineInterface)->CreateAudioRecorder(mEngineInterface, objectItf, audioSource, + audioSink, + sizeof(ids) / sizeof(ids[0]), ids, reqs); +} + diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_EngineOpenSLES_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_EngineOpenSLES_android.h new file mode 100644 index 0000000..3d238a8 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_EngineOpenSLES_android.h @@ -0,0 +1,65 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_ENGINE_OPENSLES_H +#define OBOE_ENGINE_OPENSLES_H + +#include +#include + +#include +#include + +namespace oboe { + +/** + * INTERNAL USE ONLY + */ +class EngineOpenSLES { +public: + static EngineOpenSLES &getInstance(); + + SLresult open(); + + void close(); + + SLresult createOutputMix(SLObjectItf *objectItf); + + SLresult createAudioPlayer(SLObjectItf *objectItf, + SLDataSource *audioSource, + SLDataSink *audioSink); + SLresult createAudioRecorder(SLObjectItf *objectItf, + SLDataSource *audioSource, + SLDataSink *audioSink); + +private: + // Make this a safe Singleton + EngineOpenSLES()= default; + ~EngineOpenSLES()= default; + EngineOpenSLES(const EngineOpenSLES&)= delete; + EngineOpenSLES& operator=(const EngineOpenSLES&)= delete; + + std::mutex mLock; + int32_t mOpenCount = 0; + + SLObjectItf mEngineObject = nullptr; + SLEngineItf mEngineInterface = nullptr; +}; + +} // namespace oboe + + +#endif //OBOE_ENGINE_OPENSLES_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OpenSLESUtilities_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OpenSLESUtilities_android.cpp new file mode 100644 index 0000000..e0e701a --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OpenSLESUtilities_android.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "oboe_opensles_OpenSLESUtilities_android.h" + +namespace oboe { + +/* + * OSLES Helpers + */ + +const char *getSLErrStr(SLresult code) { + switch (code) { + case SL_RESULT_SUCCESS: + return "SL_RESULT_SUCCESS"; + case SL_RESULT_PRECONDITIONS_VIOLATED: + return "SL_RESULT_PRECONDITIONS_VIOLATED"; + case SL_RESULT_PARAMETER_INVALID: + return "SL_RESULT_PARAMETER_INVALID"; + case SL_RESULT_MEMORY_FAILURE: + return "SL_RESULT_MEMORY_FAILURE"; + case SL_RESULT_RESOURCE_ERROR: + return "SL_RESULT_RESOURCE_ERROR"; + case SL_RESULT_RESOURCE_LOST: + return "SL_RESULT_RESOURCE_LOST"; + case SL_RESULT_IO_ERROR: + return "SL_RESULT_IO_ERROR"; + case SL_RESULT_BUFFER_INSUFFICIENT: + return "SL_RESULT_BUFFER_INSUFFICIENT"; + case SL_RESULT_CONTENT_CORRUPTED: + return "SL_RESULT_CONTENT_CORRUPTED"; + case SL_RESULT_CONTENT_UNSUPPORTED: + return "SL_RESULT_CONTENT_UNSUPPORTED"; + case SL_RESULT_CONTENT_NOT_FOUND: + return "SL_RESULT_CONTENT_NOT_FOUND"; + case SL_RESULT_PERMISSION_DENIED: + return "SL_RESULT_PERMISSION_DENIED"; + case SL_RESULT_FEATURE_UNSUPPORTED: + return "SL_RESULT_FEATURE_UNSUPPORTED"; + case SL_RESULT_INTERNAL_ERROR: + return "SL_RESULT_INTERNAL_ERROR"; + case SL_RESULT_UNKNOWN_ERROR: + return "SL_RESULT_UNKNOWN_ERROR"; + case SL_RESULT_OPERATION_ABORTED: + return "SL_RESULT_OPERATION_ABORTED"; + case SL_RESULT_CONTROL_LOST: + return "SL_RESULT_CONTROL_LOST"; + default: + return "Unknown SL error"; + } +} + +SLAndroidDataFormat_PCM_EX OpenSLES_createExtendedFormat( + SLDataFormat_PCM format, SLuint32 representation) { + SLAndroidDataFormat_PCM_EX format_pcm_ex; + format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; + format_pcm_ex.numChannels = format.numChannels; + format_pcm_ex.sampleRate = format.samplesPerSec; + format_pcm_ex.bitsPerSample = format.bitsPerSample; + format_pcm_ex.containerSize = format.containerSize; + format_pcm_ex.channelMask = format.channelMask; + format_pcm_ex.endianness = format.endianness; + format_pcm_ex.representation = representation; + return format_pcm_ex; +} + +SLuint32 OpenSLES_ConvertFormatToRepresentation(AudioFormat format) { + switch(format) { + case AudioFormat::I16: + return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; + case AudioFormat::Float: + return SL_ANDROID_PCM_REPRESENTATION_FLOAT; + case AudioFormat::I24: + case AudioFormat::I32: + case AudioFormat::Invalid: + case AudioFormat::Unspecified: + default: + return 0; + } +} + +} // namespace oboe diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OpenSLESUtilities_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OpenSLESUtilities_android.h new file mode 100644 index 0000000..7736d35 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OpenSLESUtilities_android.h @@ -0,0 +1,44 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_OPENSLES_OPENSLESUTILITIES_H +#define OBOE_OPENSLES_OPENSLESUTILITIES_H + +#include +#include "oboe_oboe_Oboe_android.h" + +namespace oboe { + +const char *getSLErrStr(SLresult code); + +/** + * Creates an extended PCM format from the supplied format and data representation. This method + * should only be called on Android devices with API level 21+. API 21 introduced the + * SLAndroidDataFormat_PCM_EX object which allows audio samples to be represented using + * single precision floating-point. + * + * @param format + * @param representation + * @return the extended PCM format + */ +SLAndroidDataFormat_PCM_EX OpenSLES_createExtendedFormat(SLDataFormat_PCM format, + SLuint32 representation); + +SLuint32 OpenSLES_ConvertFormatToRepresentation(AudioFormat format); + +} // namespace oboe + +#endif //OBOE_OPENSLES_OPENSLESUTILITIES_H diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OutputMixerOpenSLES_android.cpp b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OutputMixerOpenSLES_android.cpp new file mode 100644 index 0000000..b4c9e62 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OutputMixerOpenSLES_android.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "oboe_common_OboeDebug_android.h" +#include "oboe_opensles_EngineOpenSLES_android.h" +#include "oboe_opensles_OpenSLESUtilities_android.h" +#include "oboe_opensles_OutputMixerOpenSLES_android.h" + +using namespace oboe; + +OutputMixerOpenSL &OutputMixerOpenSL::getInstance() { + static OutputMixerOpenSL sInstance; + return sInstance; +} + +SLresult OutputMixerOpenSL::open() { + std::lock_guard lock(mLock); + + SLresult result = SL_RESULT_SUCCESS; + if (mOpenCount++ == 0) { + // get the output mixer + result = EngineOpenSLES::getInstance().createOutputMix(&mOutputMixObject); + if (SL_RESULT_SUCCESS != result) { + LOGE("OutputMixerOpenSL() - createOutputMix() result:%s", getSLErrStr(result)); + goto error; + } + + // realize the output mix + result = (*mOutputMixObject)->Realize(mOutputMixObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + LOGE("OutputMixerOpenSL() - Realize() mOutputMixObject result:%s", getSLErrStr(result)); + goto error; + } + } + + return result; + +error: + close(); + return result; +} + +void OutputMixerOpenSL::close() { + std::lock_guard lock(mLock); + + if (--mOpenCount == 0) { + // destroy output mix object, and invalidate all associated interfaces + if (mOutputMixObject != nullptr) { + (*mOutputMixObject)->Destroy(mOutputMixObject); + mOutputMixObject = nullptr; + } + } +} + +SLresult OutputMixerOpenSL::createAudioPlayer(SLObjectItf *objectItf, + SLDataSource *audioSource) { + SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, mOutputMixObject}; + SLDataSink audioSink = {&loc_outmix, NULL}; + return EngineOpenSLES::getInstance().createAudioPlayer(objectItf, audioSource, &audioSink); +} diff --git a/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OutputMixerOpenSLES_android.h b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OutputMixerOpenSLES_android.h new file mode 100644 index 0000000..813fd01 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/internal/oboe/oboe_opensles_OutputMixerOpenSLES_android.h @@ -0,0 +1,58 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBOE_OUTPUT_MIXER_OPENSLES_H +#define OBOE_OUTPUT_MIXER_OPENSLES_H + +#include +#include + +#include +#include + +namespace oboe { + +/** + * INTERNAL USE ONLY + */ + +class OutputMixerOpenSL { +public: + static OutputMixerOpenSL &getInstance(); + + SLresult open(); + + void close(); + + SLresult createAudioPlayer(SLObjectItf *objectItf, + SLDataSource *audioSource); + +private: + // Make this a safe Singleton + OutputMixerOpenSL()= default; + ~OutputMixerOpenSL()= default; + OutputMixerOpenSL(const OutputMixerOpenSL&)= delete; + OutputMixerOpenSL& operator=(const OutputMixerOpenSL&)= delete; + + std::mutex mLock; + int32_t mOpenCount = 0; + + SLObjectItf mOutputMixObject = nullptr; +}; + +} // namespace oboe + +#endif //OBOE_OUTPUT_MIXER_OPENSLES_H diff --git a/vendor/github.com/ebitengine/oto/v3/player.go b/vendor/github.com/ebitengine/oto/v3/player.go new file mode 100644 index 0000000..ad14860 --- /dev/null +++ b/vendor/github.com/ebitengine/oto/v3/player.go @@ -0,0 +1,85 @@ +// Copyright 2021 The Oto Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oto + +import ( + "github.com/ebitengine/oto/v3/internal/mux" +) + +// Player is a PCM (pulse-code modulation) audio player. +type Player struct { + player *mux.Player +} + +// Pause pauses its playing. +func (p *Player) Pause() { + p.player.Pause() +} + +// Play starts its playing if it doesn't play. +func (p *Player) Play() { + p.player.Play() +} + +// IsPlaying reports whether this player is playing. +func (p *Player) IsPlaying() bool { + return p.player.IsPlaying() +} + +// Reset clears the underyling buffer and pauses its playing. +// +// Deprecated: use Pause or Seek instead. +func (p *Player) Reset() { + p.player.Reset() +} + +// Volume returns the current volume in the range of [0, 1]. +// The default volume is 1. +func (p *Player) Volume() float64 { + return p.player.Volume() +} + +// SetVolume sets the current volume in the range of [0, 1]. +func (p *Player) SetVolume(volume float64) { + p.player.SetVolume(volume) +} + +// BufferedSize returns the byte size of the buffer data that is not sent to the audio hardware yet. +func (p *Player) BufferedSize() int { + return p.player.BufferedSize() +} + +// Err returns an error if this player has an error. +func (p *Player) Err() error { + return p.player.Err() +} + +// SetBufferSize sets the buffer size. +// If 0 is specified, the default buffer size is used. +func (p *Player) SetBufferSize(bufferSize int) { + p.player.SetBufferSize(bufferSize) +} + +// Seek implements io.Seeker. +// +// Seek returns an error when the underlying source doesn't implement io.Seeker. +func (p *Player) Seek(offset int64, whence int) (int64, error) { + return p.player.Seek(offset, whence) +} + +// Close implements io.Closer. +func (p *Player) Close() error { + return p.player.Close() +} diff --git a/vendor/github.com/ebitengine/purego/.gitignore b/vendor/github.com/ebitengine/purego/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/vendor/github.com/ebitengine/purego/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/vendor/github.com/ebitengine/purego/LICENSE b/vendor/github.com/ebitengine/purego/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/github.com/ebitengine/purego/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/ebitengine/purego/README.md b/vendor/github.com/ebitengine/purego/README.md new file mode 100644 index 0000000..8f40f8b --- /dev/null +++ b/vendor/github.com/ebitengine/purego/README.md @@ -0,0 +1,96 @@ +# purego +[![Go Reference](https://pkg.go.dev/badge/github.com/ebitengine/purego?GOOS=darwin.svg)](https://pkg.go.dev/github.com/ebitengine/purego?GOOS=darwin) + +A library for calling C functions from Go without Cgo. + +> This is beta software so expect bugs and potentially API breaking changes +> but each release will be tagged to avoid breaking people's code. +> Bug reports are encouraged. + +## Motivation + +The [Ebitengine](https://github.com/hajimehoshi/ebiten) game engine was ported to use only Go on Windows. This enabled +cross-compiling to Windows from any other operating system simply by setting `GOOS=windows`. The purego project was +born to bring that same vision to the other platforms supported by Ebitengine. + +## Benefits + +- **Simple Cross-Compilation**: No C means you can build for other platforms easily without a C compiler. +- **Faster Compilation**: Efficiently cache your entirely Go builds. +- **Smaller Binaries**: Using Cgo generates a C wrapper function for each C function called. Purego doesn't! +- **Dynamic Linking**: Load symbols at runtime and use it as a plugin system. +- **Foreign Function Interface**: Call into other languages that are compiled into shared objects. +- **Cgo Fallback**: Works even with CGO_ENABLED=1 so incremental porting is possible. +This also means unsupported GOARCHs (freebsd/riscv64, linux/mips, etc.) will still work +except for float arguments and return values. + +## Supported Platforms + +- **FreeBSD**: amd64, arm64 +- **Linux**: amd64, arm64 +- **macOS / iOS**: amd64, arm64 +- **Windows**: 386*, amd64, arm*, arm64 + +`*` These architectures only support SyscallN and NewCallback + +## Example + +This example only works on macOS and Linux. For a complete example look at [libc](https://github.com/ebitengine/purego/tree/main/examples/libc) which supports Windows and FreeBSD. + +```go +package main + +import ( + "fmt" + "runtime" + + "github.com/ebitengine/purego" +) + +func getSystemLibrary() string { + switch runtime.GOOS { + case "darwin": + return "/usr/lib/libSystem.B.dylib" + case "linux": + return "libc.so.6" + default: + panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS)) + } +} + +func main() { + libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + var puts func(string) + purego.RegisterLibFunc(&puts, libc, "puts") + puts("Calling C from Go without Cgo!") +} +``` + +Then to run: `CGO_ENABLED=0 go run main.go` + +## Questions + +If you have questions about how to incorporate purego in your project or want to discuss +how it works join the [Discord](https://discord.com/channels/842049801528016967/1123106378731487345)! + +### External Code + +Purego uses code that originates from the Go runtime. These files are under the BSD-3 +License that can be found [in the Go Source](https://github.com/golang/go/blob/master/LICENSE). +This is a list of the copied files: + +* `abi_*.h` from package `runtime/cgo` +* `zcallback_darwin_*.s` from package `runtime` +* `internal/fakecgo/abi_*.h` from package `runtime/cgo` +* `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo` +* `internal/fakecgo/callbacks.go` from package `runtime/cgo` +* `internal/fakecgo/go_GOOS_GOARCH.go` from package `runtime/cgo` +* `internal/fakecgo/iscgo.go` from package `runtime/cgo` +* `internal/fakecgo/setenv.go` from package `runtime/cgo` +* `internal/fakecgo/freebsd.go` from package `runtime/cgo` + +The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of +`#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636)) diff --git a/vendor/github.com/ebitengine/purego/abi_amd64.h b/vendor/github.com/ebitengine/purego/abi_amd64.h new file mode 100644 index 0000000..9949435 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/abi_amd64.h @@ -0,0 +1,99 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Macros for transitioning from the host ABI to Go ABI0. +// +// These save the frame pointer, so in general, functions that use +// these should have zero frame size to suppress the automatic frame +// pointer, though it's harmless to not do this. + +#ifdef GOOS_windows + +// REGS_HOST_TO_ABI0_STACK is the stack bytes used by +// PUSH_REGS_HOST_TO_ABI0. +#define REGS_HOST_TO_ABI0_STACK (28*8 + 8) + +// PUSH_REGS_HOST_TO_ABI0 prepares for transitioning from +// the host ABI to Go ABI0 code. It saves all registers that are +// callee-save in the host ABI and caller-save in Go ABI0 and prepares +// for entry to Go. +// +// Save DI SI BP BX R12 R13 R14 R15 X6-X15 registers and the DF flag. +// Clear the DF flag for the Go ABI. +// MXCSR matches the Go ABI, so we don't have to set that, +// and Go doesn't modify it, so we don't have to save it. +#define PUSH_REGS_HOST_TO_ABI0() \ + PUSHFQ \ + CLD \ + ADJSP $(REGS_HOST_TO_ABI0_STACK - 8) \ + MOVQ DI, (0*0)(SP) \ + MOVQ SI, (1*8)(SP) \ + MOVQ BP, (2*8)(SP) \ + MOVQ BX, (3*8)(SP) \ + MOVQ R12, (4*8)(SP) \ + MOVQ R13, (5*8)(SP) \ + MOVQ R14, (6*8)(SP) \ + MOVQ R15, (7*8)(SP) \ + MOVUPS X6, (8*8)(SP) \ + MOVUPS X7, (10*8)(SP) \ + MOVUPS X8, (12*8)(SP) \ + MOVUPS X9, (14*8)(SP) \ + MOVUPS X10, (16*8)(SP) \ + MOVUPS X11, (18*8)(SP) \ + MOVUPS X12, (20*8)(SP) \ + MOVUPS X13, (22*8)(SP) \ + MOVUPS X14, (24*8)(SP) \ + MOVUPS X15, (26*8)(SP) + +#define POP_REGS_HOST_TO_ABI0() \ + MOVQ (0*0)(SP), DI \ + MOVQ (1*8)(SP), SI \ + MOVQ (2*8)(SP), BP \ + MOVQ (3*8)(SP), BX \ + MOVQ (4*8)(SP), R12 \ + MOVQ (5*8)(SP), R13 \ + MOVQ (6*8)(SP), R14 \ + MOVQ (7*8)(SP), R15 \ + MOVUPS (8*8)(SP), X6 \ + MOVUPS (10*8)(SP), X7 \ + MOVUPS (12*8)(SP), X8 \ + MOVUPS (14*8)(SP), X9 \ + MOVUPS (16*8)(SP), X10 \ + MOVUPS (18*8)(SP), X11 \ + MOVUPS (20*8)(SP), X12 \ + MOVUPS (22*8)(SP), X13 \ + MOVUPS (24*8)(SP), X14 \ + MOVUPS (26*8)(SP), X15 \ + ADJSP $-(REGS_HOST_TO_ABI0_STACK - 8) \ + POPFQ + +#else +// SysV ABI + +#define REGS_HOST_TO_ABI0_STACK (6*8) + +// SysV MXCSR matches the Go ABI, so we don't have to set that, +// and Go doesn't modify it, so we don't have to save it. +// Both SysV and Go require DF to be cleared, so that's already clear. +// The SysV and Go frame pointer conventions are compatible. +#define PUSH_REGS_HOST_TO_ABI0() \ + ADJSP $(REGS_HOST_TO_ABI0_STACK) \ + MOVQ BP, (5*8)(SP) \ + LEAQ (5*8)(SP), BP \ + MOVQ BX, (0*8)(SP) \ + MOVQ R12, (1*8)(SP) \ + MOVQ R13, (2*8)(SP) \ + MOVQ R14, (3*8)(SP) \ + MOVQ R15, (4*8)(SP) + +#define POP_REGS_HOST_TO_ABI0() \ + MOVQ (0*8)(SP), BX \ + MOVQ (1*8)(SP), R12 \ + MOVQ (2*8)(SP), R13 \ + MOVQ (3*8)(SP), R14 \ + MOVQ (4*8)(SP), R15 \ + MOVQ (5*8)(SP), BP \ + ADJSP $-(REGS_HOST_TO_ABI0_STACK) + +#endif diff --git a/vendor/github.com/ebitengine/purego/abi_arm64.h b/vendor/github.com/ebitengine/purego/abi_arm64.h new file mode 100644 index 0000000..5d5061e --- /dev/null +++ b/vendor/github.com/ebitengine/purego/abi_arm64.h @@ -0,0 +1,39 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Macros for transitioning from the host ABI to Go ABI0. +// +// These macros save and restore the callee-saved registers +// from the stack, but they don't adjust stack pointer, so +// the user should prepare stack space in advance. +// SAVE_R19_TO_R28(offset) saves R19 ~ R28 to the stack space +// of ((offset)+0*8)(RSP) ~ ((offset)+9*8)(RSP). +// +// SAVE_F8_TO_F15(offset) saves F8 ~ F15 to the stack space +// of ((offset)+0*8)(RSP) ~ ((offset)+7*8)(RSP). +// +// R29 is not saved because Go will save and restore it. + +#define SAVE_R19_TO_R28(offset) \ + STP (R19, R20), ((offset)+0*8)(RSP) \ + STP (R21, R22), ((offset)+2*8)(RSP) \ + STP (R23, R24), ((offset)+4*8)(RSP) \ + STP (R25, R26), ((offset)+6*8)(RSP) \ + STP (R27, g), ((offset)+8*8)(RSP) +#define RESTORE_R19_TO_R28(offset) \ + LDP ((offset)+0*8)(RSP), (R19, R20) \ + LDP ((offset)+2*8)(RSP), (R21, R22) \ + LDP ((offset)+4*8)(RSP), (R23, R24) \ + LDP ((offset)+6*8)(RSP), (R25, R26) \ + LDP ((offset)+8*8)(RSP), (R27, g) /* R28 */ +#define SAVE_F8_TO_F15(offset) \ + FSTPD (F8, F9), ((offset)+0*8)(RSP) \ + FSTPD (F10, F11), ((offset)+2*8)(RSP) \ + FSTPD (F12, F13), ((offset)+4*8)(RSP) \ + FSTPD (F14, F15), ((offset)+6*8)(RSP) +#define RESTORE_F8_TO_F15(offset) \ + FLDPD ((offset)+0*8)(RSP), (F8, F9) \ + FLDPD ((offset)+2*8)(RSP), (F10, F11) \ + FLDPD ((offset)+4*8)(RSP), (F12, F13) \ + FLDPD ((offset)+6*8)(RSP), (F14, F15) diff --git a/vendor/github.com/ebitengine/purego/cgo.go b/vendor/github.com/ebitengine/purego/cgo.go new file mode 100644 index 0000000..7d5abef --- /dev/null +++ b/vendor/github.com/ebitengine/purego/cgo.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build cgo && (darwin || freebsd || linux) + +package purego + +// if CGO_ENABLED=1 import the Cgo runtime to ensure that it is set up properly. +// This is required since some frameworks need TLS setup the C way which Go doesn't do. +// We currently don't support ios in fakecgo mode so force Cgo or fail +// Even if CGO_ENABLED=1 the Cgo runtime is not imported unless `import "C"` is used. +// which will import this package automatically. Normally this isn't an issue since it +// usually isn't possible to call into C without using that import. However, with purego +// it is since we don't use `import "C"`! +import ( + _ "runtime/cgo" + + _ "github.com/ebitengine/purego/internal/cgo" +) diff --git a/vendor/github.com/ebitengine/purego/dlerror.go b/vendor/github.com/ebitengine/purego/dlerror.go new file mode 100644 index 0000000..cf4c050 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlerror.go @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +package purego + +// Dlerror represents an error value returned from Dlopen, Dlsym, or Dlclose. +type Dlerror struct { + s string +} + +func (e Dlerror) Error() string { + return e.s +} diff --git a/vendor/github.com/ebitengine/purego/dlfcn.go b/vendor/github.com/ebitengine/purego/dlfcn.go new file mode 100644 index 0000000..2ee6f34 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn.go @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +package purego + +import ( + "unsafe" +) + +// Unix Specification for dlfcn.h: https://pubs.opengroup.org/onlinepubs/7908799/xsh/dlfcn.h.html + +var ( + fnDlopen func(path string, mode int) uintptr + fnDlsym func(handle uintptr, name string) uintptr + fnDlerror func() string + fnDlclose func(handle uintptr) bool +) + +func init() { + RegisterFunc(&fnDlopen, dlopenABI0) + RegisterFunc(&fnDlsym, dlsymABI0) + RegisterFunc(&fnDlerror, dlerrorABI0) + RegisterFunc(&fnDlclose, dlcloseABI0) +} + +// Dlopen examines the dynamic library or bundle file specified by path. If the file is compatible +// with the current process and has not already been loaded into the +// current process, it is loaded and linked. After being linked, if it contains +// any initializer functions, they are called, before Dlopen +// returns. It returns a handle that can be used with Dlsym and Dlclose. +// A second call to Dlopen with the same path will return the same handle, but the internal +// reference count for the handle will be incremented. Therefore, all +// Dlopen calls should be balanced with a Dlclose call. +func Dlopen(path string, mode int) (uintptr, error) { + u := fnDlopen(path, mode) + if u == 0 { + return 0, Dlerror{fnDlerror()} + } + return u, nil +} + +// Dlsym takes a "handle" of a dynamic library returned by Dlopen and the symbol name. +// It returns the address where that symbol is loaded into memory. If the symbol is not found, +// in the specified library or any of the libraries that were automatically loaded by Dlopen +// when that library was loaded, Dlsym returns zero. +func Dlsym(handle uintptr, name string) (uintptr, error) { + u := fnDlsym(handle, name) + if u == 0 { + return 0, Dlerror{fnDlerror()} + } + return u, nil +} + +// Dlclose decrements the reference count on the dynamic library handle. +// If the reference count drops to zero and no other loaded libraries +// use symbols in it, then the dynamic library is unloaded. +func Dlclose(handle uintptr) error { + if fnDlclose(handle) { + return Dlerror{fnDlerror()} + } + return nil +} + +//go:linkname openLibrary openLibrary +func openLibrary(name string) (uintptr, error) { + return Dlopen(name, RTLD_NOW|RTLD_GLOBAL) +} + +func loadSymbol(handle uintptr, name string) (uintptr, error) { + return Dlsym(handle, name) +} + +// these functions exist in dlfcn_stubs.s and are calling C functions linked to in dlfcn_GOOS.go +// the indirection is necessary because a function is actually a pointer to the pointer to the code. +// sadly, I do not know of anyway to remove the assembly stubs entirely because //go:linkname doesn't +// appear to work if you link directly to the C function on darwin arm64. + +//go:linkname dlopen dlopen +var dlopen uintptr +var dlopenABI0 = uintptr(unsafe.Pointer(&dlopen)) + +//go:linkname dlsym dlsym +var dlsym uintptr +var dlsymABI0 = uintptr(unsafe.Pointer(&dlsym)) + +//go:linkname dlclose dlclose +var dlclose uintptr +var dlcloseABI0 = uintptr(unsafe.Pointer(&dlclose)) + +//go:linkname dlerror dlerror +var dlerror uintptr +var dlerrorABI0 = uintptr(unsafe.Pointer(&dlerror)) diff --git a/vendor/github.com/ebitengine/purego/dlfcn_darwin.go b/vendor/github.com/ebitengine/purego/dlfcn_darwin.go new file mode 100644 index 0000000..b343f84 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn_darwin.go @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package purego + +// Source for constants: https://opensource.apple.com/source/dyld/dyld-360.14/include/dlfcn.h.auto.html + +const ( + RTLD_DEFAULT = ^uintptr(0) - 1 // Pseudo-handle for dlsym so search for any loaded symbol + RTLD_LAZY = 0x1 // Relocations are performed at an implementation-dependent time. + RTLD_NOW = 0x2 // Relocations are performed when the object is loaded. + RTLD_LOCAL = 0x4 // All symbols are not made available for relocation processing by other modules. + RTLD_GLOBAL = 0x8 // All symbols are available for relocation processing of other modules. +) + +//go:cgo_import_dynamic purego_dlopen dlopen "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib" + +//go:cgo_import_dynamic purego_dlopen dlopen "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib" diff --git a/vendor/github.com/ebitengine/purego/dlfcn_freebsd.go b/vendor/github.com/ebitengine/purego/dlfcn_freebsd.go new file mode 100644 index 0000000..38f0678 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn_freebsd.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package purego + +// Constants as defined in https://github.com/freebsd/freebsd-src/blob/main/include/dlfcn.h +const ( + RTLD_DEFAULT = ^uintptr(0) - 2 // Pseudo-handle for dlsym so search for any loaded symbol + RTLD_LAZY = 0x00001 // Relocations are performed at an implementation-dependent time. + RTLD_NOW = 0x00002 // Relocations are performed when the object is loaded. + RTLD_LOCAL = 0x00000 // All symbols are not made available for relocation processing by other modules. + RTLD_GLOBAL = 0x00100 // All symbols are available for relocation processing of other modules. +) diff --git a/vendor/github.com/ebitengine/purego/dlfcn_linux.go b/vendor/github.com/ebitengine/purego/dlfcn_linux.go new file mode 100644 index 0000000..3011275 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn_linux.go @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package purego + +// Source for constants: https://codebrowser.dev/glibc/glibc/bits/dlfcn.h.html + +const ( + RTLD_DEFAULT = 0x00000 // Pseudo-handle for dlsym so search for any loaded symbol + RTLD_LAZY = 0x00001 // Relocations are performed at an implementation-dependent time. + RTLD_NOW = 0x00002 // Relocations are performed when the object is loaded. + RTLD_LOCAL = 0x00000 // All symbols are not made available for relocation processing by other modules. + RTLD_GLOBAL = 0x00100 // All symbols are available for relocation processing of other modules. +) diff --git a/vendor/github.com/ebitengine/purego/dlfcn_nocgo_freebsd.go b/vendor/github.com/ebitengine/purego/dlfcn_nocgo_freebsd.go new file mode 100644 index 0000000..1bd6612 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn_nocgo_freebsd.go @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package purego + +//go:cgo_import_dynamic purego_dlopen dlopen "libc.so.7" +//go:cgo_import_dynamic purego_dlsym dlsym "libc.so.7" +//go:cgo_import_dynamic purego_dlerror dlerror "libc.so.7" +//go:cgo_import_dynamic purego_dlclose dlclose "libc.so.7" diff --git a/vendor/github.com/ebitengine/purego/dlfcn_nocgo_linux.go b/vendor/github.com/ebitengine/purego/dlfcn_nocgo_linux.go new file mode 100644 index 0000000..2c8009f --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn_nocgo_linux.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package purego + +// if there is no Cgo we must link to each of the functions from dlfcn.h +// then the functions are called inside dlfcn_stubs.s + +//go:cgo_import_dynamic purego_dlopen dlopen "libdl.so.2" +//go:cgo_import_dynamic purego_dlsym dlsym "libdl.so.2" +//go:cgo_import_dynamic purego_dlerror dlerror "libdl.so.2" +//go:cgo_import_dynamic purego_dlclose dlclose "libdl.so.2" + +// on amd64 we don't need the following line - on 386 we do... +// anyway - with those lines the output is better (but doesn't matter) - without it on amd64 we get multiple DT_NEEDED with "libc.so.6" etc + +//go:cgo_import_dynamic _ _ "libdl.so.2" diff --git a/vendor/github.com/ebitengine/purego/dlfcn_stubs.s b/vendor/github.com/ebitengine/purego/dlfcn_stubs.s new file mode 100644 index 0000000..fa434bf --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn_stubs.s @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || !cgo && (freebsd || linux) + +#include "textflag.h" + +// func dlopen(path *byte, mode int) (ret uintptr) +TEXT dlopen(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_dlopen(SB) + RET + +// func dlsym(handle uintptr, symbol *byte) (ret uintptr) +TEXT dlsym(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_dlsym(SB) + RET + +// func dlerror() (ret *byte) +TEXT dlerror(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_dlerror(SB) + RET + +// func dlclose(handle uintptr) (ret int) +TEXT dlclose(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_dlclose(SB) + RET diff --git a/vendor/github.com/ebitengine/purego/func.go b/vendor/github.com/ebitengine/purego/func.go new file mode 100644 index 0000000..03d7dae --- /dev/null +++ b/vendor/github.com/ebitengine/purego/func.go @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux || windows + +package purego + +import ( + "math" + "reflect" + "runtime" + "unsafe" + + "github.com/ebitengine/purego/internal/strings" +) + +// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). +// It panics if it can't find the name symbol. +func RegisterLibFunc(fptr interface{}, handle uintptr, name string) { + sym, err := loadSymbol(handle, name) + if err != nil { + panic(err) + } + RegisterFunc(fptr, sym) +} + +// RegisterFunc takes a pointer to a Go function representing the calling convention of the C function. +// fptr will be set to a function that when called will call the C function given by cfn with the +// parameters passed in the correct registers and stack. +// +// A panic is produced if the type is not a function pointer or if the function returns more than 1 value. +// +// These conversions describe how a Go type in the fptr will be used to call +// the C function. It is important to note that there is no way to verify that fptr +// matches the C function. This also holds true for struct types where the padding +// needs to be ensured to match that of C; RegisterFunc does not verify this. +// +// # Type Conversions (Go <=> C) +// +// string <=> char* +// bool <=> _Bool +// uintptr <=> uintptr_t +// uint <=> uint32_t or uint64_t +// uint8 <=> uint8_t +// uint16 <=> uint16_t +// uint32 <=> uint32_t +// uint64 <=> uint64_t +// int <=> int32_t or int64_t +// int8 <=> int8_t +// int16 <=> int16_t +// int32 <=> int32_t +// int64 <=> int64_t +// float32 <=> float +// float64 <=> double +// struct <=> struct (WIP - darwin only) +// func <=> C function +// unsafe.Pointer, *T <=> void* +// []T => void* +// +// There is a special case when the last argument of fptr is a variadic interface (or []interface} +// it will be expanded into a call to the C function as if it had the arguments in that slice. +// This means that using arg ...interface{} is like a cast to the function with the arguments inside arg. +// This is not the same as C variadic. +// +// # Memory +// +// In general it is not possible for purego to guarantee the lifetimes of objects returned or received from +// calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't +// hold onto a reference to Go memory. This is the same as the [Cgo rules]. +// +// However, there are some special cases. When passing a string as an argument if the string does not end in a null +// terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for +// that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some +// undefined time. However, if the string does already contain a null-terminated byte then no copy is done. +// It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory. +// This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function +// returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory +// and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced. +// This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue +// to point to C memory (because it's a buffer for example) then use a pointer to byte and then convert that to a slice +// using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime +// of the pointer +// +// # Structs +// +// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However, +// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure +// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example. +// +// # Example +// +// All functions below call this C function: +// +// char *foo(char *str); +// +// // Let purego convert types +// var foo func(s string) string +// goString := foo("copied") +// // Go will garbage collect this string +// +// // Manually, handle allocations +// var foo2 func(b string) *byte +// mustFree := foo2("not copied\x00") +// defer free(mustFree) +// +// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C +func RegisterFunc(fptr interface{}, cfn uintptr) { + fn := reflect.ValueOf(fptr).Elem() + ty := fn.Type() + if ty.Kind() != reflect.Func { + panic("purego: fptr must be a function pointer") + } + if ty.NumOut() > 1 { + panic("purego: function can only return zero or one values") + } + if cfn == 0 { + panic("purego: cfn is nil") + } + { + // this code checks how many registers and stack this function will use + // to avoid crashing with too many arguments + var ints int + var floats int + var stack int + for i := 0; i < ty.NumIn(); i++ { + arg := ty.In(i) + switch arg.Kind() { + case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice, + reflect.Func, reflect.Bool: + if ints < numOfIntegerRegisters() { + ints++ + } else { + stack++ + } + case reflect.Float32, reflect.Float64: + if floats < numOfFloats { + floats++ + } else { + stack++ + } + case reflect.Struct: + if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") { + panic("purego: struct arguments are only supported on darwin amd64 & arm64") + } + if arg.Size() == 0 { + continue + } + addInt := func(u uintptr) { + ints++ + } + addFloat := func(u uintptr) { + floats++ + } + addStack := func(u uintptr) { + stack++ + } + _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil) + default: + panic("purego: unsupported kind " + arg.Kind().String()) + } + } + sizeOfStack := maxArgs - numOfIntegerRegisters() + if stack > sizeOfStack { + panic("purego: too many arguments") + } + } + v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { + if len(args) > 0 { + if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok { + // subtract one from args bc the last argument in args is []interface{} + // which we are currently expanding + tmp := make([]reflect.Value, len(args)-1+len(variadic)) + n := copy(tmp, args[:len(args)-1]) + for i, v := range variadic { + tmp[n+i] = reflect.ValueOf(v) + } + args = tmp + } + } + var sysargs [maxArgs]uintptr + stack := sysargs[numOfIntegerRegisters():] + var floats [numOfFloats]uintptr + var numInts int + var numFloats int + var numStack int + var addStack, addInt, addFloat func(x uintptr) + if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { + // Windows arm64 uses the same calling convention as macOS and Linux + addStack = func(x uintptr) { + stack[numStack] = x + numStack++ + } + addInt = func(x uintptr) { + if numInts >= numOfIntegerRegisters() { + addStack(x) + } else { + sysargs[numInts] = x + numInts++ + } + } + addFloat = func(x uintptr) { + if numFloats < len(floats) { + floats[numFloats] = x + numFloats++ + } else { + addStack(x) + } + } + } else { + // On Windows amd64 the arguments are passed in the numbered registered. + // So the first int is in the first integer register and the first float + // is in the second floating register if there is already a first int. + // This is in contrast to how macOS and Linux pass arguments which + // tries to use as many registers as possible in the calling convention. + addStack = func(x uintptr) { + sysargs[numStack] = x + numStack++ + } + addInt = addStack + addFloat = addStack + } + + var keepAlive []interface{} + defer func() { + runtime.KeepAlive(keepAlive) + runtime.KeepAlive(args) + }() + for _, v := range args { + switch v.Kind() { + case reflect.String: + ptr := strings.CString(v.String()) + keepAlive = append(keepAlive, ptr) + addInt(uintptr(unsafe.Pointer(ptr))) + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + addInt(uintptr(v.Uint())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + addInt(uintptr(v.Int())) + case reflect.Ptr, reflect.UnsafePointer, reflect.Slice: + // There is no need to keepAlive this pointer separately because it is kept alive in the args variable + addInt(v.Pointer()) + case reflect.Func: + addInt(NewCallback(v.Interface())) + case reflect.Bool: + if v.Bool() { + addInt(1) + } else { + addInt(0) + } + case reflect.Float32: + addFloat(uintptr(math.Float32bits(float32(v.Float())))) + case reflect.Float64: + addFloat(uintptr(math.Float64bits(v.Float()))) + case reflect.Struct: + keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive) + default: + panic("purego: unsupported kind: " + v.Kind().String()) + } + } + // TODO: support structs + var r1, r2 uintptr + if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { + // Use the normal arm64 calling convention even on Windows + syscall := syscall15Args{ + cfn, + sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], + sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], + sysargs[12], sysargs[13], sysargs[14], + floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], + 0, 0, 0, + } + runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall)) + r1, r2 = syscall.r1, syscall.r2 + } else { + // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats + r1, r2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], + sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], + sysargs[12], sysargs[13], sysargs[14]) + } + if ty.NumOut() == 0 { + return nil + } + outType := ty.Out(0) + v := reflect.New(outType).Elem() + switch outType.Kind() { + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v.SetUint(uint64(r1)) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v.SetInt(int64(r1)) + case reflect.Bool: + v.SetBool(byte(r1) != 0) + case reflect.UnsafePointer: + // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer + v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1))) + case reflect.Ptr: + // It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem() + v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem() + case reflect.Func: + // wrap this C function in a nicely typed Go function + v = reflect.New(outType) + RegisterFunc(v.Interface(), r1) + case reflect.String: + v.SetString(strings.GoString(r1)) + case reflect.Float32: + // NOTE: r2 is only the floating return value on 64bit platforms. + // On 32bit platforms r2 is the upper part of a 64bit return. + v.SetFloat(float64(math.Float32frombits(uint32(r2)))) + case reflect.Float64: + // NOTE: r2 is only the floating return value on 64bit platforms. + // On 32bit platforms r2 is the upper part of a 64bit return. + v.SetFloat(math.Float64frombits(uint64(r2))) + default: + panic("purego: unsupported return kind: " + outType.Kind().String()) + } + return []reflect.Value{v} + }) + fn.Set(v) +} + +func roundUpTo8(val uintptr) uintptr { + return (val + 7) &^ 7 +} + +func numOfIntegerRegisters() int { + switch runtime.GOARCH { + case "arm64": + return 8 + case "amd64": + return 6 + // TODO: figure out why 386 tests are not working + /*case "386": + return 0 + case "arm": + return 4*/ + default: + panic("purego: unknown GOARCH (" + runtime.GOARCH + ")") + } +} diff --git a/vendor/github.com/ebitengine/purego/go_runtime.go b/vendor/github.com/ebitengine/purego/go_runtime.go new file mode 100644 index 0000000..9593dac --- /dev/null +++ b/vendor/github.com/ebitengine/purego/go_runtime.go @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux || windows + +package purego + +import ( + "unsafe" +) + +//go:linkname runtime_cgocall runtime.cgocall +func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go + +//go:linkname runtime_noescape runtime.noescape +//go:noescape +func runtime_noescape(p unsafe.Pointer) unsafe.Pointer // from runtime/stubs.go diff --git a/vendor/github.com/ebitengine/purego/internal/cgo/dlfcn_cgo_unix.go b/vendor/github.com/ebitengine/purego/internal/cgo/dlfcn_cgo_unix.go new file mode 100644 index 0000000..7e4dd04 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/cgo/dlfcn_cgo_unix.go @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +//go:build freebsd || linux + +package cgo + +/* + #cgo LDFLAGS: -ldl + +#include +*/ +import "C" + +// all that is needed is to assign each dl function because then its +// symbol will then be made available to the linker and linked to inside dlfcn.go +var ( + _ = C.dlopen + _ = C.dlsym + _ = C.dlerror + _ = C.dlclose +) diff --git a/vendor/github.com/ebitengine/purego/internal/cgo/empty.go b/vendor/github.com/ebitengine/purego/internal/cgo/empty.go new file mode 100644 index 0000000..1d7cffe --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/cgo/empty.go @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +package cgo + +// Empty so that importing this package doesn't cause issue for certain platforms. diff --git a/vendor/github.com/ebitengine/purego/internal/cgo/syscall_cgo_unix.go b/vendor/github.com/ebitengine/purego/internal/cgo/syscall_cgo_unix.go new file mode 100644 index 0000000..029a259 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/cgo/syscall_cgo_unix.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build freebsd || (linux && !(arm64 || amd64)) + +package cgo + +// this file is placed inside internal/cgo and not package purego +// because Cgo and assembly files can't be in the same package. + +/* + #cgo LDFLAGS: -ldl + +#include +#include +#include +#include + +typedef struct syscall15Args { + uintptr_t fn; + uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15; + uintptr_t f1, f2, f3, f4, f5, f6, f7, f8; + uintptr_t r1, r2, err; +} syscall15Args; + +void syscall15(struct syscall15Args *args) { + assert((args->f1|args->f2|args->f3|args->f4|args->f5|args->f6|args->f7|args->f8) == 0); + uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, + uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12, + uintptr_t a13, uintptr_t a14, uintptr_t a15); + *(void**)(&func_name) = (void*)(args->fn); + uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9, + args->a10,args->a11,args->a12,args->a13,args->a14,args->a15); + args->r1 = r1; + args->err = errno; +} + +*/ +import "C" +import "unsafe" + +// assign purego.syscall15XABI0 to the C version of this function. +var Syscall15XABI0 = unsafe.Pointer(C.syscall15) + +//go:nosplit +func Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { + args := C.syscall15Args{ + C.uintptr_t(fn), C.uintptr_t(a1), C.uintptr_t(a2), C.uintptr_t(a3), + C.uintptr_t(a4), C.uintptr_t(a5), C.uintptr_t(a6), + C.uintptr_t(a7), C.uintptr_t(a8), C.uintptr_t(a9), C.uintptr_t(a10), C.uintptr_t(a11), C.uintptr_t(a12), + C.uintptr_t(a13), C.uintptr_t(a14), C.uintptr_t(a15), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + } + C.syscall15(&args) + return uintptr(args.r1), uintptr(args.r2), uintptr(args.err) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_amd64.h b/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_amd64.h new file mode 100644 index 0000000..9949435 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_amd64.h @@ -0,0 +1,99 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Macros for transitioning from the host ABI to Go ABI0. +// +// These save the frame pointer, so in general, functions that use +// these should have zero frame size to suppress the automatic frame +// pointer, though it's harmless to not do this. + +#ifdef GOOS_windows + +// REGS_HOST_TO_ABI0_STACK is the stack bytes used by +// PUSH_REGS_HOST_TO_ABI0. +#define REGS_HOST_TO_ABI0_STACK (28*8 + 8) + +// PUSH_REGS_HOST_TO_ABI0 prepares for transitioning from +// the host ABI to Go ABI0 code. It saves all registers that are +// callee-save in the host ABI and caller-save in Go ABI0 and prepares +// for entry to Go. +// +// Save DI SI BP BX R12 R13 R14 R15 X6-X15 registers and the DF flag. +// Clear the DF flag for the Go ABI. +// MXCSR matches the Go ABI, so we don't have to set that, +// and Go doesn't modify it, so we don't have to save it. +#define PUSH_REGS_HOST_TO_ABI0() \ + PUSHFQ \ + CLD \ + ADJSP $(REGS_HOST_TO_ABI0_STACK - 8) \ + MOVQ DI, (0*0)(SP) \ + MOVQ SI, (1*8)(SP) \ + MOVQ BP, (2*8)(SP) \ + MOVQ BX, (3*8)(SP) \ + MOVQ R12, (4*8)(SP) \ + MOVQ R13, (5*8)(SP) \ + MOVQ R14, (6*8)(SP) \ + MOVQ R15, (7*8)(SP) \ + MOVUPS X6, (8*8)(SP) \ + MOVUPS X7, (10*8)(SP) \ + MOVUPS X8, (12*8)(SP) \ + MOVUPS X9, (14*8)(SP) \ + MOVUPS X10, (16*8)(SP) \ + MOVUPS X11, (18*8)(SP) \ + MOVUPS X12, (20*8)(SP) \ + MOVUPS X13, (22*8)(SP) \ + MOVUPS X14, (24*8)(SP) \ + MOVUPS X15, (26*8)(SP) + +#define POP_REGS_HOST_TO_ABI0() \ + MOVQ (0*0)(SP), DI \ + MOVQ (1*8)(SP), SI \ + MOVQ (2*8)(SP), BP \ + MOVQ (3*8)(SP), BX \ + MOVQ (4*8)(SP), R12 \ + MOVQ (5*8)(SP), R13 \ + MOVQ (6*8)(SP), R14 \ + MOVQ (7*8)(SP), R15 \ + MOVUPS (8*8)(SP), X6 \ + MOVUPS (10*8)(SP), X7 \ + MOVUPS (12*8)(SP), X8 \ + MOVUPS (14*8)(SP), X9 \ + MOVUPS (16*8)(SP), X10 \ + MOVUPS (18*8)(SP), X11 \ + MOVUPS (20*8)(SP), X12 \ + MOVUPS (22*8)(SP), X13 \ + MOVUPS (24*8)(SP), X14 \ + MOVUPS (26*8)(SP), X15 \ + ADJSP $-(REGS_HOST_TO_ABI0_STACK - 8) \ + POPFQ + +#else +// SysV ABI + +#define REGS_HOST_TO_ABI0_STACK (6*8) + +// SysV MXCSR matches the Go ABI, so we don't have to set that, +// and Go doesn't modify it, so we don't have to save it. +// Both SysV and Go require DF to be cleared, so that's already clear. +// The SysV and Go frame pointer conventions are compatible. +#define PUSH_REGS_HOST_TO_ABI0() \ + ADJSP $(REGS_HOST_TO_ABI0_STACK) \ + MOVQ BP, (5*8)(SP) \ + LEAQ (5*8)(SP), BP \ + MOVQ BX, (0*8)(SP) \ + MOVQ R12, (1*8)(SP) \ + MOVQ R13, (2*8)(SP) \ + MOVQ R14, (3*8)(SP) \ + MOVQ R15, (4*8)(SP) + +#define POP_REGS_HOST_TO_ABI0() \ + MOVQ (0*8)(SP), BX \ + MOVQ (1*8)(SP), R12 \ + MOVQ (2*8)(SP), R13 \ + MOVQ (3*8)(SP), R14 \ + MOVQ (4*8)(SP), R15 \ + MOVQ (5*8)(SP), BP \ + ADJSP $-(REGS_HOST_TO_ABI0_STACK) + +#endif diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_arm64.h b/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_arm64.h new file mode 100644 index 0000000..5d5061e --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_arm64.h @@ -0,0 +1,39 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Macros for transitioning from the host ABI to Go ABI0. +// +// These macros save and restore the callee-saved registers +// from the stack, but they don't adjust stack pointer, so +// the user should prepare stack space in advance. +// SAVE_R19_TO_R28(offset) saves R19 ~ R28 to the stack space +// of ((offset)+0*8)(RSP) ~ ((offset)+9*8)(RSP). +// +// SAVE_F8_TO_F15(offset) saves F8 ~ F15 to the stack space +// of ((offset)+0*8)(RSP) ~ ((offset)+7*8)(RSP). +// +// R29 is not saved because Go will save and restore it. + +#define SAVE_R19_TO_R28(offset) \ + STP (R19, R20), ((offset)+0*8)(RSP) \ + STP (R21, R22), ((offset)+2*8)(RSP) \ + STP (R23, R24), ((offset)+4*8)(RSP) \ + STP (R25, R26), ((offset)+6*8)(RSP) \ + STP (R27, g), ((offset)+8*8)(RSP) +#define RESTORE_R19_TO_R28(offset) \ + LDP ((offset)+0*8)(RSP), (R19, R20) \ + LDP ((offset)+2*8)(RSP), (R21, R22) \ + LDP ((offset)+4*8)(RSP), (R23, R24) \ + LDP ((offset)+6*8)(RSP), (R25, R26) \ + LDP ((offset)+8*8)(RSP), (R27, g) /* R28 */ +#define SAVE_F8_TO_F15(offset) \ + FSTPD (F8, F9), ((offset)+0*8)(RSP) \ + FSTPD (F10, F11), ((offset)+2*8)(RSP) \ + FSTPD (F12, F13), ((offset)+4*8)(RSP) \ + FSTPD (F14, F15), ((offset)+6*8)(RSP) +#define RESTORE_F8_TO_F15(offset) \ + FLDPD ((offset)+0*8)(RSP), (F8, F9) \ + FLDPD ((offset)+2*8)(RSP), (F10, F11) \ + FLDPD ((offset)+4*8)(RSP), (F12, F13) \ + FLDPD ((offset)+6*8)(RSP), (F14, F15) diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_amd64.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_amd64.s new file mode 100644 index 0000000..2b7eb57 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_amd64.s @@ -0,0 +1,39 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" +#include "abi_amd64.h" + +// Called by C code generated by cmd/cgo. +// func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) +// Saves C callee-saved registers and calls cgocallback with three arguments. +// fn is the PC of a func(a unsafe.Pointer) function. +// This signature is known to SWIG, so we can't change it. +TEXT crosscall2(SB), NOSPLIT, $0-0 + PUSH_REGS_HOST_TO_ABI0() + + // Make room for arguments to cgocallback. + ADJSP $0x18 + +#ifndef GOOS_windows + MOVQ DI, 0x0(SP) // fn + MOVQ SI, 0x8(SP) // arg + + // Skip n in DX. + MOVQ CX, 0x10(SP) // ctxt + +#else + MOVQ CX, 0x0(SP) // fn + MOVQ DX, 0x8(SP) // arg + + // Skip n in R8. + MOVQ R9, 0x10(SP) // ctxt + +#endif + + CALL runtime·cgocallback(SB) + + ADJSP $-0x18 + POP_REGS_HOST_TO_ABI0() + RET diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm64.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm64.s new file mode 100644 index 0000000..50e5261 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm64.s @@ -0,0 +1,36 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" +#include "abi_arm64.h" + +// Called by C code generated by cmd/cgo. +// func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) +// Saves C callee-saved registers and calls cgocallback with three arguments. +// fn is the PC of a func(a unsafe.Pointer) function. +TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 +/* + * We still need to save all callee save register as before, and then + * push 3 args for fn (R0, R1, R3), skipping R2. + * Also note that at procedure entry in gc world, 8(RSP) will be the + * first arg. + */ + SUB $(8*24), RSP + STP (R0, R1), (8*1)(RSP) + MOVD R3, (8*3)(RSP) + + SAVE_R19_TO_R28(8*4) + SAVE_F8_TO_F15(8*14) + STP (R29, R30), (8*22)(RSP) + + // Initialize Go ABI environment + BL runtime·load_g(SB) + BL runtime·cgocallback(SB) + + RESTORE_R19_TO_R28(8*4) + RESTORE_F8_TO_F15(8*14) + LDP (8*22)(RSP), (R29, R30) + + ADD $(8*24), RSP + RET diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/callbacks.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/callbacks.go new file mode 100644 index 0000000..f6a079a --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/callbacks.go @@ -0,0 +1,93 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin || freebsd || linux + +package fakecgo + +import ( + _ "unsafe" +) + +// TODO: decide if we need _runtime_cgo_panic_internal + +//go:linkname x_cgo_init_trampoline x_cgo_init_trampoline +//go:linkname _cgo_init _cgo_init +var x_cgo_init_trampoline byte +var _cgo_init = &x_cgo_init_trampoline + +// Creates a new system thread without updating any Go state. +// +// This method is invoked during shared library loading to create a new OS +// thread to perform the runtime initialization. This method is similar to +// _cgo_sys_thread_start except that it doesn't update any Go state. + +//go:linkname x_cgo_thread_start_trampoline x_cgo_thread_start_trampoline +//go:linkname _cgo_thread_start _cgo_thread_start +var x_cgo_thread_start_trampoline byte +var _cgo_thread_start = &x_cgo_thread_start_trampoline + +// Notifies that the runtime has been initialized. +// +// We currently block at every CGO entry point (via _cgo_wait_runtime_init_done) +// to ensure that the runtime has been initialized before the CGO call is +// executed. This is necessary for shared libraries where we kickoff runtime +// initialization in a separate thread and return without waiting for this +// thread to complete the init. + +//go:linkname x_cgo_notify_runtime_init_done_trampoline x_cgo_notify_runtime_init_done_trampoline +//go:linkname _cgo_notify_runtime_init_done _cgo_notify_runtime_init_done +var x_cgo_notify_runtime_init_done_trampoline byte +var _cgo_notify_runtime_init_done = &x_cgo_notify_runtime_init_done_trampoline + +// Indicates whether a dummy thread key has been created or not. +// +// When calling go exported function from C, we register a destructor +// callback, for a dummy thread key, by using pthread_key_create. + +//go:linkname _cgo_pthread_key_created _cgo_pthread_key_created +var x_cgo_pthread_key_created uintptr +var _cgo_pthread_key_created = &x_cgo_pthread_key_created + +// Set the x_crosscall2_ptr C function pointer variable point to crosscall2. +// It's for the runtime package to call at init time. +func set_crosscall2() { + // nothing needs to be done here for fakecgo + // because it's possible to just call cgocallback directly +} + +//go:linkname _set_crosscall2 runtime.set_crosscall2 +var _set_crosscall2 = set_crosscall2 + +// Store the g into the thread-specific value. +// So that pthread_key_destructor will dropm when the thread is exiting. + +//go:linkname x_cgo_bindm_trampoline x_cgo_bindm_trampoline +//go:linkname _cgo_bindm _cgo_bindm +var x_cgo_bindm_trampoline byte +var _cgo_bindm = &x_cgo_bindm_trampoline + +// TODO: decide if we need x_cgo_set_context_function +// TODO: decide if we need _cgo_yield + +var ( + // In Go 1.20 the race detector was rewritten to pure Go + // on darwin. This means that when CGO_ENABLED=0 is set + // fakecgo is built with race detector code. This is not + // good since this code is pretending to be C. The go:norace + // pragma is not enough, since it only applies to the native + // ABIInternal function. The ABIO wrapper (which is necessary, + // since all references to text symbols from assembly will use it) + // does not inherit the go:norace pragma, so it will still be + // instrumented by the race detector. + // + // To circumvent this issue, using closure calls in the + // assembly, which forces the compiler to use the ABIInternal + // native implementation (which has go:norace) instead. + threadentry_call = threadentry + x_cgo_init_call = x_cgo_init + x_cgo_setenv_call = x_cgo_setenv + x_cgo_unsetenv_call = x_cgo_unsetenv + x_cgo_thread_start_call = x_cgo_thread_start +) diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/doc.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/doc.go new file mode 100644 index 0000000..efbe421 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/doc.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd + +// Package fakecgo implements the Cgo runtime (runtime/cgo) entirely in Go. +// This allows code that calls into C to function properly when CGO_ENABLED=0. +// +// # Goals +// +// fakecgo attempts to replicate the same naming structure as in the runtime. +// For example, functions that have the prefix "gcc_*" are named "go_*". +// This makes it easier to port other GOOSs and GOARCHs as well as to keep +// it in sync with runtime/cgo. +// +// # Support +// +// Currently, fakecgo only supports macOS on amd64 & arm64. It also cannot +// be used with -buildmode=c-archive because that requires special initialization +// that fakecgo does not implement at the moment. +// +// # Usage +// +// Using fakecgo is easy just import _ "github.com/ebitengine/purego" and then +// set the environment variable CGO_ENABLED=0. +// The recommended usage for fakecgo is to prefer using runtime/cgo if possible +// but if cross-compiling or fast build times are important fakecgo is available. +// Purego will pick which ever Cgo runtime is available and prefer the one that +// comes with Go (runtime/cgo). +package fakecgo + +//go:generate go run gen.go +//go:generate gofmt -s -w symbols.go diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/freebsd.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/freebsd.go new file mode 100644 index 0000000..bb73a70 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/freebsd.go @@ -0,0 +1,27 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build freebsd && !cgo + +package fakecgo + +import _ "unsafe" // for go:linkname + +// Supply environ and __progname, because we don't +// link against the standard FreeBSD crt0.o and the +// libc dynamic library needs them. + +// Note: when building with cross-compiling or CGO_ENABLED=0, add +// the following argument to `go` so that these symbols are defined by +// making fakecgo the Cgo. +// -gcflags="github.com/ebitengine/purego/internal/fakecgo=-std" + +//go:linkname _environ environ +//go:linkname _progname __progname + +//go:cgo_export_dynamic environ +//go:cgo_export_dynamic __progname + +var _environ uintptr +var _progname uintptr diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_amd64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_amd64.go new file mode 100644 index 0000000..fb3a3f7 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_amd64.go @@ -0,0 +1,71 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fakecgo + +import "unsafe" + +//go:nosplit +//go:norace +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + size = pthread_get_stacksize_np(pthread_self()) + pthread_attr_init(&attr) + pthread_attr_setstacksize(&attr, size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +//go:norace +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +//go:nosplit +//go:norace +func x_cgo_init(g *G, setg uintptr) { + var size size_t + + setg_func = setg + + size = pthread_get_stacksize_np(pthread_self()) + g.stacklo = uintptr(unsafe.Add(unsafe.Pointer(&size), -size+4096)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_arm64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_arm64.go new file mode 100644 index 0000000..b000b3f --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_arm64.go @@ -0,0 +1,86 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fakecgo + +import "unsafe" + +//go:nosplit +//go:norace +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + size = pthread_get_stacksize_np(pthread_self()) + pthread_attr_init(&attr) + pthread_attr_setstacksize(&attr, size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +//go:norace +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + // TODO: support ios + //#if TARGET_OS_IPHONE + // darwin_arm_init_thread_exception_port(); + //#endif + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +// x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) +// This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us +// Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup +// This function can't be go:systemstack since go is not in a state where the systemcheck would work. +// +//go:nosplit +//go:norace +func x_cgo_init(g *G, setg uintptr) { + var size size_t + + setg_func = setg + size = pthread_get_stacksize_np(pthread_self()) + g.stacklo = uintptr(unsafe.Add(unsafe.Pointer(&size), -size+4096)) + + //TODO: support ios + //#if TARGET_OS_IPHONE + // darwin_arm_init_mach_exception_handler(); + // darwin_arm_init_thread_exception_port(); + // init_working_dir(); + //#endif +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_amd64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_amd64.go new file mode 100644 index 0000000..9aa57ef --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_amd64.go @@ -0,0 +1,93 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fakecgo + +import "unsafe" + +//go:nosplit +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + pthread_attr_init(&attr) + pthread_attr_getstacksize(&attr, &size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +//go:nosplit +func x_cgo_init(g *G, setg uintptr) { + var size size_t + var attr *pthread_attr_t + + /* The memory sanitizer distributed with versions of clang + before 3.8 has a bug: if you call mmap before malloc, mmap + may return an address that is later overwritten by the msan + library. Avoid this problem by forcing a call to malloc + here, before we ever call malloc. + + This is only required for the memory sanitizer, so it's + unfortunate that we always run it. It should be possible + to remove this when we no longer care about versions of + clang before 3.8. The test for this is + misc/cgo/testsanitizers. + + GCC works hard to eliminate a seemingly unnecessary call to + malloc, so we actually use the memory we allocate. */ + + setg_func = setg + attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) + if attr == nil { + println("fakecgo: malloc failed") + abort() + } + pthread_attr_init(attr) + pthread_attr_getstacksize(attr, &size) + // runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))` + // but this should be OK since we are taking the address of the first variable in this function. + g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 + pthread_attr_destroy(attr) + free(unsafe.Pointer(attr)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_arm64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_arm64.go new file mode 100644 index 0000000..1db518e --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_arm64.go @@ -0,0 +1,96 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fakecgo + +import "unsafe" + +//go:nosplit +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + pthread_attr_init(&attr) + pthread_attr_getstacksize(&attr, &size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +// x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) +// This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us +// Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup +// This function can't be go:systemstack since go is not in a state where the systemcheck would work. +// +//go:nosplit +func x_cgo_init(g *G, setg uintptr) { + var size size_t + var attr *pthread_attr_t + + /* The memory sanitizer distributed with versions of clang + before 3.8 has a bug: if you call mmap before malloc, mmap + may return an address that is later overwritten by the msan + library. Avoid this problem by forcing a call to malloc + here, before we ever call malloc. + + This is only required for the memory sanitizer, so it's + unfortunate that we always run it. It should be possible + to remove this when we no longer care about versions of + clang before 3.8. The test for this is + misc/cgo/testsanitizers. + + GCC works hard to eliminate a seemingly unnecessary call to + malloc, so we actually use the memory we allocate. */ + + setg_func = setg + attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) + if attr == nil { + println("fakecgo: malloc failed") + abort() + } + pthread_attr_init(attr) + pthread_attr_getstacksize(attr, &size) + g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 + pthread_attr_destroy(attr) + free(unsafe.Pointer(attr)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go new file mode 100644 index 0000000..71da112 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +package fakecgo + +import ( + "syscall" + "unsafe" +) + +var ( + pthread_g pthread_key_t + + runtime_init_cond = PTHREAD_COND_INITIALIZER + runtime_init_mu = PTHREAD_MUTEX_INITIALIZER + runtime_init_done int +) + +//go:nosplit +func x_cgo_notify_runtime_init_done() { + pthread_mutex_lock(&runtime_init_mu) + runtime_init_done = 1 + pthread_cond_broadcast(&runtime_init_cond) + pthread_mutex_unlock(&runtime_init_mu) +} + +// Store the g into a thread-specific value associated with the pthread key pthread_g. +// And pthread_key_destructor will dropm when the thread is exiting. +func x_cgo_bindm(g unsafe.Pointer) { + // We assume this will always succeed, otherwise, there might be extra M leaking, + // when a C thread exits after a cgo call. + // We only invoke this function once per thread in runtime.needAndBindM, + // and the next calls just reuse the bound m. + pthread_setspecific(pthread_g, g) +} + +// _cgo_try_pthread_create retries pthread_create if it fails with +// EAGAIN. +// +//go:nosplit +//go:norace +func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe.Pointer, arg *ThreadStart) int { + var ts syscall.Timespec + // tries needs to be the same type as syscall.Timespec.Nsec + // but the fields are int32 on 32bit and int64 on 64bit. + // tries is assigned to syscall.Timespec.Nsec in order to match its type. + tries := ts.Nsec + var err int + + for tries = 0; tries < 20; tries++ { + err = int(pthread_create(thread, attr, pfn, unsafe.Pointer(arg))) + if err == 0 { + pthread_detach(*thread) + return 0 + } + if err != int(syscall.EAGAIN) { + return err + } + ts.Sec = 0 + ts.Nsec = (tries + 1) * 1000 * 1000 // Milliseconds. + nanosleep(&ts, nil) + } + return int(syscall.EAGAIN) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_amd64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_amd64.go new file mode 100644 index 0000000..9aa57ef --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_amd64.go @@ -0,0 +1,93 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fakecgo + +import "unsafe" + +//go:nosplit +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + pthread_attr_init(&attr) + pthread_attr_getstacksize(&attr, &size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +//go:nosplit +func x_cgo_init(g *G, setg uintptr) { + var size size_t + var attr *pthread_attr_t + + /* The memory sanitizer distributed with versions of clang + before 3.8 has a bug: if you call mmap before malloc, mmap + may return an address that is later overwritten by the msan + library. Avoid this problem by forcing a call to malloc + here, before we ever call malloc. + + This is only required for the memory sanitizer, so it's + unfortunate that we always run it. It should be possible + to remove this when we no longer care about versions of + clang before 3.8. The test for this is + misc/cgo/testsanitizers. + + GCC works hard to eliminate a seemingly unnecessary call to + malloc, so we actually use the memory we allocate. */ + + setg_func = setg + attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) + if attr == nil { + println("fakecgo: malloc failed") + abort() + } + pthread_attr_init(attr) + pthread_attr_getstacksize(attr, &size) + // runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))` + // but this should be OK since we are taking the address of the first variable in this function. + g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 + pthread_attr_destroy(attr) + free(unsafe.Pointer(attr)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_arm64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_arm64.go new file mode 100644 index 0000000..1db518e --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_arm64.go @@ -0,0 +1,96 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fakecgo + +import "unsafe" + +//go:nosplit +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + pthread_attr_init(&attr) + pthread_attr_getstacksize(&attr, &size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +// x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) +// This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us +// Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup +// This function can't be go:systemstack since go is not in a state where the systemcheck would work. +// +//go:nosplit +func x_cgo_init(g *G, setg uintptr) { + var size size_t + var attr *pthread_attr_t + + /* The memory sanitizer distributed with versions of clang + before 3.8 has a bug: if you call mmap before malloc, mmap + may return an address that is later overwritten by the msan + library. Avoid this problem by forcing a call to malloc + here, before we ever call malloc. + + This is only required for the memory sanitizer, so it's + unfortunate that we always run it. It should be possible + to remove this when we no longer care about versions of + clang before 3.8. The test for this is + misc/cgo/testsanitizers. + + GCC works hard to eliminate a seemingly unnecessary call to + malloc, so we actually use the memory we allocate. */ + + setg_func = setg + attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) + if attr == nil { + println("fakecgo: malloc failed") + abort() + } + pthread_attr_init(attr) + pthread_attr_getstacksize(attr, &size) + g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 + pthread_attr_destroy(attr) + free(unsafe.Pointer(attr)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_setenv.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_setenv.go new file mode 100644 index 0000000..818372e --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_setenv.go @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +package fakecgo + +//go:nosplit +//go:norace +func x_cgo_setenv(arg *[2]*byte) { + setenv(arg[0], arg[1], 1) +} + +//go:nosplit +//go:norace +func x_cgo_unsetenv(arg *[1]*byte) { + unsetenv(arg[0]) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_util.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_util.go new file mode 100644 index 0000000..7a43b42 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_util.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +package fakecgo + +import "unsafe" + +// _cgo_thread_start is split into three parts in cgo since only one part is system dependent (keep it here for easier handling) + +// _cgo_thread_start(ThreadStart *arg) (runtime/cgo/gcc_util.c) +// This get's called instead of the go code for creating new threads +// -> pthread_* stuff is used, so threads are setup correctly for C +// If this is missing, TLS is only setup correctly on thread 1! +// This function should be go:systemstack instead of go:nosplit (but that requires runtime) +// +//go:nosplit +//go:norace +func x_cgo_thread_start(arg *ThreadStart) { + var ts *ThreadStart + // Make our own copy that can persist after we return. + // _cgo_tsan_acquire(); + ts = (*ThreadStart)(malloc(unsafe.Sizeof(*ts))) + // _cgo_tsan_release(); + if ts == nil { + println("fakecgo: out of memory in thread_start") + abort() + } + // *ts = *arg would cause a writebarrier so use memmove instead + memmove(unsafe.Pointer(ts), unsafe.Pointer(arg), unsafe.Sizeof(*ts)) + _cgo_sys_thread_start(ts) // OS-dependent half +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/iscgo.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/iscgo.go new file mode 100644 index 0000000..ce17d18 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/iscgo.go @@ -0,0 +1,19 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin || freebsd || linux + +// The runtime package contains an uninitialized definition +// for runtime·iscgo. Override it to tell the runtime we're here. +// There are various function pointers that should be set too, +// but those depend on dynamic linker magic to get initialized +// correctly, and sometimes they break. This variable is a +// backup: it depends only on old C style static linking rules. + +package fakecgo + +import _ "unsafe" // for go:linkname + +//go:linkname _iscgo runtime.iscgo +var _iscgo bool = true diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo.go new file mode 100644 index 0000000..c12d403 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +package fakecgo + +type ( + size_t uintptr + sigset_t [128]byte + pthread_attr_t [64]byte + pthread_t int + pthread_key_t uint64 +) + +// for pthread_sigmask: + +type sighow int32 + +const ( + SIG_BLOCK sighow = 0 + SIG_UNBLOCK sighow = 1 + SIG_SETMASK sighow = 2 +) + +type G struct { + stacklo uintptr + stackhi uintptr +} + +type ThreadStart struct { + g *G + tls *uintptr + fn uintptr +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_darwin.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_darwin.go new file mode 100644 index 0000000..03c9171 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_darwin.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package fakecgo + +type ( + pthread_mutex_t struct { + sig int64 + opaque [56]byte + } + pthread_cond_t struct { + sig int64 + opaque [40]byte + } +) + +var ( + PTHREAD_COND_INITIALIZER = pthread_cond_t{sig: 0x3CB0B1BB} + PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{sig: 0x32AAABA7} +) diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_freebsd.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_freebsd.go new file mode 100644 index 0000000..baf03fa --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_freebsd.go @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package fakecgo + +type ( + pthread_cond_t uintptr + pthread_mutex_t uintptr +) + +var ( + PTHREAD_COND_INITIALIZER = pthread_cond_t(0) + PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t(0) +) diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_linux.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_linux.go new file mode 100644 index 0000000..93aa5b2 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_linux.go @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package fakecgo + +type ( + pthread_cond_t [48]byte + pthread_mutex_t [48]byte +) + +var ( + PTHREAD_COND_INITIALIZER = pthread_cond_t{} + PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{} +) diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/setenv.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/setenv.go new file mode 100644 index 0000000..b69d4b3 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/setenv.go @@ -0,0 +1,19 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin || freebsd || linux + +package fakecgo + +import _ "unsafe" // for go:linkname + +//go:linkname x_cgo_setenv_trampoline x_cgo_setenv_trampoline +//go:linkname _cgo_setenv runtime._cgo_setenv +var x_cgo_setenv_trampoline byte +var _cgo_setenv = &x_cgo_setenv_trampoline + +//go:linkname x_cgo_unsetenv_trampoline x_cgo_unsetenv_trampoline +//go:linkname _cgo_unsetenv runtime._cgo_unsetenv +var x_cgo_unsetenv_trampoline byte +var _cgo_unsetenv = &x_cgo_unsetenv_trampoline diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go new file mode 100644 index 0000000..d7401f7 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go @@ -0,0 +1,184 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +package fakecgo + +import ( + "syscall" + "unsafe" +) + +// setg_trampoline calls setg with the G provided +func setg_trampoline(setg uintptr, G uintptr) + +//go:linkname memmove runtime.memmove +func memmove(to, from unsafe.Pointer, n uintptr) + +// call5 takes fn the C function and 5 arguments and calls the function with those arguments +func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr + +func malloc(size uintptr) unsafe.Pointer { + ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0) + // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer + return *(*unsafe.Pointer)(unsafe.Pointer(&ret)) +} + +func free(ptr unsafe.Pointer) { + call5(freeABI0, uintptr(ptr), 0, 0, 0, 0) +} + +func setenv(name *byte, value *byte, overwrite int32) int32 { + return int32(call5(setenvABI0, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite), 0, 0)) +} + +func unsetenv(name *byte) int32 { + return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0)) +} + +func sigfillset(set *sigset_t) int32 { + return int32(call5(sigfillsetABI0, uintptr(unsafe.Pointer(set)), 0, 0, 0, 0)) +} + +func nanosleep(ts *syscall.Timespec, rem *syscall.Timespec) int32 { + return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0)) +} + +func abort() { + call5(abortABI0, 0, 0, 0, 0, 0) +} + +func pthread_attr_init(attr *pthread_attr_t) int32 { + return int32(call5(pthread_attr_initABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) +} + +func pthread_create(thread *pthread_t, attr *pthread_attr_t, start unsafe.Pointer, arg unsafe.Pointer) int32 { + return int32(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg), 0)) +} + +func pthread_detach(thread pthread_t) int32 { + return int32(call5(pthread_detachABI0, uintptr(thread), 0, 0, 0, 0)) +} + +func pthread_sigmask(how sighow, ign *sigset_t, oset *sigset_t) int32 { + return int32(call5(pthread_sigmaskABI0, uintptr(how), uintptr(unsafe.Pointer(ign)), uintptr(unsafe.Pointer(oset)), 0, 0)) +} + +func pthread_self() pthread_t { + return pthread_t(call5(pthread_selfABI0, 0, 0, 0, 0, 0)) +} + +func pthread_get_stacksize_np(thread pthread_t) size_t { + return size_t(call5(pthread_get_stacksize_npABI0, uintptr(thread), 0, 0, 0, 0)) +} + +func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 { + return int32(call5(pthread_attr_getstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)), 0, 0, 0)) +} + +func pthread_attr_setstacksize(attr *pthread_attr_t, size size_t) int32 { + return int32(call5(pthread_attr_setstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(size), 0, 0, 0)) +} + +func pthread_attr_destroy(attr *pthread_attr_t) int32 { + return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) +} + +func pthread_mutex_lock(mutex *pthread_mutex_t) int32 { + return int32(call5(pthread_mutex_lockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) +} + +func pthread_mutex_unlock(mutex *pthread_mutex_t) int32 { + return int32(call5(pthread_mutex_unlockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) +} + +func pthread_cond_broadcast(cond *pthread_cond_t) int32 { + return int32(call5(pthread_cond_broadcastABI0, uintptr(unsafe.Pointer(cond)), 0, 0, 0, 0)) +} + +func pthread_setspecific(key pthread_key_t, value unsafe.Pointer) int32 { + return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0)) +} + +//go:linkname _malloc _malloc +var _malloc uintptr +var mallocABI0 = uintptr(unsafe.Pointer(&_malloc)) + +//go:linkname _free _free +var _free uintptr +var freeABI0 = uintptr(unsafe.Pointer(&_free)) + +//go:linkname _setenv _setenv +var _setenv uintptr +var setenvABI0 = uintptr(unsafe.Pointer(&_setenv)) + +//go:linkname _unsetenv _unsetenv +var _unsetenv uintptr +var unsetenvABI0 = uintptr(unsafe.Pointer(&_unsetenv)) + +//go:linkname _sigfillset _sigfillset +var _sigfillset uintptr +var sigfillsetABI0 = uintptr(unsafe.Pointer(&_sigfillset)) + +//go:linkname _nanosleep _nanosleep +var _nanosleep uintptr +var nanosleepABI0 = uintptr(unsafe.Pointer(&_nanosleep)) + +//go:linkname _abort _abort +var _abort uintptr +var abortABI0 = uintptr(unsafe.Pointer(&_abort)) + +//go:linkname _pthread_attr_init _pthread_attr_init +var _pthread_attr_init uintptr +var pthread_attr_initABI0 = uintptr(unsafe.Pointer(&_pthread_attr_init)) + +//go:linkname _pthread_create _pthread_create +var _pthread_create uintptr +var pthread_createABI0 = uintptr(unsafe.Pointer(&_pthread_create)) + +//go:linkname _pthread_detach _pthread_detach +var _pthread_detach uintptr +var pthread_detachABI0 = uintptr(unsafe.Pointer(&_pthread_detach)) + +//go:linkname _pthread_sigmask _pthread_sigmask +var _pthread_sigmask uintptr +var pthread_sigmaskABI0 = uintptr(unsafe.Pointer(&_pthread_sigmask)) + +//go:linkname _pthread_self _pthread_self +var _pthread_self uintptr +var pthread_selfABI0 = uintptr(unsafe.Pointer(&_pthread_self)) + +//go:linkname _pthread_get_stacksize_np _pthread_get_stacksize_np +var _pthread_get_stacksize_np uintptr +var pthread_get_stacksize_npABI0 = uintptr(unsafe.Pointer(&_pthread_get_stacksize_np)) + +//go:linkname _pthread_attr_getstacksize _pthread_attr_getstacksize +var _pthread_attr_getstacksize uintptr +var pthread_attr_getstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_getstacksize)) + +//go:linkname _pthread_attr_setstacksize _pthread_attr_setstacksize +var _pthread_attr_setstacksize uintptr +var pthread_attr_setstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_setstacksize)) + +//go:linkname _pthread_attr_destroy _pthread_attr_destroy +var _pthread_attr_destroy uintptr +var pthread_attr_destroyABI0 = uintptr(unsafe.Pointer(&_pthread_attr_destroy)) + +//go:linkname _pthread_mutex_lock _pthread_mutex_lock +var _pthread_mutex_lock uintptr +var pthread_mutex_lockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_lock)) + +//go:linkname _pthread_mutex_unlock _pthread_mutex_unlock +var _pthread_mutex_unlock uintptr +var pthread_mutex_unlockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_unlock)) + +//go:linkname _pthread_cond_broadcast _pthread_cond_broadcast +var _pthread_cond_broadcast uintptr +var pthread_cond_broadcastABI0 = uintptr(unsafe.Pointer(&_pthread_cond_broadcast)) + +//go:linkname _pthread_setspecific _pthread_setspecific +var _pthread_setspecific uintptr +var pthread_setspecificABI0 = uintptr(unsafe.Pointer(&_pthread_setspecific)) diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_darwin.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_darwin.go new file mode 100644 index 0000000..7341fec --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_darwin.go @@ -0,0 +1,27 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package fakecgo + +//go:cgo_import_dynamic purego_malloc malloc "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_free free "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_setenv setenv "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_unsetenv unsetenv "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_sigfillset sigfillset "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_nanosleep nanosleep "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_abort abort "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_create pthread_create "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_detach pthread_detach "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_self pthread_self "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "/usr/lib/libSystem.B.dylib" diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_freebsd.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_freebsd.go new file mode 100644 index 0000000..bff096d --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_freebsd.go @@ -0,0 +1,27 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package fakecgo + +//go:cgo_import_dynamic purego_malloc malloc "libc.so.7" +//go:cgo_import_dynamic purego_free free "libc.so.7" +//go:cgo_import_dynamic purego_setenv setenv "libc.so.7" +//go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so.7" +//go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7" +//go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7" +//go:cgo_import_dynamic purego_abort abort "libc.so.7" +//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so" +//go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so" +//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so" +//go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so" +//go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so" +//go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so" +//go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so" +//go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so" +//go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so" +//go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so" +//go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so" +//go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so" +//go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so" diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_linux.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_linux.go new file mode 100644 index 0000000..ee3ab7a --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_linux.go @@ -0,0 +1,27 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package fakecgo + +//go:cgo_import_dynamic purego_malloc malloc "libc.so.6" +//go:cgo_import_dynamic purego_free free "libc.so.6" +//go:cgo_import_dynamic purego_setenv setenv "libc.so.6" +//go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so.6" +//go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.6" +//go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.6" +//go:cgo_import_dynamic purego_abort abort "libc.so.6" +//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so.0" diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s new file mode 100644 index 0000000..24b6206 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || linux || freebsd + +/* +trampoline for emulating required C functions for cgo in go (see cgo.go) +(we convert cdecl calling convention to go and vice-versa) + +Since we're called from go and call into C we can cheat a bit with the calling conventions: + - in go all the registers are caller saved + - in C we have a couple of callee saved registers + +=> we can use BX, R12, R13, R14, R15 instead of the stack + +C Calling convention cdecl used here (we only need integer args): +1. arg: DI +2. arg: SI +3. arg: DX +4. arg: CX +5. arg: R8 +6. arg: R9 +We don't need floats with these functions -> AX=0 +return value will be in AX +*/ +#include "textflag.h" +#include "go_asm.h" + +// these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions. + +TEXT x_cgo_init_trampoline(SB), NOSPLIT, $16 + MOVQ DI, AX + MOVQ SI, BX + MOVQ ·x_cgo_init_call(SB), DX + MOVQ (DX), CX + CALL CX + RET + +TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8 + MOVQ DI, AX + MOVQ ·x_cgo_thread_start_call(SB), DX + MOVQ (DX), CX + CALL CX + RET + +TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8 + MOVQ DI, AX + MOVQ ·x_cgo_setenv_call(SB), DX + MOVQ (DX), CX + CALL CX + RET + +TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8 + MOVQ DI, AX + MOVQ ·x_cgo_unsetenv_call(SB), DX + MOVQ (DX), CX + CALL CX + RET + +TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0 + CALL ·x_cgo_notify_runtime_init_done(SB) + RET + +TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 + CALL ·x_cgo_bindm(SB) + RET + +// func setg_trampoline(setg uintptr, g uintptr) +TEXT ·setg_trampoline(SB), NOSPLIT, $0-16 + MOVQ G+8(FP), DI + MOVQ setg+0(FP), BX + XORL AX, AX + CALL BX + RET + +TEXT threadentry_trampoline(SB), NOSPLIT, $16 + MOVQ DI, AX + MOVQ ·threadentry_call(SB), DX + MOVQ (DX), CX + CALL CX + RET + +TEXT ·call5(SB), NOSPLIT, $0-56 + MOVQ fn+0(FP), BX + MOVQ a1+8(FP), DI + MOVQ a2+16(FP), SI + MOVQ a3+24(FP), DX + MOVQ a4+32(FP), CX + MOVQ a5+40(FP), R8 + + XORL AX, AX // no floats + + PUSHQ BP // save BP + MOVQ SP, BP // save SP inside BP bc BP is callee-saved + SUBQ $16, SP // allocate space for alignment + ANDQ $-16, SP // align on 16 bytes for SSE + + CALL BX + + MOVQ BP, SP // get SP back + POPQ BP // restore BP + + MOVQ AX, ret+48(FP) + RET diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s new file mode 100644 index 0000000..9c80fe2 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +#include "textflag.h" +#include "go_asm.h" + +// these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions. + +TEXT x_cgo_init_trampoline(SB), NOSPLIT, $0-0 + MOVD R0, 8(RSP) + MOVD R1, 16(RSP) + MOVD ·x_cgo_init_call(SB), R26 + MOVD (R26), R2 + CALL (R2) + RET + +TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $0-0 + MOVD R0, 8(RSP) + MOVD ·x_cgo_thread_start_call(SB), R26 + MOVD (R26), R2 + CALL (R2) + RET + +TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $0-0 + MOVD R0, 8(RSP) + MOVD ·x_cgo_setenv_call(SB), R26 + MOVD (R26), R2 + CALL (R2) + RET + +TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $0-0 + MOVD R0, 8(RSP) + MOVD ·x_cgo_unsetenv_call(SB), R26 + MOVD (R26), R2 + CALL (R2) + RET + +TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0-0 + CALL ·x_cgo_notify_runtime_init_done(SB) + RET + +TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 + CALL ·x_cgo_bindm(SB) + RET + +// func setg_trampoline(setg uintptr, g uintptr) +TEXT ·setg_trampoline(SB), NOSPLIT, $0-16 + MOVD G+8(FP), R0 + MOVD setg+0(FP), R1 + CALL R1 + RET + +TEXT threadentry_trampoline(SB), NOSPLIT, $0-0 + MOVD R0, 8(RSP) + MOVD ·threadentry_call(SB), R26 + MOVD (R26), R2 + CALL (R2) + MOVD $0, R0 // TODO: get the return value from threadentry + RET + +TEXT ·call5(SB), NOSPLIT, $0-0 + MOVD fn+0(FP), R6 + MOVD a1+8(FP), R0 + MOVD a2+16(FP), R1 + MOVD a3+24(FP), R2 + MOVD a4+32(FP), R3 + MOVD a5+40(FP), R4 + CALL R6 + MOVD R0, ret+48(FP) + RET diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_stubs.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_stubs.s new file mode 100644 index 0000000..e872637 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_stubs.s @@ -0,0 +1,90 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +#include "textflag.h" + +// these stubs are here because it is not possible to go:linkname directly the C functions on darwin arm64 + +TEXT _malloc(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_malloc(SB) + RET + +TEXT _free(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_free(SB) + RET + +TEXT _setenv(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_setenv(SB) + RET + +TEXT _unsetenv(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_unsetenv(SB) + RET + +TEXT _sigfillset(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_sigfillset(SB) + RET + +TEXT _nanosleep(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_nanosleep(SB) + RET + +TEXT _abort(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_abort(SB) + RET + +TEXT _pthread_attr_init(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_attr_init(SB) + RET + +TEXT _pthread_create(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_create(SB) + RET + +TEXT _pthread_detach(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_detach(SB) + RET + +TEXT _pthread_sigmask(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_sigmask(SB) + RET + +TEXT _pthread_self(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_self(SB) + RET + +TEXT _pthread_get_stacksize_np(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_get_stacksize_np(SB) + RET + +TEXT _pthread_attr_getstacksize(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_attr_getstacksize(SB) + RET + +TEXT _pthread_attr_setstacksize(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_attr_setstacksize(SB) + RET + +TEXT _pthread_attr_destroy(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_attr_destroy(SB) + RET + +TEXT _pthread_mutex_lock(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_mutex_lock(SB) + RET + +TEXT _pthread_mutex_unlock(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_mutex_unlock(SB) + RET + +TEXT _pthread_cond_broadcast(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_cond_broadcast(SB) + RET + +TEXT _pthread_setspecific(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_setspecific(SB) + RET diff --git a/vendor/github.com/ebitengine/purego/internal/strings/strings.go b/vendor/github.com/ebitengine/purego/internal/strings/strings.go new file mode 100644 index 0000000..5b0d252 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/strings/strings.go @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package strings + +import ( + "unsafe" +) + +// hasSuffix tests whether the string s ends with suffix. +func hasSuffix(s, suffix string) bool { + return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix +} + +// CString converts a go string to *byte that can be passed to C code. +func CString(name string) *byte { + if hasSuffix(name, "\x00") { + return &(*(*[]byte)(unsafe.Pointer(&name)))[0] + } + b := make([]byte, len(name)+1) + copy(b, name) + return &b[0] +} + +// GoString copies a null-terminated char* to a Go string. +func GoString(c uintptr) string { + // We take the address and then dereference it to trick go vet from creating a possible misuse of unsafe.Pointer + ptr := *(*unsafe.Pointer)(unsafe.Pointer(&c)) + if ptr == nil { + return "" + } + var length int + for { + if *(*byte)(unsafe.Add(ptr, uintptr(length))) == '\x00' { + break + } + length++ + } + return string(unsafe.Slice((*byte)(ptr), length)) +} diff --git a/vendor/github.com/ebitengine/purego/is_ios.go b/vendor/github.com/ebitengine/purego/is_ios.go new file mode 100644 index 0000000..ed31da9 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/is_ios.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package purego + +// if you are getting this error it means that you have +// CGO_ENABLED=0 while trying to build for ios. +// purego does not support this mode yet. +// the fix is to set CGO_ENABLED=1 which will require +// a C compiler. +var _ = _PUREGO_REQUIRES_CGO_ON_IOS diff --git a/vendor/github.com/ebitengine/purego/nocgo.go b/vendor/github.com/ebitengine/purego/nocgo.go new file mode 100644 index 0000000..5b989ea --- /dev/null +++ b/vendor/github.com/ebitengine/purego/nocgo.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || freebsd || linux) + +package purego + +// if CGO_ENABLED=0 import fakecgo to setup the Cgo runtime correctly. +// This is required since some frameworks need TLS setup the C way which Go doesn't do. +// We currently don't support ios in fakecgo mode so force Cgo or fail +// +// The way that the Cgo runtime (runtime/cgo) works is by setting some variables found +// in runtime with non-null GCC compiled functions. The variables that are replaced are +// var ( +// iscgo bool // in runtime/cgo.go +// _cgo_init unsafe.Pointer // in runtime/cgo.go +// _cgo_thread_start unsafe.Pointer // in runtime/cgo.go +// _cgo_notify_runtime_init_done unsafe.Pointer // in runtime/cgo.go +// _cgo_setenv unsafe.Pointer // in runtime/env_posix.go +// _cgo_unsetenv unsafe.Pointer // in runtime/env_posix.go +// ) +// importing fakecgo will set these (using //go:linkname) with functions written +// entirely in Go (except for some assembly trampolines to change GCC ABI to Go ABI). +// Doing so makes it possible to build applications that call into C without CGO_ENABLED=1. +import _ "github.com/ebitengine/purego/internal/fakecgo" diff --git a/vendor/github.com/ebitengine/purego/objc/objc_runtime_darwin.go b/vendor/github.com/ebitengine/purego/objc/objc_runtime_darwin.go new file mode 100644 index 0000000..ed691e2 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/objc/objc_runtime_darwin.go @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +// Package objc is a low-level pure Go objective-c runtime. This package is easy to use incorrectly, so it is best +// to use a wrapper that provides the functionality you need in a safer way. +package objc + +import ( + "errors" + "fmt" + "math" + "reflect" + "regexp" + "unicode" + "unsafe" + + "github.com/ebitengine/purego" +) + +// TODO: support try/catch? +// https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime +var ( + objc_msgSend_fn uintptr + objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID + objc_msgSendSuper2_fn uintptr + objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID + objc_getClass func(name string) Class + objc_getProtocol func(name string) *Protocol + objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class + objc_registerClassPair func(class Class) + sel_registerName func(name string) SEL + class_getSuperclass func(class Class) Class + class_getInstanceVariable func(class Class, name string) Ivar + class_getInstanceSize func(class Class) uintptr + class_addMethod func(class Class, name SEL, imp IMP, types string) bool + class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool + class_addProtocol func(class Class, protocol *Protocol) bool + ivar_getOffset func(ivar Ivar) uintptr + ivar_getName func(ivar Ivar) string + object_getClass func(obj ID) Class + object_getIvar func(obj ID, ivar Ivar) ID + object_setIvar func(obj ID, ivar Ivar, value ID) + protocol_getName func(protocol *Protocol) string + protocol_isEqual func(p *Protocol, p2 *Protocol) bool +) + +func init() { + objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) + if err != nil { + panic(fmt.Errorf("objc: %w", err)) + } + objc_msgSend_fn, err = purego.Dlsym(objc, "objc_msgSend") + if err != nil { + panic(fmt.Errorf("objc: %w", err)) + } + purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) + objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2") + if err != nil { + panic(fmt.Errorf("objc: %w", err)) + } + purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn) + purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") + purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") + purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol") + purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair") + purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair") + purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName") + purego.RegisterLibFunc(&class_getSuperclass, objc, "class_getSuperclass") + purego.RegisterLibFunc(&class_getInstanceVariable, objc, "class_getInstanceVariable") + purego.RegisterLibFunc(&class_addMethod, objc, "class_addMethod") + purego.RegisterLibFunc(&class_addIvar, objc, "class_addIvar") + purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol") + purego.RegisterLibFunc(&class_getInstanceSize, objc, "class_getInstanceSize") + purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset") + purego.RegisterLibFunc(&ivar_getName, objc, "ivar_getName") + purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName") + purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual") + purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar") + purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar") +} + +// ID is an opaque pointer to some Objective-C object +type ID uintptr + +// Class returns the class of the object. +func (id ID) Class() Class { + return object_getClass(id) +} + +// Send is a convenience method for sending messages to objects. This function takes a SEL +// instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result +// of RegisterName. +func (id ID) Send(sel SEL, args ...interface{}) ID { + return objc_msgSend(id, sel, args...) +} + +// GetIvar reads the value of an instance variable in an object. +func (id ID) GetIvar(ivar Ivar) ID { + return object_getIvar(id, ivar) +} + +// SetIvar sets the value of an instance variable in an object. +func (id ID) SetIvar(ivar Ivar, value ID) { + object_setIvar(id, ivar, value) +} + +// Send is a convenience method for sending messages to objects that can return any type. +// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. +// It is best to cache the result of RegisterName. +func Send[T any](id ID, sel SEL, args ...any) T { + var fn func(id ID, sel SEL, args ...any) T + purego.RegisterFunc(&fn, objc_msgSend_fn) + return fn(id, sel, args...) +} + +// objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword +// as the receiver of a message. It specifies the class definition of the particular superclass that should +// be messaged. +type objc_super struct { + receiver ID + superClass Class +} + +// SendSuper is a convenience method for sending message to object's super. This function takes a SEL +// instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result +// of RegisterName. +func (id ID) SendSuper(sel SEL, args ...interface{}) ID { + super := &objc_super{ + receiver: id, + superClass: id.Class(), + } + return objc_msgSendSuper2(super, sel, args...) +} + +// SendSuper is a convenience method for sending message to object's super that can return any type. +// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. +// It is best to cache the result of RegisterName. +func SendSuper[T any](id ID, sel SEL, args ...any) T { + super := &objc_super{ + receiver: id, + superClass: id.Class(), + } + var fn func(objcSuper *objc_super, sel SEL, args ...any) T + purego.RegisterFunc(&fn, objc_msgSendSuper2_fn) + return fn(super, sel, args...) +} + +// SEL is an opaque type that represents a method selector +type SEL uintptr + +// RegisterName registers a method with the Objective-C runtime system, maps the method name to a selector, +// and returns the selector value. This function grabs the global Objective-c lock. It is best the cache the +// result of this function. +func RegisterName(name string) SEL { + return sel_registerName(name) +} + +// Class is an opaque type that represents an Objective-C class. +type Class uintptr + +// GetClass returns the Class object for the named class, or nil if the class is not registered with the Objective-C runtime. +func GetClass(name string) Class { + return objc_getClass(name) +} + +// AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created +// +// Deprecated: use RegisterClass instead +func AllocateClassPair(super Class, name string, extraBytes uintptr) Class { + return objc_allocateClassPair(super, name, extraBytes) +} + +// MethodDef represents the Go function and the selector that ObjC uses to access that function. +type MethodDef struct { + Cmd SEL + Fn any +} + +// IvarAttrib is the attribute that an ivar has. It affects if and which methods are automatically +// generated when creating a class with RegisterClass. See [Apple Docs] for an understanding of these attributes. +// The fields are still accessible using objc.GetIvar and objc.SetIvar regardless of the value of IvarAttrib. +// +// Take for example this Objective-C code: +// +// @property (readwrite) float value; +// +// In Go, the functions can be accessed as followed: +// +// var value = purego.Send[float32](id, purego.RegisterName("value")) +// id.Send(purego.RegisterName("setValue:"), 3.46) +// +// [Apple Docs]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html +type IvarAttrib int + +const ( + ReadOnly IvarAttrib = 1 << iota + ReadWrite +) + +// FieldDef is a definition of a field to add to an Objective-C class. +// The name of the field is what will be used to access it through the Ivar. If the type is bool +// the name cannot start with `is` since a getter will be generated with the name `isBoolName`. +// The name also cannot contain any spaces. +// The type is the Go equivalent type of the Ivar. +// Attribute determines if a getter and or setter method is generated for this field. +type FieldDef struct { + Name string + Type reflect.Type + Attribute IvarAttrib +} + +// ivarRegex checks to make sure the Ivar is correctly formatted +var ivarRegex = regexp.MustCompile("[a-z_][a-zA-Z0-9_]*") + +// RegisterClass takes the name of the class to create, the superclass, a list of protocols this class +// implements, a list of fields this class has and a list of methods. It returns the created class or an error +// describing what went wrong. +func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars []FieldDef, methods []MethodDef) (Class, error) { + class := objc_allocateClassPair(superClass, name, 0) + if class == 0 { + return 0, fmt.Errorf("objc: failed to create class with name '%s'", name) + } + // Add Protocols + for _, p := range protocols { + if !class.AddProtocol(p) { + return 0, fmt.Errorf("objc: couldn't add Protocol %s", protocol_getName(p)) + } + } + // Add exported methods based on the selectors returned from ClassDef(string) SEL + for idx, def := range methods { + imp, err := func() (imp IMP, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("objc: failed to create IMP: %s", r) + } + }() + return NewIMP(def.Fn), nil + }() + if err != nil { + return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err) + } + encoding, err := encodeFunc(def.Fn) + if err != nil { + return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err) + } + if !class.AddMethod(def.Cmd, imp, encoding) { + return 0, fmt.Errorf("objc: couldn't add Method at index %d", idx) + } + } + // Add Ivars + for _, instVar := range ivars { + ivar := instVar + if !ivarRegex.MatchString(ivar.Name) { + return 0, fmt.Errorf("objc: Ivar must start with a lowercase letter and only contain ASCII letters and numbers: '%s'", ivar.Name) + } + size := ivar.Type.Size() + alignment := uint8(math.Log2(float64(ivar.Type.Align()))) + enc, err := encodeType(ivar.Type, false) + if err != nil { + return 0, fmt.Errorf("objc: couldn't add Ivar %s: %w", ivar.Name, err) + } + if !class_addIvar(class, ivar.Name, size, alignment, enc) { + return 0, fmt.Errorf("objc: couldn't add Ivar %s", ivar.Name) + } + offset := class.InstanceVariable(ivar.Name).Offset() + switch ivar.Attribute { + case ReadWrite: + ty := reflect.FuncOf( + []reflect.Type{ + reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), ivar.Type, + }, + nil, false, + ) + var encoding string + if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil { + return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err) + } + val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { + // on entry the first and second arguments are ID and SEL followed by the value + if len(args) != 3 { + panic(fmt.Sprintf("objc: incorrect number of args. expected 3 got %d", len(args))) + } + // The following reflect code does the equivalent of this: + // + // ((*struct { + // Padding [offset]byte + // Value int + // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 + // + // However, since the type of the variable is unknown reflection is used to actually assign the value + id := args[0].Interface().(ID) + ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet + reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem().Set(args[2]) + return nil + }).Interface() + // this code only works for ascii but that shouldn't be a problem + selector := "set" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:] + ":\x00" + class.AddMethod(RegisterName(selector), NewIMP(val), encoding) + fallthrough // also implement the read method + case ReadOnly: + ty := reflect.FuncOf( + []reflect.Type{ + reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), + }, + []reflect.Type{ivar.Type}, false, + ) + var encoding string + if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil { + return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err) + } + val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { + // on entry the first and second arguments are ID and SEL + if len(args) != 2 { + panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args))) + } + id := args[0].Interface().(ID) + ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet + // the variable is located at an offset from the id + return []reflect.Value{reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem()} + }).Interface() + if ivar.Type.Kind() == reflect.Bool { + // this code only works for ascii but that shouldn't be a problem + ivar.Name = "is" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:] + } + class.AddMethod(RegisterName(ivar.Name), NewIMP(val), encoding) + default: + return 0, fmt.Errorf("objc: unknown Ivar Attribute (%d)", ivar.Attribute) + } + } + objc_registerClassPair(class) + return class, nil +} + +const ( + encId = "@" + encClass = "#" + encSelector = ":" + encChar = "c" + encUChar = "C" + encShort = "s" + encUShort = "S" + encInt = "i" + encUInt = "I" + encLong = "l" + encULong = "L" + encFloat = "f" + encDouble = "d" + encBool = "B" + encVoid = "v" + encPtr = "^" + encCharPtr = "*" + encStructBegin = "{" + encStructEnd = "}" + encUnsafePtr = "^v" +) + +// encodeType returns a string representing a type as if it was given to @encode(typ) +// Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 +func encodeType(typ reflect.Type, insidePtr bool) (string, error) { + switch typ { + case reflect.TypeOf(Class(0)): + return encClass, nil + case reflect.TypeOf(ID(0)): + return encId, nil + case reflect.TypeOf(SEL(0)): + return encSelector, nil + } + + kind := typ.Kind() + switch kind { + case reflect.Bool: + return encBool, nil + case reflect.Int: + return encLong, nil + case reflect.Int8: + return encChar, nil + case reflect.Int16: + return encShort, nil + case reflect.Int32: + return encInt, nil + case reflect.Int64: + return encULong, nil + case reflect.Uint: + return encULong, nil + case reflect.Uint8: + return encUChar, nil + case reflect.Uint16: + return encUShort, nil + case reflect.Uint32: + return encUInt, nil + case reflect.Uint64: + return encULong, nil + case reflect.Uintptr: + return encPtr, nil + case reflect.Float32: + return encFloat, nil + case reflect.Float64: + return encDouble, nil + case reflect.Ptr: + enc, err := encodeType(typ.Elem(), true) + return encPtr + enc, err + case reflect.Struct: + if insidePtr { + return encStructBegin + typ.Name() + encStructEnd, nil + } + encoding := encStructBegin + encoding += typ.Name() + encoding += "=" + for i := 0; i < typ.NumField(); i++ { + f := typ.Field(i) + tmp, err := encodeType(f.Type, false) + if err != nil { + return "", err + } + encoding += tmp + } + encoding = encStructEnd + return encoding, nil + case reflect.UnsafePointer: + return encUnsafePtr, nil + case reflect.String: + return encCharPtr, nil + } + + return "", errors.New(fmt.Sprintf("unhandled/invalid kind %v typed %v", kind, typ)) +} + +// encodeFunc returns a functions type as if it was given to @encode(fn) +func encodeFunc(fn interface{}) (string, error) { + typ := reflect.TypeOf(fn) + if typ.Kind() != reflect.Func { + return "", errors.New("not a func") + } + + encoding := "" + switch typ.NumOut() { + case 0: + encoding += encVoid + case 1: + tmp, err := encodeType(typ.Out(0), false) + if err != nil { + return "", err + } + encoding += tmp + default: + return "", errors.New("too many output parameters") + } + + if typ.NumIn() < 2 { + return "", errors.New("func doesn't take ID and SEL as its first two parameters") + } + + encoding += encId + + for i := 1; i < typ.NumIn(); i++ { + tmp, err := encodeType(typ.In(i), false) + if err != nil { + return "", err + } + encoding += tmp + } + return encoding, nil +} + +// SuperClass returns the superclass of a class. +// You should usually use NSObject‘s superclass method instead of this function. +func (c Class) SuperClass() Class { + return class_getSuperclass(c) +} + +// AddMethod adds a new method to a class with a given name and implementation. +// The types argument is a string containing the mapping of parameters and return type. +// Since the function must take at least two arguments—self and _cmd, the second and third +// characters must be “@:” (the first character is the return type). +func (c Class) AddMethod(name SEL, imp IMP, types string) bool { + return class_addMethod(c, name, imp, types) +} + +// AddIvar adds a new instance variable to a class. +// It may only be called after AllocateClassPair and before Register. +// Adding an instance variable to an existing class is not supported. +// The class must not be a metaclass. Adding an instance variable to a metaclass is not supported. +// It takes the instance of the type of the Ivar and a string representing the type. +// +// Deprecated: use RegisterClass instead +func (c Class) AddIvar(name string, ty interface{}, types string) bool { + typeOf := reflect.TypeOf(ty) + size := typeOf.Size() + alignment := uint8(math.Log2(float64(typeOf.Align()))) + return class_addIvar(c, name, size, alignment, types) +} + +// AddProtocol adds a protocol to a class. +// Returns true if the protocol was added successfully, otherwise false (for example, +// the class already conforms to that protocol). +func (c Class) AddProtocol(protocol *Protocol) bool { + return class_addProtocol(c, protocol) +} + +// InstanceSize returns the size in bytes of instances of the class or 0 if cls is nil +func (c Class) InstanceSize() uintptr { + return class_getInstanceSize(c) +} + +// InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name. +func (c Class) InstanceVariable(name string) Ivar { + return class_getInstanceVariable(c, name) +} + +// Register registers a class that was allocated using AllocateClassPair. +// It can now be used to make objects by sending it either alloc and init or new. +// +// Deprecated: use RegisterClass instead +func (c Class) Register() { + objc_registerClassPair(c) +} + +// Ivar an opaque type that represents an instance variable. +type Ivar uintptr + +// Offset returns the offset of an instance variable that can be used to assign and read the Ivar's value. +// +// For instance variables of type ID or other object types, call Ivar and SetIvar instead +// of using this offset to access the instance variable data directly. +func (i Ivar) Offset() uintptr { + return ivar_getOffset(i) +} + +func (i Ivar) Name() string { + return ivar_getName(i) +} + +// Protocol is a type that declares methods that can be implemented by any class. +type Protocol [0]func() + +// GetProtocol returns the protocol for the given name or nil if there is no protocol by that name. +func GetProtocol(name string) *Protocol { + return objc_getProtocol(name) +} + +// Equals return true if the two protocols are the same. +func (p *Protocol) Equals(p2 *Protocol) bool { + return protocol_isEqual(p, p2) +} + +// IMP is a function pointer that can be called by Objective-C code. +type IMP uintptr + +// NewIMP takes a Go function that takes (ID, SEL) as its first two arguments. +// It returns an IMP function pointer that can be called by Objective-C code. +// The function panics if an error occurs. +// The function pointer is never deallocated. +func NewIMP(fn interface{}) IMP { + ty := reflect.TypeOf(fn) + if ty.Kind() != reflect.Func { + panic("objc: not a function") + } + // IMP is stricter than a normal callback + // id (*IMP)(id, SEL, ...) + switch { + case ty.NumIn() < 2: + fallthrough + case ty.In(0) != reflect.TypeOf(ID(0)): + fallthrough + case ty.In(1) != reflect.TypeOf(SEL(0)): + panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) + } + return IMP(purego.NewCallback(fn)) +} diff --git a/vendor/github.com/ebitengine/purego/struct_amd64.go b/vendor/github.com/ebitengine/purego/struct_amd64.go new file mode 100644 index 0000000..e16a611 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/struct_amd64.go @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +package purego + +import ( + "math" + "reflect" +) + +// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf +// https://gitlab.com/x86-psABIs/x86-64-ABI +// Class determines where the 8 byte value goes. +// Higher value classes win over lower value classes +const ( + _NO_CLASS = 0b0000 + _SSE = 0b0001 + _X87 = 0b0011 // long double not used in Go + _INTEGER = 0b0111 + _MEMORY = 0b1111 +) + +func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} { + if v.Type().Size() == 0 { + return keepAlive + } + + // if greater than 64 bytes place on stack + if v.Type().Size() > 8*8 { + placeStack(v, addStack) + return keepAlive + } + var ( + savedNumFloats = *numFloats + savedNumInts = *numInts + savedNumStack = *numStack + ) + placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt) + if placeOnStack { + // reset any values placed in registers + *numFloats = savedNumFloats + *numInts = savedNumInts + *numStack = savedNumStack + placeStack(v, addStack) + } + return keepAlive +} + +func postMerger(t reflect.Type) bool { + // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other + // eightbyte isn’t SSEUP, the whole argument is passed in memory. + if t.Kind() != reflect.Struct { + return false + } + if t.Size() <= 2*8 { + return false + } + first := getFirst(t).Kind() + if first != reflect.Float32 && first != reflect.Float64 { + return false + } + return true +} + +func getFirst(t reflect.Type) reflect.Type { + first := t.Field(0).Type + if first.Kind() == reflect.Struct { + return getFirst(first) + } + return first +} + +func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) { + ok = true + var val uint64 + var shift byte // # of bits to shift + var flushed bool + class := _NO_CLASS + flush := func() { + flushed = true + if class == _SSE { + addFloat(uintptr(val)) + } else { + addInt(uintptr(val)) + } + val = 0 + shift = 0 + class = _NO_CLASS + } + var place func(v reflect.Value) + place = func(v reflect.Value) { + var numFields int + if v.Kind() == reflect.Struct { + numFields = v.Type().NumField() + } else { + numFields = v.Type().Len() + } + + for i := 0; i < numFields; i++ { + flushed = false + var f reflect.Value + if v.Kind() == reflect.Struct { + f = v.Field(i) + } else { + f = v.Index(i) + } + switch f.Kind() { + case reflect.Struct: + place(f) + case reflect.Bool: + if f.Bool() { + val |= 1 + } + shift += 8 + class |= _INTEGER + case reflect.Pointer: + ok = false + return + case reflect.Int8: + val |= uint64(f.Int()&0xFF) << shift + shift += 8 + class |= _INTEGER + case reflect.Int16: + val |= uint64(f.Int()&0xFFFF) << shift + shift += 16 + class |= _INTEGER + case reflect.Int32: + val |= uint64(f.Int()&0xFFFF_FFFF) << shift + shift += 32 + class |= _INTEGER + case reflect.Int64: + val = uint64(f.Int()) + shift = 64 + class = _INTEGER + case reflect.Uint8: + val |= f.Uint() << shift + shift += 8 + class |= _INTEGER + case reflect.Uint16: + val |= f.Uint() << shift + shift += 16 + class |= _INTEGER + case reflect.Uint32: + val |= f.Uint() << shift + shift += 32 + class |= _INTEGER + case reflect.Uint64: + val = f.Uint() + shift = 64 + class = _INTEGER + case reflect.Float32: + val |= uint64(math.Float32bits(float32(f.Float()))) << shift + shift += 32 + class |= _SSE + case reflect.Float64: + if v.Type().Size() > 16 { + ok = false + return + } + val = uint64(math.Float64bits(f.Float())) + shift = 64 + class = _SSE + case reflect.Array: + place(f) + default: + panic("purego: unsupported kind " + f.Kind().String()) + } + + if shift == 64 { + flush() + } else if shift > 64 { + // Should never happen, but may if we forget to reset shift after flush (or forget to flush), + // better fall apart here, than corrupt arguments. + panic("purego: tryPlaceRegisters shift > 64") + } + } + } + + place(v) + if !flushed { + flush() + } + return ok +} + +func placeStack(v reflect.Value, addStack func(uintptr)) { + for i := 0; i < v.Type().NumField(); i++ { + f := v.Field(i) + switch f.Kind() { + case reflect.Pointer: + addStack(f.Pointer()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + addStack(uintptr(f.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + addStack(uintptr(f.Uint())) + case reflect.Float32: + addStack(uintptr(math.Float32bits(float32(f.Float())))) + case reflect.Float64: + addStack(uintptr(math.Float64bits(f.Float()))) + case reflect.Struct: + placeStack(f, addStack) + default: + panic("purego: unsupported kind " + f.Kind().String()) + } + } +} diff --git a/vendor/github.com/ebitengine/purego/struct_arm64.go b/vendor/github.com/ebitengine/purego/struct_arm64.go new file mode 100644 index 0000000..32c14d4 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/struct_arm64.go @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +package purego + +import ( + "math" + "reflect" +) + +// https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst +const ( + _NO_CLASS = 0b00 + _FLOAT = 0b01 + _INT = 0b11 +) + +func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} { + if v.Type().Size() == 0 { + return keepAlive + } + + if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 { + // if this doesn't fit entirely in registers then + // each element goes onto the stack + if hfa && *numFloats+v.NumField() > numOfFloats { + *numFloats = numOfFloats + } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() { + *numInts = numOfIntegerRegisters() + } + + placeRegisters(v, addFloat, addInt) + } else { + keepAlive = placeStack(v, keepAlive, addInt) + } + return keepAlive // the struct was allocated so don't panic +} + +func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { + var val uint64 + var shift byte + var flushed bool + class := _NO_CLASS + var place func(v reflect.Value) + place = func(v reflect.Value) { + var numFields int + if v.Kind() == reflect.Struct { + numFields = v.Type().NumField() + } else { + numFields = v.Type().Len() + } + for k := 0; k < numFields; k++ { + flushed = false + var f reflect.Value + if v.Kind() == reflect.Struct { + f = v.Field(k) + } else { + f = v.Index(k) + } + if shift >= 64 { + shift = 0 + flushed = true + if class == _FLOAT { + addFloat(uintptr(val)) + } else { + addInt(uintptr(val)) + } + } + switch f.Type().Kind() { + case reflect.Struct: + place(f) + case reflect.Bool: + if f.Bool() { + val |= 1 + } + shift += 8 + class |= _INT + case reflect.Uint8: + val |= f.Uint() << shift + shift += 8 + class |= _INT + case reflect.Uint16: + val |= f.Uint() << shift + shift += 16 + class |= _INT + case reflect.Uint32: + val |= f.Uint() << shift + shift += 32 + class |= _INT + case reflect.Uint64: + addInt(uintptr(f.Uint())) + shift = 0 + flushed = true + case reflect.Int8: + val |= uint64(f.Int()&0xFF) << shift + shift += 8 + class |= _INT + case reflect.Int16: + val |= uint64(f.Int()&0xFFFF) << shift + shift += 16 + class |= _INT + case reflect.Int32: + val |= uint64(f.Int()&0xFFFF_FFFF) << shift + shift += 32 + class |= _INT + case reflect.Int64: + addInt(uintptr(f.Int())) + shift = 0 + flushed = true + case reflect.Float32: + if class == _FLOAT { + addFloat(uintptr(val)) + val = 0 + shift = 0 + } + val |= uint64(math.Float32bits(float32(f.Float()))) << shift + shift += 32 + class |= _FLOAT + case reflect.Float64: + addFloat(uintptr(math.Float64bits(float64(f.Float())))) + shift = 0 + flushed = true + case reflect.Array: + place(f) + default: + panic("purego: unsupported kind " + f.Kind().String()) + } + } + } + place(v) + if !flushed { + if class == _FLOAT { + addFloat(uintptr(val)) + } else { + addInt(uintptr(val)) + } + } +} + +func placeStack(v reflect.Value, keepAlive []interface{}, addInt func(uintptr)) []interface{} { + // Struct is too big to be placed in registers. + // Copy to heap and place the pointer in register + ptrStruct := reflect.New(v.Type()) + ptrStruct.Elem().Set(v) + ptr := ptrStruct.Elem().Addr().UnsafePointer() + keepAlive = append(keepAlive, ptr) + addInt(uintptr(ptr)) + return keepAlive +} + +// isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a +// Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]). +// This type of struct will be placed more compactly than the individual fields. +// +// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst +func isHFA(t reflect.Type) bool { + // round up struct size to nearest 8 see section B.4 + structSize := roundUpTo8(t.Size()) + if structSize == 0 || t.NumField() > 4 { + return false + } + first := t.Field(0) + switch first.Type.Kind() { + case reflect.Float32, reflect.Float64: + firstKind := first.Type.Kind() + for i := 0; i < t.NumField(); i++ { + if t.Field(i).Type.Kind() != firstKind { + return false + } + } + return true + case reflect.Array: + switch first.Type.Elem().Kind() { + case reflect.Float32, reflect.Float64: + return true + default: + return false + } + case reflect.Struct: + for i := 0; i < first.Type.NumField(); i++ { + if !isHFA(first.Type) { + return false + } + } + return true + default: + return false + } +} + +// isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type +// and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]). +// A short vector is a machine type that is composed of repeated instances of one fundamental integral or +// floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]). +// This type of struct will be placed more compactly than the individual fields. +// +// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst +func isHVA(t reflect.Type) bool { + // round up struct size to nearest 8 see section B.4 + structSize := roundUpTo8(t.Size()) + if structSize == 0 || (structSize != 8 && structSize != 16) { + return false + } + first := t.Field(0) + switch first.Type.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: + firstKind := first.Type.Kind() + for i := 0; i < t.NumField(); i++ { + if t.Field(i).Type.Kind() != firstKind { + return false + } + } + return true + case reflect.Array: + switch first.Type.Elem().Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: + return true + default: + return false + } + default: + return false + } +} diff --git a/vendor/github.com/ebitengine/purego/struct_other.go b/vendor/github.com/ebitengine/purego/struct_other.go new file mode 100644 index 0000000..d191d96 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/struct_other.go @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +//go:build !amd64 && !arm64 + +package purego + +import "reflect" + +func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} { + panic("purego: struct arguments are not supported") +} diff --git a/vendor/github.com/ebitengine/purego/sys_amd64.s b/vendor/github.com/ebitengine/purego/sys_amd64.s new file mode 100644 index 0000000..eedf9b8 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/sys_amd64.s @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +#include "textflag.h" +#include "abi_amd64.h" +#include "go_asm.h" +#include "funcdata.h" + +#define STACK_SIZE 80 +#define PTR_ADDRESS (STACK_SIZE - 8) + +// syscall15X calls a function in libc on behalf of the syscall package. +// syscall15X takes a pointer to a struct like: +// struct { +// fn uintptr +// a1 uintptr +// a2 uintptr +// a3 uintptr +// a4 uintptr +// a5 uintptr +// a6 uintptr +// a7 uintptr +// a8 uintptr +// a9 uintptr +// a10 uintptr +// a11 uintptr +// a12 uintptr +// a13 uintptr +// a14 uintptr +// a15 uintptr +// r1 uintptr +// r2 uintptr +// err uintptr +// } +// syscall15X must be called on the g0 stack with the +// C calling convention (use libcCall). +GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 +DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) +TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0 + PUSHQ BP + MOVQ SP, BP + SUBQ $STACK_SIZE, SP + MOVQ DI, PTR_ADDRESS(BP) // save the pointer + MOVQ DI, R11 + + MOVQ syscall15Args_f1(R11), X0 // f1 + MOVQ syscall15Args_f2(R11), X1 // f2 + MOVQ syscall15Args_f3(R11), X2 // f3 + MOVQ syscall15Args_f4(R11), X3 // f4 + MOVQ syscall15Args_f5(R11), X4 // f5 + MOVQ syscall15Args_f6(R11), X5 // f6 + MOVQ syscall15Args_f7(R11), X6 // f7 + MOVQ syscall15Args_f8(R11), X7 // f8 + + MOVQ syscall15Args_a1(R11), DI // a1 + MOVQ syscall15Args_a2(R11), SI // a2 + MOVQ syscall15Args_a3(R11), DX // a3 + MOVQ syscall15Args_a4(R11), CX // a4 + MOVQ syscall15Args_a5(R11), R8 // a5 + MOVQ syscall15Args_a6(R11), R9 // a6 + + // push the remaining paramters onto the stack + MOVQ syscall15Args_a7(R11), R12 + MOVQ R12, 0(SP) // push a7 + MOVQ syscall15Args_a8(R11), R12 + MOVQ R12, 8(SP) // push a8 + MOVQ syscall15Args_a9(R11), R12 + MOVQ R12, 16(SP) // push a9 + MOVQ syscall15Args_a10(R11), R12 + MOVQ R12, 24(SP) // push a10 + MOVQ syscall15Args_a11(R11), R12 + MOVQ R12, 32(SP) // push a11 + MOVQ syscall15Args_a12(R11), R12 + MOVQ R12, 40(SP) // push a12 + MOVQ syscall15Args_a13(R11), R12 + MOVQ R12, 48(SP) // push a13 + MOVQ syscall15Args_a14(R11), R12 + MOVQ R12, 56(SP) // push a14 + MOVQ syscall15Args_a15(R11), R12 + MOVQ R12, 64(SP) // push a15 + XORL AX, AX // vararg: say "no float args" + + MOVQ syscall15Args_fn(R11), R10 // fn + CALL R10 + + MOVQ PTR_ADDRESS(BP), DI // get the pointer back + MOVQ AX, syscall15Args_r1(DI) // r1 + MOVQ X0, syscall15Args_r2(DI) // r2 + + XORL AX, AX // no error (it's ignored anyway) + ADDQ $STACK_SIZE, SP + MOVQ BP, SP + POPQ BP + RET + +TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 + MOVQ 0(SP), AX // save the return address to calculate the cb index + MOVQ 8(SP), R10 // get the return SP so that we can align register args with stack args + ADDQ $8, SP // remove return address from stack, we are not returning to callbackasm, but to its caller. + + // make space for first six int and 8 float arguments below the frame + ADJSP $14*8, SP + MOVSD X0, (1*8)(SP) + MOVSD X1, (2*8)(SP) + MOVSD X2, (3*8)(SP) + MOVSD X3, (4*8)(SP) + MOVSD X4, (5*8)(SP) + MOVSD X5, (6*8)(SP) + MOVSD X6, (7*8)(SP) + MOVSD X7, (8*8)(SP) + MOVQ DI, (9*8)(SP) + MOVQ SI, (10*8)(SP) + MOVQ DX, (11*8)(SP) + MOVQ CX, (12*8)(SP) + MOVQ R8, (13*8)(SP) + MOVQ R9, (14*8)(SP) + LEAQ 8(SP), R8 // R8 = address of args vector + + PUSHQ R10 // push the stack pointer below registers + + // determine index into runtime·cbs table + MOVQ $callbackasm(SB), DX + SUBQ DX, AX + MOVQ $0, DX + MOVQ $5, CX // divide by 5 because each call instruction in ·callbacks is 5 bytes long + DIVL CX + SUBQ $1, AX // subtract 1 because return PC is to the next slot + + // Switch from the host ABI to the Go ABI. + PUSH_REGS_HOST_TO_ABI0() + + // Create a struct callbackArgs on our stack to be passed as + // the "frame" to cgocallback and on to callbackWrap. + // $24 to make enough room for the arguments to runtime.cgocallback + SUBQ $(24+callbackArgs__size), SP + MOVQ AX, (24+callbackArgs_index)(SP) // callback index + MOVQ R8, (24+callbackArgs_args)(SP) // address of args vector + MOVQ $0, (24+callbackArgs_result)(SP) // result + LEAQ 24(SP), AX // take the address of callbackArgs + + // Call cgocallback, which will call callbackWrap(frame). + MOVQ ·callbackWrap_call(SB), DI // Get the ABIInternal function pointer + MOVQ (DI), DI // without by using a closure. + MOVQ AX, SI // frame (address of callbackArgs) + MOVQ $0, CX // context + + CALL crosscall2(SB) // runtime.cgocallback(fn, frame, ctxt uintptr) + + // Get callback result. + MOVQ (24+callbackArgs_result)(SP), AX + ADDQ $(24+callbackArgs__size), SP // remove callbackArgs struct + + POP_REGS_HOST_TO_ABI0() + + POPQ R10 // get the SP back + ADJSP $-14*8, SP // remove arguments + + MOVQ R10, 0(SP) + + RET diff --git a/vendor/github.com/ebitengine/purego/sys_arm64.s b/vendor/github.com/ebitengine/purego/sys_arm64.s new file mode 100644 index 0000000..9cdabd2 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/sys_arm64.s @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux || windows + +#include "textflag.h" +#include "go_asm.h" +#include "funcdata.h" + +#define STACK_SIZE 64 +#define PTR_ADDRESS (STACK_SIZE - 8) + +// syscall15X calls a function in libc on behalf of the syscall package. +// syscall15X takes a pointer to a struct like: +// struct { +// fn uintptr +// a1 uintptr +// a2 uintptr +// a3 uintptr +// a4 uintptr +// a5 uintptr +// a6 uintptr +// a7 uintptr +// a8 uintptr +// a9 uintptr +// a10 uintptr +// a11 uintptr +// a12 uintptr +// a13 uintptr +// a14 uintptr +// a15 uintptr +// r1 uintptr +// r2 uintptr +// err uintptr +// } +// syscall15X must be called on the g0 stack with the +// C calling convention (use libcCall). +GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 +DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) +TEXT syscall15X(SB), NOSPLIT, $0 + SUB $STACK_SIZE, RSP // push structure pointer + MOVD R0, PTR_ADDRESS(RSP) + MOVD R0, R9 + + FMOVD syscall15Args_f1(R9), F0 // f1 + FMOVD syscall15Args_f2(R9), F1 // f2 + FMOVD syscall15Args_f3(R9), F2 // f3 + FMOVD syscall15Args_f4(R9), F3 // f4 + FMOVD syscall15Args_f5(R9), F4 // f5 + FMOVD syscall15Args_f6(R9), F5 // f6 + FMOVD syscall15Args_f7(R9), F6 // f7 + FMOVD syscall15Args_f8(R9), F7 // f8 + + MOVD syscall15Args_a1(R9), R0 // a1 + MOVD syscall15Args_a2(R9), R1 // a2 + MOVD syscall15Args_a3(R9), R2 // a3 + MOVD syscall15Args_a4(R9), R3 // a4 + MOVD syscall15Args_a5(R9), R4 // a5 + MOVD syscall15Args_a6(R9), R5 // a6 + MOVD syscall15Args_a7(R9), R6 // a7 + MOVD syscall15Args_a8(R9), R7 // a8 + + MOVD syscall15Args_a9(R9), R10 + MOVD R10, 0(RSP) // push a9 onto stack + MOVD syscall15Args_a10(R9), R10 + MOVD R10, 8(RSP) // push a10 onto stack + MOVD syscall15Args_a11(R9), R10 + MOVD R10, 16(RSP) // push a11 onto stack + MOVD syscall15Args_a12(R9), R10 + MOVD R10, 24(RSP) // push a12 onto stack + MOVD syscall15Args_a13(R9), R10 + MOVD R10, 32(RSP) // push a13 onto stack + MOVD syscall15Args_a14(R9), R10 + MOVD R10, 40(RSP) // push a14 onto stack + MOVD syscall15Args_a15(R9), R10 + MOVD R10, 48(RSP) // push a15 onto stack + + MOVD syscall15Args_fn(R9), R10 // fn + BL (R10) + + MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer + ADD $STACK_SIZE, RSP + MOVD R0, syscall15Args_r1(R2) // save r1 + FMOVD F0, syscall15Args_r2(R2) // save r2 + RET diff --git a/vendor/github.com/ebitengine/purego/sys_unix_arm64.s b/vendor/github.com/ebitengine/purego/sys_unix_arm64.s new file mode 100644 index 0000000..6da06b4 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/sys_unix_arm64.s @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 The Ebitengine Authors + +//go:build darwin || freebsd || linux + +#include "textflag.h" +#include "go_asm.h" +#include "funcdata.h" +#include "abi_arm64.h" + +TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 + NO_LOCAL_POINTERS + + // On entry, the trampoline in zcallback_darwin_arm64.s left + // the callback index in R12 (which is volatile in the C ABI). + + // Save callback register arguments R0-R7 and F0-F7. + // We do this at the top of the frame so they're contiguous with stack arguments. + SUB $(16*8), RSP, R14 + FSTPD (F0, F1), (0*8)(R14) + FSTPD (F2, F3), (2*8)(R14) + FSTPD (F4, F5), (4*8)(R14) + FSTPD (F6, F7), (6*8)(R14) + STP (R0, R1), (8*8)(R14) + STP (R2, R3), (10*8)(R14) + STP (R4, R5), (12*8)(R14) + STP (R6, R7), (14*8)(R14) + + // Adjust SP by frame size. + SUB $(26*8), RSP + + // It is important to save R27 because the go assembler + // uses it for move instructions for a variable. + // This line: + // MOVD ·callbackWrap_call(SB), R0 + // Creates the instructions: + // ADRP 14335(PC), R27 + // MOVD 388(27), R0 + // R27 is a callee saved register so we are responsible + // for ensuring its value doesn't change. So save it and + // restore it at the end of this function. + // R30 is the link register. crosscall2 doesn't save it + // so it's saved here. + STP (R27, R30), 0(RSP) + + // Create a struct callbackArgs on our stack. + MOVD $(callbackArgs__size)(RSP), R13 + MOVD R12, callbackArgs_index(R13) // callback index + MOVD R14, callbackArgs_args(R13) // address of args vector + MOVD ZR, callbackArgs_result(R13) // result + + // Move parameters into registers + // Get the ABIInternal function pointer + // without by using a closure. + MOVD ·callbackWrap_call(SB), R0 + MOVD (R0), R0 // fn unsafe.Pointer + MOVD R13, R1 // frame (&callbackArgs{...}) + MOVD $0, R3 // ctxt uintptr + + BL crosscall2(SB) + + // Get callback result. + MOVD $(callbackArgs__size)(RSP), R13 + MOVD callbackArgs_result(R13), R0 + + // Restore LR and R27 + LDP 0(RSP), (R27, R30) + ADD $(26*8), RSP + + RET diff --git a/vendor/github.com/ebitengine/purego/syscall.go b/vendor/github.com/ebitengine/purego/syscall.go new file mode 100644 index 0000000..f38e4a2 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/syscall.go @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux || windows + +package purego + +const ( + maxArgs = 15 + numOfFloats = 8 // arm64 and amd64 both have 8 float registers +) + +// SyscallN takes fn, a C function pointer and a list of arguments as uintptr. +// There is an internal maximum number of arguments that SyscallN can take. It panics +// when the maximum is exceeded. It returns the result and the libc error code if there is one. +// +// NOTE: SyscallN does not properly call functions that have both integer and float parameters. +// See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 +// for an explanation of why that is. +// +// On amd64, if there are more than 8 floats the 9th and so on will be placed incorrectly on the +// stack. +// +// The pragma go:nosplit is not needed at this function declaration because it uses go:uintptrescapes +// which forces all the objects that the uintptrs point to onto the heap where a stack split won't affect +// their memory location. +// +//go:uintptrescapes +func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { + if fn == 0 { + panic("purego: fn is nil") + } + if len(args) > maxArgs { + panic("purego: too many arguments to SyscallN") + } + // add padding so there is no out-of-bounds slicing + var tmp [maxArgs]uintptr + copy(tmp[:], args) + return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14]) +} diff --git a/vendor/github.com/ebitengine/purego/syscall_cgo_linux.go b/vendor/github.com/ebitengine/purego/syscall_cgo_linux.go new file mode 100644 index 0000000..524a757 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/syscall_cgo_linux.go @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build cgo && !(amd64 || arm64) + +package purego + +import ( + _ "unsafe" // for go:linkname + + "github.com/ebitengine/purego/internal/cgo" +) + +var syscall15XABI0 = uintptr(cgo.Syscall15XABI0) + +// this is only here to make the assembly files happy :) +type syscall15Args struct { + fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr + f1, f2, f3, f4, f5, f6, f7, f8 uintptr + r1, r2, err uintptr +} + +//go:nosplit +func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { + return cgo.Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) +} + +func NewCallback(_ interface{}) uintptr { + panic("purego: NewCallback on Linux is only supported on amd64/arm64") +} diff --git a/vendor/github.com/ebitengine/purego/syscall_sysv.go b/vendor/github.com/ebitengine/purego/syscall_sysv.go new file mode 100644 index 0000000..c0860f7 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/syscall_sysv.go @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || (linux && (amd64 || arm64)) + +package purego + +import ( + "reflect" + "runtime" + "sync" + "unsafe" +) + +var syscall15XABI0 uintptr + +type syscall15Args struct { + fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr + f1, f2, f3, f4, f5, f6, f7, f8 uintptr + r1, r2, err uintptr +} + +//go:nosplit +func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { + args := syscall15Args{ + fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, + a1, a2, a3, a4, a5, a6, a7, a8, + r1, r2, err, + } + runtime_cgocall(syscall15XABI0, unsafe.Pointer(&args)) + return args.r1, args.r2, args.err +} + +// NewCallback converts a Go function to a function pointer conforming to the C calling convention. +// This is useful when interoperating with C code requiring callbacks. The argument is expected to be a +// function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size +// of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated +// for these callbacks is never released. At least 2000 callbacks can always be created. Although this function +// provides similar functionality to windows.NewCallback it is distinct. +func NewCallback(fn interface{}) uintptr { + return compileCallback(fn) +} + +// maxCb is the maximum number of callbacks +// only increase this if you have added more to the callbackasm function +const maxCB = 2000 + +var cbs struct { + lock sync.Mutex + numFn int // the number of functions currently in cbs.funcs + funcs [maxCB]reflect.Value // the saved callbacks +} + +type callbackArgs struct { + index uintptr + // args points to the argument block. + // + // The structure of the arguments goes + // float registers followed by the + // integer registers followed by the stack. + // + // This variable is treated as a continuous + // block of memory containing all of the arguments + // for this callback. + args unsafe.Pointer + // Below are out-args from callbackWrap + result uintptr +} + +func compileCallback(fn interface{}) uintptr { + val := reflect.ValueOf(fn) + if val.Kind() != reflect.Func { + panic("purego: the type must be a function but was not") + } + if val.IsNil() { + panic("purego: function must not be nil") + } + ty := val.Type() + for i := 0; i < ty.NumIn(); i++ { + in := ty.In(i) + switch in.Kind() { + case reflect.Struct, reflect.Interface, reflect.Func, reflect.Slice, + reflect.Chan, reflect.Complex64, reflect.Complex128, + reflect.String, reflect.Map, reflect.Invalid: + panic("purego: unsupported argument type: " + in.Kind().String()) + } + } +output: + switch { + case ty.NumOut() == 1: + switch ty.Out(0).Kind() { + case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Bool, reflect.UnsafePointer: + break output + } + panic("purego: unsupported return type: " + ty.String()) + case ty.NumOut() > 1: + panic("purego: callbacks can only have one return") + } + cbs.lock.Lock() + defer cbs.lock.Unlock() + if cbs.numFn >= maxCB { + panic("purego: the maximum number of callbacks has been reached") + } + cbs.funcs[cbs.numFn] = val + cbs.numFn++ + return callbackasmAddr(cbs.numFn - 1) +} + +const ptrSize = unsafe.Sizeof((*int)(nil)) + +const callbackMaxFrame = 64 * ptrSize + +// callbackasm is implemented in zcallback_GOOS_GOARCH.s +// +//go:linkname __callbackasm callbackasm +var __callbackasm byte +var callbackasmABI0 = uintptr(unsafe.Pointer(&__callbackasm)) + +// callbackWrap_call allows the calling of the ABIInternal wrapper +// which is required for runtime.cgocallback without the +// tag which is only allowed in the runtime. +// This closure is used inside sys_darwin_GOARCH.s +var callbackWrap_call = callbackWrap + +// callbackWrap is called by assembly code which determines which Go function to call. +// This function takes the arguments and passes them to the Go function and returns the result. +func callbackWrap(a *callbackArgs) { + cbs.lock.Lock() + fn := cbs.funcs[a.index] + cbs.lock.Unlock() + fnType := fn.Type() + args := make([]reflect.Value, fnType.NumIn()) + frame := (*[callbackMaxFrame]uintptr)(a.args) + var floatsN int // floatsN represents the number of float arguments processed + var intsN int // intsN represents the number of integer arguments processed + // stack points to the index into frame of the current stack element. + // The stack begins after the float and integer registers. + stack := numOfIntegerRegisters() + numOfFloats + for i := range args { + var pos int + switch fnType.In(i).Kind() { + case reflect.Float32, reflect.Float64: + if floatsN >= numOfFloats { + pos = stack + stack++ + } else { + pos = floatsN + } + floatsN++ + default: + if intsN >= numOfIntegerRegisters() { + pos = stack + stack++ + } else { + // the integers begin after the floats in frame + pos = intsN + numOfFloats + } + intsN++ + } + args[i] = reflect.NewAt(fnType.In(i), unsafe.Pointer(&frame[pos])).Elem() + } + ret := fn.Call(args) + if len(ret) > 0 { + switch k := ret[0].Kind(); k { + case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr: + a.result = uintptr(ret[0].Uint()) + case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: + a.result = uintptr(ret[0].Int()) + case reflect.Bool: + if ret[0].Bool() { + a.result = 1 + } else { + a.result = 0 + } + case reflect.Pointer: + a.result = ret[0].Pointer() + case reflect.UnsafePointer: + a.result = ret[0].Pointer() + default: + panic("purego: unsupported kind: " + k.String()) + } + } +} + +// callbackasmAddr returns address of runtime.callbackasm +// function adjusted by i. +// On x86 and amd64, runtime.callbackasm is a series of CALL instructions, +// and we want callback to arrive at +// correspondent call instruction instead of start of +// runtime.callbackasm. +// On ARM, runtime.callbackasm is a series of mov and branch instructions. +// R12 is loaded with the callback index. Each entry is two instructions, +// hence 8 bytes. +func callbackasmAddr(i int) uintptr { + var entrySize int + switch runtime.GOARCH { + default: + panic("purego: unsupported architecture") + case "386", "amd64": + entrySize = 5 + case "arm", "arm64": + // On ARM and ARM64, each entry is a MOV instruction + // followed by a branch instruction + entrySize = 8 + } + return callbackasmABI0 + uintptr(i*entrySize) +} diff --git a/vendor/github.com/ebitengine/purego/syscall_windows.go b/vendor/github.com/ebitengine/purego/syscall_windows.go new file mode 100644 index 0000000..9a0d86c --- /dev/null +++ b/vendor/github.com/ebitengine/purego/syscall_windows.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package purego + +import ( + "syscall" + _ "unsafe" // only for go:linkname + + "golang.org/x/sys/windows" +) + +var syscall15XABI0 uintptr + +type syscall15Args struct { + fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr + f1, f2, f3, f4, f5, f6, f7, f8 uintptr + r1, r2, err uintptr +} + +func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { + r1, r2, errno := syscall.Syscall15(fn, 15, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) + return r1, r2, uintptr(errno) +} + +// NewCallback converts a Go function to a function pointer conforming to the stdcall calling convention. +// This is useful when interoperating with Windows code requiring callbacks. The argument is expected to be a +// function with one uintptr-sized result. The function must not have arguments with size larger than the +// size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory +// allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024 +// callbacks can always be created. Although this function is similiar to the darwin version it may act +// differently. +func NewCallback(fn interface{}) uintptr { + return syscall.NewCallback(fn) +} + +//go:linkname openLibrary openLibrary +func openLibrary(name string) (uintptr, error) { + handle, err := windows.LoadLibrary(name) + return uintptr(handle), err +} + +func loadSymbol(handle uintptr, name string) (uintptr, error) { + return windows.GetProcAddress(windows.Handle(handle), name) +} diff --git a/vendor/github.com/ebitengine/purego/zcallback_amd64.s b/vendor/github.com/ebitengine/purego/zcallback_amd64.s new file mode 100644 index 0000000..6a778bf --- /dev/null +++ b/vendor/github.com/ebitengine/purego/zcallback_amd64.s @@ -0,0 +1,2014 @@ +// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. + +//go:build darwin || freebsd || linux + +// runtime·callbackasm is called by external code to +// execute Go implemented callback function. It is not +// called from the start, instead runtime·compilecallback +// always returns address into runtime·callbackasm offset +// appropriately so different callbacks start with different +// CALL instruction in runtime·callbackasm. This determines +// which Go callback function is executed later on. +#include "textflag.h" + +TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) diff --git a/vendor/github.com/ebitengine/purego/zcallback_arm64.s b/vendor/github.com/ebitengine/purego/zcallback_arm64.s new file mode 100644 index 0000000..c079b80 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/zcallback_arm64.s @@ -0,0 +1,4014 @@ +// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. + +//go:build darwin || freebsd || linux + +// External code calls into callbackasm at an offset corresponding +// to the callback index. Callbackasm is a table of MOV and B instructions. +// The MOV instruction loads R12 with the callback index, and the +// B instruction branches to callbackasm1. +// callbackasm1 takes the callback index from R12 and +// indexes into an array that stores information about each callback. +// It then calls the Go implementation for that callback. +#include "textflag.h" + +TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 + MOVD $0, R12 + B callbackasm1(SB) + MOVD $1, R12 + B callbackasm1(SB) + MOVD $2, R12 + B callbackasm1(SB) + MOVD $3, R12 + B callbackasm1(SB) + MOVD $4, R12 + B callbackasm1(SB) + MOVD $5, R12 + B callbackasm1(SB) + MOVD $6, R12 + B callbackasm1(SB) + MOVD $7, R12 + B callbackasm1(SB) + MOVD $8, R12 + B callbackasm1(SB) + MOVD $9, R12 + B callbackasm1(SB) + MOVD $10, R12 + B callbackasm1(SB) + MOVD $11, R12 + B callbackasm1(SB) + MOVD $12, R12 + B callbackasm1(SB) + MOVD $13, R12 + B callbackasm1(SB) + MOVD $14, R12 + B callbackasm1(SB) + MOVD $15, R12 + B callbackasm1(SB) + MOVD $16, R12 + B callbackasm1(SB) + MOVD $17, R12 + B callbackasm1(SB) + MOVD $18, R12 + B callbackasm1(SB) + MOVD $19, R12 + B callbackasm1(SB) + MOVD $20, R12 + B callbackasm1(SB) + MOVD $21, R12 + B callbackasm1(SB) + MOVD $22, R12 + B callbackasm1(SB) + MOVD $23, R12 + B callbackasm1(SB) + MOVD $24, R12 + B callbackasm1(SB) + MOVD $25, R12 + B callbackasm1(SB) + MOVD $26, R12 + B callbackasm1(SB) + MOVD $27, R12 + B callbackasm1(SB) + MOVD $28, R12 + B callbackasm1(SB) + MOVD $29, R12 + B callbackasm1(SB) + MOVD $30, R12 + B callbackasm1(SB) + MOVD $31, R12 + B callbackasm1(SB) + MOVD $32, R12 + B callbackasm1(SB) + MOVD $33, R12 + B callbackasm1(SB) + MOVD $34, R12 + B callbackasm1(SB) + MOVD $35, R12 + B callbackasm1(SB) + MOVD $36, R12 + B callbackasm1(SB) + MOVD $37, R12 + B callbackasm1(SB) + MOVD $38, R12 + B callbackasm1(SB) + MOVD $39, R12 + B callbackasm1(SB) + MOVD $40, R12 + B callbackasm1(SB) + MOVD $41, R12 + B callbackasm1(SB) + MOVD $42, R12 + B callbackasm1(SB) + MOVD $43, R12 + B callbackasm1(SB) + MOVD $44, R12 + B callbackasm1(SB) + MOVD $45, R12 + B callbackasm1(SB) + MOVD $46, R12 + B callbackasm1(SB) + MOVD $47, R12 + B callbackasm1(SB) + MOVD $48, R12 + B callbackasm1(SB) + MOVD $49, R12 + B callbackasm1(SB) + MOVD $50, R12 + B callbackasm1(SB) + MOVD $51, R12 + B callbackasm1(SB) + MOVD $52, R12 + B callbackasm1(SB) + MOVD $53, R12 + B callbackasm1(SB) + MOVD $54, R12 + B callbackasm1(SB) + MOVD $55, R12 + B callbackasm1(SB) + MOVD $56, R12 + B callbackasm1(SB) + MOVD $57, R12 + B callbackasm1(SB) + MOVD $58, R12 + B callbackasm1(SB) + MOVD $59, R12 + B callbackasm1(SB) + MOVD $60, R12 + B callbackasm1(SB) + MOVD $61, R12 + B callbackasm1(SB) + MOVD $62, R12 + B callbackasm1(SB) + MOVD $63, R12 + B callbackasm1(SB) + MOVD $64, R12 + B callbackasm1(SB) + MOVD $65, R12 + B callbackasm1(SB) + MOVD $66, R12 + B callbackasm1(SB) + MOVD $67, R12 + B callbackasm1(SB) + MOVD $68, R12 + B callbackasm1(SB) + MOVD $69, R12 + B callbackasm1(SB) + MOVD $70, R12 + B callbackasm1(SB) + MOVD $71, R12 + B callbackasm1(SB) + MOVD $72, R12 + B callbackasm1(SB) + MOVD $73, R12 + B callbackasm1(SB) + MOVD $74, R12 + B callbackasm1(SB) + MOVD $75, R12 + B callbackasm1(SB) + MOVD $76, R12 + B callbackasm1(SB) + MOVD $77, R12 + B callbackasm1(SB) + MOVD $78, R12 + B callbackasm1(SB) + MOVD $79, R12 + B callbackasm1(SB) + MOVD $80, R12 + B callbackasm1(SB) + MOVD $81, R12 + B callbackasm1(SB) + MOVD $82, R12 + B callbackasm1(SB) + MOVD $83, R12 + B callbackasm1(SB) + MOVD $84, R12 + B callbackasm1(SB) + MOVD $85, R12 + B callbackasm1(SB) + MOVD $86, R12 + B callbackasm1(SB) + MOVD $87, R12 + B callbackasm1(SB) + MOVD $88, R12 + B callbackasm1(SB) + MOVD $89, R12 + B callbackasm1(SB) + MOVD $90, R12 + B callbackasm1(SB) + MOVD $91, R12 + B callbackasm1(SB) + MOVD $92, R12 + B callbackasm1(SB) + MOVD $93, R12 + B callbackasm1(SB) + MOVD $94, R12 + B callbackasm1(SB) + MOVD $95, R12 + B callbackasm1(SB) + MOVD $96, R12 + B callbackasm1(SB) + MOVD $97, R12 + B callbackasm1(SB) + MOVD $98, R12 + B callbackasm1(SB) + MOVD $99, R12 + B callbackasm1(SB) + MOVD $100, R12 + B callbackasm1(SB) + MOVD $101, R12 + B callbackasm1(SB) + MOVD $102, R12 + B callbackasm1(SB) + MOVD $103, R12 + B callbackasm1(SB) + MOVD $104, R12 + B callbackasm1(SB) + MOVD $105, R12 + B callbackasm1(SB) + MOVD $106, R12 + B callbackasm1(SB) + MOVD $107, R12 + B callbackasm1(SB) + MOVD $108, R12 + B callbackasm1(SB) + MOVD $109, R12 + B callbackasm1(SB) + MOVD $110, R12 + B callbackasm1(SB) + MOVD $111, R12 + B callbackasm1(SB) + MOVD $112, R12 + B callbackasm1(SB) + MOVD $113, R12 + B callbackasm1(SB) + MOVD $114, R12 + B callbackasm1(SB) + MOVD $115, R12 + B callbackasm1(SB) + MOVD $116, R12 + B callbackasm1(SB) + MOVD $117, R12 + B callbackasm1(SB) + MOVD $118, R12 + B callbackasm1(SB) + MOVD $119, R12 + B callbackasm1(SB) + MOVD $120, R12 + B callbackasm1(SB) + MOVD $121, R12 + B callbackasm1(SB) + MOVD $122, R12 + B callbackasm1(SB) + MOVD $123, R12 + B callbackasm1(SB) + MOVD $124, R12 + B callbackasm1(SB) + MOVD $125, R12 + B callbackasm1(SB) + MOVD $126, R12 + B callbackasm1(SB) + MOVD $127, R12 + B callbackasm1(SB) + MOVD $128, R12 + B callbackasm1(SB) + MOVD $129, R12 + B callbackasm1(SB) + MOVD $130, R12 + B callbackasm1(SB) + MOVD $131, R12 + B callbackasm1(SB) + MOVD $132, R12 + B callbackasm1(SB) + MOVD $133, R12 + B callbackasm1(SB) + MOVD $134, R12 + B callbackasm1(SB) + MOVD $135, R12 + B callbackasm1(SB) + MOVD $136, R12 + B callbackasm1(SB) + MOVD $137, R12 + B callbackasm1(SB) + MOVD $138, R12 + B callbackasm1(SB) + MOVD $139, R12 + B callbackasm1(SB) + MOVD $140, R12 + B callbackasm1(SB) + MOVD $141, R12 + B callbackasm1(SB) + MOVD $142, R12 + B callbackasm1(SB) + MOVD $143, R12 + B callbackasm1(SB) + MOVD $144, R12 + B callbackasm1(SB) + MOVD $145, R12 + B callbackasm1(SB) + MOVD $146, R12 + B callbackasm1(SB) + MOVD $147, R12 + B callbackasm1(SB) + MOVD $148, R12 + B callbackasm1(SB) + MOVD $149, R12 + B callbackasm1(SB) + MOVD $150, R12 + B callbackasm1(SB) + MOVD $151, R12 + B callbackasm1(SB) + MOVD $152, R12 + B callbackasm1(SB) + MOVD $153, R12 + B callbackasm1(SB) + MOVD $154, R12 + B callbackasm1(SB) + MOVD $155, R12 + B callbackasm1(SB) + MOVD $156, R12 + B callbackasm1(SB) + MOVD $157, R12 + B callbackasm1(SB) + MOVD $158, R12 + B callbackasm1(SB) + MOVD $159, R12 + B callbackasm1(SB) + MOVD $160, R12 + B callbackasm1(SB) + MOVD $161, R12 + B callbackasm1(SB) + MOVD $162, R12 + B callbackasm1(SB) + MOVD $163, R12 + B callbackasm1(SB) + MOVD $164, R12 + B callbackasm1(SB) + MOVD $165, R12 + B callbackasm1(SB) + MOVD $166, R12 + B callbackasm1(SB) + MOVD $167, R12 + B callbackasm1(SB) + MOVD $168, R12 + B callbackasm1(SB) + MOVD $169, R12 + B callbackasm1(SB) + MOVD $170, R12 + B callbackasm1(SB) + MOVD $171, R12 + B callbackasm1(SB) + MOVD $172, R12 + B callbackasm1(SB) + MOVD $173, R12 + B callbackasm1(SB) + MOVD $174, R12 + B callbackasm1(SB) + MOVD $175, R12 + B callbackasm1(SB) + MOVD $176, R12 + B callbackasm1(SB) + MOVD $177, R12 + B callbackasm1(SB) + MOVD $178, R12 + B callbackasm1(SB) + MOVD $179, R12 + B callbackasm1(SB) + MOVD $180, R12 + B callbackasm1(SB) + MOVD $181, R12 + B callbackasm1(SB) + MOVD $182, R12 + B callbackasm1(SB) + MOVD $183, R12 + B callbackasm1(SB) + MOVD $184, R12 + B callbackasm1(SB) + MOVD $185, R12 + B callbackasm1(SB) + MOVD $186, R12 + B callbackasm1(SB) + MOVD $187, R12 + B callbackasm1(SB) + MOVD $188, R12 + B callbackasm1(SB) + MOVD $189, R12 + B callbackasm1(SB) + MOVD $190, R12 + B callbackasm1(SB) + MOVD $191, R12 + B callbackasm1(SB) + MOVD $192, R12 + B callbackasm1(SB) + MOVD $193, R12 + B callbackasm1(SB) + MOVD $194, R12 + B callbackasm1(SB) + MOVD $195, R12 + B callbackasm1(SB) + MOVD $196, R12 + B callbackasm1(SB) + MOVD $197, R12 + B callbackasm1(SB) + MOVD $198, R12 + B callbackasm1(SB) + MOVD $199, R12 + B callbackasm1(SB) + MOVD $200, R12 + B callbackasm1(SB) + MOVD $201, R12 + B callbackasm1(SB) + MOVD $202, R12 + B callbackasm1(SB) + MOVD $203, R12 + B callbackasm1(SB) + MOVD $204, R12 + B callbackasm1(SB) + MOVD $205, R12 + B callbackasm1(SB) + MOVD $206, R12 + B callbackasm1(SB) + MOVD $207, R12 + B callbackasm1(SB) + MOVD $208, R12 + B callbackasm1(SB) + MOVD $209, R12 + B callbackasm1(SB) + MOVD $210, R12 + B callbackasm1(SB) + MOVD $211, R12 + B callbackasm1(SB) + MOVD $212, R12 + B callbackasm1(SB) + MOVD $213, R12 + B callbackasm1(SB) + MOVD $214, R12 + B callbackasm1(SB) + MOVD $215, R12 + B callbackasm1(SB) + MOVD $216, R12 + B callbackasm1(SB) + MOVD $217, R12 + B callbackasm1(SB) + MOVD $218, R12 + B callbackasm1(SB) + MOVD $219, R12 + B callbackasm1(SB) + MOVD $220, R12 + B callbackasm1(SB) + MOVD $221, R12 + B callbackasm1(SB) + MOVD $222, R12 + B callbackasm1(SB) + MOVD $223, R12 + B callbackasm1(SB) + MOVD $224, R12 + B callbackasm1(SB) + MOVD $225, R12 + B callbackasm1(SB) + MOVD $226, R12 + B callbackasm1(SB) + MOVD $227, R12 + B callbackasm1(SB) + MOVD $228, R12 + B callbackasm1(SB) + MOVD $229, R12 + B callbackasm1(SB) + MOVD $230, R12 + B callbackasm1(SB) + MOVD $231, R12 + B callbackasm1(SB) + MOVD $232, R12 + B callbackasm1(SB) + MOVD $233, R12 + B callbackasm1(SB) + MOVD $234, R12 + B callbackasm1(SB) + MOVD $235, R12 + B callbackasm1(SB) + MOVD $236, R12 + B callbackasm1(SB) + MOVD $237, R12 + B callbackasm1(SB) + MOVD $238, R12 + B callbackasm1(SB) + MOVD $239, R12 + B callbackasm1(SB) + MOVD $240, R12 + B callbackasm1(SB) + MOVD $241, R12 + B callbackasm1(SB) + MOVD $242, R12 + B callbackasm1(SB) + MOVD $243, R12 + B callbackasm1(SB) + MOVD $244, R12 + B callbackasm1(SB) + MOVD $245, R12 + B callbackasm1(SB) + MOVD $246, R12 + B callbackasm1(SB) + MOVD $247, R12 + B callbackasm1(SB) + MOVD $248, R12 + B callbackasm1(SB) + MOVD $249, R12 + B callbackasm1(SB) + MOVD $250, R12 + B callbackasm1(SB) + MOVD $251, R12 + B callbackasm1(SB) + MOVD $252, R12 + B callbackasm1(SB) + MOVD $253, R12 + B callbackasm1(SB) + MOVD $254, R12 + B callbackasm1(SB) + MOVD $255, R12 + B callbackasm1(SB) + MOVD $256, R12 + B callbackasm1(SB) + MOVD $257, R12 + B callbackasm1(SB) + MOVD $258, R12 + B callbackasm1(SB) + MOVD $259, R12 + B callbackasm1(SB) + MOVD $260, R12 + B callbackasm1(SB) + MOVD $261, R12 + B callbackasm1(SB) + MOVD $262, R12 + B callbackasm1(SB) + MOVD $263, R12 + B callbackasm1(SB) + MOVD $264, R12 + B callbackasm1(SB) + MOVD $265, R12 + B callbackasm1(SB) + MOVD $266, R12 + B callbackasm1(SB) + MOVD $267, R12 + B callbackasm1(SB) + MOVD $268, R12 + B callbackasm1(SB) + MOVD $269, R12 + B callbackasm1(SB) + MOVD $270, R12 + B callbackasm1(SB) + MOVD $271, R12 + B callbackasm1(SB) + MOVD $272, R12 + B callbackasm1(SB) + MOVD $273, R12 + B callbackasm1(SB) + MOVD $274, R12 + B callbackasm1(SB) + MOVD $275, R12 + B callbackasm1(SB) + MOVD $276, R12 + B callbackasm1(SB) + MOVD $277, R12 + B callbackasm1(SB) + MOVD $278, R12 + B callbackasm1(SB) + MOVD $279, R12 + B callbackasm1(SB) + MOVD $280, R12 + B callbackasm1(SB) + MOVD $281, R12 + B callbackasm1(SB) + MOVD $282, R12 + B callbackasm1(SB) + MOVD $283, R12 + B callbackasm1(SB) + MOVD $284, R12 + B callbackasm1(SB) + MOVD $285, R12 + B callbackasm1(SB) + MOVD $286, R12 + B callbackasm1(SB) + MOVD $287, R12 + B callbackasm1(SB) + MOVD $288, R12 + B callbackasm1(SB) + MOVD $289, R12 + B callbackasm1(SB) + MOVD $290, R12 + B callbackasm1(SB) + MOVD $291, R12 + B callbackasm1(SB) + MOVD $292, R12 + B callbackasm1(SB) + MOVD $293, R12 + B callbackasm1(SB) + MOVD $294, R12 + B callbackasm1(SB) + MOVD $295, R12 + B callbackasm1(SB) + MOVD $296, R12 + B callbackasm1(SB) + MOVD $297, R12 + B callbackasm1(SB) + MOVD $298, R12 + B callbackasm1(SB) + MOVD $299, R12 + B callbackasm1(SB) + MOVD $300, R12 + B callbackasm1(SB) + MOVD $301, R12 + B callbackasm1(SB) + MOVD $302, R12 + B callbackasm1(SB) + MOVD $303, R12 + B callbackasm1(SB) + MOVD $304, R12 + B callbackasm1(SB) + MOVD $305, R12 + B callbackasm1(SB) + MOVD $306, R12 + B callbackasm1(SB) + MOVD $307, R12 + B callbackasm1(SB) + MOVD $308, R12 + B callbackasm1(SB) + MOVD $309, R12 + B callbackasm1(SB) + MOVD $310, R12 + B callbackasm1(SB) + MOVD $311, R12 + B callbackasm1(SB) + MOVD $312, R12 + B callbackasm1(SB) + MOVD $313, R12 + B callbackasm1(SB) + MOVD $314, R12 + B callbackasm1(SB) + MOVD $315, R12 + B callbackasm1(SB) + MOVD $316, R12 + B callbackasm1(SB) + MOVD $317, R12 + B callbackasm1(SB) + MOVD $318, R12 + B callbackasm1(SB) + MOVD $319, R12 + B callbackasm1(SB) + MOVD $320, R12 + B callbackasm1(SB) + MOVD $321, R12 + B callbackasm1(SB) + MOVD $322, R12 + B callbackasm1(SB) + MOVD $323, R12 + B callbackasm1(SB) + MOVD $324, R12 + B callbackasm1(SB) + MOVD $325, R12 + B callbackasm1(SB) + MOVD $326, R12 + B callbackasm1(SB) + MOVD $327, R12 + B callbackasm1(SB) + MOVD $328, R12 + B callbackasm1(SB) + MOVD $329, R12 + B callbackasm1(SB) + MOVD $330, R12 + B callbackasm1(SB) + MOVD $331, R12 + B callbackasm1(SB) + MOVD $332, R12 + B callbackasm1(SB) + MOVD $333, R12 + B callbackasm1(SB) + MOVD $334, R12 + B callbackasm1(SB) + MOVD $335, R12 + B callbackasm1(SB) + MOVD $336, R12 + B callbackasm1(SB) + MOVD $337, R12 + B callbackasm1(SB) + MOVD $338, R12 + B callbackasm1(SB) + MOVD $339, R12 + B callbackasm1(SB) + MOVD $340, R12 + B callbackasm1(SB) + MOVD $341, R12 + B callbackasm1(SB) + MOVD $342, R12 + B callbackasm1(SB) + MOVD $343, R12 + B callbackasm1(SB) + MOVD $344, R12 + B callbackasm1(SB) + MOVD $345, R12 + B callbackasm1(SB) + MOVD $346, R12 + B callbackasm1(SB) + MOVD $347, R12 + B callbackasm1(SB) + MOVD $348, R12 + B callbackasm1(SB) + MOVD $349, R12 + B callbackasm1(SB) + MOVD $350, R12 + B callbackasm1(SB) + MOVD $351, R12 + B callbackasm1(SB) + MOVD $352, R12 + B callbackasm1(SB) + MOVD $353, R12 + B callbackasm1(SB) + MOVD $354, R12 + B callbackasm1(SB) + MOVD $355, R12 + B callbackasm1(SB) + MOVD $356, R12 + B callbackasm1(SB) + MOVD $357, R12 + B callbackasm1(SB) + MOVD $358, R12 + B callbackasm1(SB) + MOVD $359, R12 + B callbackasm1(SB) + MOVD $360, R12 + B callbackasm1(SB) + MOVD $361, R12 + B callbackasm1(SB) + MOVD $362, R12 + B callbackasm1(SB) + MOVD $363, R12 + B callbackasm1(SB) + MOVD $364, R12 + B callbackasm1(SB) + MOVD $365, R12 + B callbackasm1(SB) + MOVD $366, R12 + B callbackasm1(SB) + MOVD $367, R12 + B callbackasm1(SB) + MOVD $368, R12 + B callbackasm1(SB) + MOVD $369, R12 + B callbackasm1(SB) + MOVD $370, R12 + B callbackasm1(SB) + MOVD $371, R12 + B callbackasm1(SB) + MOVD $372, R12 + B callbackasm1(SB) + MOVD $373, R12 + B callbackasm1(SB) + MOVD $374, R12 + B callbackasm1(SB) + MOVD $375, R12 + B callbackasm1(SB) + MOVD $376, R12 + B callbackasm1(SB) + MOVD $377, R12 + B callbackasm1(SB) + MOVD $378, R12 + B callbackasm1(SB) + MOVD $379, R12 + B callbackasm1(SB) + MOVD $380, R12 + B callbackasm1(SB) + MOVD $381, R12 + B callbackasm1(SB) + MOVD $382, R12 + B callbackasm1(SB) + MOVD $383, R12 + B callbackasm1(SB) + MOVD $384, R12 + B callbackasm1(SB) + MOVD $385, R12 + B callbackasm1(SB) + MOVD $386, R12 + B callbackasm1(SB) + MOVD $387, R12 + B callbackasm1(SB) + MOVD $388, R12 + B callbackasm1(SB) + MOVD $389, R12 + B callbackasm1(SB) + MOVD $390, R12 + B callbackasm1(SB) + MOVD $391, R12 + B callbackasm1(SB) + MOVD $392, R12 + B callbackasm1(SB) + MOVD $393, R12 + B callbackasm1(SB) + MOVD $394, R12 + B callbackasm1(SB) + MOVD $395, R12 + B callbackasm1(SB) + MOVD $396, R12 + B callbackasm1(SB) + MOVD $397, R12 + B callbackasm1(SB) + MOVD $398, R12 + B callbackasm1(SB) + MOVD $399, R12 + B callbackasm1(SB) + MOVD $400, R12 + B callbackasm1(SB) + MOVD $401, R12 + B callbackasm1(SB) + MOVD $402, R12 + B callbackasm1(SB) + MOVD $403, R12 + B callbackasm1(SB) + MOVD $404, R12 + B callbackasm1(SB) + MOVD $405, R12 + B callbackasm1(SB) + MOVD $406, R12 + B callbackasm1(SB) + MOVD $407, R12 + B callbackasm1(SB) + MOVD $408, R12 + B callbackasm1(SB) + MOVD $409, R12 + B callbackasm1(SB) + MOVD $410, R12 + B callbackasm1(SB) + MOVD $411, R12 + B callbackasm1(SB) + MOVD $412, R12 + B callbackasm1(SB) + MOVD $413, R12 + B callbackasm1(SB) + MOVD $414, R12 + B callbackasm1(SB) + MOVD $415, R12 + B callbackasm1(SB) + MOVD $416, R12 + B callbackasm1(SB) + MOVD $417, R12 + B callbackasm1(SB) + MOVD $418, R12 + B callbackasm1(SB) + MOVD $419, R12 + B callbackasm1(SB) + MOVD $420, R12 + B callbackasm1(SB) + MOVD $421, R12 + B callbackasm1(SB) + MOVD $422, R12 + B callbackasm1(SB) + MOVD $423, R12 + B callbackasm1(SB) + MOVD $424, R12 + B callbackasm1(SB) + MOVD $425, R12 + B callbackasm1(SB) + MOVD $426, R12 + B callbackasm1(SB) + MOVD $427, R12 + B callbackasm1(SB) + MOVD $428, R12 + B callbackasm1(SB) + MOVD $429, R12 + B callbackasm1(SB) + MOVD $430, R12 + B callbackasm1(SB) + MOVD $431, R12 + B callbackasm1(SB) + MOVD $432, R12 + B callbackasm1(SB) + MOVD $433, R12 + B callbackasm1(SB) + MOVD $434, R12 + B callbackasm1(SB) + MOVD $435, R12 + B callbackasm1(SB) + MOVD $436, R12 + B callbackasm1(SB) + MOVD $437, R12 + B callbackasm1(SB) + MOVD $438, R12 + B callbackasm1(SB) + MOVD $439, R12 + B callbackasm1(SB) + MOVD $440, R12 + B callbackasm1(SB) + MOVD $441, R12 + B callbackasm1(SB) + MOVD $442, R12 + B callbackasm1(SB) + MOVD $443, R12 + B callbackasm1(SB) + MOVD $444, R12 + B callbackasm1(SB) + MOVD $445, R12 + B callbackasm1(SB) + MOVD $446, R12 + B callbackasm1(SB) + MOVD $447, R12 + B callbackasm1(SB) + MOVD $448, R12 + B callbackasm1(SB) + MOVD $449, R12 + B callbackasm1(SB) + MOVD $450, R12 + B callbackasm1(SB) + MOVD $451, R12 + B callbackasm1(SB) + MOVD $452, R12 + B callbackasm1(SB) + MOVD $453, R12 + B callbackasm1(SB) + MOVD $454, R12 + B callbackasm1(SB) + MOVD $455, R12 + B callbackasm1(SB) + MOVD $456, R12 + B callbackasm1(SB) + MOVD $457, R12 + B callbackasm1(SB) + MOVD $458, R12 + B callbackasm1(SB) + MOVD $459, R12 + B callbackasm1(SB) + MOVD $460, R12 + B callbackasm1(SB) + MOVD $461, R12 + B callbackasm1(SB) + MOVD $462, R12 + B callbackasm1(SB) + MOVD $463, R12 + B callbackasm1(SB) + MOVD $464, R12 + B callbackasm1(SB) + MOVD $465, R12 + B callbackasm1(SB) + MOVD $466, R12 + B callbackasm1(SB) + MOVD $467, R12 + B callbackasm1(SB) + MOVD $468, R12 + B callbackasm1(SB) + MOVD $469, R12 + B callbackasm1(SB) + MOVD $470, R12 + B callbackasm1(SB) + MOVD $471, R12 + B callbackasm1(SB) + MOVD $472, R12 + B callbackasm1(SB) + MOVD $473, R12 + B callbackasm1(SB) + MOVD $474, R12 + B callbackasm1(SB) + MOVD $475, R12 + B callbackasm1(SB) + MOVD $476, R12 + B callbackasm1(SB) + MOVD $477, R12 + B callbackasm1(SB) + MOVD $478, R12 + B callbackasm1(SB) + MOVD $479, R12 + B callbackasm1(SB) + MOVD $480, R12 + B callbackasm1(SB) + MOVD $481, R12 + B callbackasm1(SB) + MOVD $482, R12 + B callbackasm1(SB) + MOVD $483, R12 + B callbackasm1(SB) + MOVD $484, R12 + B callbackasm1(SB) + MOVD $485, R12 + B callbackasm1(SB) + MOVD $486, R12 + B callbackasm1(SB) + MOVD $487, R12 + B callbackasm1(SB) + MOVD $488, R12 + B callbackasm1(SB) + MOVD $489, R12 + B callbackasm1(SB) + MOVD $490, R12 + B callbackasm1(SB) + MOVD $491, R12 + B callbackasm1(SB) + MOVD $492, R12 + B callbackasm1(SB) + MOVD $493, R12 + B callbackasm1(SB) + MOVD $494, R12 + B callbackasm1(SB) + MOVD $495, R12 + B callbackasm1(SB) + MOVD $496, R12 + B callbackasm1(SB) + MOVD $497, R12 + B callbackasm1(SB) + MOVD $498, R12 + B callbackasm1(SB) + MOVD $499, R12 + B callbackasm1(SB) + MOVD $500, R12 + B callbackasm1(SB) + MOVD $501, R12 + B callbackasm1(SB) + MOVD $502, R12 + B callbackasm1(SB) + MOVD $503, R12 + B callbackasm1(SB) + MOVD $504, R12 + B callbackasm1(SB) + MOVD $505, R12 + B callbackasm1(SB) + MOVD $506, R12 + B callbackasm1(SB) + MOVD $507, R12 + B callbackasm1(SB) + MOVD $508, R12 + B callbackasm1(SB) + MOVD $509, R12 + B callbackasm1(SB) + MOVD $510, R12 + B callbackasm1(SB) + MOVD $511, R12 + B callbackasm1(SB) + MOVD $512, R12 + B callbackasm1(SB) + MOVD $513, R12 + B callbackasm1(SB) + MOVD $514, R12 + B callbackasm1(SB) + MOVD $515, R12 + B callbackasm1(SB) + MOVD $516, R12 + B callbackasm1(SB) + MOVD $517, R12 + B callbackasm1(SB) + MOVD $518, R12 + B callbackasm1(SB) + MOVD $519, R12 + B callbackasm1(SB) + MOVD $520, R12 + B callbackasm1(SB) + MOVD $521, R12 + B callbackasm1(SB) + MOVD $522, R12 + B callbackasm1(SB) + MOVD $523, R12 + B callbackasm1(SB) + MOVD $524, R12 + B callbackasm1(SB) + MOVD $525, R12 + B callbackasm1(SB) + MOVD $526, R12 + B callbackasm1(SB) + MOVD $527, R12 + B callbackasm1(SB) + MOVD $528, R12 + B callbackasm1(SB) + MOVD $529, R12 + B callbackasm1(SB) + MOVD $530, R12 + B callbackasm1(SB) + MOVD $531, R12 + B callbackasm1(SB) + MOVD $532, R12 + B callbackasm1(SB) + MOVD $533, R12 + B callbackasm1(SB) + MOVD $534, R12 + B callbackasm1(SB) + MOVD $535, R12 + B callbackasm1(SB) + MOVD $536, R12 + B callbackasm1(SB) + MOVD $537, R12 + B callbackasm1(SB) + MOVD $538, R12 + B callbackasm1(SB) + MOVD $539, R12 + B callbackasm1(SB) + MOVD $540, R12 + B callbackasm1(SB) + MOVD $541, R12 + B callbackasm1(SB) + MOVD $542, R12 + B callbackasm1(SB) + MOVD $543, R12 + B callbackasm1(SB) + MOVD $544, R12 + B callbackasm1(SB) + MOVD $545, R12 + B callbackasm1(SB) + MOVD $546, R12 + B callbackasm1(SB) + MOVD $547, R12 + B callbackasm1(SB) + MOVD $548, R12 + B callbackasm1(SB) + MOVD $549, R12 + B callbackasm1(SB) + MOVD $550, R12 + B callbackasm1(SB) + MOVD $551, R12 + B callbackasm1(SB) + MOVD $552, R12 + B callbackasm1(SB) + MOVD $553, R12 + B callbackasm1(SB) + MOVD $554, R12 + B callbackasm1(SB) + MOVD $555, R12 + B callbackasm1(SB) + MOVD $556, R12 + B callbackasm1(SB) + MOVD $557, R12 + B callbackasm1(SB) + MOVD $558, R12 + B callbackasm1(SB) + MOVD $559, R12 + B callbackasm1(SB) + MOVD $560, R12 + B callbackasm1(SB) + MOVD $561, R12 + B callbackasm1(SB) + MOVD $562, R12 + B callbackasm1(SB) + MOVD $563, R12 + B callbackasm1(SB) + MOVD $564, R12 + B callbackasm1(SB) + MOVD $565, R12 + B callbackasm1(SB) + MOVD $566, R12 + B callbackasm1(SB) + MOVD $567, R12 + B callbackasm1(SB) + MOVD $568, R12 + B callbackasm1(SB) + MOVD $569, R12 + B callbackasm1(SB) + MOVD $570, R12 + B callbackasm1(SB) + MOVD $571, R12 + B callbackasm1(SB) + MOVD $572, R12 + B callbackasm1(SB) + MOVD $573, R12 + B callbackasm1(SB) + MOVD $574, R12 + B callbackasm1(SB) + MOVD $575, R12 + B callbackasm1(SB) + MOVD $576, R12 + B callbackasm1(SB) + MOVD $577, R12 + B callbackasm1(SB) + MOVD $578, R12 + B callbackasm1(SB) + MOVD $579, R12 + B callbackasm1(SB) + MOVD $580, R12 + B callbackasm1(SB) + MOVD $581, R12 + B callbackasm1(SB) + MOVD $582, R12 + B callbackasm1(SB) + MOVD $583, R12 + B callbackasm1(SB) + MOVD $584, R12 + B callbackasm1(SB) + MOVD $585, R12 + B callbackasm1(SB) + MOVD $586, R12 + B callbackasm1(SB) + MOVD $587, R12 + B callbackasm1(SB) + MOVD $588, R12 + B callbackasm1(SB) + MOVD $589, R12 + B callbackasm1(SB) + MOVD $590, R12 + B callbackasm1(SB) + MOVD $591, R12 + B callbackasm1(SB) + MOVD $592, R12 + B callbackasm1(SB) + MOVD $593, R12 + B callbackasm1(SB) + MOVD $594, R12 + B callbackasm1(SB) + MOVD $595, R12 + B callbackasm1(SB) + MOVD $596, R12 + B callbackasm1(SB) + MOVD $597, R12 + B callbackasm1(SB) + MOVD $598, R12 + B callbackasm1(SB) + MOVD $599, R12 + B callbackasm1(SB) + MOVD $600, R12 + B callbackasm1(SB) + MOVD $601, R12 + B callbackasm1(SB) + MOVD $602, R12 + B callbackasm1(SB) + MOVD $603, R12 + B callbackasm1(SB) + MOVD $604, R12 + B callbackasm1(SB) + MOVD $605, R12 + B callbackasm1(SB) + MOVD $606, R12 + B callbackasm1(SB) + MOVD $607, R12 + B callbackasm1(SB) + MOVD $608, R12 + B callbackasm1(SB) + MOVD $609, R12 + B callbackasm1(SB) + MOVD $610, R12 + B callbackasm1(SB) + MOVD $611, R12 + B callbackasm1(SB) + MOVD $612, R12 + B callbackasm1(SB) + MOVD $613, R12 + B callbackasm1(SB) + MOVD $614, R12 + B callbackasm1(SB) + MOVD $615, R12 + B callbackasm1(SB) + MOVD $616, R12 + B callbackasm1(SB) + MOVD $617, R12 + B callbackasm1(SB) + MOVD $618, R12 + B callbackasm1(SB) + MOVD $619, R12 + B callbackasm1(SB) + MOVD $620, R12 + B callbackasm1(SB) + MOVD $621, R12 + B callbackasm1(SB) + MOVD $622, R12 + B callbackasm1(SB) + MOVD $623, R12 + B callbackasm1(SB) + MOVD $624, R12 + B callbackasm1(SB) + MOVD $625, R12 + B callbackasm1(SB) + MOVD $626, R12 + B callbackasm1(SB) + MOVD $627, R12 + B callbackasm1(SB) + MOVD $628, R12 + B callbackasm1(SB) + MOVD $629, R12 + B callbackasm1(SB) + MOVD $630, R12 + B callbackasm1(SB) + MOVD $631, R12 + B callbackasm1(SB) + MOVD $632, R12 + B callbackasm1(SB) + MOVD $633, R12 + B callbackasm1(SB) + MOVD $634, R12 + B callbackasm1(SB) + MOVD $635, R12 + B callbackasm1(SB) + MOVD $636, R12 + B callbackasm1(SB) + MOVD $637, R12 + B callbackasm1(SB) + MOVD $638, R12 + B callbackasm1(SB) + MOVD $639, R12 + B callbackasm1(SB) + MOVD $640, R12 + B callbackasm1(SB) + MOVD $641, R12 + B callbackasm1(SB) + MOVD $642, R12 + B callbackasm1(SB) + MOVD $643, R12 + B callbackasm1(SB) + MOVD $644, R12 + B callbackasm1(SB) + MOVD $645, R12 + B callbackasm1(SB) + MOVD $646, R12 + B callbackasm1(SB) + MOVD $647, R12 + B callbackasm1(SB) + MOVD $648, R12 + B callbackasm1(SB) + MOVD $649, R12 + B callbackasm1(SB) + MOVD $650, R12 + B callbackasm1(SB) + MOVD $651, R12 + B callbackasm1(SB) + MOVD $652, R12 + B callbackasm1(SB) + MOVD $653, R12 + B callbackasm1(SB) + MOVD $654, R12 + B callbackasm1(SB) + MOVD $655, R12 + B callbackasm1(SB) + MOVD $656, R12 + B callbackasm1(SB) + MOVD $657, R12 + B callbackasm1(SB) + MOVD $658, R12 + B callbackasm1(SB) + MOVD $659, R12 + B callbackasm1(SB) + MOVD $660, R12 + B callbackasm1(SB) + MOVD $661, R12 + B callbackasm1(SB) + MOVD $662, R12 + B callbackasm1(SB) + MOVD $663, R12 + B callbackasm1(SB) + MOVD $664, R12 + B callbackasm1(SB) + MOVD $665, R12 + B callbackasm1(SB) + MOVD $666, R12 + B callbackasm1(SB) + MOVD $667, R12 + B callbackasm1(SB) + MOVD $668, R12 + B callbackasm1(SB) + MOVD $669, R12 + B callbackasm1(SB) + MOVD $670, R12 + B callbackasm1(SB) + MOVD $671, R12 + B callbackasm1(SB) + MOVD $672, R12 + B callbackasm1(SB) + MOVD $673, R12 + B callbackasm1(SB) + MOVD $674, R12 + B callbackasm1(SB) + MOVD $675, R12 + B callbackasm1(SB) + MOVD $676, R12 + B callbackasm1(SB) + MOVD $677, R12 + B callbackasm1(SB) + MOVD $678, R12 + B callbackasm1(SB) + MOVD $679, R12 + B callbackasm1(SB) + MOVD $680, R12 + B callbackasm1(SB) + MOVD $681, R12 + B callbackasm1(SB) + MOVD $682, R12 + B callbackasm1(SB) + MOVD $683, R12 + B callbackasm1(SB) + MOVD $684, R12 + B callbackasm1(SB) + MOVD $685, R12 + B callbackasm1(SB) + MOVD $686, R12 + B callbackasm1(SB) + MOVD $687, R12 + B callbackasm1(SB) + MOVD $688, R12 + B callbackasm1(SB) + MOVD $689, R12 + B callbackasm1(SB) + MOVD $690, R12 + B callbackasm1(SB) + MOVD $691, R12 + B callbackasm1(SB) + MOVD $692, R12 + B callbackasm1(SB) + MOVD $693, R12 + B callbackasm1(SB) + MOVD $694, R12 + B callbackasm1(SB) + MOVD $695, R12 + B callbackasm1(SB) + MOVD $696, R12 + B callbackasm1(SB) + MOVD $697, R12 + B callbackasm1(SB) + MOVD $698, R12 + B callbackasm1(SB) + MOVD $699, R12 + B callbackasm1(SB) + MOVD $700, R12 + B callbackasm1(SB) + MOVD $701, R12 + B callbackasm1(SB) + MOVD $702, R12 + B callbackasm1(SB) + MOVD $703, R12 + B callbackasm1(SB) + MOVD $704, R12 + B callbackasm1(SB) + MOVD $705, R12 + B callbackasm1(SB) + MOVD $706, R12 + B callbackasm1(SB) + MOVD $707, R12 + B callbackasm1(SB) + MOVD $708, R12 + B callbackasm1(SB) + MOVD $709, R12 + B callbackasm1(SB) + MOVD $710, R12 + B callbackasm1(SB) + MOVD $711, R12 + B callbackasm1(SB) + MOVD $712, R12 + B callbackasm1(SB) + MOVD $713, R12 + B callbackasm1(SB) + MOVD $714, R12 + B callbackasm1(SB) + MOVD $715, R12 + B callbackasm1(SB) + MOVD $716, R12 + B callbackasm1(SB) + MOVD $717, R12 + B callbackasm1(SB) + MOVD $718, R12 + B callbackasm1(SB) + MOVD $719, R12 + B callbackasm1(SB) + MOVD $720, R12 + B callbackasm1(SB) + MOVD $721, R12 + B callbackasm1(SB) + MOVD $722, R12 + B callbackasm1(SB) + MOVD $723, R12 + B callbackasm1(SB) + MOVD $724, R12 + B callbackasm1(SB) + MOVD $725, R12 + B callbackasm1(SB) + MOVD $726, R12 + B callbackasm1(SB) + MOVD $727, R12 + B callbackasm1(SB) + MOVD $728, R12 + B callbackasm1(SB) + MOVD $729, R12 + B callbackasm1(SB) + MOVD $730, R12 + B callbackasm1(SB) + MOVD $731, R12 + B callbackasm1(SB) + MOVD $732, R12 + B callbackasm1(SB) + MOVD $733, R12 + B callbackasm1(SB) + MOVD $734, R12 + B callbackasm1(SB) + MOVD $735, R12 + B callbackasm1(SB) + MOVD $736, R12 + B callbackasm1(SB) + MOVD $737, R12 + B callbackasm1(SB) + MOVD $738, R12 + B callbackasm1(SB) + MOVD $739, R12 + B callbackasm1(SB) + MOVD $740, R12 + B callbackasm1(SB) + MOVD $741, R12 + B callbackasm1(SB) + MOVD $742, R12 + B callbackasm1(SB) + MOVD $743, R12 + B callbackasm1(SB) + MOVD $744, R12 + B callbackasm1(SB) + MOVD $745, R12 + B callbackasm1(SB) + MOVD $746, R12 + B callbackasm1(SB) + MOVD $747, R12 + B callbackasm1(SB) + MOVD $748, R12 + B callbackasm1(SB) + MOVD $749, R12 + B callbackasm1(SB) + MOVD $750, R12 + B callbackasm1(SB) + MOVD $751, R12 + B callbackasm1(SB) + MOVD $752, R12 + B callbackasm1(SB) + MOVD $753, R12 + B callbackasm1(SB) + MOVD $754, R12 + B callbackasm1(SB) + MOVD $755, R12 + B callbackasm1(SB) + MOVD $756, R12 + B callbackasm1(SB) + MOVD $757, R12 + B callbackasm1(SB) + MOVD $758, R12 + B callbackasm1(SB) + MOVD $759, R12 + B callbackasm1(SB) + MOVD $760, R12 + B callbackasm1(SB) + MOVD $761, R12 + B callbackasm1(SB) + MOVD $762, R12 + B callbackasm1(SB) + MOVD $763, R12 + B callbackasm1(SB) + MOVD $764, R12 + B callbackasm1(SB) + MOVD $765, R12 + B callbackasm1(SB) + MOVD $766, R12 + B callbackasm1(SB) + MOVD $767, R12 + B callbackasm1(SB) + MOVD $768, R12 + B callbackasm1(SB) + MOVD $769, R12 + B callbackasm1(SB) + MOVD $770, R12 + B callbackasm1(SB) + MOVD $771, R12 + B callbackasm1(SB) + MOVD $772, R12 + B callbackasm1(SB) + MOVD $773, R12 + B callbackasm1(SB) + MOVD $774, R12 + B callbackasm1(SB) + MOVD $775, R12 + B callbackasm1(SB) + MOVD $776, R12 + B callbackasm1(SB) + MOVD $777, R12 + B callbackasm1(SB) + MOVD $778, R12 + B callbackasm1(SB) + MOVD $779, R12 + B callbackasm1(SB) + MOVD $780, R12 + B callbackasm1(SB) + MOVD $781, R12 + B callbackasm1(SB) + MOVD $782, R12 + B callbackasm1(SB) + MOVD $783, R12 + B callbackasm1(SB) + MOVD $784, R12 + B callbackasm1(SB) + MOVD $785, R12 + B callbackasm1(SB) + MOVD $786, R12 + B callbackasm1(SB) + MOVD $787, R12 + B callbackasm1(SB) + MOVD $788, R12 + B callbackasm1(SB) + MOVD $789, R12 + B callbackasm1(SB) + MOVD $790, R12 + B callbackasm1(SB) + MOVD $791, R12 + B callbackasm1(SB) + MOVD $792, R12 + B callbackasm1(SB) + MOVD $793, R12 + B callbackasm1(SB) + MOVD $794, R12 + B callbackasm1(SB) + MOVD $795, R12 + B callbackasm1(SB) + MOVD $796, R12 + B callbackasm1(SB) + MOVD $797, R12 + B callbackasm1(SB) + MOVD $798, R12 + B callbackasm1(SB) + MOVD $799, R12 + B callbackasm1(SB) + MOVD $800, R12 + B callbackasm1(SB) + MOVD $801, R12 + B callbackasm1(SB) + MOVD $802, R12 + B callbackasm1(SB) + MOVD $803, R12 + B callbackasm1(SB) + MOVD $804, R12 + B callbackasm1(SB) + MOVD $805, R12 + B callbackasm1(SB) + MOVD $806, R12 + B callbackasm1(SB) + MOVD $807, R12 + B callbackasm1(SB) + MOVD $808, R12 + B callbackasm1(SB) + MOVD $809, R12 + B callbackasm1(SB) + MOVD $810, R12 + B callbackasm1(SB) + MOVD $811, R12 + B callbackasm1(SB) + MOVD $812, R12 + B callbackasm1(SB) + MOVD $813, R12 + B callbackasm1(SB) + MOVD $814, R12 + B callbackasm1(SB) + MOVD $815, R12 + B callbackasm1(SB) + MOVD $816, R12 + B callbackasm1(SB) + MOVD $817, R12 + B callbackasm1(SB) + MOVD $818, R12 + B callbackasm1(SB) + MOVD $819, R12 + B callbackasm1(SB) + MOVD $820, R12 + B callbackasm1(SB) + MOVD $821, R12 + B callbackasm1(SB) + MOVD $822, R12 + B callbackasm1(SB) + MOVD $823, R12 + B callbackasm1(SB) + MOVD $824, R12 + B callbackasm1(SB) + MOVD $825, R12 + B callbackasm1(SB) + MOVD $826, R12 + B callbackasm1(SB) + MOVD $827, R12 + B callbackasm1(SB) + MOVD $828, R12 + B callbackasm1(SB) + MOVD $829, R12 + B callbackasm1(SB) + MOVD $830, R12 + B callbackasm1(SB) + MOVD $831, R12 + B callbackasm1(SB) + MOVD $832, R12 + B callbackasm1(SB) + MOVD $833, R12 + B callbackasm1(SB) + MOVD $834, R12 + B callbackasm1(SB) + MOVD $835, R12 + B callbackasm1(SB) + MOVD $836, R12 + B callbackasm1(SB) + MOVD $837, R12 + B callbackasm1(SB) + MOVD $838, R12 + B callbackasm1(SB) + MOVD $839, R12 + B callbackasm1(SB) + MOVD $840, R12 + B callbackasm1(SB) + MOVD $841, R12 + B callbackasm1(SB) + MOVD $842, R12 + B callbackasm1(SB) + MOVD $843, R12 + B callbackasm1(SB) + MOVD $844, R12 + B callbackasm1(SB) + MOVD $845, R12 + B callbackasm1(SB) + MOVD $846, R12 + B callbackasm1(SB) + MOVD $847, R12 + B callbackasm1(SB) + MOVD $848, R12 + B callbackasm1(SB) + MOVD $849, R12 + B callbackasm1(SB) + MOVD $850, R12 + B callbackasm1(SB) + MOVD $851, R12 + B callbackasm1(SB) + MOVD $852, R12 + B callbackasm1(SB) + MOVD $853, R12 + B callbackasm1(SB) + MOVD $854, R12 + B callbackasm1(SB) + MOVD $855, R12 + B callbackasm1(SB) + MOVD $856, R12 + B callbackasm1(SB) + MOVD $857, R12 + B callbackasm1(SB) + MOVD $858, R12 + B callbackasm1(SB) + MOVD $859, R12 + B callbackasm1(SB) + MOVD $860, R12 + B callbackasm1(SB) + MOVD $861, R12 + B callbackasm1(SB) + MOVD $862, R12 + B callbackasm1(SB) + MOVD $863, R12 + B callbackasm1(SB) + MOVD $864, R12 + B callbackasm1(SB) + MOVD $865, R12 + B callbackasm1(SB) + MOVD $866, R12 + B callbackasm1(SB) + MOVD $867, R12 + B callbackasm1(SB) + MOVD $868, R12 + B callbackasm1(SB) + MOVD $869, R12 + B callbackasm1(SB) + MOVD $870, R12 + B callbackasm1(SB) + MOVD $871, R12 + B callbackasm1(SB) + MOVD $872, R12 + B callbackasm1(SB) + MOVD $873, R12 + B callbackasm1(SB) + MOVD $874, R12 + B callbackasm1(SB) + MOVD $875, R12 + B callbackasm1(SB) + MOVD $876, R12 + B callbackasm1(SB) + MOVD $877, R12 + B callbackasm1(SB) + MOVD $878, R12 + B callbackasm1(SB) + MOVD $879, R12 + B callbackasm1(SB) + MOVD $880, R12 + B callbackasm1(SB) + MOVD $881, R12 + B callbackasm1(SB) + MOVD $882, R12 + B callbackasm1(SB) + MOVD $883, R12 + B callbackasm1(SB) + MOVD $884, R12 + B callbackasm1(SB) + MOVD $885, R12 + B callbackasm1(SB) + MOVD $886, R12 + B callbackasm1(SB) + MOVD $887, R12 + B callbackasm1(SB) + MOVD $888, R12 + B callbackasm1(SB) + MOVD $889, R12 + B callbackasm1(SB) + MOVD $890, R12 + B callbackasm1(SB) + MOVD $891, R12 + B callbackasm1(SB) + MOVD $892, R12 + B callbackasm1(SB) + MOVD $893, R12 + B callbackasm1(SB) + MOVD $894, R12 + B callbackasm1(SB) + MOVD $895, R12 + B callbackasm1(SB) + MOVD $896, R12 + B callbackasm1(SB) + MOVD $897, R12 + B callbackasm1(SB) + MOVD $898, R12 + B callbackasm1(SB) + MOVD $899, R12 + B callbackasm1(SB) + MOVD $900, R12 + B callbackasm1(SB) + MOVD $901, R12 + B callbackasm1(SB) + MOVD $902, R12 + B callbackasm1(SB) + MOVD $903, R12 + B callbackasm1(SB) + MOVD $904, R12 + B callbackasm1(SB) + MOVD $905, R12 + B callbackasm1(SB) + MOVD $906, R12 + B callbackasm1(SB) + MOVD $907, R12 + B callbackasm1(SB) + MOVD $908, R12 + B callbackasm1(SB) + MOVD $909, R12 + B callbackasm1(SB) + MOVD $910, R12 + B callbackasm1(SB) + MOVD $911, R12 + B callbackasm1(SB) + MOVD $912, R12 + B callbackasm1(SB) + MOVD $913, R12 + B callbackasm1(SB) + MOVD $914, R12 + B callbackasm1(SB) + MOVD $915, R12 + B callbackasm1(SB) + MOVD $916, R12 + B callbackasm1(SB) + MOVD $917, R12 + B callbackasm1(SB) + MOVD $918, R12 + B callbackasm1(SB) + MOVD $919, R12 + B callbackasm1(SB) + MOVD $920, R12 + B callbackasm1(SB) + MOVD $921, R12 + B callbackasm1(SB) + MOVD $922, R12 + B callbackasm1(SB) + MOVD $923, R12 + B callbackasm1(SB) + MOVD $924, R12 + B callbackasm1(SB) + MOVD $925, R12 + B callbackasm1(SB) + MOVD $926, R12 + B callbackasm1(SB) + MOVD $927, R12 + B callbackasm1(SB) + MOVD $928, R12 + B callbackasm1(SB) + MOVD $929, R12 + B callbackasm1(SB) + MOVD $930, R12 + B callbackasm1(SB) + MOVD $931, R12 + B callbackasm1(SB) + MOVD $932, R12 + B callbackasm1(SB) + MOVD $933, R12 + B callbackasm1(SB) + MOVD $934, R12 + B callbackasm1(SB) + MOVD $935, R12 + B callbackasm1(SB) + MOVD $936, R12 + B callbackasm1(SB) + MOVD $937, R12 + B callbackasm1(SB) + MOVD $938, R12 + B callbackasm1(SB) + MOVD $939, R12 + B callbackasm1(SB) + MOVD $940, R12 + B callbackasm1(SB) + MOVD $941, R12 + B callbackasm1(SB) + MOVD $942, R12 + B callbackasm1(SB) + MOVD $943, R12 + B callbackasm1(SB) + MOVD $944, R12 + B callbackasm1(SB) + MOVD $945, R12 + B callbackasm1(SB) + MOVD $946, R12 + B callbackasm1(SB) + MOVD $947, R12 + B callbackasm1(SB) + MOVD $948, R12 + B callbackasm1(SB) + MOVD $949, R12 + B callbackasm1(SB) + MOVD $950, R12 + B callbackasm1(SB) + MOVD $951, R12 + B callbackasm1(SB) + MOVD $952, R12 + B callbackasm1(SB) + MOVD $953, R12 + B callbackasm1(SB) + MOVD $954, R12 + B callbackasm1(SB) + MOVD $955, R12 + B callbackasm1(SB) + MOVD $956, R12 + B callbackasm1(SB) + MOVD $957, R12 + B callbackasm1(SB) + MOVD $958, R12 + B callbackasm1(SB) + MOVD $959, R12 + B callbackasm1(SB) + MOVD $960, R12 + B callbackasm1(SB) + MOVD $961, R12 + B callbackasm1(SB) + MOVD $962, R12 + B callbackasm1(SB) + MOVD $963, R12 + B callbackasm1(SB) + MOVD $964, R12 + B callbackasm1(SB) + MOVD $965, R12 + B callbackasm1(SB) + MOVD $966, R12 + B callbackasm1(SB) + MOVD $967, R12 + B callbackasm1(SB) + MOVD $968, R12 + B callbackasm1(SB) + MOVD $969, R12 + B callbackasm1(SB) + MOVD $970, R12 + B callbackasm1(SB) + MOVD $971, R12 + B callbackasm1(SB) + MOVD $972, R12 + B callbackasm1(SB) + MOVD $973, R12 + B callbackasm1(SB) + MOVD $974, R12 + B callbackasm1(SB) + MOVD $975, R12 + B callbackasm1(SB) + MOVD $976, R12 + B callbackasm1(SB) + MOVD $977, R12 + B callbackasm1(SB) + MOVD $978, R12 + B callbackasm1(SB) + MOVD $979, R12 + B callbackasm1(SB) + MOVD $980, R12 + B callbackasm1(SB) + MOVD $981, R12 + B callbackasm1(SB) + MOVD $982, R12 + B callbackasm1(SB) + MOVD $983, R12 + B callbackasm1(SB) + MOVD $984, R12 + B callbackasm1(SB) + MOVD $985, R12 + B callbackasm1(SB) + MOVD $986, R12 + B callbackasm1(SB) + MOVD $987, R12 + B callbackasm1(SB) + MOVD $988, R12 + B callbackasm1(SB) + MOVD $989, R12 + B callbackasm1(SB) + MOVD $990, R12 + B callbackasm1(SB) + MOVD $991, R12 + B callbackasm1(SB) + MOVD $992, R12 + B callbackasm1(SB) + MOVD $993, R12 + B callbackasm1(SB) + MOVD $994, R12 + B callbackasm1(SB) + MOVD $995, R12 + B callbackasm1(SB) + MOVD $996, R12 + B callbackasm1(SB) + MOVD $997, R12 + B callbackasm1(SB) + MOVD $998, R12 + B callbackasm1(SB) + MOVD $999, R12 + B callbackasm1(SB) + MOVD $1000, R12 + B callbackasm1(SB) + MOVD $1001, R12 + B callbackasm1(SB) + MOVD $1002, R12 + B callbackasm1(SB) + MOVD $1003, R12 + B callbackasm1(SB) + MOVD $1004, R12 + B callbackasm1(SB) + MOVD $1005, R12 + B callbackasm1(SB) + MOVD $1006, R12 + B callbackasm1(SB) + MOVD $1007, R12 + B callbackasm1(SB) + MOVD $1008, R12 + B callbackasm1(SB) + MOVD $1009, R12 + B callbackasm1(SB) + MOVD $1010, R12 + B callbackasm1(SB) + MOVD $1011, R12 + B callbackasm1(SB) + MOVD $1012, R12 + B callbackasm1(SB) + MOVD $1013, R12 + B callbackasm1(SB) + MOVD $1014, R12 + B callbackasm1(SB) + MOVD $1015, R12 + B callbackasm1(SB) + MOVD $1016, R12 + B callbackasm1(SB) + MOVD $1017, R12 + B callbackasm1(SB) + MOVD $1018, R12 + B callbackasm1(SB) + MOVD $1019, R12 + B callbackasm1(SB) + MOVD $1020, R12 + B callbackasm1(SB) + MOVD $1021, R12 + B callbackasm1(SB) + MOVD $1022, R12 + B callbackasm1(SB) + MOVD $1023, R12 + B callbackasm1(SB) + MOVD $1024, R12 + B callbackasm1(SB) + MOVD $1025, R12 + B callbackasm1(SB) + MOVD $1026, R12 + B callbackasm1(SB) + MOVD $1027, R12 + B callbackasm1(SB) + MOVD $1028, R12 + B callbackasm1(SB) + MOVD $1029, R12 + B callbackasm1(SB) + MOVD $1030, R12 + B callbackasm1(SB) + MOVD $1031, R12 + B callbackasm1(SB) + MOVD $1032, R12 + B callbackasm1(SB) + MOVD $1033, R12 + B callbackasm1(SB) + MOVD $1034, R12 + B callbackasm1(SB) + MOVD $1035, R12 + B callbackasm1(SB) + MOVD $1036, R12 + B callbackasm1(SB) + MOVD $1037, R12 + B callbackasm1(SB) + MOVD $1038, R12 + B callbackasm1(SB) + MOVD $1039, R12 + B callbackasm1(SB) + MOVD $1040, R12 + B callbackasm1(SB) + MOVD $1041, R12 + B callbackasm1(SB) + MOVD $1042, R12 + B callbackasm1(SB) + MOVD $1043, R12 + B callbackasm1(SB) + MOVD $1044, R12 + B callbackasm1(SB) + MOVD $1045, R12 + B callbackasm1(SB) + MOVD $1046, R12 + B callbackasm1(SB) + MOVD $1047, R12 + B callbackasm1(SB) + MOVD $1048, R12 + B callbackasm1(SB) + MOVD $1049, R12 + B callbackasm1(SB) + MOVD $1050, R12 + B callbackasm1(SB) + MOVD $1051, R12 + B callbackasm1(SB) + MOVD $1052, R12 + B callbackasm1(SB) + MOVD $1053, R12 + B callbackasm1(SB) + MOVD $1054, R12 + B callbackasm1(SB) + MOVD $1055, R12 + B callbackasm1(SB) + MOVD $1056, R12 + B callbackasm1(SB) + MOVD $1057, R12 + B callbackasm1(SB) + MOVD $1058, R12 + B callbackasm1(SB) + MOVD $1059, R12 + B callbackasm1(SB) + MOVD $1060, R12 + B callbackasm1(SB) + MOVD $1061, R12 + B callbackasm1(SB) + MOVD $1062, R12 + B callbackasm1(SB) + MOVD $1063, R12 + B callbackasm1(SB) + MOVD $1064, R12 + B callbackasm1(SB) + MOVD $1065, R12 + B callbackasm1(SB) + MOVD $1066, R12 + B callbackasm1(SB) + MOVD $1067, R12 + B callbackasm1(SB) + MOVD $1068, R12 + B callbackasm1(SB) + MOVD $1069, R12 + B callbackasm1(SB) + MOVD $1070, R12 + B callbackasm1(SB) + MOVD $1071, R12 + B callbackasm1(SB) + MOVD $1072, R12 + B callbackasm1(SB) + MOVD $1073, R12 + B callbackasm1(SB) + MOVD $1074, R12 + B callbackasm1(SB) + MOVD $1075, R12 + B callbackasm1(SB) + MOVD $1076, R12 + B callbackasm1(SB) + MOVD $1077, R12 + B callbackasm1(SB) + MOVD $1078, R12 + B callbackasm1(SB) + MOVD $1079, R12 + B callbackasm1(SB) + MOVD $1080, R12 + B callbackasm1(SB) + MOVD $1081, R12 + B callbackasm1(SB) + MOVD $1082, R12 + B callbackasm1(SB) + MOVD $1083, R12 + B callbackasm1(SB) + MOVD $1084, R12 + B callbackasm1(SB) + MOVD $1085, R12 + B callbackasm1(SB) + MOVD $1086, R12 + B callbackasm1(SB) + MOVD $1087, R12 + B callbackasm1(SB) + MOVD $1088, R12 + B callbackasm1(SB) + MOVD $1089, R12 + B callbackasm1(SB) + MOVD $1090, R12 + B callbackasm1(SB) + MOVD $1091, R12 + B callbackasm1(SB) + MOVD $1092, R12 + B callbackasm1(SB) + MOVD $1093, R12 + B callbackasm1(SB) + MOVD $1094, R12 + B callbackasm1(SB) + MOVD $1095, R12 + B callbackasm1(SB) + MOVD $1096, R12 + B callbackasm1(SB) + MOVD $1097, R12 + B callbackasm1(SB) + MOVD $1098, R12 + B callbackasm1(SB) + MOVD $1099, R12 + B callbackasm1(SB) + MOVD $1100, R12 + B callbackasm1(SB) + MOVD $1101, R12 + B callbackasm1(SB) + MOVD $1102, R12 + B callbackasm1(SB) + MOVD $1103, R12 + B callbackasm1(SB) + MOVD $1104, R12 + B callbackasm1(SB) + MOVD $1105, R12 + B callbackasm1(SB) + MOVD $1106, R12 + B callbackasm1(SB) + MOVD $1107, R12 + B callbackasm1(SB) + MOVD $1108, R12 + B callbackasm1(SB) + MOVD $1109, R12 + B callbackasm1(SB) + MOVD $1110, R12 + B callbackasm1(SB) + MOVD $1111, R12 + B callbackasm1(SB) + MOVD $1112, R12 + B callbackasm1(SB) + MOVD $1113, R12 + B callbackasm1(SB) + MOVD $1114, R12 + B callbackasm1(SB) + MOVD $1115, R12 + B callbackasm1(SB) + MOVD $1116, R12 + B callbackasm1(SB) + MOVD $1117, R12 + B callbackasm1(SB) + MOVD $1118, R12 + B callbackasm1(SB) + MOVD $1119, R12 + B callbackasm1(SB) + MOVD $1120, R12 + B callbackasm1(SB) + MOVD $1121, R12 + B callbackasm1(SB) + MOVD $1122, R12 + B callbackasm1(SB) + MOVD $1123, R12 + B callbackasm1(SB) + MOVD $1124, R12 + B callbackasm1(SB) + MOVD $1125, R12 + B callbackasm1(SB) + MOVD $1126, R12 + B callbackasm1(SB) + MOVD $1127, R12 + B callbackasm1(SB) + MOVD $1128, R12 + B callbackasm1(SB) + MOVD $1129, R12 + B callbackasm1(SB) + MOVD $1130, R12 + B callbackasm1(SB) + MOVD $1131, R12 + B callbackasm1(SB) + MOVD $1132, R12 + B callbackasm1(SB) + MOVD $1133, R12 + B callbackasm1(SB) + MOVD $1134, R12 + B callbackasm1(SB) + MOVD $1135, R12 + B callbackasm1(SB) + MOVD $1136, R12 + B callbackasm1(SB) + MOVD $1137, R12 + B callbackasm1(SB) + MOVD $1138, R12 + B callbackasm1(SB) + MOVD $1139, R12 + B callbackasm1(SB) + MOVD $1140, R12 + B callbackasm1(SB) + MOVD $1141, R12 + B callbackasm1(SB) + MOVD $1142, R12 + B callbackasm1(SB) + MOVD $1143, R12 + B callbackasm1(SB) + MOVD $1144, R12 + B callbackasm1(SB) + MOVD $1145, R12 + B callbackasm1(SB) + MOVD $1146, R12 + B callbackasm1(SB) + MOVD $1147, R12 + B callbackasm1(SB) + MOVD $1148, R12 + B callbackasm1(SB) + MOVD $1149, R12 + B callbackasm1(SB) + MOVD $1150, R12 + B callbackasm1(SB) + MOVD $1151, R12 + B callbackasm1(SB) + MOVD $1152, R12 + B callbackasm1(SB) + MOVD $1153, R12 + B callbackasm1(SB) + MOVD $1154, R12 + B callbackasm1(SB) + MOVD $1155, R12 + B callbackasm1(SB) + MOVD $1156, R12 + B callbackasm1(SB) + MOVD $1157, R12 + B callbackasm1(SB) + MOVD $1158, R12 + B callbackasm1(SB) + MOVD $1159, R12 + B callbackasm1(SB) + MOVD $1160, R12 + B callbackasm1(SB) + MOVD $1161, R12 + B callbackasm1(SB) + MOVD $1162, R12 + B callbackasm1(SB) + MOVD $1163, R12 + B callbackasm1(SB) + MOVD $1164, R12 + B callbackasm1(SB) + MOVD $1165, R12 + B callbackasm1(SB) + MOVD $1166, R12 + B callbackasm1(SB) + MOVD $1167, R12 + B callbackasm1(SB) + MOVD $1168, R12 + B callbackasm1(SB) + MOVD $1169, R12 + B callbackasm1(SB) + MOVD $1170, R12 + B callbackasm1(SB) + MOVD $1171, R12 + B callbackasm1(SB) + MOVD $1172, R12 + B callbackasm1(SB) + MOVD $1173, R12 + B callbackasm1(SB) + MOVD $1174, R12 + B callbackasm1(SB) + MOVD $1175, R12 + B callbackasm1(SB) + MOVD $1176, R12 + B callbackasm1(SB) + MOVD $1177, R12 + B callbackasm1(SB) + MOVD $1178, R12 + B callbackasm1(SB) + MOVD $1179, R12 + B callbackasm1(SB) + MOVD $1180, R12 + B callbackasm1(SB) + MOVD $1181, R12 + B callbackasm1(SB) + MOVD $1182, R12 + B callbackasm1(SB) + MOVD $1183, R12 + B callbackasm1(SB) + MOVD $1184, R12 + B callbackasm1(SB) + MOVD $1185, R12 + B callbackasm1(SB) + MOVD $1186, R12 + B callbackasm1(SB) + MOVD $1187, R12 + B callbackasm1(SB) + MOVD $1188, R12 + B callbackasm1(SB) + MOVD $1189, R12 + B callbackasm1(SB) + MOVD $1190, R12 + B callbackasm1(SB) + MOVD $1191, R12 + B callbackasm1(SB) + MOVD $1192, R12 + B callbackasm1(SB) + MOVD $1193, R12 + B callbackasm1(SB) + MOVD $1194, R12 + B callbackasm1(SB) + MOVD $1195, R12 + B callbackasm1(SB) + MOVD $1196, R12 + B callbackasm1(SB) + MOVD $1197, R12 + B callbackasm1(SB) + MOVD $1198, R12 + B callbackasm1(SB) + MOVD $1199, R12 + B callbackasm1(SB) + MOVD $1200, R12 + B callbackasm1(SB) + MOVD $1201, R12 + B callbackasm1(SB) + MOVD $1202, R12 + B callbackasm1(SB) + MOVD $1203, R12 + B callbackasm1(SB) + MOVD $1204, R12 + B callbackasm1(SB) + MOVD $1205, R12 + B callbackasm1(SB) + MOVD $1206, R12 + B callbackasm1(SB) + MOVD $1207, R12 + B callbackasm1(SB) + MOVD $1208, R12 + B callbackasm1(SB) + MOVD $1209, R12 + B callbackasm1(SB) + MOVD $1210, R12 + B callbackasm1(SB) + MOVD $1211, R12 + B callbackasm1(SB) + MOVD $1212, R12 + B callbackasm1(SB) + MOVD $1213, R12 + B callbackasm1(SB) + MOVD $1214, R12 + B callbackasm1(SB) + MOVD $1215, R12 + B callbackasm1(SB) + MOVD $1216, R12 + B callbackasm1(SB) + MOVD $1217, R12 + B callbackasm1(SB) + MOVD $1218, R12 + B callbackasm1(SB) + MOVD $1219, R12 + B callbackasm1(SB) + MOVD $1220, R12 + B callbackasm1(SB) + MOVD $1221, R12 + B callbackasm1(SB) + MOVD $1222, R12 + B callbackasm1(SB) + MOVD $1223, R12 + B callbackasm1(SB) + MOVD $1224, R12 + B callbackasm1(SB) + MOVD $1225, R12 + B callbackasm1(SB) + MOVD $1226, R12 + B callbackasm1(SB) + MOVD $1227, R12 + B callbackasm1(SB) + MOVD $1228, R12 + B callbackasm1(SB) + MOVD $1229, R12 + B callbackasm1(SB) + MOVD $1230, R12 + B callbackasm1(SB) + MOVD $1231, R12 + B callbackasm1(SB) + MOVD $1232, R12 + B callbackasm1(SB) + MOVD $1233, R12 + B callbackasm1(SB) + MOVD $1234, R12 + B callbackasm1(SB) + MOVD $1235, R12 + B callbackasm1(SB) + MOVD $1236, R12 + B callbackasm1(SB) + MOVD $1237, R12 + B callbackasm1(SB) + MOVD $1238, R12 + B callbackasm1(SB) + MOVD $1239, R12 + B callbackasm1(SB) + MOVD $1240, R12 + B callbackasm1(SB) + MOVD $1241, R12 + B callbackasm1(SB) + MOVD $1242, R12 + B callbackasm1(SB) + MOVD $1243, R12 + B callbackasm1(SB) + MOVD $1244, R12 + B callbackasm1(SB) + MOVD $1245, R12 + B callbackasm1(SB) + MOVD $1246, R12 + B callbackasm1(SB) + MOVD $1247, R12 + B callbackasm1(SB) + MOVD $1248, R12 + B callbackasm1(SB) + MOVD $1249, R12 + B callbackasm1(SB) + MOVD $1250, R12 + B callbackasm1(SB) + MOVD $1251, R12 + B callbackasm1(SB) + MOVD $1252, R12 + B callbackasm1(SB) + MOVD $1253, R12 + B callbackasm1(SB) + MOVD $1254, R12 + B callbackasm1(SB) + MOVD $1255, R12 + B callbackasm1(SB) + MOVD $1256, R12 + B callbackasm1(SB) + MOVD $1257, R12 + B callbackasm1(SB) + MOVD $1258, R12 + B callbackasm1(SB) + MOVD $1259, R12 + B callbackasm1(SB) + MOVD $1260, R12 + B callbackasm1(SB) + MOVD $1261, R12 + B callbackasm1(SB) + MOVD $1262, R12 + B callbackasm1(SB) + MOVD $1263, R12 + B callbackasm1(SB) + MOVD $1264, R12 + B callbackasm1(SB) + MOVD $1265, R12 + B callbackasm1(SB) + MOVD $1266, R12 + B callbackasm1(SB) + MOVD $1267, R12 + B callbackasm1(SB) + MOVD $1268, R12 + B callbackasm1(SB) + MOVD $1269, R12 + B callbackasm1(SB) + MOVD $1270, R12 + B callbackasm1(SB) + MOVD $1271, R12 + B callbackasm1(SB) + MOVD $1272, R12 + B callbackasm1(SB) + MOVD $1273, R12 + B callbackasm1(SB) + MOVD $1274, R12 + B callbackasm1(SB) + MOVD $1275, R12 + B callbackasm1(SB) + MOVD $1276, R12 + B callbackasm1(SB) + MOVD $1277, R12 + B callbackasm1(SB) + MOVD $1278, R12 + B callbackasm1(SB) + MOVD $1279, R12 + B callbackasm1(SB) + MOVD $1280, R12 + B callbackasm1(SB) + MOVD $1281, R12 + B callbackasm1(SB) + MOVD $1282, R12 + B callbackasm1(SB) + MOVD $1283, R12 + B callbackasm1(SB) + MOVD $1284, R12 + B callbackasm1(SB) + MOVD $1285, R12 + B callbackasm1(SB) + MOVD $1286, R12 + B callbackasm1(SB) + MOVD $1287, R12 + B callbackasm1(SB) + MOVD $1288, R12 + B callbackasm1(SB) + MOVD $1289, R12 + B callbackasm1(SB) + MOVD $1290, R12 + B callbackasm1(SB) + MOVD $1291, R12 + B callbackasm1(SB) + MOVD $1292, R12 + B callbackasm1(SB) + MOVD $1293, R12 + B callbackasm1(SB) + MOVD $1294, R12 + B callbackasm1(SB) + MOVD $1295, R12 + B callbackasm1(SB) + MOVD $1296, R12 + B callbackasm1(SB) + MOVD $1297, R12 + B callbackasm1(SB) + MOVD $1298, R12 + B callbackasm1(SB) + MOVD $1299, R12 + B callbackasm1(SB) + MOVD $1300, R12 + B callbackasm1(SB) + MOVD $1301, R12 + B callbackasm1(SB) + MOVD $1302, R12 + B callbackasm1(SB) + MOVD $1303, R12 + B callbackasm1(SB) + MOVD $1304, R12 + B callbackasm1(SB) + MOVD $1305, R12 + B callbackasm1(SB) + MOVD $1306, R12 + B callbackasm1(SB) + MOVD $1307, R12 + B callbackasm1(SB) + MOVD $1308, R12 + B callbackasm1(SB) + MOVD $1309, R12 + B callbackasm1(SB) + MOVD $1310, R12 + B callbackasm1(SB) + MOVD $1311, R12 + B callbackasm1(SB) + MOVD $1312, R12 + B callbackasm1(SB) + MOVD $1313, R12 + B callbackasm1(SB) + MOVD $1314, R12 + B callbackasm1(SB) + MOVD $1315, R12 + B callbackasm1(SB) + MOVD $1316, R12 + B callbackasm1(SB) + MOVD $1317, R12 + B callbackasm1(SB) + MOVD $1318, R12 + B callbackasm1(SB) + MOVD $1319, R12 + B callbackasm1(SB) + MOVD $1320, R12 + B callbackasm1(SB) + MOVD $1321, R12 + B callbackasm1(SB) + MOVD $1322, R12 + B callbackasm1(SB) + MOVD $1323, R12 + B callbackasm1(SB) + MOVD $1324, R12 + B callbackasm1(SB) + MOVD $1325, R12 + B callbackasm1(SB) + MOVD $1326, R12 + B callbackasm1(SB) + MOVD $1327, R12 + B callbackasm1(SB) + MOVD $1328, R12 + B callbackasm1(SB) + MOVD $1329, R12 + B callbackasm1(SB) + MOVD $1330, R12 + B callbackasm1(SB) + MOVD $1331, R12 + B callbackasm1(SB) + MOVD $1332, R12 + B callbackasm1(SB) + MOVD $1333, R12 + B callbackasm1(SB) + MOVD $1334, R12 + B callbackasm1(SB) + MOVD $1335, R12 + B callbackasm1(SB) + MOVD $1336, R12 + B callbackasm1(SB) + MOVD $1337, R12 + B callbackasm1(SB) + MOVD $1338, R12 + B callbackasm1(SB) + MOVD $1339, R12 + B callbackasm1(SB) + MOVD $1340, R12 + B callbackasm1(SB) + MOVD $1341, R12 + B callbackasm1(SB) + MOVD $1342, R12 + B callbackasm1(SB) + MOVD $1343, R12 + B callbackasm1(SB) + MOVD $1344, R12 + B callbackasm1(SB) + MOVD $1345, R12 + B callbackasm1(SB) + MOVD $1346, R12 + B callbackasm1(SB) + MOVD $1347, R12 + B callbackasm1(SB) + MOVD $1348, R12 + B callbackasm1(SB) + MOVD $1349, R12 + B callbackasm1(SB) + MOVD $1350, R12 + B callbackasm1(SB) + MOVD $1351, R12 + B callbackasm1(SB) + MOVD $1352, R12 + B callbackasm1(SB) + MOVD $1353, R12 + B callbackasm1(SB) + MOVD $1354, R12 + B callbackasm1(SB) + MOVD $1355, R12 + B callbackasm1(SB) + MOVD $1356, R12 + B callbackasm1(SB) + MOVD $1357, R12 + B callbackasm1(SB) + MOVD $1358, R12 + B callbackasm1(SB) + MOVD $1359, R12 + B callbackasm1(SB) + MOVD $1360, R12 + B callbackasm1(SB) + MOVD $1361, R12 + B callbackasm1(SB) + MOVD $1362, R12 + B callbackasm1(SB) + MOVD $1363, R12 + B callbackasm1(SB) + MOVD $1364, R12 + B callbackasm1(SB) + MOVD $1365, R12 + B callbackasm1(SB) + MOVD $1366, R12 + B callbackasm1(SB) + MOVD $1367, R12 + B callbackasm1(SB) + MOVD $1368, R12 + B callbackasm1(SB) + MOVD $1369, R12 + B callbackasm1(SB) + MOVD $1370, R12 + B callbackasm1(SB) + MOVD $1371, R12 + B callbackasm1(SB) + MOVD $1372, R12 + B callbackasm1(SB) + MOVD $1373, R12 + B callbackasm1(SB) + MOVD $1374, R12 + B callbackasm1(SB) + MOVD $1375, R12 + B callbackasm1(SB) + MOVD $1376, R12 + B callbackasm1(SB) + MOVD $1377, R12 + B callbackasm1(SB) + MOVD $1378, R12 + B callbackasm1(SB) + MOVD $1379, R12 + B callbackasm1(SB) + MOVD $1380, R12 + B callbackasm1(SB) + MOVD $1381, R12 + B callbackasm1(SB) + MOVD $1382, R12 + B callbackasm1(SB) + MOVD $1383, R12 + B callbackasm1(SB) + MOVD $1384, R12 + B callbackasm1(SB) + MOVD $1385, R12 + B callbackasm1(SB) + MOVD $1386, R12 + B callbackasm1(SB) + MOVD $1387, R12 + B callbackasm1(SB) + MOVD $1388, R12 + B callbackasm1(SB) + MOVD $1389, R12 + B callbackasm1(SB) + MOVD $1390, R12 + B callbackasm1(SB) + MOVD $1391, R12 + B callbackasm1(SB) + MOVD $1392, R12 + B callbackasm1(SB) + MOVD $1393, R12 + B callbackasm1(SB) + MOVD $1394, R12 + B callbackasm1(SB) + MOVD $1395, R12 + B callbackasm1(SB) + MOVD $1396, R12 + B callbackasm1(SB) + MOVD $1397, R12 + B callbackasm1(SB) + MOVD $1398, R12 + B callbackasm1(SB) + MOVD $1399, R12 + B callbackasm1(SB) + MOVD $1400, R12 + B callbackasm1(SB) + MOVD $1401, R12 + B callbackasm1(SB) + MOVD $1402, R12 + B callbackasm1(SB) + MOVD $1403, R12 + B callbackasm1(SB) + MOVD $1404, R12 + B callbackasm1(SB) + MOVD $1405, R12 + B callbackasm1(SB) + MOVD $1406, R12 + B callbackasm1(SB) + MOVD $1407, R12 + B callbackasm1(SB) + MOVD $1408, R12 + B callbackasm1(SB) + MOVD $1409, R12 + B callbackasm1(SB) + MOVD $1410, R12 + B callbackasm1(SB) + MOVD $1411, R12 + B callbackasm1(SB) + MOVD $1412, R12 + B callbackasm1(SB) + MOVD $1413, R12 + B callbackasm1(SB) + MOVD $1414, R12 + B callbackasm1(SB) + MOVD $1415, R12 + B callbackasm1(SB) + MOVD $1416, R12 + B callbackasm1(SB) + MOVD $1417, R12 + B callbackasm1(SB) + MOVD $1418, R12 + B callbackasm1(SB) + MOVD $1419, R12 + B callbackasm1(SB) + MOVD $1420, R12 + B callbackasm1(SB) + MOVD $1421, R12 + B callbackasm1(SB) + MOVD $1422, R12 + B callbackasm1(SB) + MOVD $1423, R12 + B callbackasm1(SB) + MOVD $1424, R12 + B callbackasm1(SB) + MOVD $1425, R12 + B callbackasm1(SB) + MOVD $1426, R12 + B callbackasm1(SB) + MOVD $1427, R12 + B callbackasm1(SB) + MOVD $1428, R12 + B callbackasm1(SB) + MOVD $1429, R12 + B callbackasm1(SB) + MOVD $1430, R12 + B callbackasm1(SB) + MOVD $1431, R12 + B callbackasm1(SB) + MOVD $1432, R12 + B callbackasm1(SB) + MOVD $1433, R12 + B callbackasm1(SB) + MOVD $1434, R12 + B callbackasm1(SB) + MOVD $1435, R12 + B callbackasm1(SB) + MOVD $1436, R12 + B callbackasm1(SB) + MOVD $1437, R12 + B callbackasm1(SB) + MOVD $1438, R12 + B callbackasm1(SB) + MOVD $1439, R12 + B callbackasm1(SB) + MOVD $1440, R12 + B callbackasm1(SB) + MOVD $1441, R12 + B callbackasm1(SB) + MOVD $1442, R12 + B callbackasm1(SB) + MOVD $1443, R12 + B callbackasm1(SB) + MOVD $1444, R12 + B callbackasm1(SB) + MOVD $1445, R12 + B callbackasm1(SB) + MOVD $1446, R12 + B callbackasm1(SB) + MOVD $1447, R12 + B callbackasm1(SB) + MOVD $1448, R12 + B callbackasm1(SB) + MOVD $1449, R12 + B callbackasm1(SB) + MOVD $1450, R12 + B callbackasm1(SB) + MOVD $1451, R12 + B callbackasm1(SB) + MOVD $1452, R12 + B callbackasm1(SB) + MOVD $1453, R12 + B callbackasm1(SB) + MOVD $1454, R12 + B callbackasm1(SB) + MOVD $1455, R12 + B callbackasm1(SB) + MOVD $1456, R12 + B callbackasm1(SB) + MOVD $1457, R12 + B callbackasm1(SB) + MOVD $1458, R12 + B callbackasm1(SB) + MOVD $1459, R12 + B callbackasm1(SB) + MOVD $1460, R12 + B callbackasm1(SB) + MOVD $1461, R12 + B callbackasm1(SB) + MOVD $1462, R12 + B callbackasm1(SB) + MOVD $1463, R12 + B callbackasm1(SB) + MOVD $1464, R12 + B callbackasm1(SB) + MOVD $1465, R12 + B callbackasm1(SB) + MOVD $1466, R12 + B callbackasm1(SB) + MOVD $1467, R12 + B callbackasm1(SB) + MOVD $1468, R12 + B callbackasm1(SB) + MOVD $1469, R12 + B callbackasm1(SB) + MOVD $1470, R12 + B callbackasm1(SB) + MOVD $1471, R12 + B callbackasm1(SB) + MOVD $1472, R12 + B callbackasm1(SB) + MOVD $1473, R12 + B callbackasm1(SB) + MOVD $1474, R12 + B callbackasm1(SB) + MOVD $1475, R12 + B callbackasm1(SB) + MOVD $1476, R12 + B callbackasm1(SB) + MOVD $1477, R12 + B callbackasm1(SB) + MOVD $1478, R12 + B callbackasm1(SB) + MOVD $1479, R12 + B callbackasm1(SB) + MOVD $1480, R12 + B callbackasm1(SB) + MOVD $1481, R12 + B callbackasm1(SB) + MOVD $1482, R12 + B callbackasm1(SB) + MOVD $1483, R12 + B callbackasm1(SB) + MOVD $1484, R12 + B callbackasm1(SB) + MOVD $1485, R12 + B callbackasm1(SB) + MOVD $1486, R12 + B callbackasm1(SB) + MOVD $1487, R12 + B callbackasm1(SB) + MOVD $1488, R12 + B callbackasm1(SB) + MOVD $1489, R12 + B callbackasm1(SB) + MOVD $1490, R12 + B callbackasm1(SB) + MOVD $1491, R12 + B callbackasm1(SB) + MOVD $1492, R12 + B callbackasm1(SB) + MOVD $1493, R12 + B callbackasm1(SB) + MOVD $1494, R12 + B callbackasm1(SB) + MOVD $1495, R12 + B callbackasm1(SB) + MOVD $1496, R12 + B callbackasm1(SB) + MOVD $1497, R12 + B callbackasm1(SB) + MOVD $1498, R12 + B callbackasm1(SB) + MOVD $1499, R12 + B callbackasm1(SB) + MOVD $1500, R12 + B callbackasm1(SB) + MOVD $1501, R12 + B callbackasm1(SB) + MOVD $1502, R12 + B callbackasm1(SB) + MOVD $1503, R12 + B callbackasm1(SB) + MOVD $1504, R12 + B callbackasm1(SB) + MOVD $1505, R12 + B callbackasm1(SB) + MOVD $1506, R12 + B callbackasm1(SB) + MOVD $1507, R12 + B callbackasm1(SB) + MOVD $1508, R12 + B callbackasm1(SB) + MOVD $1509, R12 + B callbackasm1(SB) + MOVD $1510, R12 + B callbackasm1(SB) + MOVD $1511, R12 + B callbackasm1(SB) + MOVD $1512, R12 + B callbackasm1(SB) + MOVD $1513, R12 + B callbackasm1(SB) + MOVD $1514, R12 + B callbackasm1(SB) + MOVD $1515, R12 + B callbackasm1(SB) + MOVD $1516, R12 + B callbackasm1(SB) + MOVD $1517, R12 + B callbackasm1(SB) + MOVD $1518, R12 + B callbackasm1(SB) + MOVD $1519, R12 + B callbackasm1(SB) + MOVD $1520, R12 + B callbackasm1(SB) + MOVD $1521, R12 + B callbackasm1(SB) + MOVD $1522, R12 + B callbackasm1(SB) + MOVD $1523, R12 + B callbackasm1(SB) + MOVD $1524, R12 + B callbackasm1(SB) + MOVD $1525, R12 + B callbackasm1(SB) + MOVD $1526, R12 + B callbackasm1(SB) + MOVD $1527, R12 + B callbackasm1(SB) + MOVD $1528, R12 + B callbackasm1(SB) + MOVD $1529, R12 + B callbackasm1(SB) + MOVD $1530, R12 + B callbackasm1(SB) + MOVD $1531, R12 + B callbackasm1(SB) + MOVD $1532, R12 + B callbackasm1(SB) + MOVD $1533, R12 + B callbackasm1(SB) + MOVD $1534, R12 + B callbackasm1(SB) + MOVD $1535, R12 + B callbackasm1(SB) + MOVD $1536, R12 + B callbackasm1(SB) + MOVD $1537, R12 + B callbackasm1(SB) + MOVD $1538, R12 + B callbackasm1(SB) + MOVD $1539, R12 + B callbackasm1(SB) + MOVD $1540, R12 + B callbackasm1(SB) + MOVD $1541, R12 + B callbackasm1(SB) + MOVD $1542, R12 + B callbackasm1(SB) + MOVD $1543, R12 + B callbackasm1(SB) + MOVD $1544, R12 + B callbackasm1(SB) + MOVD $1545, R12 + B callbackasm1(SB) + MOVD $1546, R12 + B callbackasm1(SB) + MOVD $1547, R12 + B callbackasm1(SB) + MOVD $1548, R12 + B callbackasm1(SB) + MOVD $1549, R12 + B callbackasm1(SB) + MOVD $1550, R12 + B callbackasm1(SB) + MOVD $1551, R12 + B callbackasm1(SB) + MOVD $1552, R12 + B callbackasm1(SB) + MOVD $1553, R12 + B callbackasm1(SB) + MOVD $1554, R12 + B callbackasm1(SB) + MOVD $1555, R12 + B callbackasm1(SB) + MOVD $1556, R12 + B callbackasm1(SB) + MOVD $1557, R12 + B callbackasm1(SB) + MOVD $1558, R12 + B callbackasm1(SB) + MOVD $1559, R12 + B callbackasm1(SB) + MOVD $1560, R12 + B callbackasm1(SB) + MOVD $1561, R12 + B callbackasm1(SB) + MOVD $1562, R12 + B callbackasm1(SB) + MOVD $1563, R12 + B callbackasm1(SB) + MOVD $1564, R12 + B callbackasm1(SB) + MOVD $1565, R12 + B callbackasm1(SB) + MOVD $1566, R12 + B callbackasm1(SB) + MOVD $1567, R12 + B callbackasm1(SB) + MOVD $1568, R12 + B callbackasm1(SB) + MOVD $1569, R12 + B callbackasm1(SB) + MOVD $1570, R12 + B callbackasm1(SB) + MOVD $1571, R12 + B callbackasm1(SB) + MOVD $1572, R12 + B callbackasm1(SB) + MOVD $1573, R12 + B callbackasm1(SB) + MOVD $1574, R12 + B callbackasm1(SB) + MOVD $1575, R12 + B callbackasm1(SB) + MOVD $1576, R12 + B callbackasm1(SB) + MOVD $1577, R12 + B callbackasm1(SB) + MOVD $1578, R12 + B callbackasm1(SB) + MOVD $1579, R12 + B callbackasm1(SB) + MOVD $1580, R12 + B callbackasm1(SB) + MOVD $1581, R12 + B callbackasm1(SB) + MOVD $1582, R12 + B callbackasm1(SB) + MOVD $1583, R12 + B callbackasm1(SB) + MOVD $1584, R12 + B callbackasm1(SB) + MOVD $1585, R12 + B callbackasm1(SB) + MOVD $1586, R12 + B callbackasm1(SB) + MOVD $1587, R12 + B callbackasm1(SB) + MOVD $1588, R12 + B callbackasm1(SB) + MOVD $1589, R12 + B callbackasm1(SB) + MOVD $1590, R12 + B callbackasm1(SB) + MOVD $1591, R12 + B callbackasm1(SB) + MOVD $1592, R12 + B callbackasm1(SB) + MOVD $1593, R12 + B callbackasm1(SB) + MOVD $1594, R12 + B callbackasm1(SB) + MOVD $1595, R12 + B callbackasm1(SB) + MOVD $1596, R12 + B callbackasm1(SB) + MOVD $1597, R12 + B callbackasm1(SB) + MOVD $1598, R12 + B callbackasm1(SB) + MOVD $1599, R12 + B callbackasm1(SB) + MOVD $1600, R12 + B callbackasm1(SB) + MOVD $1601, R12 + B callbackasm1(SB) + MOVD $1602, R12 + B callbackasm1(SB) + MOVD $1603, R12 + B callbackasm1(SB) + MOVD $1604, R12 + B callbackasm1(SB) + MOVD $1605, R12 + B callbackasm1(SB) + MOVD $1606, R12 + B callbackasm1(SB) + MOVD $1607, R12 + B callbackasm1(SB) + MOVD $1608, R12 + B callbackasm1(SB) + MOVD $1609, R12 + B callbackasm1(SB) + MOVD $1610, R12 + B callbackasm1(SB) + MOVD $1611, R12 + B callbackasm1(SB) + MOVD $1612, R12 + B callbackasm1(SB) + MOVD $1613, R12 + B callbackasm1(SB) + MOVD $1614, R12 + B callbackasm1(SB) + MOVD $1615, R12 + B callbackasm1(SB) + MOVD $1616, R12 + B callbackasm1(SB) + MOVD $1617, R12 + B callbackasm1(SB) + MOVD $1618, R12 + B callbackasm1(SB) + MOVD $1619, R12 + B callbackasm1(SB) + MOVD $1620, R12 + B callbackasm1(SB) + MOVD $1621, R12 + B callbackasm1(SB) + MOVD $1622, R12 + B callbackasm1(SB) + MOVD $1623, R12 + B callbackasm1(SB) + MOVD $1624, R12 + B callbackasm1(SB) + MOVD $1625, R12 + B callbackasm1(SB) + MOVD $1626, R12 + B callbackasm1(SB) + MOVD $1627, R12 + B callbackasm1(SB) + MOVD $1628, R12 + B callbackasm1(SB) + MOVD $1629, R12 + B callbackasm1(SB) + MOVD $1630, R12 + B callbackasm1(SB) + MOVD $1631, R12 + B callbackasm1(SB) + MOVD $1632, R12 + B callbackasm1(SB) + MOVD $1633, R12 + B callbackasm1(SB) + MOVD $1634, R12 + B callbackasm1(SB) + MOVD $1635, R12 + B callbackasm1(SB) + MOVD $1636, R12 + B callbackasm1(SB) + MOVD $1637, R12 + B callbackasm1(SB) + MOVD $1638, R12 + B callbackasm1(SB) + MOVD $1639, R12 + B callbackasm1(SB) + MOVD $1640, R12 + B callbackasm1(SB) + MOVD $1641, R12 + B callbackasm1(SB) + MOVD $1642, R12 + B callbackasm1(SB) + MOVD $1643, R12 + B callbackasm1(SB) + MOVD $1644, R12 + B callbackasm1(SB) + MOVD $1645, R12 + B callbackasm1(SB) + MOVD $1646, R12 + B callbackasm1(SB) + MOVD $1647, R12 + B callbackasm1(SB) + MOVD $1648, R12 + B callbackasm1(SB) + MOVD $1649, R12 + B callbackasm1(SB) + MOVD $1650, R12 + B callbackasm1(SB) + MOVD $1651, R12 + B callbackasm1(SB) + MOVD $1652, R12 + B callbackasm1(SB) + MOVD $1653, R12 + B callbackasm1(SB) + MOVD $1654, R12 + B callbackasm1(SB) + MOVD $1655, R12 + B callbackasm1(SB) + MOVD $1656, R12 + B callbackasm1(SB) + MOVD $1657, R12 + B callbackasm1(SB) + MOVD $1658, R12 + B callbackasm1(SB) + MOVD $1659, R12 + B callbackasm1(SB) + MOVD $1660, R12 + B callbackasm1(SB) + MOVD $1661, R12 + B callbackasm1(SB) + MOVD $1662, R12 + B callbackasm1(SB) + MOVD $1663, R12 + B callbackasm1(SB) + MOVD $1664, R12 + B callbackasm1(SB) + MOVD $1665, R12 + B callbackasm1(SB) + MOVD $1666, R12 + B callbackasm1(SB) + MOVD $1667, R12 + B callbackasm1(SB) + MOVD $1668, R12 + B callbackasm1(SB) + MOVD $1669, R12 + B callbackasm1(SB) + MOVD $1670, R12 + B callbackasm1(SB) + MOVD $1671, R12 + B callbackasm1(SB) + MOVD $1672, R12 + B callbackasm1(SB) + MOVD $1673, R12 + B callbackasm1(SB) + MOVD $1674, R12 + B callbackasm1(SB) + MOVD $1675, R12 + B callbackasm1(SB) + MOVD $1676, R12 + B callbackasm1(SB) + MOVD $1677, R12 + B callbackasm1(SB) + MOVD $1678, R12 + B callbackasm1(SB) + MOVD $1679, R12 + B callbackasm1(SB) + MOVD $1680, R12 + B callbackasm1(SB) + MOVD $1681, R12 + B callbackasm1(SB) + MOVD $1682, R12 + B callbackasm1(SB) + MOVD $1683, R12 + B callbackasm1(SB) + MOVD $1684, R12 + B callbackasm1(SB) + MOVD $1685, R12 + B callbackasm1(SB) + MOVD $1686, R12 + B callbackasm1(SB) + MOVD $1687, R12 + B callbackasm1(SB) + MOVD $1688, R12 + B callbackasm1(SB) + MOVD $1689, R12 + B callbackasm1(SB) + MOVD $1690, R12 + B callbackasm1(SB) + MOVD $1691, R12 + B callbackasm1(SB) + MOVD $1692, R12 + B callbackasm1(SB) + MOVD $1693, R12 + B callbackasm1(SB) + MOVD $1694, R12 + B callbackasm1(SB) + MOVD $1695, R12 + B callbackasm1(SB) + MOVD $1696, R12 + B callbackasm1(SB) + MOVD $1697, R12 + B callbackasm1(SB) + MOVD $1698, R12 + B callbackasm1(SB) + MOVD $1699, R12 + B callbackasm1(SB) + MOVD $1700, R12 + B callbackasm1(SB) + MOVD $1701, R12 + B callbackasm1(SB) + MOVD $1702, R12 + B callbackasm1(SB) + MOVD $1703, R12 + B callbackasm1(SB) + MOVD $1704, R12 + B callbackasm1(SB) + MOVD $1705, R12 + B callbackasm1(SB) + MOVD $1706, R12 + B callbackasm1(SB) + MOVD $1707, R12 + B callbackasm1(SB) + MOVD $1708, R12 + B callbackasm1(SB) + MOVD $1709, R12 + B callbackasm1(SB) + MOVD $1710, R12 + B callbackasm1(SB) + MOVD $1711, R12 + B callbackasm1(SB) + MOVD $1712, R12 + B callbackasm1(SB) + MOVD $1713, R12 + B callbackasm1(SB) + MOVD $1714, R12 + B callbackasm1(SB) + MOVD $1715, R12 + B callbackasm1(SB) + MOVD $1716, R12 + B callbackasm1(SB) + MOVD $1717, R12 + B callbackasm1(SB) + MOVD $1718, R12 + B callbackasm1(SB) + MOVD $1719, R12 + B callbackasm1(SB) + MOVD $1720, R12 + B callbackasm1(SB) + MOVD $1721, R12 + B callbackasm1(SB) + MOVD $1722, R12 + B callbackasm1(SB) + MOVD $1723, R12 + B callbackasm1(SB) + MOVD $1724, R12 + B callbackasm1(SB) + MOVD $1725, R12 + B callbackasm1(SB) + MOVD $1726, R12 + B callbackasm1(SB) + MOVD $1727, R12 + B callbackasm1(SB) + MOVD $1728, R12 + B callbackasm1(SB) + MOVD $1729, R12 + B callbackasm1(SB) + MOVD $1730, R12 + B callbackasm1(SB) + MOVD $1731, R12 + B callbackasm1(SB) + MOVD $1732, R12 + B callbackasm1(SB) + MOVD $1733, R12 + B callbackasm1(SB) + MOVD $1734, R12 + B callbackasm1(SB) + MOVD $1735, R12 + B callbackasm1(SB) + MOVD $1736, R12 + B callbackasm1(SB) + MOVD $1737, R12 + B callbackasm1(SB) + MOVD $1738, R12 + B callbackasm1(SB) + MOVD $1739, R12 + B callbackasm1(SB) + MOVD $1740, R12 + B callbackasm1(SB) + MOVD $1741, R12 + B callbackasm1(SB) + MOVD $1742, R12 + B callbackasm1(SB) + MOVD $1743, R12 + B callbackasm1(SB) + MOVD $1744, R12 + B callbackasm1(SB) + MOVD $1745, R12 + B callbackasm1(SB) + MOVD $1746, R12 + B callbackasm1(SB) + MOVD $1747, R12 + B callbackasm1(SB) + MOVD $1748, R12 + B callbackasm1(SB) + MOVD $1749, R12 + B callbackasm1(SB) + MOVD $1750, R12 + B callbackasm1(SB) + MOVD $1751, R12 + B callbackasm1(SB) + MOVD $1752, R12 + B callbackasm1(SB) + MOVD $1753, R12 + B callbackasm1(SB) + MOVD $1754, R12 + B callbackasm1(SB) + MOVD $1755, R12 + B callbackasm1(SB) + MOVD $1756, R12 + B callbackasm1(SB) + MOVD $1757, R12 + B callbackasm1(SB) + MOVD $1758, R12 + B callbackasm1(SB) + MOVD $1759, R12 + B callbackasm1(SB) + MOVD $1760, R12 + B callbackasm1(SB) + MOVD $1761, R12 + B callbackasm1(SB) + MOVD $1762, R12 + B callbackasm1(SB) + MOVD $1763, R12 + B callbackasm1(SB) + MOVD $1764, R12 + B callbackasm1(SB) + MOVD $1765, R12 + B callbackasm1(SB) + MOVD $1766, R12 + B callbackasm1(SB) + MOVD $1767, R12 + B callbackasm1(SB) + MOVD $1768, R12 + B callbackasm1(SB) + MOVD $1769, R12 + B callbackasm1(SB) + MOVD $1770, R12 + B callbackasm1(SB) + MOVD $1771, R12 + B callbackasm1(SB) + MOVD $1772, R12 + B callbackasm1(SB) + MOVD $1773, R12 + B callbackasm1(SB) + MOVD $1774, R12 + B callbackasm1(SB) + MOVD $1775, R12 + B callbackasm1(SB) + MOVD $1776, R12 + B callbackasm1(SB) + MOVD $1777, R12 + B callbackasm1(SB) + MOVD $1778, R12 + B callbackasm1(SB) + MOVD $1779, R12 + B callbackasm1(SB) + MOVD $1780, R12 + B callbackasm1(SB) + MOVD $1781, R12 + B callbackasm1(SB) + MOVD $1782, R12 + B callbackasm1(SB) + MOVD $1783, R12 + B callbackasm1(SB) + MOVD $1784, R12 + B callbackasm1(SB) + MOVD $1785, R12 + B callbackasm1(SB) + MOVD $1786, R12 + B callbackasm1(SB) + MOVD $1787, R12 + B callbackasm1(SB) + MOVD $1788, R12 + B callbackasm1(SB) + MOVD $1789, R12 + B callbackasm1(SB) + MOVD $1790, R12 + B callbackasm1(SB) + MOVD $1791, R12 + B callbackasm1(SB) + MOVD $1792, R12 + B callbackasm1(SB) + MOVD $1793, R12 + B callbackasm1(SB) + MOVD $1794, R12 + B callbackasm1(SB) + MOVD $1795, R12 + B callbackasm1(SB) + MOVD $1796, R12 + B callbackasm1(SB) + MOVD $1797, R12 + B callbackasm1(SB) + MOVD $1798, R12 + B callbackasm1(SB) + MOVD $1799, R12 + B callbackasm1(SB) + MOVD $1800, R12 + B callbackasm1(SB) + MOVD $1801, R12 + B callbackasm1(SB) + MOVD $1802, R12 + B callbackasm1(SB) + MOVD $1803, R12 + B callbackasm1(SB) + MOVD $1804, R12 + B callbackasm1(SB) + MOVD $1805, R12 + B callbackasm1(SB) + MOVD $1806, R12 + B callbackasm1(SB) + MOVD $1807, R12 + B callbackasm1(SB) + MOVD $1808, R12 + B callbackasm1(SB) + MOVD $1809, R12 + B callbackasm1(SB) + MOVD $1810, R12 + B callbackasm1(SB) + MOVD $1811, R12 + B callbackasm1(SB) + MOVD $1812, R12 + B callbackasm1(SB) + MOVD $1813, R12 + B callbackasm1(SB) + MOVD $1814, R12 + B callbackasm1(SB) + MOVD $1815, R12 + B callbackasm1(SB) + MOVD $1816, R12 + B callbackasm1(SB) + MOVD $1817, R12 + B callbackasm1(SB) + MOVD $1818, R12 + B callbackasm1(SB) + MOVD $1819, R12 + B callbackasm1(SB) + MOVD $1820, R12 + B callbackasm1(SB) + MOVD $1821, R12 + B callbackasm1(SB) + MOVD $1822, R12 + B callbackasm1(SB) + MOVD $1823, R12 + B callbackasm1(SB) + MOVD $1824, R12 + B callbackasm1(SB) + MOVD $1825, R12 + B callbackasm1(SB) + MOVD $1826, R12 + B callbackasm1(SB) + MOVD $1827, R12 + B callbackasm1(SB) + MOVD $1828, R12 + B callbackasm1(SB) + MOVD $1829, R12 + B callbackasm1(SB) + MOVD $1830, R12 + B callbackasm1(SB) + MOVD $1831, R12 + B callbackasm1(SB) + MOVD $1832, R12 + B callbackasm1(SB) + MOVD $1833, R12 + B callbackasm1(SB) + MOVD $1834, R12 + B callbackasm1(SB) + MOVD $1835, R12 + B callbackasm1(SB) + MOVD $1836, R12 + B callbackasm1(SB) + MOVD $1837, R12 + B callbackasm1(SB) + MOVD $1838, R12 + B callbackasm1(SB) + MOVD $1839, R12 + B callbackasm1(SB) + MOVD $1840, R12 + B callbackasm1(SB) + MOVD $1841, R12 + B callbackasm1(SB) + MOVD $1842, R12 + B callbackasm1(SB) + MOVD $1843, R12 + B callbackasm1(SB) + MOVD $1844, R12 + B callbackasm1(SB) + MOVD $1845, R12 + B callbackasm1(SB) + MOVD $1846, R12 + B callbackasm1(SB) + MOVD $1847, R12 + B callbackasm1(SB) + MOVD $1848, R12 + B callbackasm1(SB) + MOVD $1849, R12 + B callbackasm1(SB) + MOVD $1850, R12 + B callbackasm1(SB) + MOVD $1851, R12 + B callbackasm1(SB) + MOVD $1852, R12 + B callbackasm1(SB) + MOVD $1853, R12 + B callbackasm1(SB) + MOVD $1854, R12 + B callbackasm1(SB) + MOVD $1855, R12 + B callbackasm1(SB) + MOVD $1856, R12 + B callbackasm1(SB) + MOVD $1857, R12 + B callbackasm1(SB) + MOVD $1858, R12 + B callbackasm1(SB) + MOVD $1859, R12 + B callbackasm1(SB) + MOVD $1860, R12 + B callbackasm1(SB) + MOVD $1861, R12 + B callbackasm1(SB) + MOVD $1862, R12 + B callbackasm1(SB) + MOVD $1863, R12 + B callbackasm1(SB) + MOVD $1864, R12 + B callbackasm1(SB) + MOVD $1865, R12 + B callbackasm1(SB) + MOVD $1866, R12 + B callbackasm1(SB) + MOVD $1867, R12 + B callbackasm1(SB) + MOVD $1868, R12 + B callbackasm1(SB) + MOVD $1869, R12 + B callbackasm1(SB) + MOVD $1870, R12 + B callbackasm1(SB) + MOVD $1871, R12 + B callbackasm1(SB) + MOVD $1872, R12 + B callbackasm1(SB) + MOVD $1873, R12 + B callbackasm1(SB) + MOVD $1874, R12 + B callbackasm1(SB) + MOVD $1875, R12 + B callbackasm1(SB) + MOVD $1876, R12 + B callbackasm1(SB) + MOVD $1877, R12 + B callbackasm1(SB) + MOVD $1878, R12 + B callbackasm1(SB) + MOVD $1879, R12 + B callbackasm1(SB) + MOVD $1880, R12 + B callbackasm1(SB) + MOVD $1881, R12 + B callbackasm1(SB) + MOVD $1882, R12 + B callbackasm1(SB) + MOVD $1883, R12 + B callbackasm1(SB) + MOVD $1884, R12 + B callbackasm1(SB) + MOVD $1885, R12 + B callbackasm1(SB) + MOVD $1886, R12 + B callbackasm1(SB) + MOVD $1887, R12 + B callbackasm1(SB) + MOVD $1888, R12 + B callbackasm1(SB) + MOVD $1889, R12 + B callbackasm1(SB) + MOVD $1890, R12 + B callbackasm1(SB) + MOVD $1891, R12 + B callbackasm1(SB) + MOVD $1892, R12 + B callbackasm1(SB) + MOVD $1893, R12 + B callbackasm1(SB) + MOVD $1894, R12 + B callbackasm1(SB) + MOVD $1895, R12 + B callbackasm1(SB) + MOVD $1896, R12 + B callbackasm1(SB) + MOVD $1897, R12 + B callbackasm1(SB) + MOVD $1898, R12 + B callbackasm1(SB) + MOVD $1899, R12 + B callbackasm1(SB) + MOVD $1900, R12 + B callbackasm1(SB) + MOVD $1901, R12 + B callbackasm1(SB) + MOVD $1902, R12 + B callbackasm1(SB) + MOVD $1903, R12 + B callbackasm1(SB) + MOVD $1904, R12 + B callbackasm1(SB) + MOVD $1905, R12 + B callbackasm1(SB) + MOVD $1906, R12 + B callbackasm1(SB) + MOVD $1907, R12 + B callbackasm1(SB) + MOVD $1908, R12 + B callbackasm1(SB) + MOVD $1909, R12 + B callbackasm1(SB) + MOVD $1910, R12 + B callbackasm1(SB) + MOVD $1911, R12 + B callbackasm1(SB) + MOVD $1912, R12 + B callbackasm1(SB) + MOVD $1913, R12 + B callbackasm1(SB) + MOVD $1914, R12 + B callbackasm1(SB) + MOVD $1915, R12 + B callbackasm1(SB) + MOVD $1916, R12 + B callbackasm1(SB) + MOVD $1917, R12 + B callbackasm1(SB) + MOVD $1918, R12 + B callbackasm1(SB) + MOVD $1919, R12 + B callbackasm1(SB) + MOVD $1920, R12 + B callbackasm1(SB) + MOVD $1921, R12 + B callbackasm1(SB) + MOVD $1922, R12 + B callbackasm1(SB) + MOVD $1923, R12 + B callbackasm1(SB) + MOVD $1924, R12 + B callbackasm1(SB) + MOVD $1925, R12 + B callbackasm1(SB) + MOVD $1926, R12 + B callbackasm1(SB) + MOVD $1927, R12 + B callbackasm1(SB) + MOVD $1928, R12 + B callbackasm1(SB) + MOVD $1929, R12 + B callbackasm1(SB) + MOVD $1930, R12 + B callbackasm1(SB) + MOVD $1931, R12 + B callbackasm1(SB) + MOVD $1932, R12 + B callbackasm1(SB) + MOVD $1933, R12 + B callbackasm1(SB) + MOVD $1934, R12 + B callbackasm1(SB) + MOVD $1935, R12 + B callbackasm1(SB) + MOVD $1936, R12 + B callbackasm1(SB) + MOVD $1937, R12 + B callbackasm1(SB) + MOVD $1938, R12 + B callbackasm1(SB) + MOVD $1939, R12 + B callbackasm1(SB) + MOVD $1940, R12 + B callbackasm1(SB) + MOVD $1941, R12 + B callbackasm1(SB) + MOVD $1942, R12 + B callbackasm1(SB) + MOVD $1943, R12 + B callbackasm1(SB) + MOVD $1944, R12 + B callbackasm1(SB) + MOVD $1945, R12 + B callbackasm1(SB) + MOVD $1946, R12 + B callbackasm1(SB) + MOVD $1947, R12 + B callbackasm1(SB) + MOVD $1948, R12 + B callbackasm1(SB) + MOVD $1949, R12 + B callbackasm1(SB) + MOVD $1950, R12 + B callbackasm1(SB) + MOVD $1951, R12 + B callbackasm1(SB) + MOVD $1952, R12 + B callbackasm1(SB) + MOVD $1953, R12 + B callbackasm1(SB) + MOVD $1954, R12 + B callbackasm1(SB) + MOVD $1955, R12 + B callbackasm1(SB) + MOVD $1956, R12 + B callbackasm1(SB) + MOVD $1957, R12 + B callbackasm1(SB) + MOVD $1958, R12 + B callbackasm1(SB) + MOVD $1959, R12 + B callbackasm1(SB) + MOVD $1960, R12 + B callbackasm1(SB) + MOVD $1961, R12 + B callbackasm1(SB) + MOVD $1962, R12 + B callbackasm1(SB) + MOVD $1963, R12 + B callbackasm1(SB) + MOVD $1964, R12 + B callbackasm1(SB) + MOVD $1965, R12 + B callbackasm1(SB) + MOVD $1966, R12 + B callbackasm1(SB) + MOVD $1967, R12 + B callbackasm1(SB) + MOVD $1968, R12 + B callbackasm1(SB) + MOVD $1969, R12 + B callbackasm1(SB) + MOVD $1970, R12 + B callbackasm1(SB) + MOVD $1971, R12 + B callbackasm1(SB) + MOVD $1972, R12 + B callbackasm1(SB) + MOVD $1973, R12 + B callbackasm1(SB) + MOVD $1974, R12 + B callbackasm1(SB) + MOVD $1975, R12 + B callbackasm1(SB) + MOVD $1976, R12 + B callbackasm1(SB) + MOVD $1977, R12 + B callbackasm1(SB) + MOVD $1978, R12 + B callbackasm1(SB) + MOVD $1979, R12 + B callbackasm1(SB) + MOVD $1980, R12 + B callbackasm1(SB) + MOVD $1981, R12 + B callbackasm1(SB) + MOVD $1982, R12 + B callbackasm1(SB) + MOVD $1983, R12 + B callbackasm1(SB) + MOVD $1984, R12 + B callbackasm1(SB) + MOVD $1985, R12 + B callbackasm1(SB) + MOVD $1986, R12 + B callbackasm1(SB) + MOVD $1987, R12 + B callbackasm1(SB) + MOVD $1988, R12 + B callbackasm1(SB) + MOVD $1989, R12 + B callbackasm1(SB) + MOVD $1990, R12 + B callbackasm1(SB) + MOVD $1991, R12 + B callbackasm1(SB) + MOVD $1992, R12 + B callbackasm1(SB) + MOVD $1993, R12 + B callbackasm1(SB) + MOVD $1994, R12 + B callbackasm1(SB) + MOVD $1995, R12 + B callbackasm1(SB) + MOVD $1996, R12 + B callbackasm1(SB) + MOVD $1997, R12 + B callbackasm1(SB) + MOVD $1998, R12 + B callbackasm1(SB) + MOVD $1999, R12 + B callbackasm1(SB) diff --git a/vendor/github.com/gdamore/encoding/.appveyor.yml b/vendor/github.com/gdamore/encoding/.appveyor.yml new file mode 100644 index 0000000..19a4c5d --- /dev/null +++ b/vendor/github.com/gdamore/encoding/.appveyor.yml @@ -0,0 +1,13 @@ +version: 1.0.{build} +clone_folder: c:\gopath\src\github.com\gdamore\encoding +environment: + GOPATH: c:\gopath +build_script: +- go version +- go env +- SET PATH=%LOCALAPPDATA%\atom\bin;%GOPATH%\bin;%PATH% +- go get -t ./... +- go build +- go install ./... +test_script: +- go test ./... diff --git a/vendor/github.com/gdamore/encoding/CODE_OF_CONDUCT.md b/vendor/github.com/gdamore/encoding/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..65527da --- /dev/null +++ b/vendor/github.com/gdamore/encoding/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at garrett@damore.org. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/vendor/github.com/gdamore/encoding/LICENSE b/vendor/github.com/gdamore/encoding/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/vendor/github.com/gdamore/encoding/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/gdamore/encoding/README.md b/vendor/github.com/gdamore/encoding/README.md new file mode 100644 index 0000000..2ce29a9 --- /dev/null +++ b/vendor/github.com/gdamore/encoding/README.md @@ -0,0 +1,20 @@ +## encoding + + +[![Linux](https://img.shields.io/github/actions/workflow/status/gdamore/encoding/linux.yml?branch=main&logoColor=grey&logo=linux&label=)](https://github.com/gdamore/encoding/actions/workflows/linux.yml) +[![Windows](https://img.shields.io/github/actions/workflow/status/gdamore/encoding/windows.yml?branch=main&logoColor=grey&logo=windows&label=)](https://github.com/gdamore/encoding/actions/workflows/windows.yml) +[![Apache License](https://img.shields.io/github/license/gdamore/encoding.svg?logoColor=silver&logo=opensourceinitiative&color=blue&label=)](https://github.com/gdamore/encoding/blob/master/LICENSE) +[![Coverage](https://img.shields.io/codecov/c/github/gdamore/encoding?logoColor=grey&logo=codecov&label=)](https://codecov.io/gh/gdamore/encoding) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/gdamore/encoding) + +Package encoding provides a number of encodings that are missing from the +standard Go [encoding]("https://godoc.org/golang.org/x/text/encoding") package. + +We hope that we can contribute these to the standard Go library someday. It +turns out that some of these are useful for dealing with I/O streams coming +from non-UTF friendly sources. + +The UTF8 Encoder is also useful for situations where valid UTF-8 might be +carried in streams that contain non-valid UTF; in particular I use it for +helping me cope with terminals that embed escape sequences in otherwise +valid UTF-8. diff --git a/vendor/github.com/gdamore/encoding/SECURITY.md b/vendor/github.com/gdamore/encoding/SECURITY.md new file mode 100644 index 0000000..b9f6496 --- /dev/null +++ b/vendor/github.com/gdamore/encoding/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +We take security very seriously in mangos, since you may be using it in +Internet-facing applications. + +## Reporting a Vulnerability + +To report a vulnerability, please contact us on our discord. +You may also send an email to garrett@damore.org, or info@staysail.tech. + +We will keep the reporter updated on any status updates on a regular basis, +and will respond within two business days for any reported security issue. diff --git a/vendor/github.com/gdamore/encoding/ascii.go b/vendor/github.com/gdamore/encoding/ascii.go new file mode 100644 index 0000000..b7321f4 --- /dev/null +++ b/vendor/github.com/gdamore/encoding/ascii.go @@ -0,0 +1,36 @@ +// Copyright 2015 Garrett D'Amore +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "golang.org/x/text/encoding" +) + +// ASCII represents the 7-bit US-ASCII scheme. It decodes directly to +// UTF-8 without change, as all ASCII values are legal UTF-8. +// Unicode values less than 128 (i.e. 7 bits) map 1:1 with ASCII. +// It encodes runes outside of that to 0x1A, the ASCII substitution character. +var ASCII encoding.Encoding + +func init() { + amap := make(map[byte]rune) + for i := 128; i <= 255; i++ { + amap[byte(i)] = RuneError + } + + cm := &Charmap{Map: amap} + cm.Init() + ASCII = cm +} diff --git a/vendor/github.com/gdamore/encoding/charmap.go b/vendor/github.com/gdamore/encoding/charmap.go new file mode 100644 index 0000000..e8089c4 --- /dev/null +++ b/vendor/github.com/gdamore/encoding/charmap.go @@ -0,0 +1,195 @@ +// Copyright 2024 Garrett D'Amore +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "sync" + "unicode/utf8" + + "golang.org/x/text/encoding" + "golang.org/x/text/transform" +) + +const ( + // RuneError is an alias for the UTF-8 replacement rune, '\uFFFD'. + RuneError = '\uFFFD' + + // RuneSelf is the rune below which UTF-8 and the Unicode values are + // identical. Its also the limit for ASCII. + RuneSelf = 0x80 + + // ASCIISub is the ASCII substitution character. + ASCIISub = '\x1a' +) + +// Charmap is a structure for setting up encodings for 8-bit character sets, +// for transforming between UTF8 and that other character set. It has some +// ideas borrowed from golang.org/x/text/encoding/charmap, but it uses a +// different implementation. This implementation uses maps, and supports +// user-defined maps. +// +// We do assume that a character map has a reasonable substitution character, +// and that valid encodings are stable (exactly a 1:1 map) and stateless +// (that is there is no shift character or anything like that.) Hence this +// approach will not work for many East Asian character sets. +// +// Measurement shows little or no measurable difference in the performance of +// the two approaches. The difference was down to a couple of nsec/op, and +// no consistent pattern as to which ran faster. With the conversion to +// UTF-8 the code takes about 25 nsec/op. The conversion in the reverse +// direction takes about 100 nsec/op. (The larger cost for conversion +// from UTF-8 is most likely due to the need to convert the UTF-8 byte stream +// to a rune before conversion. +type Charmap struct { + transform.NopResetter + bytes map[rune]byte + runes [256][]byte + once sync.Once + + // The map between bytes and runes. To indicate that a specific + // byte value is invalid for a charcter set, use the rune + // utf8.RuneError. Values that are absent from this map will + // be assumed to have the identity mapping -- that is the default + // is to assume ISO8859-1, where all 8-bit characters have the same + // numeric value as their Unicode runes. (Not to be confused with + // the UTF-8 values, which *will* be different for non-ASCII runes.) + // + // If no values less than RuneSelf are changed (or have non-identity + // mappings), then the character set is assumed to be an ASCII + // superset, and certain assumptions and optimizations become + // available for ASCII bytes. + Map map[byte]rune + + // The ReplacementChar is the byte value to use for substitution. + // It should normally be ASCIISub for ASCII encodings. This may be + // unset (left to zero) for mappings that are strictly ASCII supersets. + // In that case ASCIISub will be assumed instead. + ReplacementChar byte +} + +type cmapDecoder struct { + transform.NopResetter + runes [256][]byte +} + +type cmapEncoder struct { + transform.NopResetter + bytes map[rune]byte + replace byte +} + +// Init initializes internal values of a character map. This should +// be done early, to minimize the cost of allocation of transforms +// later. It is not strictly necessary however, as the allocation +// functions will arrange to call it if it has not already been done. +func (c *Charmap) Init() { + c.once.Do(c.initialize) +} + +func (c *Charmap) initialize() { + c.bytes = make(map[rune]byte) + ascii := true + + for i := 0; i < 256; i++ { + r, ok := c.Map[byte(i)] + if !ok { + r = rune(i) + } + if r < 128 && r != rune(i) { + ascii = false + } + if r != RuneError { + c.bytes[r] = byte(i) + } + utf := make([]byte, utf8.RuneLen(r)) + utf8.EncodeRune(utf, r) + c.runes[i] = utf + } + if ascii && c.ReplacementChar == '\x00' { + c.ReplacementChar = ASCIISub + } +} + +// NewDecoder returns a Decoder the converts from the 8-bit +// character set to UTF-8. Unknown mappings, if any, are mapped +// to '\uFFFD'. +func (c *Charmap) NewDecoder() *encoding.Decoder { + c.Init() + return &encoding.Decoder{Transformer: &cmapDecoder{runes: c.runes}} +} + +// NewEncoder returns a Transformer that converts from UTF8 to the +// 8-bit character set. Unknown mappings are mapped to 0x1A. +func (c *Charmap) NewEncoder() *encoding.Encoder { + c.Init() + return &encoding.Encoder{ + Transformer: &cmapEncoder{ + bytes: c.bytes, + replace: c.ReplacementChar, + }, + } +} + +func (d *cmapDecoder) Transform(dst, src []byte, atEOF bool) (int, int, error) { + var e error + var ndst, nsrc int + + for _, c := range src { + b := d.runes[c] + l := len(b) + + if ndst+l > len(dst) { + e = transform.ErrShortDst + break + } + for i := 0; i < l; i++ { + dst[ndst] = b[i] + ndst++ + } + nsrc++ + } + return ndst, nsrc, e +} + +func (d *cmapEncoder) Transform(dst, src []byte, atEOF bool) (int, int, error) { + var e error + var ndst, nsrc int + for nsrc < len(src) { + if ndst >= len(dst) { + e = transform.ErrShortDst + break + } + + r, sz := utf8.DecodeRune(src[nsrc:]) + if r == utf8.RuneError && sz == 1 { + // If its inconclusive due to insufficient data in + // in the source, report it + if atEOF && !utf8.FullRune(src[nsrc:]) { + e = transform.ErrShortSrc + break + } + } + + if c, ok := d.bytes[r]; ok { + dst[ndst] = c + } else { + dst[ndst] = d.replace + } + nsrc += sz + ndst++ + } + + return ndst, nsrc, e +} diff --git a/vendor/github.com/gdamore/encoding/doc.go b/vendor/github.com/gdamore/encoding/doc.go new file mode 100644 index 0000000..8a7b48d --- /dev/null +++ b/vendor/github.com/gdamore/encoding/doc.go @@ -0,0 +1,17 @@ +// Copyright 2015 Garrett D'Amore +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package encoding provides a few of the encoding structures that are +// missing from the Go x/text/encoding tree. +package encoding diff --git a/vendor/github.com/gdamore/encoding/ebcdic.go b/vendor/github.com/gdamore/encoding/ebcdic.go new file mode 100644 index 0000000..8e13f1a --- /dev/null +++ b/vendor/github.com/gdamore/encoding/ebcdic.go @@ -0,0 +1,273 @@ +// Copyright 2015 Garrett D'Amore +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "golang.org/x/text/encoding" +) + +// EBCDIC represents the 8-bit EBCDIC scheme, found in some mainframe +// environments. If you don't know what this is, consider yourself lucky. +var EBCDIC encoding.Encoding + +func init() { + cm := &Charmap{ + ReplacementChar: '\x3f', + Map: map[byte]rune{ + // 0x00-0x03 match + 0x04: RuneError, + 0x05: '\t', + 0x06: RuneError, + 0x07: '\x7f', + 0x08: RuneError, + 0x09: RuneError, + 0x0a: RuneError, + // 0x0b-0x13 match + 0x14: RuneError, + 0x15: '\x85', // Not in any ISO code + 0x16: '\x08', + 0x17: RuneError, + // 0x18-0x19 match + 0x1a: RuneError, + 0x1b: RuneError, + // 0x1c-0x1f match + 0x20: RuneError, + 0x21: RuneError, + 0x22: RuneError, + 0x23: RuneError, + 0x24: RuneError, + 0x25: '\n', + 0x26: '\x17', + 0x27: '\x1b', + 0x28: RuneError, + 0x29: RuneError, + 0x2a: RuneError, + 0x2b: RuneError, + 0x2c: RuneError, + 0x2d: '\x05', + 0x2e: '\x06', + 0x2f: '\x07', + 0x30: RuneError, + 0x31: RuneError, + 0x32: '\x16', + 0x33: RuneError, + 0x34: RuneError, + 0x35: RuneError, + 0x36: RuneError, + 0x37: '\x04', + 0x38: RuneError, + 0x39: RuneError, + 0x3a: RuneError, + 0x3b: RuneError, + 0x3c: '\x14', + 0x3d: '\x15', + 0x3e: RuneError, + 0x3f: '\x1a', // also replacement char + 0x40: ' ', + 0x41: '\xa0', + 0x42: RuneError, + 0x43: RuneError, + 0x44: RuneError, + 0x45: RuneError, + 0x46: RuneError, + 0x47: RuneError, + 0x48: RuneError, + 0x49: RuneError, + 0x4a: RuneError, + 0x4b: '.', + 0x4c: '<', + 0x4d: '(', + 0x4e: '+', + 0x4f: '|', + 0x50: '&', + 0x51: RuneError, + 0x52: RuneError, + 0x53: RuneError, + 0x54: RuneError, + 0x55: RuneError, + 0x56: RuneError, + 0x57: RuneError, + 0x58: RuneError, + 0x59: RuneError, + 0x5a: '!', + 0x5b: '$', + 0x5c: '*', + 0x5d: ')', + 0x5e: ';', + 0x5f: '¬', + 0x60: '-', + 0x61: '/', + 0x62: RuneError, + 0x63: RuneError, + 0x64: RuneError, + 0x65: RuneError, + 0x66: RuneError, + 0x67: RuneError, + 0x68: RuneError, + 0x69: RuneError, + 0x6a: '¦', + 0x6b: ',', + 0x6c: '%', + 0x6d: '_', + 0x6e: '>', + 0x6f: '?', + 0x70: RuneError, + 0x71: RuneError, + 0x72: RuneError, + 0x73: RuneError, + 0x74: RuneError, + 0x75: RuneError, + 0x76: RuneError, + 0x77: RuneError, + 0x78: RuneError, + 0x79: '`', + 0x7a: ':', + 0x7b: '#', + 0x7c: '@', + 0x7d: '\'', + 0x7e: '=', + 0x7f: '"', + 0x80: RuneError, + 0x81: 'a', + 0x82: 'b', + 0x83: 'c', + 0x84: 'd', + 0x85: 'e', + 0x86: 'f', + 0x87: 'g', + 0x88: 'h', + 0x89: 'i', + 0x8a: RuneError, + 0x8b: RuneError, + 0x8c: RuneError, + 0x8d: RuneError, + 0x8e: RuneError, + 0x8f: '±', + 0x90: RuneError, + 0x91: 'j', + 0x92: 'k', + 0x93: 'l', + 0x94: 'm', + 0x95: 'n', + 0x96: 'o', + 0x97: 'p', + 0x98: 'q', + 0x99: 'r', + 0x9a: RuneError, + 0x9b: RuneError, + 0x9c: RuneError, + 0x9d: RuneError, + 0x9e: RuneError, + 0x9f: RuneError, + 0xa0: RuneError, + 0xa1: '~', + 0xa2: 's', + 0xa3: 't', + 0xa4: 'u', + 0xa5: 'v', + 0xa6: 'w', + 0xa7: 'x', + 0xa8: 'y', + 0xa9: 'z', + 0xaa: RuneError, + 0xab: RuneError, + 0xac: RuneError, + 0xad: RuneError, + 0xae: RuneError, + 0xaf: RuneError, + 0xb0: '^', + 0xb1: RuneError, + 0xb2: RuneError, + 0xb3: RuneError, + 0xb4: RuneError, + 0xb5: RuneError, + 0xb6: RuneError, + 0xb7: RuneError, + 0xb8: RuneError, + 0xb9: RuneError, + 0xba: '[', + 0xbb: ']', + 0xbc: RuneError, + 0xbd: RuneError, + 0xbe: RuneError, + 0xbf: RuneError, + 0xc0: '{', + 0xc1: 'A', + 0xc2: 'B', + 0xc3: 'C', + 0xc4: 'D', + 0xc5: 'E', + 0xc6: 'F', + 0xc7: 'G', + 0xc8: 'H', + 0xc9: 'I', + 0xca: '\xad', // NB: soft hyphen + 0xcb: RuneError, + 0xcc: RuneError, + 0xcd: RuneError, + 0xce: RuneError, + 0xcf: RuneError, + 0xd0: '}', + 0xd1: 'J', + 0xd2: 'K', + 0xd3: 'L', + 0xd4: 'M', + 0xd5: 'N', + 0xd6: 'O', + 0xd7: 'P', + 0xd8: 'Q', + 0xd9: 'R', + 0xda: RuneError, + 0xdb: RuneError, + 0xdc: RuneError, + 0xdd: RuneError, + 0xde: RuneError, + 0xdf: RuneError, + 0xe0: '\\', + 0xe1: '\u2007', // Non-breaking space + 0xe2: 'S', + 0xe3: 'T', + 0xe4: 'U', + 0xe5: 'V', + 0xe6: 'W', + 0xe7: 'X', + 0xe8: 'Y', + 0xe9: 'Z', + 0xea: RuneError, + 0xeb: RuneError, + 0xec: RuneError, + 0xed: RuneError, + 0xee: RuneError, + 0xef: RuneError, + 0xf0: '0', + 0xf1: '1', + 0xf2: '2', + 0xf3: '3', + 0xf4: '4', + 0xf5: '5', + 0xf6: '6', + 0xf7: '7', + 0xf8: '8', + 0xf9: '9', + 0xfa: RuneError, + 0xfb: RuneError, + 0xfc: RuneError, + 0xfd: RuneError, + 0xfe: RuneError, + 0xff: RuneError, + }} + cm.Init() + EBCDIC = cm +} diff --git a/vendor/github.com/gdamore/encoding/latin1.go b/vendor/github.com/gdamore/encoding/latin1.go new file mode 100644 index 0000000..226bf01 --- /dev/null +++ b/vendor/github.com/gdamore/encoding/latin1.go @@ -0,0 +1,33 @@ +// Copyright 2015 Garrett D'Amore +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "golang.org/x/text/encoding" +) + +// ISO8859_1 represents the 8-bit ISO8859-1 scheme. It decodes directly to +// UTF-8 without change, as all ISO8859-1 values are legal UTF-8. +// Unicode values less than 256 (i.e. 8 bits) map 1:1 with 8859-1. +// It encodes runes outside of that to 0x1A, the ASCII substitution character. +var ISO8859_1 encoding.Encoding + +func init() { + cm := &Charmap{} + cm.Init() + + // 8859-1 is the 8-bit identity map for Unicode. + ISO8859_1 = cm +} diff --git a/vendor/github.com/gdamore/encoding/latin5.go b/vendor/github.com/gdamore/encoding/latin5.go new file mode 100644 index 0000000..c75ecf2 --- /dev/null +++ b/vendor/github.com/gdamore/encoding/latin5.go @@ -0,0 +1,35 @@ +// Copyright 2015 Garrett D'Amore +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "golang.org/x/text/encoding" +) + +// ISO8859_9 represents the 8-bit ISO8859-9 scheme. +var ISO8859_9 encoding.Encoding + +func init() { + cm := &Charmap{Map: map[byte]rune{ + 0xD0: 'Ğ', + 0xDD: 'İ', + 0xDE: 'Ş', + 0xF0: 'ğ', + 0xFD: 'ı', + 0xFE: 'ş', + }} + cm.Init() + ISO8859_9 = cm +} diff --git a/vendor/github.com/gdamore/encoding/utf8.go b/vendor/github.com/gdamore/encoding/utf8.go new file mode 100644 index 0000000..2d59f4b --- /dev/null +++ b/vendor/github.com/gdamore/encoding/utf8.go @@ -0,0 +1,35 @@ +// Copyright 2015 Garrett D'Amore +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "golang.org/x/text/encoding" +) + +type validUtf8 struct{} + +// UTF8 is an encoding for UTF-8. All it does is verify that the UTF-8 +// in is valid. The main reason for its existence is that it will detect +// and report ErrSrcShort or ErrDstShort, whereas the Nop encoding just +// passes every byte, blithely. +var UTF8 encoding.Encoding = validUtf8{} + +func (validUtf8) NewDecoder() *encoding.Decoder { + return &encoding.Decoder{Transformer: encoding.UTF8Validator} +} + +func (validUtf8) NewEncoder() *encoding.Encoder { + return &encoding.Encoder{Transformer: encoding.UTF8Validator} +} diff --git a/vendor/github.com/gdamore/tcell/v2/.appveyor.yml b/vendor/github.com/gdamore/tcell/v2/.appveyor.yml new file mode 100644 index 0000000..435dfe3 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/.appveyor.yml @@ -0,0 +1,13 @@ +version: 1.0.{build} +clone_folder: c:\gopath\src\github.com\gdamore\tcell +environment: + GOPATH: c:\gopath +build_script: +- go version +- go env +- SET PATH=%LOCALAPPDATA%\atom\bin;%GOPATH%\bin;%PATH% +- go get -t ./... +- go build +- go install ./... +test_script: +- go test ./... diff --git a/vendor/github.com/gdamore/tcell/v2/.gitignore b/vendor/github.com/gdamore/tcell/v2/.gitignore new file mode 100644 index 0000000..c57100a --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/.gitignore @@ -0,0 +1 @@ +coverage.txt diff --git a/vendor/github.com/gdamore/tcell/v2/.travis.yml b/vendor/github.com/gdamore/tcell/v2/.travis.yml new file mode 100644 index 0000000..967b5b3 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/.travis.yml @@ -0,0 +1,18 @@ +language: go + +go: + - 1.15.x + - master + +arch: + - amd64 + - ppc64le + +before_install: + - go get -t -v ./... + +script: + - go test -race -coverprofile=coverage.txt -covermode=atomic + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/gdamore/tcell/v2/AUTHORS b/vendor/github.com/gdamore/tcell/v2/AUTHORS new file mode 100644 index 0000000..53f87ee --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/AUTHORS @@ -0,0 +1,4 @@ +Garrett D'Amore +Zachary Yedidia +Junegunn Choi +Staysail Systems, Inc. diff --git a/vendor/github.com/gdamore/tcell/v2/CHANGESv2.md b/vendor/github.com/gdamore/tcell/v2/CHANGESv2.md new file mode 100644 index 0000000..ad97c11 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/CHANGESv2.md @@ -0,0 +1,82 @@ +## Breaking Changes in _Tcell_ v2 + +A number of changes were made to _Tcell_ for version two, and some of these are breaking. + +### Import Path + +The import path for tcell has changed to `github.com/gdamore/tcell/v2` to reflect a new major version. + +### Style Is Not Numeric + +The type `Style` has changed to a structure, to allow us to add additional data such as flags for color setting, +more attribute bits, and so forth. +Applications that relied on this being a number will need to be updated to use the accessor methods. + +### Mouse Event Changes + +The middle mouse button was reported as button 2 on Linux, but as button 3 on Windows, +and the right mouse button was reported the reverse way. +_Tcell_ now always reports the right mouse button as button 2, and the middle button as button 3. +To help make this clearer, new symbols `ButtonPrimary`, `ButtonSecondary`, and +`ButtonMiddle` are provided. +(Note that which button is right vs. left may be impacted by user preferences. +Usually the left button will be considered the Primary, and the right will be the Secondary.) +Applications may need to adjust their handling of mouse buttons 2 and 3 accordingly. + +### Terminals Removed + +A number of terminals have been removed. +These are mostly ancient definitions unlikely to be used by anyone, such as `adm3a`. + +### High Number Function Keys + +Historically terminfo reported function keys with modifiers set as a different +function key altogether. For example, Shift-F1 was reported as F13 on XTerm. +_Tcell_ now prefers to report these using the base key (such as F1) with modifiers added. +This works on XTerm and VTE based emulators, but some emulators may not support this. +The new behavior more closely aligns with behavior on Windows platforms. + +## New Features in _Tcell_ v2 + +These features are not breaking, but are introduced in version 2. + +### Improved Modifier Support + +For terminals that appear to behave like the venerable XTerm, _tcell_ +automatically adds modifier reporting for ALT, CTRL, SHIFT, and META keys +when the terminal reports them. + +### Better Support for Palettes (Themes) + +When using a color by its name or palette entry, _Tcell_ now tries to +use that palette entry as is; this should avoid some inconsistency and respect +terminal themes correctly. + +When true fidelity to RGB values is needed, the new `TrueColor()` API can be used +to create a direct color, which bypasses the palette altogether. + +### Automatic TrueColor Detection + +For some terminals, if the `Tc` or `RGB` properties are present in terminfo, +_Tcell_ will automatically assume the terminal supports 24-bit color. + +### ColorReset + +A new color value, `ColorReset` can be used on the foreground or background +to reset the color the default used by the terminal. + +### tmux Support + +_Tcell_ now has improved support for tmux, when the `$TERM` variable is set to "tmux". + +### Strikethrough Support + +_Tcell_ has support for strikethrough when the terminal supports it, using the new `StrikeThrough()` API. + +### Bracketed Paste Support + +_Tcell_ provides the long requested capability to discriminate paste event by using the +bracketed-paste capability present in some terminals. This is automatically available on +terminals that support XTerm style mouse handling, but applications must opt-in to this +by using the new `EnablePaste()` function. A new `EventPaste` type of event will be +delivered when starting and finishing a paste operation. \ No newline at end of file diff --git a/vendor/github.com/gdamore/tcell/v2/LICENSE b/vendor/github.com/gdamore/tcell/v2/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/gdamore/tcell/v2/README-wasm.md b/vendor/github.com/gdamore/tcell/v2/README-wasm.md new file mode 100644 index 0000000..faf9685 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/README-wasm.md @@ -0,0 +1,61 @@ +# WASM for _Tcell_ + +You can build _Tcell_ project into a webpage by compiling it slightly differently. This will result in a _Tcell_ project you can embed into another html page, or use as a standalone page. + +## Building your project + +WASM needs special build flags in order to work. You can build it by executing +```sh +GOOS=js GOARCH=wasm go build -o yourfile.wasm +``` + +## Additional files + +You also need 5 other files in the same directory as the wasm. Four (`tcell.html`, `tcell.js`, `termstyle.css`, and `beep.wav`) are provided in the `webfiles` directory. The last one, `wasm_exec.js`, can be copied from GOROOT into the current directory by executing +```sh +cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./ +``` + +In `tcell.js`, you also need to change the constant +```js +const wasmFilePath = "yourfile.wasm" +``` +to the file you outputed to when building. + +## Displaying your project + +### Standalone + +You can see the project (with an white background around the terminal) by serving the directory. You can do this using any framework, including another golang project: + +```golang +// server.go + +package main + +import ( + "log" + "net/http" +) + +func main() { + log.Fatal(http.ListenAndServe(":8080", + http.FileServer(http.Dir("/path/to/dir/to/serve")), + )) +} + +``` + +To see the webpage with this example, you can type in `localhost:8080/tcell.html` into your browser while `server.go` is running. + +### Embedding +It is recomended to use an iframe if you want to embed the app into a webpage: +```html + +``` + +## Other considerations + +### Accessing files + +`io.Open(filename)` and other related functions for reading file systems do not work; use `http.Get(filename)` instead. \ No newline at end of file diff --git a/vendor/github.com/gdamore/tcell/v2/README.md b/vendor/github.com/gdamore/tcell/v2/README.md new file mode 100644 index 0000000..37c7dea --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/README.md @@ -0,0 +1,288 @@ + + +# Tcell + +_Tcell_ is a _Go_ package that provides a cell based view for text terminals, like _XTerm_. +It was inspired by _termbox_, but includes many additional improvements. + +[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) +[![Linux](https://img.shields.io/github/actions/workflow/status/gdamore/tcell/linux.yml?branch=main&logoColor=grey&logo=linux&label=)](https://github.com/gdamore/tcell/actions/workflows/linux.yml) +[![Windows](https://img.shields.io/github/actions/workflow/status/gdamore/tcell/windows.yml?branch=main&logoColor=grey&logo=windows&label=)](https://github.com/gdamore/tcell/actions/workflows/windows.yml) +[![Apache License](https://img.shields.io/github/license/gdamore/tcell.svg?logoColor=silver&logo=opensourceinitiative&color=blue&label=)](https://github.com/gdamore/tcell/blob/master/LICENSE) +[![Docs](https://img.shields.io/badge/godoc-reference-blue.svg?label=&logo=go)](https://pkg.go.dev/github.com/gdamore/tcell/v2) +[![Discord](https://img.shields.io/discord/639503822733180969?label=&logo=discord)](https://discord.gg/urTTxDN) +[![Coverage](https://img.shields.io/codecov/c/github/gdamore/tcell?logoColor=grey&logo=codecov&label=)](https://codecov.io/gh/gdamore/tcell) +[![Go Report Card](https://goreportcard.com/badge/github.com/gdamore/tcell/v2)](https://goreportcard.com/report/github.com/gdamore/tcell/v2) + +Please see [here](UKRAINE.md) for an important message for the people of Russia. + +NOTE: This is version 2 of _Tcell_. There are breaking changes relative to version 1. +Version 1.x remains available using the import `github.com/gdamore/tcell`. + +## Tutorial + +A brief, and still somewhat rough, [tutorial](TUTORIAL.md) is available. + +## Examples + +- [proxima5](https://github.com/gdamore/proxima5) - space shooter ([video](https://youtu.be/jNxKTCmY_bQ)) +- [govisor](https://github.com/gdamore/govisor) - service management UI ([screenshot](http://2.bp.blogspot.com/--OsvnfzSNow/Vf7aqMw3zXI/AAAAAAAAARo/uOMtOvw4Sbg/s1600/Screen%2BShot%2B2015-09-20%2Bat%2B9.08.41%2BAM.png)) +- mouse demo - included mouse test ([screenshot](http://2.bp.blogspot.com/-fWvW5opT0es/VhIdItdKqJI/AAAAAAAAATE/7Ojc0L1SpB0/s1600/Screen%2BShot%2B2015-10-04%2Bat%2B11.47.13%2BPM.png)) +- [gomatrix](https://github.com/gdamore/gomatrix) - converted from Termbox +- [micro](https://github.com/zyedidia/micro/) - lightweight text editor with syntax-highlighting and themes +- [godu](https://github.com/viktomas/godu) - utility to discover large files/folders +- [tview](https://github.com/rivo/tview/) - rich interactive widgets +- [cview](https://code.rocketnine.space/tslocum/cview) - user interface toolkit (fork of _tview_) +- [awsome gocui](https://github.com/awesome-gocui/gocui) - Go Console User Interface +- [gomandelbrot](https://github.com/rgm3/gomandelbrot) - Mandelbrot! +- [WTF](https://github.com/senorprogrammer/wtf) - personal information dashboard +- [browsh](https://github.com/browsh-org/browsh) - modern web browser ([video](https://www.youtube.com/watch?v=HZq86XfBoRo)) +- [go-life](https://github.com/sachaos/go-life) - Conway's Game of Life +- [gowid](https://github.com/gcla/gowid) - compositional widgets for terminal UIs, inspired by _urwid_ +- [termshark](https://termshark.io) - interface for _tshark_, inspired by Wireshark, built on _gowid_ +- [go-tetris](https://github.com/MichaelS11/go-tetris) - Go Tetris with AI option +- [fzf](https://github.com/junegunn/fzf) - command-line fuzzy finder +- [ascii-fluid](https://github.com/esimov/ascii-fluid) - fluid simulation controlled by webcam +- [cbind](https://code.rocketnine.space/tslocum/cbind) - key event encoding, decoding and handling +- [tpong](https://github.com/spinzed/tpong) - old-school Pong +- [aerc](https://git.sr.ht/~sircmpwn/aerc) - email client +- [tblogs](https://github.com/ezeoleaf/tblogs) - development blogs reader +- [spinc](https://github.com/lallassu/spinc) - _irssi_ inspired chat application for Cisco Spark/WebEx +- [gorss](https://github.com/lallassu/gorss) - RSS/Atom feed reader +- [memoryalike](https://github.com/Bios-Marcel/memoryalike) - memorization game +- [lf](https://github.com/gokcehan/lf) - file manager +- [goful](https://github.com/anmitsu/goful) - CUI file manager +- [gokeybr](https://github.com/bunyk/gokeybr) - deliberately practice your typing +- [gonano](https://github.com/jbaramidze/gonano) - editor, mimics _nano_ +- [uchess](https://github.com/tmountain/uchess) - UCI chess client +- [min](https://github.com/a-h/min) - Gemini browser +- [ov](https://github.com/noborus/ov) - file pager +- [tmux-wormhole](https://github.com/gcla/tmux-wormhole) - _tmux_ plugin to transfer files +- [gruid-tcell](https://github.com/anaseto/gruid-tcell) - driver for the grid based UI and game framework +- [aretext](https://github.com/aretext/aretext) - minimalist text editor with _vim_ key bindings +- [sync](https://github.com/kyprifog/sync) - GitHub repo synchronization tool +- [statusbar](https://github.com/kyprifog/statusbar) - statusbar motivation tool for tracking periodic tasks/goals +- [todo](https://github.com/kyprifog/todo) - simple todo app +- [gosnakego](https://github.com/liweiyi88/gosnakego) - a snake game +- [gbb](https://github.com/sdemingo/gbb) - A classical bulletin board app for tildes or public unix servers +- [lil](https://github.com/andrievsky/lil) - A simple and flexible interface for any service by implementing only list and get operations +- [hero.go](https://github.com/barisbll/hero.go) - 2d monster shooter ([video](https://user-images.githubusercontent.com/40062673/277157369-240d7606-b471-4aa1-8c54-4379a513122b.mp4)) + +## Pure Go Terminfo Database + +_Tcell_ includes a full parser and expander for terminfo capability strings, +so that it can avoid hard coding escape strings for formatting. It also favors +portability, and includes support for all POSIX systems. + +The database is also flexible & extensible, and can be modified by either running +a program to build the entire database, or an entry for just a single terminal. + +## More Portable + +_Tcell_ is portable to a wide variety of systems, and is pure Go, without +any need for CGO. +_Tcell_ is believed to work with mainstream systems officially supported by golang. + +## No Async IO + +_Tcell_ is able to operate without requiring `SIGIO` signals (unlike _termbox_), +or asynchronous I/O, and can instead use standard Go file objects and Go routines. +This means it should be safe, especially for +use with programs that use exec, or otherwise need to manipulate the tty streams. +This model is also much closer to idiomatic Go, leading to fewer surprises. + +## Rich Unicode & non-Unicode support + +_Tcell_ includes enhanced support for Unicode, including wide characters and +combining characters, provided your terminal can support them. +Note that +Windows terminals generally don't support the full Unicode repertoire. + +It will also convert to and from Unicode locales, so that the program +can work with UTF-8 internally, and get reasonable output in other locales. +_Tcell_ tries hard to convert to native characters on both input and output. +On output _Tcell_ even makes use of the alternate character set to facilitate +drawing certain characters. + +## More Function Keys + +_Tcell_ also has richer support for a larger number of special keys that some +terminals can send. + +## Better Color Handling + +_Tcell_ will respect your terminal's color space as specified within your terminfo entries. +For example attempts to emit color sequences on VT100 terminals +won't result in unintended consequences. + +In legacy Windows mode, _Tcell_ supports 16 colors, bold, dim, and reverse, +instead of just termbox's 8 colors with reverse. (Note that there is some +conflation with bold/dim and colors.) +Modern Windows 10 can benefit from much richer colors however. + +_Tcell_ maps 16 colors down to 8, for terminals that need it. +(The upper 8 colors are just brighter versions of the lower 8.) + +## Better Mouse Support + +_Tcell_ supports enhanced mouse tracking mode, so your application can receive +regular mouse motion events, and wheel events, if your terminal supports it. + +(Note: The Windows 10 Terminal application suffers from a flaw in this regard, +and does not support mouse interaction. The stock Windows 10 console host +fired up with cmd.exe or PowerShell works fine however.) + +## _Termbox_ Compatibility + +A compatibility layer for _termbox_ is provided in the `compat` directory. +To use it, try importing `github.com/gdamore/tcell/termbox` instead. +Most _termbox-go_ programs will probably work without further modification. + +## Working With Unicode + +Internally _Tcell_ uses UTF-8, just like Go. +However, _Tcell_ understands how to +convert to and from other character sets, using the capabilities of +the `golang.org/x/text/encoding packages`. +Your application must supply +them, as the full set of the most common ones bloats the program by about 2 MB. +If you're lazy, and want them all anyway, see the `encoding` sub-directory. + +## Wide & Combining Characters + +The `SetContent()` API takes a primary rune, and an optional list of combining runes. +If any of the runes is a wide (East Asian) rune occupying two cells, +then the library will skip output from the following cell. Care must be +taken in the application to avoid explicitly attempting to set content in the +next cell, otherwise the results are undefined. (Normally the wide character +is displayed, and the other character is not; do not depend on that behavior.) + +Older terminal applications (especially on systems like Windows 8) lack support +for advanced Unicode, and thus may not fare well. + +## Colors + +_Tcell_ assumes the ANSI/XTerm color model, including the 256 color map that +XTerm uses when it supports 256 colors. The terminfo guidance will be +honored, with respect to the number of colors supported. Also, only +terminals which expose ANSI style `setaf` and `setab` will support color; +if you have a color terminal that only has `setf` and `setb`, please submit +a ticket. + +## 24-bit Color + +_Tcell_ _supports 24-bit color!_ (That is, if your terminal can support it.) + +NOTE: Technically the approach of using 24-bit RGB values for color is more +accurately described as "direct color", but most people use the term "true color". +We follow the (inaccurate) common convention. + +There are a few ways you can enable (or disable) true color. + +- For many terminals, we can detect it automatically if your terminal + includes the `RGB` or `Tc` capabilities (or rather it did when the database + was updated.) + +- You can force this one by setting the `COLORTERM` environment variable to + `24-bit`, `truecolor` or `24bit`. This is the same method used + by most other terminal applications that support 24-bit color. + +- If you set your `TERM` environment variable to a value with the suffix `-truecolor` + then 24-bit color compatible with XTerm and ECMA-48 will be assumed. + (This feature is deprecated. + It is recommended to use one of other methods listed above.) + +- You can disable 24-bit color by setting `TCELL_TRUECOLOR=disable` in your + environment. + +When using TrueColor, programs will display the colors that the programmer +intended, overriding any "`themes`" you may have set in your terminal +emulator. (For some cases, accurate color fidelity is more important +than respecting themes. For other cases, such as typical text apps that +only use a few colors, its more desirable to respect the themes that +the user has established.) + +## Performance + +Reasonable attempts have been made to minimize sending data to terminals, +avoiding repeated sequences or drawing the same cell on refresh updates. + +## Terminfo + +(Not relevant for Windows users.) + +The Terminfo implementation operates with a built-in database. +This should satisfy most users. However, it can also (on systems +with ncurses installed), dynamically parse the output from `infocmp` +for terminals it does not already know about. + +See the `terminfo/` directory for more information about generating +new entries for the built-in database. + +_Tcell_ requires that the terminal support the `cup` mode of cursor addressing. +Ancient terminals without the ability to position the cursor directly +are not supported. +This is unlikely to be a problem; such terminals have not been mass-produced +since the early 1970s. + +## Mouse Support + +Mouse support is detected via the `kmous` terminfo variable, however, +enablement/disablement and decoding mouse events is done using hard coded +sequences based on the XTerm X11 model. All popular +terminals with mouse tracking support this model. (Full terminfo support +is not possible as terminfo sequences are not defined.) + +On Windows, the mouse works normally. + +Mouse wheel buttons on various terminals are known to work, but the support +in terminal emulators, as well as support for various buttons and +live mouse tracking, varies widely. +Modern _xterm_, macOS _Terminal_, and _iTerm_ all work well. + +## Bracketed Paste + +Terminals that appear to support the XTerm mouse model also can support +bracketed paste, for applications that opt-in. See `EnablePaste()` for details. + +## Testability + +There is a `SimulationScreen`, that can be used to simulate a real screen +for automated testing. The supplied tests do this. The simulation contains +event delivery, screen resizing support, and capabilities to inject events +and examine "`physical`" screen contents. + +## Platforms + +### POSIX (Linux, FreeBSD, macOS, Solaris, etc.) + +Everything works using pure Go on mainstream platforms. Some more esoteric +platforms (e.g., AIX) may need to be added. Pull requests are welcome! + +### Windows + +Windows console mode applications are supported. + +Modern console applications like ConEmu and the Windows 10 terminal, +support all the good features (resize, mouse tracking, etc.) + +### WASM + +WASM is supported, but needs additional setup detailed in [README-wasm](README-wasm.md). + +### Plan9 and others + +These platforms won't work, but compilation stubs are supplied +for folks that want to include parts of this in software for those +platforms. The Simulation screen works, but as _Tcell_ doesn't know how to +allocate a real screen object on those platforms, `NewScreen()` will fail. + +If anyone has wisdom about how to improve support for these, +please let me know. PRs are especially welcome. + +### Commercial Support + +_Tcell_ is absolutely free, but if you want to obtain commercial, professional support, there are options. + +- [TideLift](https://tidelift.com/) subscriptions include support for _Tcell_, as well as many other open source packages. +- [Staysail Systems Inc.](mailto:info@staysail.tech) offers direct support, and custom development around _Tcell_ on an hourly basis. \ No newline at end of file diff --git a/vendor/github.com/gdamore/tcell/v2/SECURITY.md b/vendor/github.com/gdamore/tcell/v2/SECURITY.md new file mode 100644 index 0000000..5c0aa5a --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/SECURITY.md @@ -0,0 +1,15 @@ +# SECURITY + +It's somewhat unlikely that tcell is in a security sensitive path, +but we do take security seriously. + +## Vulnerabilityu Response + +If you report a vulnerability, we will respond within 2 business days. + +## Report a Vulnerability + +If you wish to report a vulnerability found in tcell, simply send a message +to garrett@damore.org. You may also reach us on our discord channel - +https://discord.gg/urTTxDN - a private message to `gdamore` on that channel +may be submitted instead of mail. diff --git a/vendor/github.com/gdamore/tcell/v2/TUTORIAL.md b/vendor/github.com/gdamore/tcell/v2/TUTORIAL.md new file mode 100644 index 0000000..f52fcff --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/TUTORIAL.md @@ -0,0 +1,313 @@ +# _Tcell_ Tutorial + +_Tcell_ provides a low-level, portable API for building terminal-based programs. +A [terminal emulator](https://en.wikipedia.org/wiki/Terminal_emulator) +(or a real terminal such as a DEC VT-220) is used to interact with such a program. + +_Tcell_'s interface is fairly low-level. +While it provides a reasonably portable way of dealing with all the usual terminal +features, it may be easier to utilize a higher level framework. +A number of such frameworks are listed on the _Tcell_ main [README](README.md). + +This tutorial provides the details of _Tcell_, and is appropriate for developers +wishing to create their own application frameworks or needing more direct access +to the terminal capabilities. + +## Resize events + +Applications receive an event of type `EventResize` when they are first initialized and each time the terminal is resized. +The new size is available as `Size`. + +```go +switch ev := ev.(type) { +case *tcell.EventResize: + w, h := ev.Size() + logMessage(fmt.Sprintf("Resized to %dx%d", w, h)) +} +``` + +## Key events + +When a key is pressed, applications receive an event of type `EventKey`. +This event describes the modifier keys pressed (if any) and the pressed key or rune. + +When a rune key is pressed, an event with its `Key` set to `KeyRune` is dispatched. + +When a non-rune key is pressed, it is available as the `Key` of the event. + +```go +switch ev := ev.(type) { +case *tcell.EventKey: + mod, key, ch := ev.Mod(), ev.Key(), ev.Rune() + logMessage(fmt.Sprintf("EventKey Modifiers: %d Key: %d Rune: %d", mod, key, ch)) +} +``` + +### Key event restrictions + +Terminal-based programs have less visibility into keyboard activity than graphical applications. + +When a key is pressed and held, additional key press events are sent by the terminal emulator. +The rate of these repeated events depends on the emulator's configuration. +Key release events are not available. + +It is not possible to distinguish runes typed while holding shift and runes typed using caps lock. +Capital letters are reported without the Shift modifier. + +## Mouse events + +Applications receive an event of type `EventMouse` when the mouse moves, or a mouse button is pressed or released. +Mouse events are only delivered if +`EnableMouse` has been called. + +The mouse buttons being pressed (if any) are available as `Buttons`, and the position of the mouse is available as `Position`. + +```go +switch ev := ev.(type) { +case *tcell.EventMouse: + mod := ev.Modifiers() + btns := ev.Buttons() + x, y := ev.Position() + logMessage(fmt.Sprintf("EventMouse Modifiers: %d Buttons: %d Position: %d,%d", mod, btns, x, y)) +} +``` + +### Mouse buttons + +Identifier | Alias | Description +-----------|-----------------|----------- +Button1 | ButtonPrimary | Left button +Button2 | ButtonSecondary | Right button +Button3 | ButtonMiddle | Middle button +Button4 | | Side button (thumb/next) +Button5 | | Side button (thumb/prev) +WheelUp | | Scroll wheel up +WheelDown | | Scroll wheel down +WheelLeft | | Horizontal wheel left +WheelRight | | Horizontal wheel right + +## Usage + +To create a _Tcell_ application, first initialize a screen to hold it. + +```go +s, err := tcell.NewScreen() +if err != nil { + log.Fatalf("%+v", err) +} +if err := s.Init(); err != nil { + log.Fatalf("%+v", err) +} + +// Set default text style +defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset) +s.SetStyle(defStyle) + +// Clear screen +s.Clear() +``` + +Text may be drawn on the screen using `SetContent`. + +```go +s.SetContent(0, 0, 'H', nil, defStyle) +s.SetContent(1, 0, 'i', nil, defStyle) +s.SetContent(2, 0, '!', nil, defStyle) +``` + +To draw text more easily, define a render function. + +```go +func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) { + row := y1 + col := x1 + for _, r := range []rune(text) { + s.SetContent(col, row, r, nil, style) + col++ + if col >= x2 { + row++ + col = x1 + } + if row > y2 { + break + } + } +} +``` + +Lastly, define an event loop to handle user input and update application state. + +```go +quit := func() { + s.Fini() + os.Exit(0) +} +for { + // Update screen + s.Show() + + // Poll event + ev := s.PollEvent() + + // Process event + switch ev := ev.(type) { + case *tcell.EventResize: + s.Sync() + case *tcell.EventKey: + if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC { + quit() + } + } +} +``` + +## Demo application + +The following demonstrates how to initialize a screen, draw text/graphics and handle user input. + +```go +package main + +import ( + "fmt" + "log" + + "github.com/gdamore/tcell/v2" +) + +func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) { + row := y1 + col := x1 + for _, r := range []rune(text) { + s.SetContent(col, row, r, nil, style) + col++ + if col >= x2 { + row++ + col = x1 + } + if row > y2 { + break + } + } +} + +func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) { + if y2 < y1 { + y1, y2 = y2, y1 + } + if x2 < x1 { + x1, x2 = x2, x1 + } + + // Fill background + for row := y1; row <= y2; row++ { + for col := x1; col <= x2; col++ { + s.SetContent(col, row, ' ', nil, style) + } + } + + // Draw borders + for col := x1; col <= x2; col++ { + s.SetContent(col, y1, tcell.RuneHLine, nil, style) + s.SetContent(col, y2, tcell.RuneHLine, nil, style) + } + for row := y1 + 1; row < y2; row++ { + s.SetContent(x1, row, tcell.RuneVLine, nil, style) + s.SetContent(x2, row, tcell.RuneVLine, nil, style) + } + + // Only draw corners if necessary + if y1 != y2 && x1 != x2 { + s.SetContent(x1, y1, tcell.RuneULCorner, nil, style) + s.SetContent(x2, y1, tcell.RuneURCorner, nil, style) + s.SetContent(x1, y2, tcell.RuneLLCorner, nil, style) + s.SetContent(x2, y2, tcell.RuneLRCorner, nil, style) + } + + drawText(s, x1+1, y1+1, x2-1, y2-1, style, text) +} + +func main() { + defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset) + boxStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorPurple) + + // Initialize screen + s, err := tcell.NewScreen() + if err != nil { + log.Fatalf("%+v", err) + } + if err := s.Init(); err != nil { + log.Fatalf("%+v", err) + } + s.SetStyle(defStyle) + s.EnableMouse() + s.EnablePaste() + s.Clear() + + // Draw initial boxes + drawBox(s, 1, 1, 42, 7, boxStyle, "Click and drag to draw a box") + drawBox(s, 5, 9, 32, 14, boxStyle, "Press C to reset") + + quit := func() { + // You have to catch panics in a defer, clean up, and + // re-raise them - otherwise your application can + // die without leaving any diagnostic trace. + maybePanic := recover() + s.Fini() + if maybePanic != nil { + panic(maybePanic) + } + } + defer quit() + + // Here's how to get the screen size when you need it. + // xmax, ymax := s.Size() + + // Here's an example of how to inject a keystroke where it will + // be picked up by the next PollEvent call. Note that the + // queue is LIFO, it has a limited length, and PostEvent() can + // return an error. + // s.PostEvent(tcell.NewEventKey(tcell.KeyRune, rune('a'), 0)) + + // Event loop + ox, oy := -1, -1 + for { + // Update screen + s.Show() + + // Poll event + ev := s.PollEvent() + + // Process event + switch ev := ev.(type) { + case *tcell.EventResize: + s.Sync() + case *tcell.EventKey: + if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC { + return + } else if ev.Key() == tcell.KeyCtrlL { + s.Sync() + } else if ev.Rune() == 'C' || ev.Rune() == 'c' { + s.Clear() + } + case *tcell.EventMouse: + x, y := ev.Position() + + switch ev.Buttons() { + case tcell.Button1, tcell.Button2: + if ox < 0 { + ox, oy = x, y // record location when click started + } + + case tcell.ButtonNone: + if ox >= 0 { + label := fmt.Sprintf("%d,%d to %d,%d", ox, oy, x, y) + drawBox(s, ox, oy, x, y, boxStyle, label) + ox, oy = -1, -1 + } + } + } + } +} +``` + diff --git a/vendor/github.com/gdamore/tcell/v2/UKRAINE.md b/vendor/github.com/gdamore/tcell/v2/UKRAINE.md new file mode 100644 index 0000000..d86d3e1 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/UKRAINE.md @@ -0,0 +1,77 @@ +# Ukraine, Russia, and a World Tragedy + +## A message to those inside Russia + +### Written March 4, 2022. + +It is with a very heavy heart that I write this. I am normally opposed to the use of open source +projects to communicate political positions or advocate for things outside the immediate relevancy +to that project. + +However, the events occurring in Ukraine, and specifically the unprecedented invasion of Ukraine by +Russian forces operating under orders from Russian President Vladimir Putin compel me to speak out. + +Those who know me, know that I have family, friends, and colleagues in Russia, and Ukraine both. My closest friends +have historically been Russian friends my wife's hometown of Chelyabinsk. I myself have in the past +frequently traveled to Russia, and indeed operated a software development firm with offices in St. Petersburg. +I had a special kinship with Russia and its people. + +I say "had", because I fear that the actions of Putin, and the massive disinformation campaign that his regime +has waged inside Russia, mean that it's likely that I won't see those friends again. At present, I'm not sure +my wife will see her own mother again. We no longer feel it's safe for either of us to return Russia given +actions taken by the regime to crack down on those who express disagreement. + +Russian citizens are being led to believe it is acting purely defensively, and that only legitimate military +targets are being targeted, and that all the information we have received in the West are fakes. + +I am confident that nothing could be further from the truth. + +This has caused many in Russia, including people whom I respect and believe to be smarter than this, to +stand by Putin, and endorse his actions. The claim is that the entirety of NATO is operating at the behest +of the USA, and that the entirety of Europe was poised to attack Russia. While this is clearly absurd to those +of us with any understanding of western politics, Russian citizens are being fed this lie, and believing it. + +If you're reading this from inside Russia -- YOU are the person that I hope this message reaches. Your +government is LYING to you. Of course, all governments lie all the time. But consider this. Almost the +entire world has condemned the invasion of Ukraine as criminal, and has applied sanctions. Even countries +which have poor relations with the US sanctioning Russia, as well as nations which historically have remained +neutral. (Famously neutral -- even during World War II, Switzerland has acted to apply sanctions in +concert with the rest of the world.) + +Ask yourself, why does Putin fear a free press so much, if what he says is true? Why the crack-downs on +children expressing only a desire for peace with Ukraine? Why would the entire world unified against him, +if Putin was in the right? Why would the only countries that stood with Russia against +the UN resolution to condemn these acts as crimes be Belarus, North Korea, and Syria? Even countries normally +allied to Russia could not bring themselves to do more than abstain from the vote to condemn it. + +To be clear, I do not claim that the actions taken by the West or by the Ukrainian government were completely +blameless. On the contrary, I understand that Western media is biased, and the truth is rarely exactly +as reported. I believe that there is a kernel of truth in the claims of fascists and ultra-nationalist +militias operating in Ukraine and specifically Donbas. However, I am also equally certain that Putin's +response is out of proportion, and that concerns about such militias are principally just a pretext to justify +an invasion. + +Europe is at war, unlike we've seen in my lifetime. The world is more divided, and closer to nuclear holocaust +than it has been since the Cold War. And that is 100% the fault of Putin. + +While Putin remains in power, there cannot really be any way for Russian international relations to return +to normal. Putin has set your country on a path to return to the Cold War, likely because he fancies himself +to be a new Stalin. However, unlike the Soviet Union, the Russian economy does not have the wherewithal to +stand on its own, and the invasion of Ukraine has fully ensured that Russia will not find any friends anywhere +else in Europe, and probably few places in Asia. + +The *only* paths forward for Russia are either a Russia without Putin (and those who would support his agenda), +or a complete breakdown of Russian prosperity, likely followed by the increasing international conflict that will +be the natural escalation from a country that is isolated and impoverished. Those of us observing from the West are +gravely concerned, because we cannot see any end to this madness that does not result in nuclear conflict, +unless from within. + +In the meantime, the worst prices will be paid for by innocents in Ukraine, and by young Russian mean +forced to carry out the orders of Putin's corrupt regime. + +And *that* is why I write this -- to appeal to those within Russia to open your eyes, and think with +your minds. It is right and proper to be proud of your country and its rich heritage. But it is also +right and proper to look for ways to save it from the ruinous path that its current leadership has set it upon, +and to recognize when that leadership is no longer acting in interest of the country or its people. + + - Garrett D'Amore, March 4, 2022 \ No newline at end of file diff --git a/vendor/github.com/gdamore/tcell/v2/attr.go b/vendor/github.com/gdamore/tcell/v2/attr.go new file mode 100644 index 0000000..8b1eab7 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/attr.go @@ -0,0 +1,33 @@ +// Copyright 2020 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +// AttrMask represents a mask of text attributes, apart from color. +// Note that support for attributes may vary widely across terminals. +type AttrMask int + +// Attributes are not colors, but affect the display of text. They can +// be combined. +const ( + AttrBold AttrMask = 1 << iota + AttrBlink + AttrReverse + AttrUnderline + AttrDim + AttrItalic + AttrStrikeThrough + AttrInvalid // Mark the style or attributes invalid + AttrNone AttrMask = 0 // Just normal text. +) diff --git a/vendor/github.com/gdamore/tcell/v2/cell.go b/vendor/github.com/gdamore/tcell/v2/cell.go new file mode 100644 index 0000000..0debeee --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/cell.go @@ -0,0 +1,256 @@ +// Copyright 2024 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "os" + "reflect" + + runewidth "github.com/mattn/go-runewidth" +) + +type cell struct { + currMain rune + currComb []rune + currStyle Style + lastMain rune + lastStyle Style + lastComb []rune + width int + lock bool +} + +// CellBuffer represents a two-dimensional array of character cells. +// This is primarily intended for use by Screen implementors; it +// contains much of the common code they need. To create one, just +// declare a variable of its type; no explicit initialization is necessary. +// +// CellBuffer is not thread safe. +type CellBuffer struct { + w int + h int + cells []cell +} + +// SetContent sets the contents (primary rune, combining runes, +// and style) for a cell at a given location. If the background or +// foreground of the style is set to ColorNone, then the respective +// color is left un changed. +func (cb *CellBuffer) SetContent(x int, y int, + mainc rune, combc []rune, style Style, +) { + if x >= 0 && y >= 0 && x < cb.w && y < cb.h { + c := &cb.cells[(y*cb.w)+x] + + // Wide characters: we want to mark the "wide" cells + // dirty as well as the base cell, to make sure we consider + // both cells as dirty together. We only need to do this + // if we're changing content + if (c.width > 0) && (mainc != c.currMain || !reflect.DeepEqual(combc, c.currComb)) { + for i := 0; i < c.width; i++ { + cb.SetDirty(x+i, y, true) + } + } + + c.currComb = append([]rune{}, combc...) + + if c.currMain != mainc { + c.width = runewidth.RuneWidth(mainc) + } + c.currMain = mainc + if style.fg == ColorNone { + style.fg = c.currStyle.fg + } + if style.bg == ColorNone { + style.bg = c.currStyle.bg + } + c.currStyle = style + } +} + +// GetContent returns the contents of a character cell, including the +// primary rune, any combining character runes (which will usually be +// nil), the style, and the display width in cells. (The width can be +// either 1, normally, or 2 for East Asian full-width characters.) +func (cb *CellBuffer) GetContent(x, y int) (rune, []rune, Style, int) { + var mainc rune + var combc []rune + var style Style + var width int + if x >= 0 && y >= 0 && x < cb.w && y < cb.h { + c := &cb.cells[(y*cb.w)+x] + mainc, combc, style = c.currMain, c.currComb, c.currStyle + if width = c.width; width == 0 || mainc < ' ' { + width = 1 + mainc = ' ' + } + } + return mainc, combc, style, width +} + +// Size returns the (width, height) in cells of the buffer. +func (cb *CellBuffer) Size() (int, int) { + return cb.w, cb.h +} + +// Invalidate marks all characters within the buffer as dirty. +func (cb *CellBuffer) Invalidate() { + for i := range cb.cells { + cb.cells[i].lastMain = rune(0) + } +} + +// Dirty checks if a character at the given location needs to be +// refreshed on the physical display. This returns true if the cell +// content is different since the last time it was marked clean. +func (cb *CellBuffer) Dirty(x, y int) bool { + if x >= 0 && y >= 0 && x < cb.w && y < cb.h { + c := &cb.cells[(y*cb.w)+x] + if c.lock { + return false + } + if c.lastMain == rune(0) { + return true + } + if c.lastMain != c.currMain { + return true + } + if c.lastStyle != c.currStyle { + return true + } + if len(c.lastComb) != len(c.currComb) { + return true + } + for i := range c.lastComb { + if c.lastComb[i] != c.currComb[i] { + return true + } + } + } + return false +} + +// SetDirty is normally used to indicate that a cell has +// been displayed (in which case dirty is false), or to manually +// force a cell to be marked dirty. +func (cb *CellBuffer) SetDirty(x, y int, dirty bool) { + if x >= 0 && y >= 0 && x < cb.w && y < cb.h { + c := &cb.cells[(y*cb.w)+x] + if dirty { + c.lastMain = rune(0) + } else { + if c.currMain == rune(0) { + c.currMain = ' ' + } + c.lastMain = c.currMain + c.lastComb = c.currComb + c.lastStyle = c.currStyle + } + } +} + +// LockCell locks a cell from being drawn, effectively marking it "clean" until +// the lock is removed. This can be used to prevent tcell from drawing a given +// cell, even if the underlying content has changed. For example, when drawing a +// sixel graphic directly to a TTY screen an implementer must lock the region +// underneath the graphic to prevent tcell from drawing on top of the graphic. +func (cb *CellBuffer) LockCell(x, y int) { + if x < 0 || y < 0 { + return + } + if x >= cb.w || y >= cb.h { + return + } + c := &cb.cells[(y*cb.w)+x] + c.lock = true +} + +// UnlockCell removes a lock from the cell and marks it as dirty +func (cb *CellBuffer) UnlockCell(x, y int) { + if x < 0 || y < 0 { + return + } + if x >= cb.w || y >= cb.h { + return + } + c := &cb.cells[(y*cb.w)+x] + c.lock = false + cb.SetDirty(x, y, true) +} + +// Resize is used to resize the cells array, with different dimensions, +// while preserving the original contents. The cells will be invalidated +// so that they can be redrawn. +func (cb *CellBuffer) Resize(w, h int) { + if cb.h == h && cb.w == w { + return + } + + newc := make([]cell, w*h) + for y := 0; y < h && y < cb.h; y++ { + for x := 0; x < w && x < cb.w; x++ { + oc := &cb.cells[(y*cb.w)+x] + nc := &newc[(y*w)+x] + nc.currMain = oc.currMain + nc.currComb = oc.currComb + nc.currStyle = oc.currStyle + nc.width = oc.width + nc.lastMain = rune(0) + } + } + cb.cells = newc + cb.h = h + cb.w = w +} + +// Fill fills the entire cell buffer array with the specified character +// and style. Normally choose ' ' to clear the screen. This API doesn't +// support combining characters, or characters with a width larger than one. +// If either the foreground or background are ColorNone, then the respective +// color is unchanged. +func (cb *CellBuffer) Fill(r rune, style Style) { + for i := range cb.cells { + c := &cb.cells[i] + c.currMain = r + c.currComb = nil + cs := style + if cs.fg == ColorNone { + cs.fg = c.currStyle.fg + } + if cs.bg == ColorNone { + cs.bg = c.currStyle.bg + } + c.currStyle = cs + c.width = 1 + } +} + +var runeConfig *runewidth.Condition + +func init() { + // The defaults for the runewidth package are poorly chosen for terminal + // applications. We however will honor the setting in the environment if + // it is set. + if os.Getenv("RUNEWIDTH_EASTASIAN") == "" { + runewidth.DefaultCondition.EastAsianWidth = false + } + + // For performance reasons, we create a lookup table. However, some users + // might be more memory conscious. If that's you, set the TCELL_MINIMIZE + // environment variable. + if os.Getenv("TCELL_MINIMIZE") == "" { + runewidth.CreateLUT() + } +} diff --git a/vendor/github.com/gdamore/tcell/v2/charset_stub.go b/vendor/github.com/gdamore/tcell/v2/charset_stub.go new file mode 100644 index 0000000..ec4d260 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/charset_stub.go @@ -0,0 +1,22 @@ +//go:build plan9 || nacl +// +build plan9 nacl + +// Copyright 2015 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +func getCharset() string { + return "" +} diff --git a/vendor/github.com/gdamore/tcell/v2/charset_unix.go b/vendor/github.com/gdamore/tcell/v2/charset_unix.go new file mode 100644 index 0000000..8bbf1f5 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/charset_unix.go @@ -0,0 +1,50 @@ +//go:build !windows && !nacl && !plan9 +// +build !windows,!nacl,!plan9 + +// Copyright 2016 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "os" + "strings" +) + +func getCharset() string { + // Determine the character set. This can help us later. + // Per POSIX, we search for LC_ALL first, then LC_CTYPE, and + // finally LANG. First one set wins. + locale := "" + if locale = os.Getenv("LC_ALL"); locale == "" { + if locale = os.Getenv("LC_CTYPE"); locale == "" { + locale = os.Getenv("LANG") + } + } + if locale == "POSIX" || locale == "C" { + return "US-ASCII" + } + if i := strings.IndexRune(locale, '@'); i >= 0 { + locale = locale[:i] + } + if i := strings.IndexRune(locale, '.'); i >= 0 { + locale = locale[i+1:] + } else { + // Default assumption, and on Linux we can see LC_ALL + // without a character set, which we assume implies UTF-8. + return "UTF-8" + } + // XXX: add support for aliases + return locale +} diff --git a/vendor/github.com/gdamore/tcell/v2/charset_windows.go b/vendor/github.com/gdamore/tcell/v2/charset_windows.go new file mode 100644 index 0000000..08068a0 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/charset_windows.go @@ -0,0 +1,22 @@ +//go:build windows +// +build windows + +// Copyright 2015 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +func getCharset() string { + return "UTF-16" +} diff --git a/vendor/github.com/gdamore/tcell/v2/color.go b/vendor/github.com/gdamore/tcell/v2/color.go new file mode 100644 index 0000000..face860 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/color.go @@ -0,0 +1,1128 @@ +// Copyright 2023 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "fmt" + ic "image/color" + "strconv" +) + +// Color represents a color. The low numeric values are the same as used +// by ECMA-48, and beyond that XTerm. A 24-bit RGB value may be used by +// adding in the ColorIsRGB flag. For Color names we use the W3C approved +// color names. +// +// We use a 64-bit integer to allow future expansion if we want to add an +// 8-bit alpha, while still leaving us some room for extra options. +// +// Note that on various terminals colors may be approximated however, or +// not supported at all. If no suitable representation for a color is known, +// the library will simply not set any color, deferring to whatever default +// attributes the terminal uses. +type Color uint64 + +const ( + // ColorDefault is used to leave the Color unchanged from whatever + // system or terminal default may exist. It's also the zero value. + ColorDefault Color = 0 + + // ColorValid is used to indicate the color value is actually + // valid (initialized). This is useful to permit the zero value + // to be treated as the default. + ColorValid Color = 1 << 32 + + // ColorIsRGB is used to indicate that the numeric value is not + // a known color constant, but rather an RGB value. The lower + // order 3 bytes are RGB. + ColorIsRGB Color = 1 << 33 + + // ColorSpecial is a flag used to indicate that the values have + // special meaning, and live outside of the color space(s). + ColorSpecial Color = 1 << 34 +) + +// Note that the order of these options is important -- it follows the +// definitions used by ECMA and XTerm. Hence any further named colors +// must begin at a value not less than 256. +const ( + ColorBlack = ColorValid + iota + ColorMaroon + ColorGreen + ColorOlive + ColorNavy + ColorPurple + ColorTeal + ColorSilver + ColorGray + ColorRed + ColorLime + ColorYellow + ColorBlue + ColorFuchsia + ColorAqua + ColorWhite + Color16 + Color17 + Color18 + Color19 + Color20 + Color21 + Color22 + Color23 + Color24 + Color25 + Color26 + Color27 + Color28 + Color29 + Color30 + Color31 + Color32 + Color33 + Color34 + Color35 + Color36 + Color37 + Color38 + Color39 + Color40 + Color41 + Color42 + Color43 + Color44 + Color45 + Color46 + Color47 + Color48 + Color49 + Color50 + Color51 + Color52 + Color53 + Color54 + Color55 + Color56 + Color57 + Color58 + Color59 + Color60 + Color61 + Color62 + Color63 + Color64 + Color65 + Color66 + Color67 + Color68 + Color69 + Color70 + Color71 + Color72 + Color73 + Color74 + Color75 + Color76 + Color77 + Color78 + Color79 + Color80 + Color81 + Color82 + Color83 + Color84 + Color85 + Color86 + Color87 + Color88 + Color89 + Color90 + Color91 + Color92 + Color93 + Color94 + Color95 + Color96 + Color97 + Color98 + Color99 + Color100 + Color101 + Color102 + Color103 + Color104 + Color105 + Color106 + Color107 + Color108 + Color109 + Color110 + Color111 + Color112 + Color113 + Color114 + Color115 + Color116 + Color117 + Color118 + Color119 + Color120 + Color121 + Color122 + Color123 + Color124 + Color125 + Color126 + Color127 + Color128 + Color129 + Color130 + Color131 + Color132 + Color133 + Color134 + Color135 + Color136 + Color137 + Color138 + Color139 + Color140 + Color141 + Color142 + Color143 + Color144 + Color145 + Color146 + Color147 + Color148 + Color149 + Color150 + Color151 + Color152 + Color153 + Color154 + Color155 + Color156 + Color157 + Color158 + Color159 + Color160 + Color161 + Color162 + Color163 + Color164 + Color165 + Color166 + Color167 + Color168 + Color169 + Color170 + Color171 + Color172 + Color173 + Color174 + Color175 + Color176 + Color177 + Color178 + Color179 + Color180 + Color181 + Color182 + Color183 + Color184 + Color185 + Color186 + Color187 + Color188 + Color189 + Color190 + Color191 + Color192 + Color193 + Color194 + Color195 + Color196 + Color197 + Color198 + Color199 + Color200 + Color201 + Color202 + Color203 + Color204 + Color205 + Color206 + Color207 + Color208 + Color209 + Color210 + Color211 + Color212 + Color213 + Color214 + Color215 + Color216 + Color217 + Color218 + Color219 + Color220 + Color221 + Color222 + Color223 + Color224 + Color225 + Color226 + Color227 + Color228 + Color229 + Color230 + Color231 + Color232 + Color233 + Color234 + Color235 + Color236 + Color237 + Color238 + Color239 + Color240 + Color241 + Color242 + Color243 + Color244 + Color245 + Color246 + Color247 + Color248 + Color249 + Color250 + Color251 + Color252 + Color253 + Color254 + Color255 + ColorAliceBlue + ColorAntiqueWhite + ColorAquaMarine + ColorAzure + ColorBeige + ColorBisque + ColorBlanchedAlmond + ColorBlueViolet + ColorBrown + ColorBurlyWood + ColorCadetBlue + ColorChartreuse + ColorChocolate + ColorCoral + ColorCornflowerBlue + ColorCornsilk + ColorCrimson + ColorDarkBlue + ColorDarkCyan + ColorDarkGoldenrod + ColorDarkGray + ColorDarkGreen + ColorDarkKhaki + ColorDarkMagenta + ColorDarkOliveGreen + ColorDarkOrange + ColorDarkOrchid + ColorDarkRed + ColorDarkSalmon + ColorDarkSeaGreen + ColorDarkSlateBlue + ColorDarkSlateGray + ColorDarkTurquoise + ColorDarkViolet + ColorDeepPink + ColorDeepSkyBlue + ColorDimGray + ColorDodgerBlue + ColorFireBrick + ColorFloralWhite + ColorForestGreen + ColorGainsboro + ColorGhostWhite + ColorGold + ColorGoldenrod + ColorGreenYellow + ColorHoneydew + ColorHotPink + ColorIndianRed + ColorIndigo + ColorIvory + ColorKhaki + ColorLavender + ColorLavenderBlush + ColorLawnGreen + ColorLemonChiffon + ColorLightBlue + ColorLightCoral + ColorLightCyan + ColorLightGoldenrodYellow + ColorLightGray + ColorLightGreen + ColorLightPink + ColorLightSalmon + ColorLightSeaGreen + ColorLightSkyBlue + ColorLightSlateGray + ColorLightSteelBlue + ColorLightYellow + ColorLimeGreen + ColorLinen + ColorMediumAquamarine + ColorMediumBlue + ColorMediumOrchid + ColorMediumPurple + ColorMediumSeaGreen + ColorMediumSlateBlue + ColorMediumSpringGreen + ColorMediumTurquoise + ColorMediumVioletRed + ColorMidnightBlue + ColorMintCream + ColorMistyRose + ColorMoccasin + ColorNavajoWhite + ColorOldLace + ColorOliveDrab + ColorOrange + ColorOrangeRed + ColorOrchid + ColorPaleGoldenrod + ColorPaleGreen + ColorPaleTurquoise + ColorPaleVioletRed + ColorPapayaWhip + ColorPeachPuff + ColorPeru + ColorPink + ColorPlum + ColorPowderBlue + ColorRebeccaPurple + ColorRosyBrown + ColorRoyalBlue + ColorSaddleBrown + ColorSalmon + ColorSandyBrown + ColorSeaGreen + ColorSeashell + ColorSienna + ColorSkyblue + ColorSlateBlue + ColorSlateGray + ColorSnow + ColorSpringGreen + ColorSteelBlue + ColorTan + ColorThistle + ColorTomato + ColorTurquoise + ColorViolet + ColorWheat + ColorWhiteSmoke + ColorYellowGreen +) + +// These are aliases for the color gray, because some of us spell +// it as grey. +const ( + ColorGrey = ColorGray + ColorDimGrey = ColorDimGray + ColorDarkGrey = ColorDarkGray + ColorDarkSlateGrey = ColorDarkSlateGray + ColorLightGrey = ColorLightGray + ColorLightSlateGrey = ColorLightSlateGray + ColorSlateGrey = ColorSlateGray +) + +// ColorValues maps color constants to their RGB values. +var ColorValues = map[Color]int32{ + ColorBlack: 0x000000, + ColorMaroon: 0x800000, + ColorGreen: 0x008000, + ColorOlive: 0x808000, + ColorNavy: 0x000080, + ColorPurple: 0x800080, + ColorTeal: 0x008080, + ColorSilver: 0xC0C0C0, + ColorGray: 0x808080, + ColorRed: 0xFF0000, + ColorLime: 0x00FF00, + ColorYellow: 0xFFFF00, + ColorBlue: 0x0000FF, + ColorFuchsia: 0xFF00FF, + ColorAqua: 0x00FFFF, + ColorWhite: 0xFFFFFF, + Color16: 0x000000, // black + Color17: 0x00005F, + Color18: 0x000087, + Color19: 0x0000AF, + Color20: 0x0000D7, + Color21: 0x0000FF, // blue + Color22: 0x005F00, + Color23: 0x005F5F, + Color24: 0x005F87, + Color25: 0x005FAF, + Color26: 0x005FD7, + Color27: 0x005FFF, + Color28: 0x008700, + Color29: 0x00875F, + Color30: 0x008787, + Color31: 0x0087Af, + Color32: 0x0087D7, + Color33: 0x0087FF, + Color34: 0x00AF00, + Color35: 0x00AF5F, + Color36: 0x00AF87, + Color37: 0x00AFAF, + Color38: 0x00AFD7, + Color39: 0x00AFFF, + Color40: 0x00D700, + Color41: 0x00D75F, + Color42: 0x00D787, + Color43: 0x00D7AF, + Color44: 0x00D7D7, + Color45: 0x00D7FF, + Color46: 0x00FF00, // lime + Color47: 0x00FF5F, + Color48: 0x00FF87, + Color49: 0x00FFAF, + Color50: 0x00FFd7, + Color51: 0x00FFFF, // aqua + Color52: 0x5F0000, + Color53: 0x5F005F, + Color54: 0x5F0087, + Color55: 0x5F00AF, + Color56: 0x5F00D7, + Color57: 0x5F00FF, + Color58: 0x5F5F00, + Color59: 0x5F5F5F, + Color60: 0x5F5F87, + Color61: 0x5F5FAF, + Color62: 0x5F5FD7, + Color63: 0x5F5FFF, + Color64: 0x5F8700, + Color65: 0x5F875F, + Color66: 0x5F8787, + Color67: 0x5F87AF, + Color68: 0x5F87D7, + Color69: 0x5F87FF, + Color70: 0x5FAF00, + Color71: 0x5FAF5F, + Color72: 0x5FAF87, + Color73: 0x5FAFAF, + Color74: 0x5FAFD7, + Color75: 0x5FAFFF, + Color76: 0x5FD700, + Color77: 0x5FD75F, + Color78: 0x5FD787, + Color79: 0x5FD7AF, + Color80: 0x5FD7D7, + Color81: 0x5FD7FF, + Color82: 0x5FFF00, + Color83: 0x5FFF5F, + Color84: 0x5FFF87, + Color85: 0x5FFFAF, + Color86: 0x5FFFD7, + Color87: 0x5FFFFF, + Color88: 0x870000, + Color89: 0x87005F, + Color90: 0x870087, + Color91: 0x8700AF, + Color92: 0x8700D7, + Color93: 0x8700FF, + Color94: 0x875F00, + Color95: 0x875F5F, + Color96: 0x875F87, + Color97: 0x875FAF, + Color98: 0x875FD7, + Color99: 0x875FFF, + Color100: 0x878700, + Color101: 0x87875F, + Color102: 0x878787, + Color103: 0x8787AF, + Color104: 0x8787D7, + Color105: 0x8787FF, + Color106: 0x87AF00, + Color107: 0x87AF5F, + Color108: 0x87AF87, + Color109: 0x87AFAF, + Color110: 0x87AFD7, + Color111: 0x87AFFF, + Color112: 0x87D700, + Color113: 0x87D75F, + Color114: 0x87D787, + Color115: 0x87D7AF, + Color116: 0x87D7D7, + Color117: 0x87D7FF, + Color118: 0x87FF00, + Color119: 0x87FF5F, + Color120: 0x87FF87, + Color121: 0x87FFAF, + Color122: 0x87FFD7, + Color123: 0x87FFFF, + Color124: 0xAF0000, + Color125: 0xAF005F, + Color126: 0xAF0087, + Color127: 0xAF00AF, + Color128: 0xAF00D7, + Color129: 0xAF00FF, + Color130: 0xAF5F00, + Color131: 0xAF5F5F, + Color132: 0xAF5F87, + Color133: 0xAF5FAF, + Color134: 0xAF5FD7, + Color135: 0xAF5FFF, + Color136: 0xAF8700, + Color137: 0xAF875F, + Color138: 0xAF8787, + Color139: 0xAF87AF, + Color140: 0xAF87D7, + Color141: 0xAF87FF, + Color142: 0xAFAF00, + Color143: 0xAFAF5F, + Color144: 0xAFAF87, + Color145: 0xAFAFAF, + Color146: 0xAFAFD7, + Color147: 0xAFAFFF, + Color148: 0xAFD700, + Color149: 0xAFD75F, + Color150: 0xAFD787, + Color151: 0xAFD7AF, + Color152: 0xAFD7D7, + Color153: 0xAFD7FF, + Color154: 0xAFFF00, + Color155: 0xAFFF5F, + Color156: 0xAFFF87, + Color157: 0xAFFFAF, + Color158: 0xAFFFD7, + Color159: 0xAFFFFF, + Color160: 0xD70000, + Color161: 0xD7005F, + Color162: 0xD70087, + Color163: 0xD700AF, + Color164: 0xD700D7, + Color165: 0xD700FF, + Color166: 0xD75F00, + Color167: 0xD75F5F, + Color168: 0xD75F87, + Color169: 0xD75FAF, + Color170: 0xD75FD7, + Color171: 0xD75FFF, + Color172: 0xD78700, + Color173: 0xD7875F, + Color174: 0xD78787, + Color175: 0xD787AF, + Color176: 0xD787D7, + Color177: 0xD787FF, + Color178: 0xD7AF00, + Color179: 0xD7AF5F, + Color180: 0xD7AF87, + Color181: 0xD7AFAF, + Color182: 0xD7AFD7, + Color183: 0xD7AFFF, + Color184: 0xD7D700, + Color185: 0xD7D75F, + Color186: 0xD7D787, + Color187: 0xD7D7AF, + Color188: 0xD7D7D7, + Color189: 0xD7D7FF, + Color190: 0xD7FF00, + Color191: 0xD7FF5F, + Color192: 0xD7FF87, + Color193: 0xD7FFAF, + Color194: 0xD7FFD7, + Color195: 0xD7FFFF, + Color196: 0xFF0000, // red + Color197: 0xFF005F, + Color198: 0xFF0087, + Color199: 0xFF00AF, + Color200: 0xFF00D7, + Color201: 0xFF00FF, // fuchsia + Color202: 0xFF5F00, + Color203: 0xFF5F5F, + Color204: 0xFF5F87, + Color205: 0xFF5FAF, + Color206: 0xFF5FD7, + Color207: 0xFF5FFF, + Color208: 0xFF8700, + Color209: 0xFF875F, + Color210: 0xFF8787, + Color211: 0xFF87AF, + Color212: 0xFF87D7, + Color213: 0xFF87FF, + Color214: 0xFFAF00, + Color215: 0xFFAF5F, + Color216: 0xFFAF87, + Color217: 0xFFAFAF, + Color218: 0xFFAFD7, + Color219: 0xFFAFFF, + Color220: 0xFFD700, + Color221: 0xFFD75F, + Color222: 0xFFD787, + Color223: 0xFFD7AF, + Color224: 0xFFD7D7, + Color225: 0xFFD7FF, + Color226: 0xFFFF00, // yellow + Color227: 0xFFFF5F, + Color228: 0xFFFF87, + Color229: 0xFFFFAF, + Color230: 0xFFFFD7, + Color231: 0xFFFFFF, // white + Color232: 0x080808, + Color233: 0x121212, + Color234: 0x1C1C1C, + Color235: 0x262626, + Color236: 0x303030, + Color237: 0x3A3A3A, + Color238: 0x444444, + Color239: 0x4E4E4E, + Color240: 0x585858, + Color241: 0x626262, + Color242: 0x6C6C6C, + Color243: 0x767676, + Color244: 0x808080, // grey + Color245: 0x8A8A8A, + Color246: 0x949494, + Color247: 0x9E9E9E, + Color248: 0xA8A8A8, + Color249: 0xB2B2B2, + Color250: 0xBCBCBC, + Color251: 0xC6C6C6, + Color252: 0xD0D0D0, + Color253: 0xDADADA, + Color254: 0xE4E4E4, + Color255: 0xEEEEEE, + ColorAliceBlue: 0xF0F8FF, + ColorAntiqueWhite: 0xFAEBD7, + ColorAquaMarine: 0x7FFFD4, + ColorAzure: 0xF0FFFF, + ColorBeige: 0xF5F5DC, + ColorBisque: 0xFFE4C4, + ColorBlanchedAlmond: 0xFFEBCD, + ColorBlueViolet: 0x8A2BE2, + ColorBrown: 0xA52A2A, + ColorBurlyWood: 0xDEB887, + ColorCadetBlue: 0x5F9EA0, + ColorChartreuse: 0x7FFF00, + ColorChocolate: 0xD2691E, + ColorCoral: 0xFF7F50, + ColorCornflowerBlue: 0x6495ED, + ColorCornsilk: 0xFFF8DC, + ColorCrimson: 0xDC143C, + ColorDarkBlue: 0x00008B, + ColorDarkCyan: 0x008B8B, + ColorDarkGoldenrod: 0xB8860B, + ColorDarkGray: 0xA9A9A9, + ColorDarkGreen: 0x006400, + ColorDarkKhaki: 0xBDB76B, + ColorDarkMagenta: 0x8B008B, + ColorDarkOliveGreen: 0x556B2F, + ColorDarkOrange: 0xFF8C00, + ColorDarkOrchid: 0x9932CC, + ColorDarkRed: 0x8B0000, + ColorDarkSalmon: 0xE9967A, + ColorDarkSeaGreen: 0x8FBC8F, + ColorDarkSlateBlue: 0x483D8B, + ColorDarkSlateGray: 0x2F4F4F, + ColorDarkTurquoise: 0x00CED1, + ColorDarkViolet: 0x9400D3, + ColorDeepPink: 0xFF1493, + ColorDeepSkyBlue: 0x00BFFF, + ColorDimGray: 0x696969, + ColorDodgerBlue: 0x1E90FF, + ColorFireBrick: 0xB22222, + ColorFloralWhite: 0xFFFAF0, + ColorForestGreen: 0x228B22, + ColorGainsboro: 0xDCDCDC, + ColorGhostWhite: 0xF8F8FF, + ColorGold: 0xFFD700, + ColorGoldenrod: 0xDAA520, + ColorGreenYellow: 0xADFF2F, + ColorHoneydew: 0xF0FFF0, + ColorHotPink: 0xFF69B4, + ColorIndianRed: 0xCD5C5C, + ColorIndigo: 0x4B0082, + ColorIvory: 0xFFFFF0, + ColorKhaki: 0xF0E68C, + ColorLavender: 0xE6E6FA, + ColorLavenderBlush: 0xFFF0F5, + ColorLawnGreen: 0x7CFC00, + ColorLemonChiffon: 0xFFFACD, + ColorLightBlue: 0xADD8E6, + ColorLightCoral: 0xF08080, + ColorLightCyan: 0xE0FFFF, + ColorLightGoldenrodYellow: 0xFAFAD2, + ColorLightGray: 0xD3D3D3, + ColorLightGreen: 0x90EE90, + ColorLightPink: 0xFFB6C1, + ColorLightSalmon: 0xFFA07A, + ColorLightSeaGreen: 0x20B2AA, + ColorLightSkyBlue: 0x87CEFA, + ColorLightSlateGray: 0x778899, + ColorLightSteelBlue: 0xB0C4DE, + ColorLightYellow: 0xFFFFE0, + ColorLimeGreen: 0x32CD32, + ColorLinen: 0xFAF0E6, + ColorMediumAquamarine: 0x66CDAA, + ColorMediumBlue: 0x0000CD, + ColorMediumOrchid: 0xBA55D3, + ColorMediumPurple: 0x9370DB, + ColorMediumSeaGreen: 0x3CB371, + ColorMediumSlateBlue: 0x7B68EE, + ColorMediumSpringGreen: 0x00FA9A, + ColorMediumTurquoise: 0x48D1CC, + ColorMediumVioletRed: 0xC71585, + ColorMidnightBlue: 0x191970, + ColorMintCream: 0xF5FFFA, + ColorMistyRose: 0xFFE4E1, + ColorMoccasin: 0xFFE4B5, + ColorNavajoWhite: 0xFFDEAD, + ColorOldLace: 0xFDF5E6, + ColorOliveDrab: 0x6B8E23, + ColorOrange: 0xFFA500, + ColorOrangeRed: 0xFF4500, + ColorOrchid: 0xDA70D6, + ColorPaleGoldenrod: 0xEEE8AA, + ColorPaleGreen: 0x98FB98, + ColorPaleTurquoise: 0xAFEEEE, + ColorPaleVioletRed: 0xDB7093, + ColorPapayaWhip: 0xFFEFD5, + ColorPeachPuff: 0xFFDAB9, + ColorPeru: 0xCD853F, + ColorPink: 0xFFC0CB, + ColorPlum: 0xDDA0DD, + ColorPowderBlue: 0xB0E0E6, + ColorRebeccaPurple: 0x663399, + ColorRosyBrown: 0xBC8F8F, + ColorRoyalBlue: 0x4169E1, + ColorSaddleBrown: 0x8B4513, + ColorSalmon: 0xFA8072, + ColorSandyBrown: 0xF4A460, + ColorSeaGreen: 0x2E8B57, + ColorSeashell: 0xFFF5EE, + ColorSienna: 0xA0522D, + ColorSkyblue: 0x87CEEB, + ColorSlateBlue: 0x6A5ACD, + ColorSlateGray: 0x708090, + ColorSnow: 0xFFFAFA, + ColorSpringGreen: 0x00FF7F, + ColorSteelBlue: 0x4682B4, + ColorTan: 0xD2B48C, + ColorThistle: 0xD8BFD8, + ColorTomato: 0xFF6347, + ColorTurquoise: 0x40E0D0, + ColorViolet: 0xEE82EE, + ColorWheat: 0xF5DEB3, + ColorWhiteSmoke: 0xF5F5F5, + ColorYellowGreen: 0x9ACD32, +} + +// Special colors. +const ( + // ColorReset is used to indicate that the color should use the + // vanilla terminal colors. (Basically go back to the defaults.) + ColorReset = ColorSpecial | iota + + // ColorNone indicates that we should not change the color from + // whatever is already displayed. This can only be used in limited + // circumstances. + ColorNone +) + +// ColorNames holds the written names of colors. Useful to present a list of +// recognized named colors. +var ColorNames = map[string]Color{ + "black": ColorBlack, + "maroon": ColorMaroon, + "green": ColorGreen, + "olive": ColorOlive, + "navy": ColorNavy, + "purple": ColorPurple, + "teal": ColorTeal, + "silver": ColorSilver, + "gray": ColorGray, + "red": ColorRed, + "lime": ColorLime, + "yellow": ColorYellow, + "blue": ColorBlue, + "fuchsia": ColorFuchsia, + "aqua": ColorAqua, + "white": ColorWhite, + "aliceblue": ColorAliceBlue, + "antiquewhite": ColorAntiqueWhite, + "aquamarine": ColorAquaMarine, + "azure": ColorAzure, + "beige": ColorBeige, + "bisque": ColorBisque, + "blanchedalmond": ColorBlanchedAlmond, + "blueviolet": ColorBlueViolet, + "brown": ColorBrown, + "burlywood": ColorBurlyWood, + "cadetblue": ColorCadetBlue, + "chartreuse": ColorChartreuse, + "chocolate": ColorChocolate, + "coral": ColorCoral, + "cornflowerblue": ColorCornflowerBlue, + "cornsilk": ColorCornsilk, + "crimson": ColorCrimson, + "darkblue": ColorDarkBlue, + "darkcyan": ColorDarkCyan, + "darkgoldenrod": ColorDarkGoldenrod, + "darkgray": ColorDarkGray, + "darkgreen": ColorDarkGreen, + "darkkhaki": ColorDarkKhaki, + "darkmagenta": ColorDarkMagenta, + "darkolivegreen": ColorDarkOliveGreen, + "darkorange": ColorDarkOrange, + "darkorchid": ColorDarkOrchid, + "darkred": ColorDarkRed, + "darksalmon": ColorDarkSalmon, + "darkseagreen": ColorDarkSeaGreen, + "darkslateblue": ColorDarkSlateBlue, + "darkslategray": ColorDarkSlateGray, + "darkturquoise": ColorDarkTurquoise, + "darkviolet": ColorDarkViolet, + "deeppink": ColorDeepPink, + "deepskyblue": ColorDeepSkyBlue, + "dimgray": ColorDimGray, + "dodgerblue": ColorDodgerBlue, + "firebrick": ColorFireBrick, + "floralwhite": ColorFloralWhite, + "forestgreen": ColorForestGreen, + "gainsboro": ColorGainsboro, + "ghostwhite": ColorGhostWhite, + "gold": ColorGold, + "goldenrod": ColorGoldenrod, + "greenyellow": ColorGreenYellow, + "honeydew": ColorHoneydew, + "hotpink": ColorHotPink, + "indianred": ColorIndianRed, + "indigo": ColorIndigo, + "ivory": ColorIvory, + "khaki": ColorKhaki, + "lavender": ColorLavender, + "lavenderblush": ColorLavenderBlush, + "lawngreen": ColorLawnGreen, + "lemonchiffon": ColorLemonChiffon, + "lightblue": ColorLightBlue, + "lightcoral": ColorLightCoral, + "lightcyan": ColorLightCyan, + "lightgoldenrodyellow": ColorLightGoldenrodYellow, + "lightgray": ColorLightGray, + "lightgreen": ColorLightGreen, + "lightpink": ColorLightPink, + "lightsalmon": ColorLightSalmon, + "lightseagreen": ColorLightSeaGreen, + "lightskyblue": ColorLightSkyBlue, + "lightslategray": ColorLightSlateGray, + "lightsteelblue": ColorLightSteelBlue, + "lightyellow": ColorLightYellow, + "limegreen": ColorLimeGreen, + "linen": ColorLinen, + "mediumaquamarine": ColorMediumAquamarine, + "mediumblue": ColorMediumBlue, + "mediumorchid": ColorMediumOrchid, + "mediumpurple": ColorMediumPurple, + "mediumseagreen": ColorMediumSeaGreen, + "mediumslateblue": ColorMediumSlateBlue, + "mediumspringgreen": ColorMediumSpringGreen, + "mediumturquoise": ColorMediumTurquoise, + "mediumvioletred": ColorMediumVioletRed, + "midnightblue": ColorMidnightBlue, + "mintcream": ColorMintCream, + "mistyrose": ColorMistyRose, + "moccasin": ColorMoccasin, + "navajowhite": ColorNavajoWhite, + "oldlace": ColorOldLace, + "olivedrab": ColorOliveDrab, + "orange": ColorOrange, + "orangered": ColorOrangeRed, + "orchid": ColorOrchid, + "palegoldenrod": ColorPaleGoldenrod, + "palegreen": ColorPaleGreen, + "paleturquoise": ColorPaleTurquoise, + "palevioletred": ColorPaleVioletRed, + "papayawhip": ColorPapayaWhip, + "peachpuff": ColorPeachPuff, + "peru": ColorPeru, + "pink": ColorPink, + "plum": ColorPlum, + "powderblue": ColorPowderBlue, + "rebeccapurple": ColorRebeccaPurple, + "rosybrown": ColorRosyBrown, + "royalblue": ColorRoyalBlue, + "saddlebrown": ColorSaddleBrown, + "salmon": ColorSalmon, + "sandybrown": ColorSandyBrown, + "seagreen": ColorSeaGreen, + "seashell": ColorSeashell, + "sienna": ColorSienna, + "skyblue": ColorSkyblue, + "slateblue": ColorSlateBlue, + "slategray": ColorSlateGray, + "snow": ColorSnow, + "springgreen": ColorSpringGreen, + "steelblue": ColorSteelBlue, + "tan": ColorTan, + "thistle": ColorThistle, + "tomato": ColorTomato, + "turquoise": ColorTurquoise, + "violet": ColorViolet, + "wheat": ColorWheat, + "whitesmoke": ColorWhiteSmoke, + "yellowgreen": ColorYellowGreen, + "grey": ColorGray, + "dimgrey": ColorDimGray, + "darkgrey": ColorDarkGray, + "darkslategrey": ColorDarkSlateGray, + "lightgrey": ColorLightGray, + "lightslategrey": ColorLightSlateGray, + "slategrey": ColorSlateGray, +} + +// Valid indicates the color is a valid value (has been set). +func (c Color) Valid() bool { + return c&ColorValid != 0 +} + +// IsRGB is true if the color is an RGB specific value. +func (c Color) IsRGB() bool { + return c&(ColorValid|ColorIsRGB) == (ColorValid | ColorIsRGB) +} + +// CSS returns the CSS hex string ( #ABCDEF ) if valid +// if not a valid color returns empty string +func (c Color) CSS() string { + if !c.Valid() { + return "" + } + return fmt.Sprintf("#%06X", c.Hex()) +} + +// String implements fmt.Stringer to return either the +// W3C name if it has one or the CSS hex string '#ABCDEF' +func (c Color) String() string { + if !c.Valid() { + switch c { + case ColorNone: + return "none" + case ColorDefault: + return "default" + case ColorReset: + return "reset" + } + return "" + } + return c.Name(true) +} + +// Name returns W3C name or an empty string if no arguments +// if passed true as an argument it will falls back to +// the CSS hex string if no W3C name found '#ABCDEF' +func (c Color) Name(css ...bool) string { + for name, hex := range ColorNames { + if c == hex { + return name + } + } + if len(css) > 0 && css[0] { + return c.CSS() + } + return "" +} + +// Hex returns the color's hexadecimal RGB 24-bit value with each component +// consisting of a single byte, R << 16 | G << 8 | B. If the color +// is unknown or unset, -1 is returned. +func (c Color) Hex() int32 { + if !c.Valid() { + return -1 + } + if c&ColorIsRGB != 0 { + return int32(c & 0xffffff) + } + if v, ok := ColorValues[c]; ok { + return v + } + return -1 +} + +// RGB returns the red, green, and blue components of the color, with +// each component represented as a value 0-255. In the event that the +// color cannot be broken up (not set usually), -1 is returned for each value. +func (c Color) RGB() (int32, int32, int32) { + v := c.Hex() + if v < 0 { + return -1, -1, -1 + } + return (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff +} + +// TrueColor returns the true color (RGB) version of the provided color. +// This is useful for ensuring color accuracy when using named colors. +// This will override terminal theme colors. +func (c Color) TrueColor() Color { + if !c.Valid() { + return ColorDefault + } + if c&ColorIsRGB != 0 { + return c | ColorValid + } + return Color(c.Hex()) | ColorIsRGB | ColorValid +} + +// NewRGBColor returns a new color with the given red, green, and blue values. +// Each value must be represented in the range 0-255. +func NewRGBColor(r, g, b int32) Color { + return NewHexColor(((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)) +} + +// NewHexColor returns a color using the given 24-bit RGB value. +func NewHexColor(v int32) Color { + return ColorIsRGB | Color(v) | ColorValid +} + +// GetColor creates a Color from a color name (W3C name). A hex value may +// be supplied as a string in the format "#ffffff". +func GetColor(name string) Color { + if c, ok := ColorNames[name]; ok { + return c + } + if len(name) == 7 && name[0] == '#' { + if v, e := strconv.ParseInt(name[1:], 16, 32); e == nil { + return NewHexColor(int32(v)) + } + } + return ColorDefault +} + +// PaletteColor creates a color based on the palette index. +func PaletteColor(index int) Color { + return Color(index) | ColorValid +} + +// FromImageColor converts an image/color.Color into tcell.Color. +// The alpha value is dropped, so it should be tracked separately if it is +// needed. +func FromImageColor(imageColor ic.Color) Color { + r, g, b, _ := imageColor.RGBA() + // NOTE image/color.Color RGB values range is [0, 0xFFFF] as uint32 + return NewRGBColor(int32(r>>8), int32(g>>8), int32(b>>8)) +} diff --git a/vendor/github.com/gdamore/tcell/v2/colorfit.go b/vendor/github.com/gdamore/tcell/v2/colorfit.go new file mode 100644 index 0000000..f690097 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/colorfit.go @@ -0,0 +1,53 @@ +// Copyright 2016 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "math" + + "github.com/lucasb-eyer/go-colorful" +) + +// FindColor attempts to find a given color, or the best match possible for it, +// from the palette given. This is an expensive operation, so results should +// be cached by the caller. +func FindColor(c Color, palette []Color) Color { + match := ColorDefault + dist := float64(0) + r, g, b := c.RGB() + c1 := colorful.Color{ + R: float64(r) / 255.0, + G: float64(g) / 255.0, + B: float64(b) / 255.0, + } + for _, d := range palette { + r, g, b = d.RGB() + c2 := colorful.Color{ + R: float64(r) / 255.0, + G: float64(g) / 255.0, + B: float64(b) / 255.0, + } + // CIE94 is more accurate, but really really expensive. + nd := c1.DistanceCIE76(c2) + if math.IsNaN(nd) { + nd = math.Inf(1) + } + if match == ColorDefault || nd < dist { + match = d + dist = nd + } + } + return match +} diff --git a/vendor/github.com/gdamore/tcell/v2/console_stub.go b/vendor/github.com/gdamore/tcell/v2/console_stub.go new file mode 100644 index 0000000..6ff7e92 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/console_stub.go @@ -0,0 +1,24 @@ +//go:build !windows +// +build !windows + +// Copyright 2015 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +// NewConsoleScreen returns a console based screen. This platform +// doesn't have support for any, so it returns nil and a suitable error. +func NewConsoleScreen() (Screen, error) { + return nil, ErrNoScreen +} diff --git a/vendor/github.com/gdamore/tcell/v2/console_win.go b/vendor/github.com/gdamore/tcell/v2/console_win.go new file mode 100644 index 0000000..e265250 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/console_win.go @@ -0,0 +1,1326 @@ +//go:build windows +// +build windows + +// Copyright 2024 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "errors" + "fmt" + "os" + "strings" + "sync" + "syscall" + "unicode/utf16" + "unsafe" +) + +type cScreen struct { + in syscall.Handle + out syscall.Handle + cancelflag syscall.Handle + scandone chan struct{} + quit chan struct{} + curx int + cury int + style Style + fini bool + vten bool + truecolor bool + running bool + disableAlt bool // disable the alternate screen + + w int + h int + + oscreen consoleInfo + ocursor cursorInfo + cursorStyle CursorStyle + oimode uint32 + oomode uint32 + cells CellBuffer + focusEnable bool + + mouseEnabled bool + wg sync.WaitGroup + eventQ chan Event + stopQ chan struct{} + finiOnce sync.Once + + sync.Mutex +} + +var winLock sync.Mutex + +var winPalette = []Color{ + ColorBlack, + ColorMaroon, + ColorGreen, + ColorNavy, + ColorOlive, + ColorPurple, + ColorTeal, + ColorSilver, + ColorGray, + ColorRed, + ColorLime, + ColorBlue, + ColorYellow, + ColorFuchsia, + ColorAqua, + ColorWhite, +} + +var winColors = map[Color]Color{ + ColorBlack: ColorBlack, + ColorMaroon: ColorMaroon, + ColorGreen: ColorGreen, + ColorNavy: ColorNavy, + ColorOlive: ColorOlive, + ColorPurple: ColorPurple, + ColorTeal: ColorTeal, + ColorSilver: ColorSilver, + ColorGray: ColorGray, + ColorRed: ColorRed, + ColorLime: ColorLime, + ColorBlue: ColorBlue, + ColorYellow: ColorYellow, + ColorFuchsia: ColorFuchsia, + ColorAqua: ColorAqua, + ColorWhite: ColorWhite, +} + +var ( + k32 = syscall.NewLazyDLL("kernel32.dll") + u32 = syscall.NewLazyDLL("user32.dll") +) + +// We have to bring in the kernel32 and user32 DLLs directly, so we can get +// access to some system calls that the core Go API lacks. +// +// Note that Windows appends some functions with W to indicate that wide +// characters (Unicode) are in use. The documentation refers to them +// without this suffix, as the resolution is made via preprocessor. +var ( + procReadConsoleInput = k32.NewProc("ReadConsoleInputW") + procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects") + procCreateEvent = k32.NewProc("CreateEventW") + procSetEvent = k32.NewProc("SetEvent") + procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo") + procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo") + procSetConsoleCursorPosition = k32.NewProc("SetConsoleCursorPosition") + procSetConsoleMode = k32.NewProc("SetConsoleMode") + procGetConsoleMode = k32.NewProc("GetConsoleMode") + procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo") + procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute") + procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW") + procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo") + procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize") + procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute") + procGetLargestConsoleWindowSize = k32.NewProc("GetLargestConsoleWindowSize") + procMessageBeep = u32.NewProc("MessageBeep") +) + +const ( + w32Infinite = ^uintptr(0) + w32WaitObject0 = uintptr(0) +) + +const ( + // VT100/XTerm escapes understood by the console + vtShowCursor = "\x1b[?25h" + vtHideCursor = "\x1b[?25l" + vtCursorPos = "\x1b[%d;%dH" // Note that it is Y then X + vtSgr0 = "\x1b[0m" + vtBold = "\x1b[1m" + vtUnderline = "\x1b[4m" + vtBlink = "\x1b[5m" // Not sure if this is processed + vtReverse = "\x1b[7m" + vtSetFg = "\x1b[38;5;%dm" + vtSetBg = "\x1b[48;5;%dm" + vtSetFgRGB = "\x1b[38;2;%d;%d;%dm" // RGB + vtSetBgRGB = "\x1b[48;2;%d;%d;%dm" // RGB + vtCursorDefault = "\x1b[0 q" + vtCursorBlinkingBlock = "\x1b[1 q" + vtCursorSteadyBlock = "\x1b[2 q" + vtCursorBlinkingUnderline = "\x1b[3 q" + vtCursorSteadyUnderline = "\x1b[4 q" + vtCursorBlinkingBar = "\x1b[5 q" + vtCursorSteadyBar = "\x1b[6 q" + vtDisableAm = "\x1b[?7l" + vtEnableAm = "\x1b[?7h" + vtEnterCA = "\x1b[?1049h\x1b[22;0;0t" + vtExitCA = "\x1b[?1049l\x1b[23;0;0t" +) + +var vtCursorStyles = map[CursorStyle]string{ + CursorStyleDefault: vtCursorDefault, + CursorStyleBlinkingBlock: vtCursorBlinkingBlock, + CursorStyleSteadyBlock: vtCursorSteadyBlock, + CursorStyleBlinkingUnderline: vtCursorBlinkingUnderline, + CursorStyleSteadyUnderline: vtCursorSteadyUnderline, + CursorStyleBlinkingBar: vtCursorBlinkingBar, + CursorStyleSteadyBar: vtCursorSteadyBar, +} + +// NewConsoleScreen returns a Screen for the Windows console associated +// with the current process. The Screen makes use of the Windows Console +// API to display content and read events. +func NewConsoleScreen() (Screen, error) { + return &baseScreen{screenImpl: &cScreen{}}, nil +} + +func (s *cScreen) Init() error { + s.eventQ = make(chan Event, 10) + s.quit = make(chan struct{}) + s.scandone = make(chan struct{}) + in, e := syscall.Open("CONIN$", syscall.O_RDWR, 0) + if e != nil { + return e + } + s.in = in + out, e := syscall.Open("CONOUT$", syscall.O_RDWR, 0) + if e != nil { + _ = syscall.Close(s.in) + return e + } + s.out = out + + s.truecolor = true + + // ConEmu handling of colors and scrolling when in VT output mode is extremely poor. + // The color palette will scroll even though characters do not, when + // emitting stuff for the last character. In the future we might change this to + // look at specific versions of ConEmu if they fix the bug. + // We can also try disabling auto margin mode. + tryVt := true + if os.Getenv("ConEmuPID") != "" { + s.truecolor = false + tryVt = false + } + switch os.Getenv("TCELL_TRUECOLOR") { + case "disable": + s.truecolor = false + case "enable": + s.truecolor = true + tryVt = true + } + + s.Lock() + + s.curx = -1 + s.cury = -1 + s.style = StyleDefault + s.getCursorInfo(&s.ocursor) + s.getConsoleInfo(&s.oscreen) + s.getOutMode(&s.oomode) + s.getInMode(&s.oimode) + s.resize() + + s.fini = false + s.setInMode(modeResizeEn | modeExtendFlg) + + // If a user needs to force old style console, they may do so + // by setting TCELL_VTMODE to disable. This is an undocumented safety net for now. + // It may be removed in the future. (This mostly exists because of ConEmu.) + switch os.Getenv("TCELL_VTMODE") { + case "disable": + tryVt = false + case "enable": + tryVt = true + } + switch os.Getenv("TCELL_ALTSCREEN") { + case "enable": + s.disableAlt = false // also the default + case "disable": + s.disableAlt = true + } + if tryVt { + s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline) + var om uint32 + s.getOutMode(&om) + if om&modeVtOutput == modeVtOutput { + s.vten = true + } else { + s.truecolor = false + s.setOutMode(0) + } + } else { + s.setOutMode(0) + } + + s.Unlock() + + return s.engage() +} + +func (s *cScreen) CharacterSet() string { + // We are always UTF-16LE on Windows + return "UTF-16LE" +} + +func (s *cScreen) EnableMouse(...MouseFlags) { + s.Lock() + s.mouseEnabled = true + s.enableMouse(true) + s.Unlock() +} + +func (s *cScreen) DisableMouse() { + s.Lock() + s.mouseEnabled = false + s.enableMouse(false) + s.Unlock() +} + +func (s *cScreen) enableMouse(on bool) { + if on { + s.setInMode(modeResizeEn | modeMouseEn | modeExtendFlg) + } else { + s.setInMode(modeResizeEn | modeExtendFlg) + } +} + +// Windows lacks bracketed paste (for now) + +func (s *cScreen) EnablePaste() {} + +func (s *cScreen) DisablePaste() {} + +func (s *cScreen) EnableFocus() { + s.Lock() + s.focusEnable = true + s.Unlock() +} + +func (s *cScreen) DisableFocus() { + s.Lock() + s.focusEnable = false + s.Unlock() +} + +func (s *cScreen) Fini() { + s.finiOnce.Do(func() { + close(s.quit) + s.disengage() + }) +} + +func (s *cScreen) disengage() { + s.Lock() + if !s.running { + s.Unlock() + return + } + s.running = false + stopQ := s.stopQ + _, _, _ = procSetEvent.Call(uintptr(s.cancelflag)) + close(stopQ) + s.Unlock() + + s.wg.Wait() + + if s.vten { + s.emitVtString(vtCursorStyles[CursorStyleDefault]) + s.emitVtString(vtEnableAm) + if !s.disableAlt { + s.emitVtString(vtExitCA) + } + } else if !s.disableAlt { + s.clearScreen(StyleDefault, s.vten) + s.setCursorPos(0, 0, false) + } + s.setCursorInfo(&s.ocursor) + s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y)) + s.setInMode(s.oimode) + s.setOutMode(s.oomode) + _, _, _ = procSetConsoleTextAttribute.Call( + uintptr(s.out), + uintptr(s.mapStyle(StyleDefault))) +} + +func (s *cScreen) engage() error { + s.Lock() + defer s.Unlock() + if s.running { + return errors.New("already engaged") + } + s.stopQ = make(chan struct{}) + cf, _, e := procCreateEvent.Call( + uintptr(0), + uintptr(1), + uintptr(0), + uintptr(0)) + if cf == uintptr(0) { + return e + } + s.running = true + s.cancelflag = syscall.Handle(cf) + s.enableMouse(s.mouseEnabled) + + if s.vten { + s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline) + if !s.disableAlt { + s.emitVtString(vtEnterCA) + } + s.emitVtString(vtDisableAm) + } else { + s.setOutMode(0) + } + + s.clearScreen(s.style, s.vten) + s.hideCursor() + + s.cells.Invalidate() + s.hideCursor() + s.resize() + s.draw() + s.doCursor() + + s.wg.Add(1) + go s.scanInput(s.stopQ) + return nil +} + +type cursorInfo struct { + size uint32 + visible uint32 +} + +type coord struct { + x int16 + y int16 +} + +func (c coord) uintptr() uintptr { + // little endian, put x first + return uintptr(c.x) | (uintptr(c.y) << 16) +} + +type rect struct { + left int16 + top int16 + right int16 + bottom int16 +} + +func (s *cScreen) emitVtString(vs string) { + esc := utf16.Encode([]rune(vs)) + _ = syscall.WriteConsole(s.out, &esc[0], uint32(len(esc)), nil, nil) +} + +func (s *cScreen) showCursor() { + if s.vten { + s.emitVtString(vtShowCursor) + s.emitVtString(vtCursorStyles[s.cursorStyle]) + } else { + s.setCursorInfo(&cursorInfo{size: 100, visible: 1}) + } +} + +func (s *cScreen) hideCursor() { + if s.vten { + s.emitVtString(vtHideCursor) + } else { + s.setCursorInfo(&cursorInfo{size: 1, visible: 0}) + } +} + +func (s *cScreen) ShowCursor(x, y int) { + s.Lock() + if !s.fini { + s.curx = x + s.cury = y + } + s.doCursor() + s.Unlock() +} + +func (s *cScreen) SetCursorStyle(cs CursorStyle) { + s.Lock() + if !s.fini { + if _, ok := vtCursorStyles[cs]; ok { + s.cursorStyle = cs + s.doCursor() + } + } + s.Unlock() +} + +func (s *cScreen) doCursor() { + x, y := s.curx, s.cury + + if x < 0 || y < 0 || x >= s.w || y >= s.h { + s.hideCursor() + } else { + s.setCursorPos(x, y, s.vten) + s.showCursor() + } +} + +func (s *cScreen) HideCursor() { + s.ShowCursor(-1, -1) +} + +type inputRecord struct { + typ uint16 + _ uint16 + data [16]byte +} + +const ( + keyEvent uint16 = 1 + mouseEvent uint16 = 2 + resizeEvent uint16 = 4 + menuEvent uint16 = 8 // don't use + focusEvent uint16 = 16 +) + +type mouseRecord struct { + x int16 + y int16 + btns uint32 + mod uint32 + flags uint32 +} + +type focusRecord struct { + focused int32 // actually BOOL +} + +const ( + mouseHWheeled uint32 = 0x8 + mouseVWheeled uint32 = 0x4 + // mouseDoubleClick uint32 = 0x2 + // mouseMoved uint32 = 0x1 +) + +type resizeRecord struct { + x int16 + y int16 +} + +type keyRecord struct { + isdown int32 + repeat uint16 + kcode uint16 + scode uint16 + ch uint16 + mod uint32 +} + +const ( + // Constants per Microsoft. We don't put the modifiers + // here. + vkCancel = 0x03 + vkBack = 0x08 // Backspace + vkTab = 0x09 + vkClear = 0x0c + vkReturn = 0x0d + vkPause = 0x13 + vkEscape = 0x1b + vkSpace = 0x20 + vkPrior = 0x21 // PgUp + vkNext = 0x22 // PgDn + vkEnd = 0x23 + vkHome = 0x24 + vkLeft = 0x25 + vkUp = 0x26 + vkRight = 0x27 + vkDown = 0x28 + vkPrint = 0x2a + vkPrtScr = 0x2c + vkInsert = 0x2d + vkDelete = 0x2e + vkHelp = 0x2f + vkF1 = 0x70 + vkF2 = 0x71 + vkF3 = 0x72 + vkF4 = 0x73 + vkF5 = 0x74 + vkF6 = 0x75 + vkF7 = 0x76 + vkF8 = 0x77 + vkF9 = 0x78 + vkF10 = 0x79 + vkF11 = 0x7a + vkF12 = 0x7b + vkF13 = 0x7c + vkF14 = 0x7d + vkF15 = 0x7e + vkF16 = 0x7f + vkF17 = 0x80 + vkF18 = 0x81 + vkF19 = 0x82 + vkF20 = 0x83 + vkF21 = 0x84 + vkF22 = 0x85 + vkF23 = 0x86 + vkF24 = 0x87 +) + +var vkKeys = map[uint16]Key{ + vkCancel: KeyCancel, + vkBack: KeyBackspace, + vkTab: KeyTab, + vkClear: KeyClear, + vkPause: KeyPause, + vkPrint: KeyPrint, + vkPrtScr: KeyPrint, + vkPrior: KeyPgUp, + vkNext: KeyPgDn, + vkReturn: KeyEnter, + vkEnd: KeyEnd, + vkHome: KeyHome, + vkLeft: KeyLeft, + vkUp: KeyUp, + vkRight: KeyRight, + vkDown: KeyDown, + vkInsert: KeyInsert, + vkDelete: KeyDelete, + vkHelp: KeyHelp, + vkEscape: KeyEscape, + vkSpace: ' ', + vkF1: KeyF1, + vkF2: KeyF2, + vkF3: KeyF3, + vkF4: KeyF4, + vkF5: KeyF5, + vkF6: KeyF6, + vkF7: KeyF7, + vkF8: KeyF8, + vkF9: KeyF9, + vkF10: KeyF10, + vkF11: KeyF11, + vkF12: KeyF12, + vkF13: KeyF13, + vkF14: KeyF14, + vkF15: KeyF15, + vkF16: KeyF16, + vkF17: KeyF17, + vkF18: KeyF18, + vkF19: KeyF19, + vkF20: KeyF20, + vkF21: KeyF21, + vkF22: KeyF22, + vkF23: KeyF23, + vkF24: KeyF24, +} + +// NB: All Windows platforms are little endian. We assume this +// never, ever change. The following code is endian safe. and does +// not use unsafe pointers. +func getu32(v []byte) uint32 { + return uint32(v[0]) + (uint32(v[1]) << 8) + (uint32(v[2]) << 16) + (uint32(v[3]) << 24) +} +func geti32(v []byte) int32 { + return int32(getu32(v)) +} +func getu16(v []byte) uint16 { + return uint16(v[0]) + (uint16(v[1]) << 8) +} +func geti16(v []byte) int16 { + return int16(getu16(v)) +} + +// Convert windows dwControlKeyState to modifier mask +func mod2mask(cks uint32) ModMask { + mm := ModNone + // Left or right control + ctrl := (cks & (0x0008 | 0x0004)) != 0 + // Left or right alt + alt := (cks & (0x0002 | 0x0001)) != 0 + // Filter out ctrl+alt (it means AltGr) + if !(ctrl && alt) { + if ctrl { + mm |= ModCtrl + } + if alt { + mm |= ModAlt + } + } + // Any shift + if (cks & 0x0010) != 0 { + mm |= ModShift + } + return mm +} + +func mrec2btns(mbtns, flags uint32) ButtonMask { + btns := ButtonNone + if mbtns&0x1 != 0 { + btns |= Button1 + } + if mbtns&0x2 != 0 { + btns |= Button2 + } + if mbtns&0x4 != 0 { + btns |= Button3 + } + if mbtns&0x8 != 0 { + btns |= Button4 + } + if mbtns&0x10 != 0 { + btns |= Button5 + } + if mbtns&0x20 != 0 { + btns |= Button6 + } + if mbtns&0x40 != 0 { + btns |= Button7 + } + if mbtns&0x80 != 0 { + btns |= Button8 + } + + if flags&mouseVWheeled != 0 { + if mbtns&0x80000000 == 0 { + btns |= WheelUp + } else { + btns |= WheelDown + } + } + if flags&mouseHWheeled != 0 { + if mbtns&0x80000000 == 0 { + btns |= WheelRight + } else { + btns |= WheelLeft + } + } + return btns +} + +func (s *cScreen) postEvent(ev Event) { + select { + case s.eventQ <- ev: + case <-s.quit: + } +} + +func (s *cScreen) getConsoleInput() error { + // cancelFlag comes first as WaitForMultipleObjects returns the lowest index + // in the event that both events are signalled. + waitObjects := []syscall.Handle{s.cancelflag, s.in} + // As arrays are contiguous in memory, a pointer to the first object is the + // same as a pointer to the array itself. + pWaitObjects := unsafe.Pointer(&waitObjects[0]) + + rv, _, er := procWaitForMultipleObjects.Call( + uintptr(len(waitObjects)), + uintptr(pWaitObjects), + uintptr(0), + w32Infinite) + // WaitForMultipleObjects returns WAIT_OBJECT_0 + the index. + switch rv { + case w32WaitObject0: // s.cancelFlag + return errors.New("cancelled") + case w32WaitObject0 + 1: // s.in + rec := &inputRecord{} + var nrec int32 + rv, _, er := procReadConsoleInput.Call( + uintptr(s.in), + uintptr(unsafe.Pointer(rec)), + uintptr(1), + uintptr(unsafe.Pointer(&nrec))) + if rv == 0 { + return er + } + if nrec != 1 { + return nil + } + switch rec.typ { + case keyEvent: + krec := &keyRecord{} + krec.isdown = geti32(rec.data[0:]) + krec.repeat = getu16(rec.data[4:]) + krec.kcode = getu16(rec.data[6:]) + krec.scode = getu16(rec.data[8:]) + krec.ch = getu16(rec.data[10:]) + krec.mod = getu32(rec.data[12:]) + + if krec.isdown == 0 || krec.repeat < 1 { + // it's a key release event, ignore it + return nil + } + if krec.ch != 0 { + // synthesized key code + for krec.repeat > 0 { + // convert shift+tab to backtab + if mod2mask(krec.mod) == ModShift && krec.ch == vkTab { + s.postEvent(NewEventKey(KeyBacktab, 0, ModNone)) + } else { + s.postEvent(NewEventKey(KeyRune, rune(krec.ch), mod2mask(krec.mod))) + } + krec.repeat-- + } + return nil + } + key := KeyNUL // impossible on Windows + ok := false + if key, ok = vkKeys[krec.kcode]; !ok { + return nil + } + for krec.repeat > 0 { + s.postEvent(NewEventKey(key, rune(krec.ch), mod2mask(krec.mod))) + krec.repeat-- + } + + case mouseEvent: + var mrec mouseRecord + mrec.x = geti16(rec.data[0:]) + mrec.y = geti16(rec.data[2:]) + mrec.btns = getu32(rec.data[4:]) + mrec.mod = getu32(rec.data[8:]) + mrec.flags = getu32(rec.data[12:]) + btns := mrec2btns(mrec.btns, mrec.flags) + // we ignore double click, events are delivered normally + s.postEvent(NewEventMouse(int(mrec.x), int(mrec.y), btns, mod2mask(mrec.mod))) + + case resizeEvent: + var rrec resizeRecord + rrec.x = geti16(rec.data[0:]) + rrec.y = geti16(rec.data[2:]) + s.postEvent(NewEventResize(int(rrec.x), int(rrec.y))) + + case focusEvent: + var focus focusRecord + focus.focused = geti32(rec.data[0:]) + s.Lock() + enabled := s.focusEnable + s.Unlock() + if enabled { + s.postEvent(NewEventFocus(focus.focused != 0)) + } + + default: + } + default: + return er + } + + return nil +} + +func (s *cScreen) scanInput(stopQ chan struct{}) { + defer s.wg.Done() + for { + select { + case <-stopQ: + return + default: + } + if e := s.getConsoleInput(); e != nil { + return + } + } +} + +func (s *cScreen) Colors() int { + if s.vten { + return 1 << 24 + } + // Windows console can display 8 colors, in either low or high intensity + return 16 +} + +var vgaColors = map[Color]uint16{ + ColorBlack: 0, + ColorMaroon: 0x4, + ColorGreen: 0x2, + ColorNavy: 0x1, + ColorOlive: 0x6, + ColorPurple: 0x5, + ColorTeal: 0x3, + ColorSilver: 0x7, + ColorGrey: 0x8, + ColorRed: 0xc, + ColorLime: 0xa, + ColorBlue: 0x9, + ColorYellow: 0xe, + ColorFuchsia: 0xd, + ColorAqua: 0xb, + ColorWhite: 0xf, +} + +// Windows uses RGB signals +func mapColor2RGB(c Color) uint16 { + winLock.Lock() + if v, ok := winColors[c]; ok { + c = v + } else { + v = FindColor(c, winPalette) + winColors[c] = v + c = v + } + winLock.Unlock() + + if vc, ok := vgaColors[c]; ok { + return vc + } + return 0 +} + +// Map a tcell style to Windows attributes +func (s *cScreen) mapStyle(style Style) uint16 { + f, b, a := style.Decompose() + fa := s.oscreen.attrs & 0xf + ba := (s.oscreen.attrs) >> 4 & 0xf + if f != ColorDefault && f != ColorReset { + fa = mapColor2RGB(f) + } + if b != ColorDefault && b != ColorReset { + ba = mapColor2RGB(b) + } + var attr uint16 + // We simulate reverse by doing the color swap ourselves. + // Apparently windows cannot really do this except in DBCS + // views. + if a&AttrReverse != 0 { + attr = ba + attr |= fa << 4 + } else { + attr = fa + attr |= ba << 4 + } + if a&AttrBold != 0 { + attr |= 0x8 + } + if a&AttrDim != 0 { + attr &^= 0x8 + } + if a&AttrUnderline != 0 { + // Best effort -- doesn't seem to work though. + attr |= 0x8000 + } + // Blink is unsupported + return attr +} + +func (s *cScreen) sendVtStyle(style Style) { + esc := &strings.Builder{} + + fg, bg, attrs := style.Decompose() + + esc.WriteString(vtSgr0) + + if attrs&(AttrBold|AttrDim) == AttrBold { + esc.WriteString(vtBold) + } + if attrs&AttrBlink != 0 { + esc.WriteString(vtBlink) + } + if attrs&AttrUnderline != 0 { + esc.WriteString(vtUnderline) + } + if attrs&AttrReverse != 0 { + esc.WriteString(vtReverse) + } + if fg.IsRGB() { + r, g, b := fg.RGB() + _, _ = fmt.Fprintf(esc, vtSetFgRGB, r, g, b) + } else if fg.Valid() { + _, _ = fmt.Fprintf(esc, vtSetFg, fg&0xff) + } + if bg.IsRGB() { + r, g, b := bg.RGB() + _, _ = fmt.Fprintf(esc, vtSetBgRGB, r, g, b) + } else if bg.Valid() { + _, _ = fmt.Fprintf(esc, vtSetBg, bg&0xff) + } + s.emitVtString(esc.String()) +} + +func (s *cScreen) writeString(x, y int, style Style, ch []uint16) { + // we assume the caller has hidden the cursor + if len(ch) == 0 { + return + } + s.setCursorPos(x, y, s.vten) + + if s.vten { + s.sendVtStyle(style) + } else { + _, _, _ = procSetConsoleTextAttribute.Call( + uintptr(s.out), + uintptr(s.mapStyle(style))) + } + _ = syscall.WriteConsole(s.out, &ch[0], uint32(len(ch)), nil, nil) +} + +func (s *cScreen) draw() { + // allocate a scratch line bit enough for no combining chars. + // if you have combining characters, you may pay for extra allocations. + buf := make([]uint16, 0, s.w) + wcs := buf[:] + lstyle := styleInvalid + + lx, ly := -1, -1 + ra := make([]rune, 1) + + for y := 0; y < s.h; y++ { + for x := 0; x < s.w; x++ { + mainc, combc, style, width := s.cells.GetContent(x, y) + dirty := s.cells.Dirty(x, y) + if style == StyleDefault { + style = s.style + } + + if !dirty || style != lstyle { + // write out any data queued thus far + // because we are going to skip over some + // cells, or because we need to change styles + s.writeString(lx, ly, lstyle, wcs) + wcs = buf[0:0] + lstyle = StyleDefault + if !dirty { + continue + } + } + if x > s.w-width { + mainc = ' ' + combc = nil + width = 1 + } + if len(wcs) == 0 { + lstyle = style + lx = x + ly = y + } + ra[0] = mainc + wcs = append(wcs, utf16.Encode(ra)...) + if len(combc) != 0 { + wcs = append(wcs, utf16.Encode(combc)...) + } + for dx := 0; dx < width; dx++ { + s.cells.SetDirty(x+dx, y, false) + } + x += width - 1 + } + s.writeString(lx, ly, lstyle, wcs) + wcs = buf[0:0] + lstyle = styleInvalid + } +} + +func (s *cScreen) Show() { + s.Lock() + if !s.fini { + s.hideCursor() + s.resize() + s.draw() + s.doCursor() + } + s.Unlock() +} + +func (s *cScreen) Sync() { + s.Lock() + if !s.fini { + s.cells.Invalidate() + s.hideCursor() + s.resize() + s.draw() + s.doCursor() + } + s.Unlock() +} + +type consoleInfo struct { + size coord + pos coord + attrs uint16 + win rect + maxsz coord +} + +func (s *cScreen) getConsoleInfo(info *consoleInfo) { + _, _, _ = procGetConsoleScreenBufferInfo.Call( + uintptr(s.out), + uintptr(unsafe.Pointer(info))) +} + +func (s *cScreen) getCursorInfo(info *cursorInfo) { + _, _, _ = procGetConsoleCursorInfo.Call( + uintptr(s.out), + uintptr(unsafe.Pointer(info))) +} + +func (s *cScreen) setCursorInfo(info *cursorInfo) { + _, _, _ = procSetConsoleCursorInfo.Call( + uintptr(s.out), + uintptr(unsafe.Pointer(info))) + +} + +func (s *cScreen) setCursorPos(x, y int, vtEnable bool) { + if vtEnable { + // Note that the string is Y first. Origin is 1,1. + s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+1)) + } else { + _, _, _ = procSetConsoleCursorPosition.Call( + uintptr(s.out), + coord{int16(x), int16(y)}.uintptr()) + } +} + +func (s *cScreen) setBufferSize(x, y int) { + _, _, _ = procSetConsoleScreenBufferSize.Call( + uintptr(s.out), + coord{int16(x), int16(y)}.uintptr()) +} + +func (s *cScreen) Size() (int, int) { + s.Lock() + w, h := s.w, s.h + s.Unlock() + + return w, h +} + +func (s *cScreen) SetSize(w, h int) { + xy, _, _ := procGetLargestConsoleWindowSize.Call(uintptr(s.out)) + + // xy is little endian packed + y := int(xy >> 16) + x := int(xy & 0xffff) + + if x == 0 || y == 0 { + return + } + + // This is a hacky workaround for Windows Terminal. + // Essentially Windows Terminal (Windows 11) does not support application + // initiated resizing. To detect this, we look for an extremely large size + // for the maximum width. If it is > 500, then this is almost certainly + // Windows Terminal, and won't support this. (Note that the legacy console + // does support application resizing.) + if x >= 500 { + return + } + + s.setBufferSize(x, y) + r := rect{0, 0, int16(w - 1), int16(h - 1)} + _, _, _ = procSetConsoleWindowInfo.Call( + uintptr(s.out), + uintptr(1), + uintptr(unsafe.Pointer(&r))) + + s.resize() +} + +func (s *cScreen) resize() { + info := consoleInfo{} + s.getConsoleInfo(&info) + + w := int((info.win.right - info.win.left) + 1) + h := int((info.win.bottom - info.win.top) + 1) + + if s.w == w && s.h == h { + return + } + + s.cells.Resize(w, h) + s.w = w + s.h = h + + s.setBufferSize(w, h) + + r := rect{0, 0, int16(w - 1), int16(h - 1)} + _, _, _ = procSetConsoleWindowInfo.Call( + uintptr(s.out), + uintptr(1), + uintptr(unsafe.Pointer(&r))) + select { + case s.eventQ <- NewEventResize(w, h): + default: + } +} + +func (s *cScreen) clearScreen(style Style, vtEnable bool) { + if vtEnable { + s.sendVtStyle(style) + row := strings.Repeat(" ", s.w) + for y := 0; y < s.h; y++ { + s.setCursorPos(0, y, vtEnable) + s.emitVtString(row) + } + s.setCursorPos(0, 0, vtEnable) + + } else { + pos := coord{0, 0} + attr := s.mapStyle(style) + x, y := s.w, s.h + scratch := uint32(0) + count := uint32(x * y) + + _, _, _ = procFillConsoleOutputAttribute.Call( + uintptr(s.out), + uintptr(attr), + uintptr(count), + pos.uintptr(), + uintptr(unsafe.Pointer(&scratch))) + _, _, _ = procFillConsoleOutputCharacter.Call( + uintptr(s.out), + uintptr(' '), + uintptr(count), + pos.uintptr(), + uintptr(unsafe.Pointer(&scratch))) + } +} + +const ( + // Input modes + modeExtendFlg uint32 = 0x0080 + modeMouseEn = 0x0010 + modeResizeEn = 0x0008 + // modeCooked = 0x0001 + // modeVtInput = 0x0200 + + // Output modes + modeCookedOut uint32 = 0x0001 + modeVtOutput = 0x0004 + modeNoAutoNL = 0x0008 + modeUnderline = 0x0010 // ENABLE_LVB_GRID_WORLDWIDE, needed for underlines + // modeWrapEOL = 0x0002 +) + +func (s *cScreen) setInMode(mode uint32) { + _, _, _ = procSetConsoleMode.Call( + uintptr(s.in), + uintptr(mode)) +} + +func (s *cScreen) setOutMode(mode uint32) { + _, _, _ = procSetConsoleMode.Call( + uintptr(s.out), + uintptr(mode)) +} + +func (s *cScreen) getInMode(v *uint32) { + _, _, _ = procGetConsoleMode.Call( + uintptr(s.in), + uintptr(unsafe.Pointer(v))) +} + +func (s *cScreen) getOutMode(v *uint32) { + _, _, _ = procGetConsoleMode.Call( + uintptr(s.out), + uintptr(unsafe.Pointer(v))) +} + +func (s *cScreen) SetStyle(style Style) { + s.Lock() + s.style = style + s.Unlock() +} + +// No fallback rune support, since we have Unicode. Yay! + +func (s *cScreen) RegisterRuneFallback(_ rune, _ string) { +} + +func (s *cScreen) UnregisterRuneFallback(_ rune) { +} + +func (s *cScreen) CanDisplay(_ rune, _ bool) bool { + // We presume we can display anything -- we're Unicode. + // (Sadly this not precisely true. Combining characters are especially + // poorly supported under Windows.) + return true +} + +func (s *cScreen) HasMouse() bool { + return true +} + +func (s *cScreen) Resize(int, int, int, int) {} + +func (s *cScreen) HasKey(k Key) bool { + // Microsoft has codes for some keys, but they are unusual, + // so we don't include them. We include all the typical + // 101, 105 key layout keys. + valid := map[Key]bool{ + KeyBackspace: true, + KeyTab: true, + KeyEscape: true, + KeyPause: true, + KeyPrint: true, + KeyPgUp: true, + KeyPgDn: true, + KeyEnter: true, + KeyEnd: true, + KeyHome: true, + KeyLeft: true, + KeyUp: true, + KeyRight: true, + KeyDown: true, + KeyInsert: true, + KeyDelete: true, + KeyF1: true, + KeyF2: true, + KeyF3: true, + KeyF4: true, + KeyF5: true, + KeyF6: true, + KeyF7: true, + KeyF8: true, + KeyF9: true, + KeyF10: true, + KeyF11: true, + KeyF12: true, + KeyRune: true, + } + + return valid[k] +} + +func (s *cScreen) Beep() error { + // A simple beep. If the sound card is not available, the sound is generated + // using the speaker. + // + // Reference: + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebeep + const simpleBeep = 0xffffffff + if rv, _, err := procMessageBeep.Call(simpleBeep); rv == 0 { + return err + } + return nil +} + +func (s *cScreen) Suspend() error { + s.disengage() + return nil +} + +func (s *cScreen) Resume() error { + return s.engage() +} + +func (s *cScreen) Tty() (Tty, bool) { + return nil, false +} + +func (s *cScreen) GetCells() *CellBuffer { + return &s.cells +} + +func (s *cScreen) EventQ() chan Event { + return s.eventQ +} + +func (s *cScreen) StopQ() <-chan struct{} { + return s.quit +} diff --git a/vendor/github.com/gdamore/tcell/v2/doc.go b/vendor/github.com/gdamore/tcell/v2/doc.go new file mode 100644 index 0000000..690dd27 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/doc.go @@ -0,0 +1,47 @@ +// Copyright 2018 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tcell provides a lower-level, portable API for building +// programs that interact with terminals or consoles. It works with +// both common (and many uncommon!) terminals or terminal emulators, +// and Windows console implementations. +// +// It provides support for up to 256 colors, text attributes, and box drawing +// elements. A database of terminals built from a real terminfo database +// is provided, along with code to generate new database entries. +// +// Tcell offers very rich support for mice, dependent upon the terminal +// of course. (Windows, XTerm, and iTerm 2 are known to work very well.) +// +// If the environment is not Unicode by default, such as an ISO8859 based +// locale or GB18030, Tcell can convert input and output, so that your +// terminal can operate in whatever locale is most convenient, while the +// application program can just assume "everything is UTF-8". Reasonable +// defaults are used for updating characters to something suitable for +// display. Unicode box drawing characters will be converted to use the +// alternate character set of your terminal, if native conversions are +// not available. If no ACS is available, then some ASCII fallbacks will +// be used. +// +// Note that support for non-UTF-8 locales (other than C) must be enabled +// by the application using RegisterEncoding() -- we don't have them all +// enabled by default to avoid bloating the application unnecessarily. +// (These days UTF-8 is good enough for almost everyone, and nobody should +// be using legacy locales anymore.) Also, actual glyphs for various code +// point will only be displayed if your terminal or emulator (or the font +// the emulator is using) supports them. +// +// A rich set of key codes is supported, with support for up to 65 function +// keys, and various other special keys. +package tcell diff --git a/vendor/github.com/gdamore/tcell/v2/encoding.go b/vendor/github.com/gdamore/tcell/v2/encoding.go new file mode 100644 index 0000000..b7644c2 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/encoding.go @@ -0,0 +1,140 @@ +// Copyright 2022 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "strings" + "sync" + + "golang.org/x/text/encoding" + + gencoding "github.com/gdamore/encoding" +) + +var encodings map[string]encoding.Encoding +var encodingLk sync.Mutex +var encodingFallback EncodingFallback = EncodingFallbackFail + +// RegisterEncoding may be called by the application to register an encoding. +// The presence of additional encodings will facilitate application usage with +// terminal environments where the I/O subsystem does not support Unicode. +// +// Windows systems use Unicode natively, and do not need any of the encoding +// subsystem when using Windows Console screens. +// +// Please see the Go documentation for golang.org/x/text/encoding -- most of +// the common ones exist already as stock variables. For example, ISO8859-15 +// can be registered using the following code: +// +// import "golang.org/x/text/encoding/charmap" +// +// ... +// RegisterEncoding("ISO8859-15", charmap.ISO8859_15) +// +// Aliases can be registered as well, for example "8859-15" could be an alias +// for "ISO8859-15". +// +// For POSIX systems, this package will check the environment variables +// LC_ALL, LC_CTYPE, and LANG (in that order) to determine the character set. +// These are expected to have the following pattern: +// +// $language[.$codeset[@$variant] +// +// We extract only the $codeset part, which will usually be something like +// UTF-8 or ISO8859-15 or KOI8-R. Note that if the locale is either "POSIX" +// or "C", then we assume US-ASCII (the POSIX 'portable character set' +// and assume all other characters are somehow invalid.) +// +// Modern POSIX systems and terminal emulators may use UTF-8, and for those +// systems, this API is also unnecessary. For example, Darwin (MacOS X) and +// modern Linux running modern xterm generally will out of the box without +// any of this. Use of UTF-8 is recommended when possible, as it saves +// quite a lot processing overhead. +// +// Note that some encodings are quite large (for example GB18030 which is a +// superset of Unicode) and so the application size can be expected to +// increase quite a bit as each encoding is added. + +// The East Asian encodings have been seen to add 100-200K per encoding to the +// size of the resulting binary. +func RegisterEncoding(charset string, enc encoding.Encoding) { + encodingLk.Lock() + charset = strings.ToLower(charset) + encodings[charset] = enc + encodingLk.Unlock() +} + +// EncodingFallback describes how the system behaves when the locale +// requires a character set that we do not support. The system always +// supports UTF-8 and US-ASCII. On Windows consoles, UTF-16LE is also +// supported automatically. Other character sets must be added using the +// RegisterEncoding API. (A large group of nearly all of them can be +// added using the RegisterAll function in the encoding sub package.) +type EncodingFallback int + +const ( + // EncodingFallbackFail behavior causes GetEncoding to fail + // when it cannot find an encoding. + EncodingFallbackFail = iota + + // EncodingFallbackASCII behavior causes GetEncoding to fall back + // to a 7-bit ASCII encoding, if no other encoding can be found. + EncodingFallbackASCII + + // EncodingFallbackUTF8 behavior causes GetEncoding to assume + // UTF8 can pass unmodified upon failure. Note that this behavior + // is not recommended, unless you are sure your terminal can cope + // with real UTF8 sequences. + EncodingFallbackUTF8 +) + +// SetEncodingFallback changes the behavior of GetEncoding when a suitable +// encoding is not found. The default is EncodingFallbackFail, which +// causes GetEncoding to simply return nil. +func SetEncodingFallback(fb EncodingFallback) { + encodingLk.Lock() + encodingFallback = fb + encodingLk.Unlock() +} + +// GetEncoding is used by Screen implementors who want to locate an encoding +// for the given character set name. Note that this will return nil for +// either the Unicode (UTF-8) or ASCII encodings, since we don't use +// encodings for them but instead have our own native methods. +func GetEncoding(charset string) encoding.Encoding { + charset = strings.ToLower(charset) + encodingLk.Lock() + defer encodingLk.Unlock() + if enc, ok := encodings[charset]; ok { + return enc + } + switch encodingFallback { + case EncodingFallbackASCII: + return gencoding.ASCII + case EncodingFallbackUTF8: + return encoding.Nop + } + return nil +} + +func init() { + // We always support UTF-8 and ASCII. + encodings = make(map[string]encoding.Encoding) + encodings["utf-8"] = gencoding.UTF8 + encodings["utf8"] = gencoding.UTF8 + encodings["us-ascii"] = gencoding.ASCII + encodings["ascii"] = gencoding.ASCII + encodings["iso646"] = gencoding.ASCII +} diff --git a/vendor/github.com/gdamore/tcell/v2/errors.go b/vendor/github.com/gdamore/tcell/v2/errors.go new file mode 100644 index 0000000..201dff9 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/errors.go @@ -0,0 +1,73 @@ +// Copyright 2015 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "errors" + "time" + + "github.com/gdamore/tcell/v2/terminfo" +) + +var ( + // ErrTermNotFound indicates that a suitable terminal entry could + // not be found. This can result from either not having TERM set, + // or from the TERM failing to support certain minimal functionality, + // in particular absolute cursor addressability (the cup capability) + // is required. For example, legacy "adm3" lacks this capability, + // whereas the slightly newer "adm3a" supports it. This failure + // occurs most often with "dumb". + ErrTermNotFound = terminfo.ErrTermNotFound + + // ErrNoScreen indicates that no suitable screen could be found. + // This may result from attempting to run on a platform where there + // is no support for either termios or console I/O (such as nacl), + // or from running in an environment where there is no access to + // a suitable console/terminal device. (For example, running on + // without a controlling TTY or with no /dev/tty on POSIX platforms.) + ErrNoScreen = errors.New("no suitable screen available") + + // ErrNoCharset indicates that the locale environment the + // program is not supported by the program, because no suitable + // encoding was found for it. This problem never occurs if + // the environment is UTF-8 or UTF-16. + ErrNoCharset = errors.New("character set not supported") + + // ErrEventQFull indicates that the event queue is full, and + // cannot accept more events. + ErrEventQFull = errors.New("event queue full") +) + +// An EventError is an event representing some sort of error, and carries +// an error payload. +type EventError struct { + t time.Time + err error +} + +// When returns the time when the event was created. +func (ev *EventError) When() time.Time { + return ev.t +} + +// Error implements the error. +func (ev *EventError) Error() string { + return ev.err.Error() +} + +// NewEventError creates an ErrorEvent with the given error payload. +func NewEventError(err error) *EventError { + return &EventError{t: time.Now(), err: err} +} diff --git a/vendor/github.com/gdamore/tcell/v2/event.go b/vendor/github.com/gdamore/tcell/v2/event.go new file mode 100644 index 0000000..a3b7700 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/event.go @@ -0,0 +1,53 @@ +// Copyright 2015 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "time" +) + +// Event is a generic interface used for passing around Events. +// Concrete types follow. +type Event interface { + // When reports the time when the event was generated. + When() time.Time +} + +// EventTime is a simple base event class, suitable for easy reuse. +// It can be used to deliver actual timer events as well. +type EventTime struct { + when time.Time +} + +// When returns the time stamp when the event occurred. +func (e *EventTime) When() time.Time { + return e.when +} + +// SetEventTime sets the time of occurrence for the event. +func (e *EventTime) SetEventTime(t time.Time) { + e.when = t +} + +// SetEventNow sets the time of occurrence for the event to the current time. +func (e *EventTime) SetEventNow() { + e.SetEventTime(time.Now()) +} + +// EventHandler is anything that handles events. If the handler has +// consumed the event, it should return true. False otherwise. +type EventHandler interface { + HandleEvent(Event) bool +} diff --git a/vendor/github.com/gdamore/tcell/v2/focus.go b/vendor/github.com/gdamore/tcell/v2/focus.go new file mode 100644 index 0000000..e9b93ef --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/focus.go @@ -0,0 +1,28 @@ +// Copyright 2023 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +// EventFocus is a focus event. It is sent when the terminal window (or tab) +// gets or loses focus. +type EventFocus struct { + *EventTime + + // True if the window received focus, false if it lost focus + Focused bool +} + +func NewEventFocus(focused bool) *EventFocus { + return &EventFocus{Focused: focused} +} diff --git a/vendor/github.com/gdamore/tcell/v2/interrupt.go b/vendor/github.com/gdamore/tcell/v2/interrupt.go new file mode 100644 index 0000000..70dddfc --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/interrupt.go @@ -0,0 +1,41 @@ +// Copyright 2015 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "time" +) + +// EventInterrupt is a generic wakeup event. Its can be used to +// to request a redraw. It can carry an arbitrary payload, as well. +type EventInterrupt struct { + t time.Time + v interface{} +} + +// When returns the time when this event was created. +func (ev *EventInterrupt) When() time.Time { + return ev.t +} + +// Data is used to obtain the opaque event payload. +func (ev *EventInterrupt) Data() interface{} { + return ev.v +} + +// NewEventInterrupt creates an EventInterrupt with the given payload. +func NewEventInterrupt(data interface{}) *EventInterrupt { + return &EventInterrupt{t: time.Now(), v: data} +} diff --git a/vendor/github.com/gdamore/tcell/v2/key.go b/vendor/github.com/gdamore/tcell/v2/key.go new file mode 100644 index 0000000..9741e69 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/key.go @@ -0,0 +1,470 @@ +// Copyright 2016 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "fmt" + "strings" + "time" +) + +// EventKey represents a key press. Usually this is a key press followed +// by a key release, but since terminal programs don't have a way to report +// key release events, we usually get just one event. If a key is held down +// then the terminal may synthesize repeated key presses at some predefined +// rate. We have no control over that, nor visibility into it. +// +// In some cases, we can have a modifier key, such as ModAlt, that can be +// generated with a key press. (This usually is represented by having the +// high bit set, or in some cases, by sending an ESC prior to the rune.) +// +// If the value of Key() is KeyRune, then the actual key value will be +// available with the Rune() method. This will be the case for most keys. +// In most situations, the modifiers will not be set. For example, if the +// rune is 'A', this will be reported without the ModShift bit set, since +// really can't tell if the Shift key was pressed (it might have been CAPSLOCK, +// or a terminal that only can send capitals, or keyboard with separate +// capital letters from lower case letters). +// +// Generally, terminal applications have far less visibility into keyboard +// activity than graphical applications. Hence, they should avoid depending +// overly much on availability of modifiers, or the availability of any +// specific keys. +type EventKey struct { + t time.Time + mod ModMask + key Key + ch rune +} + +// When returns the time when this Event was created, which should closely +// match the time when the key was pressed. +func (ev *EventKey) When() time.Time { + return ev.t +} + +// Rune returns the rune corresponding to the key press, if it makes sense. +// The result is only defined if the value of Key() is KeyRune. +func (ev *EventKey) Rune() rune { + return ev.ch +} + +// Key returns a virtual key code. We use this to identify specific key +// codes, such as KeyEnter, etc. Most control and function keys are reported +// with unique Key values. Normal alphanumeric and punctuation keys will +// generally return KeyRune here; the specific key can be further decoded +// using the Rune() function. +func (ev *EventKey) Key() Key { + return ev.key +} + +// Modifiers returns the modifiers that were present with the key press. Note +// that not all platforms and terminals support this equally well, and some +// cases we will not not know for sure. Hence, applications should avoid +// using this in most circumstances. +func (ev *EventKey) Modifiers() ModMask { + return ev.mod +} + +// KeyNames holds the written names of special keys. Useful to echo back a key +// name, or to look up a key from a string value. +var KeyNames = map[Key]string{ + KeyEnter: "Enter", + KeyBackspace: "Backspace", + KeyTab: "Tab", + KeyBacktab: "Backtab", + KeyEsc: "Esc", + KeyBackspace2: "Backspace2", + KeyDelete: "Delete", + KeyInsert: "Insert", + KeyUp: "Up", + KeyDown: "Down", + KeyLeft: "Left", + KeyRight: "Right", + KeyHome: "Home", + KeyEnd: "End", + KeyUpLeft: "UpLeft", + KeyUpRight: "UpRight", + KeyDownLeft: "DownLeft", + KeyDownRight: "DownRight", + KeyCenter: "Center", + KeyPgDn: "PgDn", + KeyPgUp: "PgUp", + KeyClear: "Clear", + KeyExit: "Exit", + KeyCancel: "Cancel", + KeyPause: "Pause", + KeyPrint: "Print", + KeyF1: "F1", + KeyF2: "F2", + KeyF3: "F3", + KeyF4: "F4", + KeyF5: "F5", + KeyF6: "F6", + KeyF7: "F7", + KeyF8: "F8", + KeyF9: "F9", + KeyF10: "F10", + KeyF11: "F11", + KeyF12: "F12", + KeyF13: "F13", + KeyF14: "F14", + KeyF15: "F15", + KeyF16: "F16", + KeyF17: "F17", + KeyF18: "F18", + KeyF19: "F19", + KeyF20: "F20", + KeyF21: "F21", + KeyF22: "F22", + KeyF23: "F23", + KeyF24: "F24", + KeyF25: "F25", + KeyF26: "F26", + KeyF27: "F27", + KeyF28: "F28", + KeyF29: "F29", + KeyF30: "F30", + KeyF31: "F31", + KeyF32: "F32", + KeyF33: "F33", + KeyF34: "F34", + KeyF35: "F35", + KeyF36: "F36", + KeyF37: "F37", + KeyF38: "F38", + KeyF39: "F39", + KeyF40: "F40", + KeyF41: "F41", + KeyF42: "F42", + KeyF43: "F43", + KeyF44: "F44", + KeyF45: "F45", + KeyF46: "F46", + KeyF47: "F47", + KeyF48: "F48", + KeyF49: "F49", + KeyF50: "F50", + KeyF51: "F51", + KeyF52: "F52", + KeyF53: "F53", + KeyF54: "F54", + KeyF55: "F55", + KeyF56: "F56", + KeyF57: "F57", + KeyF58: "F58", + KeyF59: "F59", + KeyF60: "F60", + KeyF61: "F61", + KeyF62: "F62", + KeyF63: "F63", + KeyF64: "F64", + KeyCtrlA: "Ctrl-A", + KeyCtrlB: "Ctrl-B", + KeyCtrlC: "Ctrl-C", + KeyCtrlD: "Ctrl-D", + KeyCtrlE: "Ctrl-E", + KeyCtrlF: "Ctrl-F", + KeyCtrlG: "Ctrl-G", + KeyCtrlJ: "Ctrl-J", + KeyCtrlK: "Ctrl-K", + KeyCtrlL: "Ctrl-L", + KeyCtrlN: "Ctrl-N", + KeyCtrlO: "Ctrl-O", + KeyCtrlP: "Ctrl-P", + KeyCtrlQ: "Ctrl-Q", + KeyCtrlR: "Ctrl-R", + KeyCtrlS: "Ctrl-S", + KeyCtrlT: "Ctrl-T", + KeyCtrlU: "Ctrl-U", + KeyCtrlV: "Ctrl-V", + KeyCtrlW: "Ctrl-W", + KeyCtrlX: "Ctrl-X", + KeyCtrlY: "Ctrl-Y", + KeyCtrlZ: "Ctrl-Z", + KeyCtrlSpace: "Ctrl-Space", + KeyCtrlUnderscore: "Ctrl-_", + KeyCtrlRightSq: "Ctrl-]", + KeyCtrlBackslash: "Ctrl-\\", + KeyCtrlCarat: "Ctrl-^", +} + +// Name returns a printable value or the key stroke. This can be used +// when printing the event, for example. +func (ev *EventKey) Name() string { + s := "" + m := []string{} + if ev.mod&ModShift != 0 { + m = append(m, "Shift") + } + if ev.mod&ModAlt != 0 { + m = append(m, "Alt") + } + if ev.mod&ModMeta != 0 { + m = append(m, "Meta") + } + if ev.mod&ModCtrl != 0 { + m = append(m, "Ctrl") + } + + ok := false + if s, ok = KeyNames[ev.key]; !ok { + if ev.key == KeyRune { + s = "Rune[" + string(ev.ch) + "]" + } else { + s = fmt.Sprintf("Key[%d,%d]", ev.key, int(ev.ch)) + } + } + if len(m) != 0 { + if ev.mod&ModCtrl != 0 && strings.HasPrefix(s, "Ctrl-") { + s = s[5:] + } + return fmt.Sprintf("%s+%s", strings.Join(m, "+"), s) + } + return s +} + +// NewEventKey attempts to create a suitable event. It parses the various +// ASCII control sequences if KeyRune is passed for Key, but if the caller +// has more precise information it should set that specifically. Callers +// that aren't sure about modifier state (most) should just pass ModNone. +func NewEventKey(k Key, ch rune, mod ModMask) *EventKey { + if k == KeyRune && (ch < ' ' || ch == 0x7f) { + // Turn specials into proper key codes. This is for + // control characters and the DEL. + k = Key(ch) + if mod == ModNone && ch < ' ' { + switch Key(ch) { + case KeyBackspace, KeyTab, KeyEsc, KeyEnter: + // these keys are directly typeable without CTRL + default: + // most likely entered with a CTRL keypress + mod = ModCtrl + } + } + } + return &EventKey{t: time.Now(), key: k, ch: ch, mod: mod} +} + +// ModMask is a mask of modifier keys. Note that it will not always be +// possible to report modifier keys. +type ModMask int16 + +// These are the modifiers keys that can be sent either with a key press, +// or a mouse event. Note that as of now, due to the confusion associated +// with Meta, and the lack of support for it on many/most platforms, the +// current implementations never use it. Instead, they use ModAlt, even for +// events that could possibly have been distinguished from ModAlt. +const ( + ModShift ModMask = 1 << iota + ModCtrl + ModAlt + ModMeta + ModNone ModMask = 0 +) + +// Key is a generic value for representing keys, and especially special +// keys (function keys, cursor movement keys, etc.) For normal keys, like +// ASCII letters, we use KeyRune, and then expect the application to +// inspect the Rune() member of the EventKey. +type Key int16 + +// This is the list of named keys. KeyRune is special however, in that it is +// a place holder key indicating that a printable character was sent. The +// actual value of the rune will be transported in the Rune of the associated +// EventKey. +const ( + KeyRune Key = iota + 256 + KeyUp + KeyDown + KeyRight + KeyLeft + KeyUpLeft + KeyUpRight + KeyDownLeft + KeyDownRight + KeyCenter + KeyPgUp + KeyPgDn + KeyHome + KeyEnd + KeyInsert + KeyDelete + KeyHelp + KeyExit + KeyClear + KeyCancel + KeyPrint + KeyPause + KeyBacktab + KeyF1 + KeyF2 + KeyF3 + KeyF4 + KeyF5 + KeyF6 + KeyF7 + KeyF8 + KeyF9 + KeyF10 + KeyF11 + KeyF12 + KeyF13 + KeyF14 + KeyF15 + KeyF16 + KeyF17 + KeyF18 + KeyF19 + KeyF20 + KeyF21 + KeyF22 + KeyF23 + KeyF24 + KeyF25 + KeyF26 + KeyF27 + KeyF28 + KeyF29 + KeyF30 + KeyF31 + KeyF32 + KeyF33 + KeyF34 + KeyF35 + KeyF36 + KeyF37 + KeyF38 + KeyF39 + KeyF40 + KeyF41 + KeyF42 + KeyF43 + KeyF44 + KeyF45 + KeyF46 + KeyF47 + KeyF48 + KeyF49 + KeyF50 + KeyF51 + KeyF52 + KeyF53 + KeyF54 + KeyF55 + KeyF56 + KeyF57 + KeyF58 + KeyF59 + KeyF60 + KeyF61 + KeyF62 + KeyF63 + KeyF64 +) + +const ( + // These key codes are used internally, and will never appear to applications. + keyPasteStart Key = iota + 16384 + keyPasteEnd +) + +// These are the control keys. Note that they overlap with other keys, +// perhaps. For example, KeyCtrlH is the same as KeyBackspace. +const ( + KeyCtrlSpace Key = iota + KeyCtrlA + KeyCtrlB + KeyCtrlC + KeyCtrlD + KeyCtrlE + KeyCtrlF + KeyCtrlG + KeyCtrlH + KeyCtrlI + KeyCtrlJ + KeyCtrlK + KeyCtrlL + KeyCtrlM + KeyCtrlN + KeyCtrlO + KeyCtrlP + KeyCtrlQ + KeyCtrlR + KeyCtrlS + KeyCtrlT + KeyCtrlU + KeyCtrlV + KeyCtrlW + KeyCtrlX + KeyCtrlY + KeyCtrlZ + KeyCtrlLeftSq // Escape + KeyCtrlBackslash + KeyCtrlRightSq + KeyCtrlCarat + KeyCtrlUnderscore +) + +// Special values - these are fixed in an attempt to make it more likely +// that aliases will encode the same way. + +// These are the defined ASCII values for key codes. They generally match +// with KeyCtrl values. +const ( + KeyNUL Key = iota + KeySOH + KeySTX + KeyETX + KeyEOT + KeyENQ + KeyACK + KeyBEL + KeyBS + KeyTAB + KeyLF + KeyVT + KeyFF + KeyCR + KeySO + KeySI + KeyDLE + KeyDC1 + KeyDC2 + KeyDC3 + KeyDC4 + KeyNAK + KeySYN + KeyETB + KeyCAN + KeyEM + KeySUB + KeyESC + KeyFS + KeyGS + KeyRS + KeyUS + KeyDEL Key = 0x7F +) + +// These keys are aliases for other names. +const ( + KeyBackspace = KeyBS + KeyTab = KeyTAB + KeyEsc = KeyESC + KeyEscape = KeyESC + KeyEnter = KeyCR + KeyBackspace2 = KeyDEL +) diff --git a/vendor/github.com/gdamore/tcell/v2/mouse.go b/vendor/github.com/gdamore/tcell/v2/mouse.go new file mode 100644 index 0000000..008c2e2 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/mouse.go @@ -0,0 +1,103 @@ +// Copyright 2020 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "time" +) + +// EventMouse is a mouse event. It is sent on either mouse up or mouse down +// events. It is also sent on mouse motion events - if the terminal supports +// it. We make every effort to ensure that mouse release events are delivered. +// Hence, click drag can be identified by a motion event with the mouse down, +// without any intervening button release. On some terminals only the initiating +// press and terminating release event will be delivered. +// +// Mouse wheel events, when reported, may appear on their own as individual +// impulses; that is, there will normally not be a release event delivered +// for mouse wheel movements. +// +// Most terminals cannot report the state of more than one button at a time -- +// and some cannot report motion events unless a button is pressed. +// +// Applications can inspect the time between events to resolve double or +// triple clicks. +type EventMouse struct { + t time.Time + btn ButtonMask + mod ModMask + x int + y int +} + +// When returns the time when this EventMouse was created. +func (ev *EventMouse) When() time.Time { + return ev.t +} + +// Buttons returns the list of buttons that were pressed or wheel motions. +func (ev *EventMouse) Buttons() ButtonMask { + return ev.btn +} + +// Modifiers returns a list of keyboard modifiers that were pressed +// with the mouse button(s). +func (ev *EventMouse) Modifiers() ModMask { + return ev.mod +} + +// Position returns the mouse position in character cells. The origin +// 0, 0 is at the upper left corner. +func (ev *EventMouse) Position() (int, int) { + return ev.x, ev.y +} + +// NewEventMouse is used to create a new mouse event. Applications +// shouldn't need to use this; its mostly for screen implementors. +func NewEventMouse(x, y int, btn ButtonMask, mod ModMask) *EventMouse { + return &EventMouse{t: time.Now(), x: x, y: y, btn: btn, mod: mod} +} + +// ButtonMask is a mask of mouse buttons and wheel events. Mouse button presses +// are normally delivered as both press and release events. Mouse wheel events +// are normally just single impulse events. Windows supports up to eight +// separate buttons plus all four wheel directions, but XTerm can only support +// mouse buttons 1-3 and wheel up/down. Its not unheard of for terminals +// to support only one or two buttons (think Macs). Old terminals, and true +// emulations (such as vt100) won't support mice at all, of course. +type ButtonMask int16 + +// These are the actual button values. Note that tcell version 1.x reversed buttons +// two and three on *nix based terminals. We use button 1 as the primary, and +// button 2 as the secondary, and button 3 (which is often missing) as the middle. +const ( + Button1 ButtonMask = 1 << iota // Usually the left (primary) mouse button. + Button2 // Usually the right (secondary) mouse button. + Button3 // Usually the middle mouse button. + Button4 // Often a side button (thumb/next). + Button5 // Often a side button (thumb/prev). + Button6 + Button7 + Button8 + WheelUp // Wheel motion up/away from user. + WheelDown // Wheel motion down/towards user. + WheelLeft // Wheel motion to left. + WheelRight // Wheel motion to right. + ButtonNone ButtonMask = 0 // No button or wheel events. + + ButtonPrimary = Button1 + ButtonSecondary = Button2 + ButtonMiddle = Button3 +) diff --git a/vendor/github.com/gdamore/tcell/v2/nonblock_bsd.go b/vendor/github.com/gdamore/tcell/v2/nonblock_bsd.go new file mode 100644 index 0000000..622888e --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/nonblock_bsd.go @@ -0,0 +1,43 @@ +// Copyright 2021 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build darwin || dragonfly || freebsd || netbsd || openbsd +// +build darwin dragonfly freebsd netbsd openbsd + +package tcell + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +// BSD systems use TIOC style ioctls. + +// tcSetBufParams is used by the tty driver on UNIX systems to configure the +// buffering parameters (minimum character count and minimum wait time in msec.) +// This also waits for output to drain first. +func tcSetBufParams(fd int, vMin uint8, vTime uint8) error { + _ = syscall.SetNonblock(fd, true) + tio, err := unix.IoctlGetTermios(fd, unix.TIOCGETA) + if err != nil { + return err + } + tio.Cc[unix.VMIN] = vMin + tio.Cc[unix.VTIME] = vTime + if err = unix.IoctlSetTermios(fd, unix.TIOCSETAW, tio); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/gdamore/tcell/v2/nonblock_unix.go b/vendor/github.com/gdamore/tcell/v2/nonblock_unix.go new file mode 100644 index 0000000..160a641 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/nonblock_unix.go @@ -0,0 +1,41 @@ +// Copyright 2021 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux || aix || zos || solaris +// +build linux aix zos solaris + +package tcell + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +// tcSetBufParams is used by the tty driver on UNIX systems to configure the +// buffering parameters (minimum character count and minimum wait time in msec.) +// This also waits for output to drain first. +func tcSetBufParams(fd int, vMin uint8, vTime uint8) error { + _ = syscall.SetNonblock(fd, true) + tio, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return err + } + tio.Cc[unix.VMIN] = vMin + tio.Cc[unix.VTIME] = vTime + if err = unix.IoctlSetTermios(fd, unix.TCSETSW, tio); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/gdamore/tcell/v2/paste.go b/vendor/github.com/gdamore/tcell/v2/paste.go new file mode 100644 index 0000000..cbe6979 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/paste.go @@ -0,0 +1,48 @@ +// Copyright 2020 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "time" +) + +// EventPaste is used to mark the start and end of a bracketed paste. +// An event with .Start() true will be sent to mark the start. +// Then a number of keys will be sent to indicate that the content +// is pasted in. At the end, an event with .Start() false will be sent. +type EventPaste struct { + start bool + t time.Time +} + +// When returns the time when this EventPaste was created. +func (ev *EventPaste) When() time.Time { + return ev.t +} + +// Start returns true if this is the start of a paste. +func (ev *EventPaste) Start() bool { + return ev.start +} + +// End returns true if this is the end of a paste. +func (ev *EventPaste) End() bool { + return !ev.start +} + +// NewEventPaste returns a new EventPaste. +func NewEventPaste(start bool) *EventPaste { + return &EventPaste{t: time.Now(), start: start} +} diff --git a/vendor/github.com/gdamore/tcell/v2/resize.go b/vendor/github.com/gdamore/tcell/v2/resize.go new file mode 100644 index 0000000..f3e2b3a --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/resize.go @@ -0,0 +1,66 @@ +// Copyright 2015 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "time" +) + +// EventResize is sent when the window size changes. +type EventResize struct { + t time.Time + ws WindowSize +} + +// NewEventResize creates an EventResize with the new updated window size, +// which is given in character cells. +func NewEventResize(width, height int) *EventResize { + ws := WindowSize{ + Width: width, + Height: height, + } + return &EventResize{t: time.Now(), ws: ws} +} + +// When returns the time when the Event was created. +func (ev *EventResize) When() time.Time { + return ev.t +} + +// Size returns the new window size as width, height in character cells. +func (ev *EventResize) Size() (int, int) { + return ev.ws.Width, ev.ws.Height +} + +// PixelSize returns the new window size as width, height in pixels. The size +// will be 0,0 if the screen doesn't support this feature +func (ev *EventResize) PixelSize() (int, int) { + return ev.ws.PixelWidth, ev.ws.PixelHeight +} + +type WindowSize struct { + Width int + Height int + PixelWidth int + PixelHeight int +} + +// CellDimensions returns the dimensions of a single cell, in pixels +func (ws WindowSize) CellDimensions() (int, int) { + if ws.PixelWidth == 0 || ws.PixelHeight == 0 { + return 0, 0 + } + return (ws.PixelWidth / ws.Width), (ws.PixelHeight / ws.Height) +} diff --git a/vendor/github.com/gdamore/tcell/v2/runes.go b/vendor/github.com/gdamore/tcell/v2/runes.go new file mode 100644 index 0000000..ed9c63b --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/runes.go @@ -0,0 +1,111 @@ +// Copyright 2015 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +// The names of these constants are chosen to match Terminfo names, +// modulo case, and changing the prefix from ACS_ to Rune. These are +// the runes we provide extra special handling for, with ASCII fallbacks +// for terminals that lack them. +const ( + RuneSterling = '£' + RuneDArrow = '↓' + RuneLArrow = '←' + RuneRArrow = '→' + RuneUArrow = '↑' + RuneBullet = '·' + RuneBoard = '░' + RuneCkBoard = '▒' + RuneDegree = '°' + RuneDiamond = '◆' + RuneGEqual = '≥' + RunePi = 'π' + RuneHLine = '─' + RuneLantern = '§' + RunePlus = '┼' + RuneLEqual = '≤' + RuneLLCorner = '└' + RuneLRCorner = '┘' + RuneNEqual = '≠' + RunePlMinus = '±' + RuneS1 = '⎺' + RuneS3 = '⎻' + RuneS7 = '⎼' + RuneS9 = '⎽' + RuneBlock = '█' + RuneTTee = '┬' + RuneRTee = '┤' + RuneLTee = '├' + RuneBTee = '┴' + RuneULCorner = '┌' + RuneURCorner = '┐' + RuneVLine = '│' +) + +// RuneFallbacks is the default map of fallback strings that will be +// used to replace a rune when no other more appropriate transformation +// is available, and the rune cannot be displayed directly. +// +// New entries may be added to this map over time, as it becomes clear +// that such is desirable. Characters that represent either letters or +// numbers should not be added to this list unless it is certain that +// the meaning will still convey unambiguously. +// +// As an example, it would be appropriate to add an ASCII mapping for +// the full width form of the letter 'A', but it would not be appropriate +// to do so a glyph representing the country China. +// +// Programs that desire richer fallbacks may register additional ones, +// or change or even remove these mappings with Screen.RegisterRuneFallback +// Screen.UnregisterRuneFallback methods. +// +// Note that Unicode is presumed to be able to display all glyphs. +// This is a pretty poor assumption, but there is no easy way to +// figure out which glyphs are supported in a given font. Hence, +// some care in selecting the characters you support in your application +// is still appropriate. +var RuneFallbacks = map[rune]string{ + RuneSterling: "f", + RuneDArrow: "v", + RuneLArrow: "<", + RuneRArrow: ">", + RuneUArrow: "^", + RuneBullet: "o", + RuneBoard: "#", + RuneCkBoard: ":", + RuneDegree: "\\", + RuneDiamond: "+", + RuneGEqual: ">", + RunePi: "*", + RuneHLine: "-", + RuneLantern: "#", + RunePlus: "+", + RuneLEqual: "<", + RuneLLCorner: "+", + RuneLRCorner: "+", + RuneNEqual: "!", + RunePlMinus: "#", + RuneS1: "~", + RuneS3: "-", + RuneS7: "-", + RuneS9: "_", + RuneBlock: "#", + RuneTTee: "+", + RuneRTee: "+", + RuneLTee: "+", + RuneBTee: "+", + RuneULCorner: "+", + RuneURCorner: "+", + RuneVLine: "|", +} diff --git a/vendor/github.com/gdamore/tcell/v2/screen.go b/vendor/github.com/gdamore/tcell/v2/screen.go new file mode 100644 index 0000000..6ab27ca --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/screen.go @@ -0,0 +1,466 @@ +// Copyright 2023 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import "sync" + +// Screen represents the physical (or emulated) screen. +// This can be a terminal window or a physical console. Platforms implement +// this differently. +type Screen interface { + // Init initializes the screen for use. + Init() error + + // Fini finalizes the screen also releasing resources. + Fini() + + // Clear logically erases the screen. + // This is effectively a short-cut for Fill(' ', StyleDefault). + Clear() + + // Fill fills the screen with the given character and style. + // The effect of filling the screen is not visible until Show + // is called (or Sync). + Fill(rune, Style) + + // SetCell is an older API, and will be removed. Please use + // SetContent instead; SetCell is implemented in terms of SetContent. + SetCell(x int, y int, style Style, ch ...rune) + + // GetContent returns the contents at the given location. If the + // coordinates are out of range, then the values will be 0, nil, + // StyleDefault. Note that the contents returned are logical contents + // and may not actually be what is displayed, but rather are what will + // be displayed if Show() or Sync() is called. The width is the width + // in screen cells; most often this will be 1, but some East Asian + // characters and emoji require two cells. + GetContent(x, y int) (primary rune, combining []rune, style Style, width int) + + // SetContent sets the contents of the given cell location. If + // the coordinates are out of range, then the operation is ignored. + // + // The first rune is the primary non-zero width rune. The array + // that follows is a possible list of combining characters to append, + // and will usually be nil (no combining characters.) + // + // The results are not displayed until Show() or Sync() is called. + // + // Note that wide (East Asian full width and emoji) runes occupy two cells, + // and attempts to place character at next cell to the right will have + // undefined effects. Wide runes that are printed in the + // last column will be replaced with a single width space on output. + SetContent(x int, y int, primary rune, combining []rune, style Style) + + // SetStyle sets the default style to use when clearing the screen + // or when StyleDefault is specified. If it is also StyleDefault, + // then whatever system/terminal default is relevant will be used. + SetStyle(style Style) + + // ShowCursor is used to display the cursor at a given location. + // If the coordinates -1, -1 are given or are otherwise outside the + // dimensions of the screen, the cursor will be hidden. + ShowCursor(x int, y int) + + // HideCursor is used to hide the cursor. It's an alias for + // ShowCursor(-1, -1).sim + HideCursor() + + // SetCursorStyle is used to set the cursor style. If the style + // is not supported (or cursor styles are not supported at all), + // then this will have no effect. + SetCursorStyle(CursorStyle) + + // Size returns the screen size as width, height. This changes in + // response to a call to Clear or Flush. + Size() (width, height int) + + // ChannelEvents is an infinite loop that waits for an event and + // channels it into the user provided channel ch. Closing the + // quit channel and calling the Fini method are cancellation + // signals. When a cancellation signal is received the method + // returns after closing ch. + // + // This method should be used as a goroutine. + // + // NOTE: PollEvent should not be called while this method is running. + ChannelEvents(ch chan<- Event, quit <-chan struct{}) + + // PollEvent waits for events to arrive. Main application loops + // must spin on this to prevent the application from stalling. + // Furthermore, this will return nil if the Screen is finalized. + PollEvent() Event + + // HasPendingEvent returns true if PollEvent would return an event + // without blocking. If the screen is stopped and PollEvent would + // return nil, then the return value from this function is unspecified. + // The purpose of this function is to allow multiple events to be collected + // at once, to minimize screen redraws. + HasPendingEvent() bool + + // PostEvent tries to post an event into the event stream. This + // can fail if the event queue is full. In that case, the event + // is dropped, and ErrEventQFull is returned. + PostEvent(ev Event) error + + // Deprecated: PostEventWait is unsafe, and will be removed + // in the future. + // + // PostEventWait is like PostEvent, but if the queue is full, it + // blocks until there is space in the queue, making delivery + // reliable. However, it is VERY important that this function + // never be called from within whatever event loop is polling + // with PollEvent(), otherwise a deadlock may arise. + // + // For this reason, when using this function, the use of a + // Goroutine is recommended to ensure no deadlock can occur. + PostEventWait(ev Event) + + // EnableMouse enables the mouse. (If your terminal supports it.) + // If no flags are specified, then all events are reported, if the + // terminal supports them. + EnableMouse(...MouseFlags) + + // DisableMouse disables the mouse. + DisableMouse() + + // EnablePaste enables bracketed paste mode, if supported. + EnablePaste() + + // DisablePaste disables bracketed paste mode. + DisablePaste() + + // EnableFocus enables reporting of focus events, if your terminal supports it. + EnableFocus() + + // DisableFocus disables reporting of focus events. + DisableFocus() + + // HasMouse returns true if the terminal (apparently) supports a + // mouse. Note that the return value of true doesn't guarantee that + // a mouse/pointing device is present; a false return definitely + // indicates no mouse support is available. + HasMouse() bool + + // Colors returns the number of colors. All colors are assumed to + // use the ANSI color map. If a terminal is monochrome, it will + // return 0. + Colors() int + + // Show makes all the content changes made using SetContent() visible + // on the display. + // + // It does so in the most efficient and least visually disruptive + // manner possible. + Show() + + // Sync works like Show(), but it updates every visible cell on the + // physical display, assuming that it is not synchronized with any + // internal model. This may be both expensive and visually jarring, + // so it should only be used when believed to actually be necessary. + // + // Typically, this is called as a result of a user-requested redraw + // (e.g. to clear up on-screen corruption caused by some other program), + // or during a resize event. + Sync() + + // CharacterSet returns information about the character set. + // This isn't the full locale, but it does give us the input/output + // character set. Note that this is just for diagnostic purposes, + // we normally translate input/output to/from UTF-8, regardless of + // what the user's environment is. + CharacterSet() string + + // RegisterRuneFallback adds a fallback for runes that are not + // part of the character set -- for example one could register + // o as a fallback for ø. This should be done cautiously for + // characters that might be displayed ordinarily in language + // specific text -- characters that could change the meaning of + // written text would be dangerous. The intention here is to + // facilitate fallback characters in pseudo-graphical applications. + // + // If the terminal has fallbacks already in place via an alternate + // character set, those are used in preference. Also, standard + // fallbacks for graphical characters in the alternate character set + // terminfo string are registered implicitly. + // + // The display string should be the same width as original rune. + // This makes it possible to register two character replacements + // for full width East Asian characters, for example. + // + // It is recommended that replacement strings consist only of + // 7-bit ASCII, since other characters may not display everywhere. + RegisterRuneFallback(r rune, subst string) + + // UnregisterRuneFallback unmaps a replacement. It will unmap + // the implicit ASCII replacements for alternate characters as well. + // When an unmapped char needs to be displayed, but no suitable + // glyph is available, '?' is emitted instead. It is not possible + // to "disable" the use of alternate characters that are supported + // by your terminal except by changing the terminal database. + UnregisterRuneFallback(r rune) + + // CanDisplay returns true if the given rune can be displayed on + // this screen. Note that this is a best-guess effort -- whether + // your fonts support the character or not may be questionable. + // Mostly this is for folks who work outside of Unicode. + // + // If checkFallbacks is true, then if any (possibly imperfect) + // fallbacks are registered, this will return true. This will + // also return true if the terminal can replace the glyph with + // one that is visually indistinguishable from the one requested. + CanDisplay(r rune, checkFallbacks bool) bool + + // Resize does nothing, since it's generally not possible to + // ask a screen to resize, but it allows the Screen to implement + // the View interface. + Resize(int, int, int, int) + + // HasKey returns true if the keyboard is believed to have the + // key. In some cases a keyboard may have keys with this name + // but no support for them, while in others a key may be reported + // as supported but not actually be usable (such as some emulators + // that hijack certain keys). Its best not to depend to strictly + // on this function, but it can be used for hinting when building + // menus, displayed hot-keys, etc. Note that KeyRune (literal + // runes) is always true. + HasKey(Key) bool + + // Suspend pauses input and output processing. It also restores the + // terminal settings to what they were when the application started. + // This can be used to, for example, run a sub-shell. + Suspend() error + + // Resume resumes after Suspend(). + Resume() error + + // Beep attempts to sound an OS-dependent audible alert and returns an error + // when unsuccessful. + Beep() error + + // SetSize attempts to resize the window. It also invalidates the cells and + // calls the resize function. Note that if the window size is changed, it will + // not be restored upon application exit. + // + // Many terminals cannot support this. Perversely, the "modern" Windows Terminal + // does not support application-initiated resizing, whereas the legacy terminal does. + // Also, some emulators can support this but may have it disabled by default. + SetSize(int, int) + + // LockRegion sets or unsets a lock on a region of cells. A lock on a + // cell prevents the cell from being redrawn. + LockRegion(x, y, width, height int, lock bool) + + // Tty returns the underlying Tty. If the screen is not a terminal, the + // returned bool will be false + Tty() (Tty, bool) +} + +// NewScreen returns a default Screen suitable for the user's terminal +// environment. +func NewScreen() (Screen, error) { + // Windows is happier if we try for a console screen first. + if s, _ := NewConsoleScreen(); s != nil { + return s, nil + } else if s, e := NewTerminfoScreen(); s != nil { + return s, nil + } else { + return nil, e + } +} + +// MouseFlags are options to modify the handling of mouse events. +// Actual events can be ORed together. +type MouseFlags int + +const ( + MouseButtonEvents = MouseFlags(1) // Click events only + MouseDragEvents = MouseFlags(2) // Click-drag events (includes button events) + MouseMotionEvents = MouseFlags(4) // All mouse events (includes click and drag events) +) + +// CursorStyle represents a given cursor style, which can include the shape and +// whether the cursor blinks or is solid. Support for changing this is not universal. +type CursorStyle int + +const ( + CursorStyleDefault = CursorStyle(iota) // The default + CursorStyleBlinkingBlock + CursorStyleSteadyBlock + CursorStyleBlinkingUnderline + CursorStyleSteadyUnderline + CursorStyleBlinkingBar + CursorStyleSteadyBar +) + +// screenImpl is a subset of Screen that can be used with baseScreen to formulate +// a complete implementation of Screen. See Screen for doc comments about methods. +type screenImpl interface { + Init() error + Fini() + SetStyle(style Style) + ShowCursor(x int, y int) + HideCursor() + SetCursorStyle(CursorStyle) + Size() (width, height int) + EnableMouse(...MouseFlags) + DisableMouse() + EnablePaste() + DisablePaste() + EnableFocus() + DisableFocus() + HasMouse() bool + Colors() int + Show() + Sync() + CharacterSet() string + RegisterRuneFallback(r rune, subst string) + UnregisterRuneFallback(r rune) + CanDisplay(r rune, checkFallbacks bool) bool + Resize(int, int, int, int) + HasKey(Key) bool + Suspend() error + Resume() error + Beep() error + SetSize(int, int) + Tty() (Tty, bool) + + // Following methods are not part of the Screen api, but are used for interaction with + // the common layer code. + + // Locker locks the underlying data structures so that we can access them + // in a thread-safe way. + sync.Locker + + // GetCells returns a pointer to the underlying CellBuffer that the implementation uses. + // Various methods will write to these for performance, but will use the lock to do so. + GetCells() *CellBuffer + + // StopQ is closed when the screen is shut down via Fini. It remains open if the screen + // is merely suspended. + StopQ() <-chan struct{} + + // EventQ delivers events. Events are posted to this by the screen in response to + // key presses, resizes, etc. Application code receives events from this via the + // Screen.PollEvent, Screen.ChannelEvents APIs. + EventQ() chan Event +} + +type baseScreen struct { + screenImpl +} + +func (b *baseScreen) SetCell(x int, y int, style Style, ch ...rune) { + if len(ch) > 0 { + b.SetContent(x, y, ch[0], ch[1:], style) + } else { + b.SetContent(x, y, ' ', nil, style) + } +} + +func (b *baseScreen) Clear() { + b.Fill(' ', StyleDefault) +} + +func (b *baseScreen) Fill(r rune, style Style) { + cb := b.GetCells() + b.Lock() + cb.Fill(r, style) + b.Unlock() +} + +func (b *baseScreen) SetContent(x, y int, mainc rune, combc []rune, st Style) { + + cells := b.GetCells() + b.Lock() + cells.SetContent(x, y, mainc, combc, st) + b.Unlock() +} + +func (b *baseScreen) GetContent(x, y int) (rune, []rune, Style, int) { + var primary rune + var combining []rune + var style Style + var width int + cells := b.GetCells() + b.Lock() + primary, combining, style, width = cells.GetContent(x, y) + b.Unlock() + return primary, combining, style, width +} + +func (b *baseScreen) LockRegion(x, y, width, height int, lock bool) { + cells := b.GetCells() + b.Lock() + for j := y; j < (y + height); j += 1 { + for i := x; i < (x + width); i += 1 { + switch lock { + case true: + cells.LockCell(i, j) + case false: + cells.UnlockCell(i, j) + } + } + } + b.Unlock() +} + +func (b *baseScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) { + defer close(ch) + for { + select { + case <-quit: + return + case <-b.StopQ(): + return + case ev := <-b.EventQ(): + select { + case <-quit: + return + case <-b.StopQ(): + return + case ch <- ev: + } + } + } +} + +func (b *baseScreen) PollEvent() Event { + select { + case <-b.StopQ(): + return nil + case ev := <-b.EventQ(): + return ev + } +} + +func (b *baseScreen) HasPendingEvent() bool { + return len(b.EventQ()) > 0 +} + +func (b *baseScreen) PostEventWait(ev Event) { + select { + case b.EventQ() <- ev: + case <-b.StopQ(): + } +} + +func (b *baseScreen) PostEvent(ev Event) error { + select { + case b.EventQ() <- ev: + return nil + default: + return ErrEventQFull + } +} diff --git a/vendor/github.com/gdamore/tcell/v2/simulation.go b/vendor/github.com/gdamore/tcell/v2/simulation.go new file mode 100644 index 0000000..eb08b8f --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/simulation.go @@ -0,0 +1,497 @@ +// Copyright 2023 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "sync" + "unicode/utf8" + + "golang.org/x/text/transform" +) + +// NewSimulationScreen returns a SimulationScreen. Note that +// SimulationScreen is also a Screen. +func NewSimulationScreen(charset string) SimulationScreen { + if charset == "" { + charset = "UTF-8" + } + ss := &simscreen{charset: charset} + ss.Screen = &baseScreen{screenImpl: ss} + return ss +} + +// SimulationScreen represents a screen simulation. This is intended to +// be a superset of normal Screens, but also adds some important interfaces +// for testing. +type SimulationScreen interface { + Screen + + // InjectKeyBytes injects a stream of bytes corresponding to + // the native encoding (see charset). It turns true if the entire + // set of bytes were processed and delivered as KeyEvents, false + // if any bytes were not fully understood. Any bytes that are not + // fully converted are discarded. + InjectKeyBytes(buf []byte) bool + + // InjectKey injects a key event. The rune is a UTF-8 rune, post + // any translation. + InjectKey(key Key, r rune, mod ModMask) + + // InjectMouse injects a mouse event. + InjectMouse(x, y int, buttons ButtonMask, mod ModMask) + + // GetContents returns screen contents as an array of + // cells, along with the physical width & height. Note that the + // physical contents will be used until the next time SetSize() + // is called. + GetContents() (cells []SimCell, width int, height int) + + // GetCursor returns the cursor details. + GetCursor() (x int, y int, visible bool) +} + +// SimCell represents a simulated screen cell. The purpose of this +// is to track on screen content. +type SimCell struct { + // Bytes is the actual character bytes. Normally this is + // rune data, but it could be be data in another encoding system. + Bytes []byte + + // Style is the style used to display the data. + Style Style + + // Runes is the list of runes, unadulterated, in UTF-8. + Runes []rune +} + +type simscreen struct { + physw int + physh int + fini bool + style Style + evch chan Event + quit chan struct{} + + front []SimCell + back CellBuffer + clear bool + cursorx int + cursory int + cursorvis bool + mouse bool + paste bool + charset string + encoder transform.Transformer + decoder transform.Transformer + fillchar rune + fillstyle Style + fallback map[rune]string + + Screen + sync.Mutex +} + +func (s *simscreen) Init() error { + s.evch = make(chan Event, 10) + s.quit = make(chan struct{}) + s.fillchar = 'X' + s.fillstyle = StyleDefault + s.mouse = false + s.physw = 80 + s.physh = 25 + s.cursorx = -1 + s.cursory = -1 + s.style = StyleDefault + + if enc := GetEncoding(s.charset); enc != nil { + s.encoder = enc.NewEncoder() + s.decoder = enc.NewDecoder() + } else { + return ErrNoCharset + } + + s.front = make([]SimCell, s.physw*s.physh) + s.back.Resize(80, 25) + + // default fallbacks + s.fallback = make(map[rune]string) + for k, v := range RuneFallbacks { + s.fallback[k] = v + } + return nil +} + +func (s *simscreen) Fini() { + s.Lock() + s.fini = true + s.back.Resize(0, 0) + s.Unlock() + if s.quit != nil { + close(s.quit) + } + s.physw = 0 + s.physh = 0 + s.front = nil +} + +func (s *simscreen) SetStyle(style Style) { + s.Lock() + s.style = style + s.Unlock() +} + +func (s *simscreen) drawCell(x, y int) int { + + mainc, combc, style, width := s.back.GetContent(x, y) + if !s.back.Dirty(x, y) { + return width + } + if x >= s.physw || y >= s.physh || x < 0 || y < 0 { + return width + } + simc := &s.front[(y*s.physw)+x] + + if style == StyleDefault { + style = s.style + } + simc.Style = style + simc.Runes = append([]rune{mainc}, combc...) + + // now emit runes - taking care to not overrun width with a + // wide character, and to ensure that we emit exactly one regular + // character followed up by any residual combing characters + + simc.Bytes = nil + + if x > s.physw-width { + simc.Runes = []rune{' '} + simc.Bytes = []byte{' '} + return width + } + + lbuf := make([]byte, 12) + ubuf := make([]byte, 12) + nout := 0 + + for _, r := range simc.Runes { + + l := utf8.EncodeRune(ubuf, r) + + nout, _, _ = s.encoder.Transform(lbuf, ubuf[:l], true) + + if nout == 0 || lbuf[0] == '\x1a' { + + // skip combining + + if subst, ok := s.fallback[r]; ok { + simc.Bytes = append(simc.Bytes, + []byte(subst)...) + + } else if r >= ' ' && r <= '~' { + simc.Bytes = append(simc.Bytes, byte(r)) + + } else if simc.Bytes == nil { + simc.Bytes = append(simc.Bytes, '?') + } + } else { + simc.Bytes = append(simc.Bytes, lbuf[:nout]...) + } + } + s.back.SetDirty(x, y, false) + return width +} + +func (s *simscreen) ShowCursor(x, y int) { + s.Lock() + s.cursorx, s.cursory = x, y + s.showCursor() + s.Unlock() +} + +func (s *simscreen) HideCursor() { + s.ShowCursor(-1, -1) +} + +func (s *simscreen) showCursor() { + + x, y := s.cursorx, s.cursory + if x < 0 || y < 0 || x >= s.physw || y >= s.physh { + s.cursorvis = false + } else { + s.cursorvis = true + } +} + +func (s *simscreen) hideCursor() { + // does not update cursor position + s.cursorvis = false +} + +func (s *simscreen) SetCursorStyle(CursorStyle) {} + +func (s *simscreen) Show() { + s.Lock() + s.resize() + s.draw() + s.Unlock() +} + +func (s *simscreen) clearScreen() { + // We emulate a hardware clear by filling with a specific pattern + for i := range s.front { + s.front[i].Style = s.fillstyle + s.front[i].Runes = []rune{s.fillchar} + s.front[i].Bytes = []byte{byte(s.fillchar)} + } + s.clear = false +} + +func (s *simscreen) draw() { + s.hideCursor() + if s.clear { + s.clearScreen() + } + + w, h := s.back.Size() + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + width := s.drawCell(x, y) + x += width - 1 + } + } + s.showCursor() +} + +func (s *simscreen) EnableMouse(...MouseFlags) { + s.mouse = true +} + +func (s *simscreen) DisableMouse() { + s.mouse = false +} + +func (s *simscreen) EnablePaste() { + s.paste = true +} + +func (s *simscreen) DisablePaste() { + s.paste = false +} + +func (s *simscreen) EnableFocus() { +} + +func (s *simscreen) DisableFocus() { +} + +func (s *simscreen) Size() (int, int) { + s.Lock() + w, h := s.back.Size() + s.Unlock() + return w, h +} + +func (s *simscreen) resize() { + w, h := s.physw, s.physh + ow, oh := s.back.Size() + if w != ow || h != oh { + s.back.Resize(w, h) + ev := NewEventResize(w, h) + s.postEvent(ev) + } +} + +func (s *simscreen) Colors() int { + return 256 +} + +func (s *simscreen) postEvent(ev Event) { + select { + case s.evch <- ev: + case <-s.quit: + } +} + +func (s *simscreen) InjectMouse(x, y int, buttons ButtonMask, mod ModMask) { + ev := NewEventMouse(x, y, buttons, mod) + s.postEvent(ev) +} + +func (s *simscreen) InjectKey(key Key, r rune, mod ModMask) { + ev := NewEventKey(key, r, mod) + s.postEvent(ev) +} + +func (s *simscreen) InjectKeyBytes(b []byte) bool { + failed := false + +outer: + for len(b) > 0 { + if b[0] >= ' ' && b[0] <= 0x7F { + // printable ASCII easy to deal with -- no encodings + ev := NewEventKey(KeyRune, rune(b[0]), ModNone) + s.postEvent(ev) + b = b[1:] + continue + } + + if b[0] < 0x80 { + mod := ModNone + // No encodings start with low numbered values + if Key(b[0]) >= KeyCtrlA && Key(b[0]) <= KeyCtrlZ { + mod = ModCtrl + } + ev := NewEventKey(Key(b[0]), 0, mod) + s.postEvent(ev) + b = b[1:] + continue + } + + utfb := make([]byte, len(b)*4) // worst case + for l := 1; l < len(b); l++ { + s.decoder.Reset() + nout, nin, _ := s.decoder.Transform(utfb, b[:l], true) + + if nout != 0 { + r, _ := utf8.DecodeRune(utfb[:nout]) + if r != utf8.RuneError { + ev := NewEventKey(KeyRune, r, ModNone) + s.postEvent(ev) + } + b = b[nin:] + continue outer + } + } + failed = true + b = b[1:] + continue + } + + return !failed +} + +func (s *simscreen) Sync() { + s.Lock() + s.clear = true + s.resize() + s.back.Invalidate() + s.draw() + s.Unlock() +} + +func (s *simscreen) CharacterSet() string { + return s.charset +} + +func (s *simscreen) SetSize(w, h int) { + s.Lock() + newc := make([]SimCell, w*h) + for row := 0; row < h && row < s.physh; row++ { + for col := 0; col < w && col < s.physw; col++ { + newc[(row*w)+col] = s.front[(row*s.physw)+col] + } + } + s.cursorx, s.cursory = -1, -1 + s.physw, s.physh = w, h + s.front = newc + s.back.Resize(w, h) + s.Unlock() +} + +func (s *simscreen) GetContents() ([]SimCell, int, int) { + s.Lock() + cells, w, h := s.front, s.physw, s.physh + s.Unlock() + return cells, w, h +} + +func (s *simscreen) GetCursor() (int, int, bool) { + s.Lock() + x, y, vis := s.cursorx, s.cursory, s.cursorvis + s.Unlock() + return x, y, vis +} + +func (s *simscreen) RegisterRuneFallback(r rune, subst string) { + s.Lock() + s.fallback[r] = subst + s.Unlock() +} + +func (s *simscreen) UnregisterRuneFallback(r rune) { + s.Lock() + delete(s.fallback, r) + s.Unlock() +} + +func (s *simscreen) CanDisplay(r rune, checkFallbacks bool) bool { + + if enc := s.encoder; enc != nil { + nb := make([]byte, 6) + ob := make([]byte, 6) + num := utf8.EncodeRune(ob, r) + + enc.Reset() + dst, _, err := enc.Transform(nb, ob[:num], true) + if dst != 0 && err == nil && nb[0] != '\x1A' { + return true + } + } + if !checkFallbacks { + return false + } + if _, ok := s.fallback[r]; ok { + return true + } + return false +} + +func (s *simscreen) HasMouse() bool { + return false +} + +func (s *simscreen) Resize(int, int, int, int) {} + +func (s *simscreen) HasKey(Key) bool { + return true +} + +func (s *simscreen) Beep() error { + return nil +} + +func (s *simscreen) Suspend() error { + return nil +} + +func (s *simscreen) Resume() error { + return nil +} + +func (s *simscreen) Tty() (Tty, bool) { + return nil, false +} + +func (s *simscreen) GetCells() *CellBuffer { + return &s.back +} + +func (s *simscreen) EventQ() chan Event { + return s.evch +} + +func (s *simscreen) StopQ() <-chan struct{} { + return s.quit +} diff --git a/vendor/github.com/gdamore/tcell/v2/stdin_unix.go b/vendor/github.com/gdamore/tcell/v2/stdin_unix.go new file mode 100644 index 0000000..b478b89 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/stdin_unix.go @@ -0,0 +1,186 @@ +// Copyright 2021 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos + +package tcell + +import ( + "errors" + "fmt" + "os" + "os/signal" + "strconv" + "sync" + "syscall" + "time" + + "golang.org/x/sys/unix" + "golang.org/x/term" +) + +// stdIoTty is an implementation of the Tty API based upon stdin/stdout. +type stdIoTty struct { + fd int + in *os.File + out *os.File + saved *term.State + sig chan os.Signal + cb func() + stopQ chan struct{} + dev string + wg sync.WaitGroup + l sync.Mutex +} + +func (tty *stdIoTty) Read(b []byte) (int, error) { + return tty.in.Read(b) +} + +func (tty *stdIoTty) Write(b []byte) (int, error) { + return tty.out.Write(b) +} + +func (tty *stdIoTty) Close() error { + return nil +} + +func (tty *stdIoTty) Start() error { + tty.l.Lock() + defer tty.l.Unlock() + + // We open another copy of /dev/tty. This is a workaround for unusual behavior + // observed in macOS, apparently caused when a sub-shell (for example) closes our + // own tty device (when it exits for example). Getting a fresh new one seems to + // resolve the problem. (We believe this is a bug in the macOS tty driver that + // fails to account for dup() references to the same file before applying close() + // related behaviors to the tty.) We're also holding the original copy we opened + // since closing that might have deleterious effects as well. The upshot is that + // we will have up to two separate file handles open on /dev/tty. (Note that when + // using stdin/stdout instead of /dev/tty this problem is not observed.) + var err error + tty.in = os.Stdin + tty.out = os.Stdout + tty.fd = int(tty.in.Fd()) + + if !term.IsTerminal(tty.fd) { + return errors.New("device is not a terminal") + } + + _ = tty.in.SetReadDeadline(time.Time{}) + saved, err := term.MakeRaw(tty.fd) // also sets vMin and vTime + if err != nil { + return err + } + tty.saved = saved + + tty.stopQ = make(chan struct{}) + tty.wg.Add(1) + go func(stopQ chan struct{}) { + defer tty.wg.Done() + for { + select { + case <-tty.sig: + tty.l.Lock() + cb := tty.cb + tty.l.Unlock() + if cb != nil { + cb() + } + case <-stopQ: + return + } + } + }(tty.stopQ) + + signal.Notify(tty.sig, syscall.SIGWINCH) + return nil +} + +func (tty *stdIoTty) Drain() error { + _ = tty.in.SetReadDeadline(time.Now()) + if err := tcSetBufParams(tty.fd, 0, 0); err != nil { + return err + } + return nil +} + +func (tty *stdIoTty) Stop() error { + tty.l.Lock() + if err := term.Restore(tty.fd, tty.saved); err != nil { + tty.l.Unlock() + return err + } + _ = tty.in.SetReadDeadline(time.Now()) + + signal.Stop(tty.sig) + close(tty.stopQ) + tty.l.Unlock() + + tty.wg.Wait() + + return nil +} + +func (tty *stdIoTty) WindowSize() (WindowSize, error) { + size := WindowSize{} + ws, err := unix.IoctlGetWinsize(tty.fd, unix.TIOCGWINSZ) + if err != nil { + return size, err + } + w := int(ws.Col) + h := int(ws.Row) + if w == 0 { + w, _ = strconv.Atoi(os.Getenv("COLUMNS")) + } + if w == 0 { + w = 80 // default + } + if h == 0 { + h, _ = strconv.Atoi(os.Getenv("LINES")) + } + if h == 0 { + h = 25 // default + } + size.Width = w + size.Height = h + size.PixelWidth = int(ws.Xpixel) + size.PixelHeight = int(ws.Ypixel) + return size, nil +} + +func (tty *stdIoTty) NotifyResize(cb func()) { + tty.l.Lock() + tty.cb = cb + tty.l.Unlock() +} + +// NewStdioTty opens a tty using standard input/output. +func NewStdIoTty() (Tty, error) { + tty := &stdIoTty{ + sig: make(chan os.Signal), + in: os.Stdin, + out: os.Stdout, + } + var err error + tty.fd = int(tty.in.Fd()) + if !term.IsTerminal(tty.fd) { + return nil, errors.New("not a terminal") + } + if tty.saved, err = term.GetState(tty.fd); err != nil { + return nil, fmt.Errorf("failed to get state: %w", err) + } + return tty, nil +} diff --git a/vendor/github.com/gdamore/tcell/v2/style.go b/vendor/github.com/gdamore/tcell/v2/style.go new file mode 100644 index 0000000..98354c8 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/style.go @@ -0,0 +1,176 @@ +// Copyright 2022 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +// Style represents a complete text style, including both foreground color, +// background color, and additional attributes such as "bold" or "underline". +// +// Note that not all terminals can display all colors or attributes, and +// many might have specific incompatibilities between specific attributes +// and color combinations. +// +// To use Style, just declare a variable of its type. +type Style struct { + fg Color + bg Color + attrs AttrMask + url string + urlId string +} + +// StyleDefault represents a default style, based upon the context. +// It is the zero value. +var StyleDefault Style + +// styleInvalid is just an arbitrary invalid style used internally. +var styleInvalid = Style{attrs: AttrInvalid} + +// Foreground returns a new style based on s, with the foreground color set +// as requested. ColorDefault can be used to select the global default. +func (s Style) Foreground(c Color) Style { + return Style{ + fg: c, + bg: s.bg, + attrs: s.attrs, + url: s.url, + urlId: s.urlId, + } +} + +// Background returns a new style based on s, with the background color set +// as requested. ColorDefault can be used to select the global default. +func (s Style) Background(c Color) Style { + return Style{ + fg: s.fg, + bg: c, + attrs: s.attrs, + url: s.url, + urlId: s.urlId, + } +} + +// Decompose breaks a style up, returning the foreground, background, +// and other attributes. The URL if set is not included. +func (s Style) Decompose() (fg Color, bg Color, attr AttrMask) { + return s.fg, s.bg, s.attrs +} + +func (s Style) setAttrs(attrs AttrMask, on bool) Style { + if on { + return Style{ + fg: s.fg, + bg: s.bg, + attrs: s.attrs | attrs, + url: s.url, + urlId: s.urlId, + } + } + return Style{ + fg: s.fg, + bg: s.bg, + attrs: s.attrs &^ attrs, + url: s.url, + urlId: s.urlId, + } +} + +// Normal returns the style with all attributes disabled. +func (s Style) Normal() Style { + return Style{ + fg: s.fg, + bg: s.bg, + } +} + +// Bold returns a new style based on s, with the bold attribute set +// as requested. +func (s Style) Bold(on bool) Style { + return s.setAttrs(AttrBold, on) +} + +// Blink returns a new style based on s, with the blink attribute set +// as requested. +func (s Style) Blink(on bool) Style { + return s.setAttrs(AttrBlink, on) +} + +// Dim returns a new style based on s, with the dim attribute set +// as requested. +func (s Style) Dim(on bool) Style { + return s.setAttrs(AttrDim, on) +} + +// Italic returns a new style based on s, with the italic attribute set +// as requested. +func (s Style) Italic(on bool) Style { + return s.setAttrs(AttrItalic, on) +} + +// Reverse returns a new style based on s, with the reverse attribute set +// as requested. (Reverse usually changes the foreground and background +// colors.) +func (s Style) Reverse(on bool) Style { + return s.setAttrs(AttrReverse, on) +} + +// Underline returns a new style based on s, with the underline attribute set +// as requested. +func (s Style) Underline(on bool) Style { + return s.setAttrs(AttrUnderline, on) +} + +// StrikeThrough sets strikethrough mode. +func (s Style) StrikeThrough(on bool) Style { + return s.setAttrs(AttrStrikeThrough, on) +} + +// Attributes returns a new style based on s, with its attributes set as +// specified. +func (s Style) Attributes(attrs AttrMask) Style { + return Style{ + fg: s.fg, + bg: s.bg, + attrs: attrs, + url: s.url, + urlId: s.urlId, + } +} + +// Url returns a style with the Url set. If the provided Url is not empty, +// and the terminal supports it, text will typically be marked up as a clickable +// link to that Url. If the Url is empty, then this mode is turned off. +func (s Style) Url(url string) Style { + return Style{ + fg: s.fg, + bg: s.bg, + attrs: s.attrs, + url: url, + urlId: s.urlId, + } +} + +// UrlId returns a style with the UrlId set. If the provided UrlId is not empty, +// any marked up Url with this style will be given the UrlId also. If the +// terminal supports it, any text with the same UrlId will be grouped as if it +// were one Url, even if it spans multiple lines. +func (s Style) UrlId(id string) Style { + return Style{ + fg: s.fg, + bg: s.bg, + attrs: s.attrs, + url: s.url, + urlId: "id=" + id, + } +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/.gitignore b/vendor/github.com/gdamore/tcell/v2/terminfo/.gitignore new file mode 100644 index 0000000..74f3c04 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/.gitignore @@ -0,0 +1 @@ +mkinfo diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/README.md b/vendor/github.com/gdamore/tcell/v2/terminfo/README.md new file mode 100644 index 0000000..20ae937 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/README.md @@ -0,0 +1,25 @@ +This package represents the parent for all terminals. + +In older versions of tcell we had (a couple of) different +external file formats for the terminal database. Those are +now removed. All terminal definitions are supplied by +one of two methods: + +1. Compiled Go code + +2. For systems with terminfo and infocmp, dynamically + generated at runtime. + +The Go code can be generated using the mkinfo utility in +this directory. The database entry should be generated +into a package in a directory named as the first character +of the package name. (This permits us to group them all +without having a huge directory of little packages.) + +It may be desirable to add new packages to the extended +package, or -- rarely -- the base package. + +Applications which want to have the large set of terminal +descriptions built into the binary can simply import the +extended package. Otherwise a smaller reasonable default +set (the base package) will be included instead. diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/TERMINALS.md b/vendor/github.com/gdamore/tcell/v2/terminfo/TERMINALS.md new file mode 100644 index 0000000..85c1e61 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/TERMINALS.md @@ -0,0 +1,7 @@ +TERMINALS +========= + +The best way to populate terminals on Debian is to install ncurses, +ncurses-term, screen, tmux, rxvt-unicode, and dvtm. This populates the +the terminfo database so that we can have a reasonable set of starting +terminals. diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/a/aixterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/aixterm/term.go new file mode 100644 index 0000000..503c919 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/a/aixterm/term.go @@ -0,0 +1,83 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package aixterm + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // IBM Aixterm Terminal Emulator + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "aixterm", + Columns: 80, + Lines: 25, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + AttrOff: "\x1b[0;10m\x1b(B", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Reverse: "\x1b[7m", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[32m\x1b[40m", + PadChar: "\x00", + AltChars: "jjkkllmmnnqqttuuvvwwxx", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[139q", + KeyDelete: "\x1b[P", + KeyBackspace: "\b", + KeyHome: "\x1b[H", + KeyEnd: "\x1b[146q", + KeyPgUp: "\x1b[150q", + KeyPgDn: "\x1b[154q", + KeyF1: "\x1b[001q", + KeyF2: "\x1b[002q", + KeyF3: "\x1b[003q", + KeyF4: "\x1b[004q", + KeyF5: "\x1b[005q", + KeyF6: "\x1b[006q", + KeyF7: "\x1b[007q", + KeyF8: "\x1b[008q", + KeyF9: "\x1b[009q", + KeyF10: "\x1b[010q", + KeyF11: "\x1b[011q", + KeyF12: "\x1b[012q", + KeyF13: "\x1b[013q", + KeyF14: "\x1b[014q", + KeyF15: "\x1b[015q", + KeyF16: "\x1b[016q", + KeyF17: "\x1b[017q", + KeyF18: "\x1b[018q", + KeyF19: "\x1b[019q", + KeyF20: "\x1b[020q", + KeyF21: "\x1b[021q", + KeyF22: "\x1b[022q", + KeyF23: "\x1b[023q", + KeyF24: "\x1b[024q", + KeyF25: "\x1b[025q", + KeyF26: "\x1b[026q", + KeyF27: "\x1b[027q", + KeyF28: "\x1b[028q", + KeyF29: "\x1b[029q", + KeyF30: "\x1b[030q", + KeyF31: "\x1b[031q", + KeyF32: "\x1b[032q", + KeyF33: "\x1b[033q", + KeyF34: "\x1b[034q", + KeyF35: "\x1b[035q", + KeyF36: "\x1b[036q", + KeyClear: "\x1b[144q", + KeyBacktab: "\x1b[Z", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/direct.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/direct.go new file mode 100644 index 0000000..db6351a --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/direct.go @@ -0,0 +1,69 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package alacritty + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // alacritty with direct color indexing + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "alacritty-direct", + Columns: 80, + Lines: 24, + Colors: 16777216, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h\x1b[22;0;0t", + ExitCA: "\x1b[?1049l\x1b[23;0;0t", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b(B\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + TrueColor: true, + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/term.go new file mode 100644 index 0000000..0101363 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/term.go @@ -0,0 +1,71 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package alacritty + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // alacritty terminal emulator + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "alacritty", + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h\x1b[22;0;0t", + ExitCA: "\x1b[?1049l\x1b[23;0;0t", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b(B\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[<", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/a/ansi/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/ansi/term.go new file mode 100644 index 0000000..5c572fd --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/a/ansi/term.go @@ -0,0 +1,43 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package ansi + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // ansi/pc-term compatible with color + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "ansi", + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + AttrOff: "\x1b[0;10m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe", + EnterAcs: "\x1b[11m", + ExitAcs: "\x1b[10m", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\x1b[D", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[L", + KeyBackspace: "\b", + KeyHome: "\x1b[H", + KeyBacktab: "\x1b[Z", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/b/beterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/b/beterm/term.go new file mode 100644 index 0000000..e6d8883 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/b/beterm/term.go @@ -0,0 +1,57 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package beterm + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // BeOS Terminal + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "beterm", + Columns: 80, + Lines: 25, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + AttrOff: "\x1b[0;10m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?4h", + ExitKeypad: "\x1b[?4l", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[m", + PadChar: "\x00", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\b", + KeyHome: "\x1b[1~", + KeyEnd: "\x1b[4~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1b[11~", + KeyF2: "\x1b[12~", + KeyF3: "\x1b[13~", + KeyF4: "\x1b[14~", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[16~", + KeyF7: "\x1b[17~", + KeyF8: "\x1b[18~", + KeyF9: "\x1b[19~", + KeyF10: "\x1b[20~", + KeyF11: "\x1b[21~", + KeyF12: "\x1b[22~", + AutoMargin: true, + InsertChar: "\x1b[@", + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/base/base.go b/vendor/github.com/gdamore/tcell/v2/terminfo/base/base.go new file mode 100644 index 0000000..fbecdfa --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/base/base.go @@ -0,0 +1,32 @@ +// Copyright 2020 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This is just a "minimalist" set of the base terminal descriptions. +// It should be sufficient for most applications. + +// Package base contains the base terminal descriptions that are likely +// to be needed by any stock application. It is imported by default in the +// terminfo package, so terminal types listed here will be available to any +// tcell application. +package base + +import ( + // The following imports just register themselves -- + // thse are the terminal types we aggregate in this package. + _ "github.com/gdamore/tcell/v2/terminfo/a/ansi" + _ "github.com/gdamore/tcell/v2/terminfo/v/vt100" + _ "github.com/gdamore/tcell/v2/terminfo/v/vt102" + _ "github.com/gdamore/tcell/v2/terminfo/v/vt220" + _ "github.com/gdamore/tcell/v2/terminfo/x/xterm" +) diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/c/cygwin/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/c/cygwin/term.go new file mode 100644 index 0000000..46a0a4a --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/c/cygwin/term.go @@ -0,0 +1,66 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package cygwin + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // ANSI emulation for Cygwin + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "cygwin", + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + AttrOff: "\x1b[0;10m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Reverse: "\x1b[7m", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe", + EnterAcs: "\x1b[11m", + ExitAcs: "\x1b[10m", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\b", + KeyHome: "\x1b[1~", + KeyEnd: "\x1b[4~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1b[[A", + KeyF2: "\x1b[[B", + KeyF3: "\x1b[[C", + KeyF4: "\x1b[[D", + KeyF5: "\x1b[[E", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF15: "\x1b[28~", + KeyF16: "\x1b[29~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + AutoMargin: true, + InsertChar: "\x1b[@", + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/d/dtterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/d/dtterm/term.go new file mode 100644 index 0000000..90a5fed --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/d/dtterm/term.go @@ -0,0 +1,71 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package dtterm + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // CDE desktop terminal + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "dtterm", + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b(B\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\b", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1b[11~", + KeyF2: "\x1b[12~", + KeyF3: "\x1b[13~", + KeyF4: "\x1b[14~", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF15: "\x1b[28~", + KeyF16: "\x1b[29~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + KeyHelp: "\x1b[28~", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/dynamic/dynamic.go b/vendor/github.com/gdamore/tcell/v2/terminfo/dynamic/dynamic.go new file mode 100644 index 0000000..047ebde --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/dynamic/dynamic.go @@ -0,0 +1,423 @@ +// Copyright 2021 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The dynamic package is used to generate a terminal description dynamically, +// using infocmp. This is really a method of last resort, as the performance +// will be slow, and it requires a working infocmp. But, the hope is that it +// will assist folks who have to deal with a terminal description that isn't +// already built in. This requires infocmp to be in the user's path, and to +// support reasonably the -1 option. + +package dynamic + +import ( + "bytes" + "errors" + "os/exec" + "regexp" + "strconv" + "strings" + + "github.com/gdamore/tcell/v2/terminfo" +) + +type termcap struct { + name string + desc string + aliases []string + bools map[string]bool + nums map[string]int + strs map[string]string +} + +func (tc *termcap) getnum(s string) int { + return (tc.nums[s]) +} + +func (tc *termcap) getflag(s string) bool { + return (tc.bools[s]) +} + +func (tc *termcap) getstr(s string) string { + return (tc.strs[s]) +} + +const ( + none = iota + control + escaped +) + +var errNotAddressable = errors.New("terminal not cursor addressable") + +func unescape(s string) string { + // Various escapes are in \x format. Control codes are + // encoded as ^M (carat followed by ASCII equivalent). + // escapes are: \e, \E - escape + // \0 NULL, \n \l \r \t \b \f \s for equivalent C escape. + buf := &bytes.Buffer{} + esc := none + + for i := 0; i < len(s); i++ { + c := s[i] + switch esc { + case none: + switch c { + case '\\': + esc = escaped + case '^': + esc = control + default: + buf.WriteByte(c) + } + case control: + buf.WriteByte(c ^ 1<<6) + esc = none + case escaped: + switch c { + case 'E', 'e': + buf.WriteByte(0x1b) + case '0', '1', '2', '3', '4', '5', '6', '7': + if i+2 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' && s[i+2] >= '0' && s[i+2] <= '7' { + buf.WriteByte(((c - '0') * 64) + ((s[i+1] - '0') * 8) + (s[i+2] - '0')) + i = i + 2 + } else if c == '0' { + buf.WriteByte(0) + } + case 'n': + buf.WriteByte('\n') + case 'r': + buf.WriteByte('\r') + case 't': + buf.WriteByte('\t') + case 'b': + buf.WriteByte('\b') + case 'f': + buf.WriteByte('\f') + case 's': + buf.WriteByte(' ') + default: + buf.WriteByte(c) + } + esc = none + } + } + return (buf.String()) +} + +func (tc *termcap) setupterm(name string) error { + cmd := exec.Command("infocmp", "-1", name) + output := &bytes.Buffer{} + cmd.Stdout = output + + tc.strs = make(map[string]string) + tc.bools = make(map[string]bool) + tc.nums = make(map[string]int) + + if err := cmd.Run(); err != nil { + return err + } + + // Now parse the output. + // We get comment lines (starting with "#"), followed by + // a header line that looks like "||...|" + // then capabilities, one per line, starting with a tab and ending + // with a comma and newline. + lines := strings.Split(output.String(), "\n") + for len(lines) > 0 && strings.HasPrefix(lines[0], "#") { + lines = lines[1:] + } + + // Ditch trailing empty last line + if lines[len(lines)-1] == "" { + lines = lines[:len(lines)-1] + } + header := lines[0] + if strings.HasSuffix(header, ",") { + header = header[:len(header)-1] + } + names := strings.Split(header, "|") + tc.name = names[0] + names = names[1:] + if len(names) > 0 { + tc.desc = names[len(names)-1] + names = names[:len(names)-1] + } + tc.aliases = names + for _, val := range lines[1:] { + if (!strings.HasPrefix(val, "\t")) || + (!strings.HasSuffix(val, ",")) { + return (errors.New("malformed infocmp: " + val)) + } + + val = val[1:] + val = val[:len(val)-1] + + if k := strings.SplitN(val, "=", 2); len(k) == 2 { + tc.strs[k[0]] = unescape(k[1]) + } else if k := strings.SplitN(val, "#", 2); len(k) == 2 { + u, err := strconv.ParseUint(k[1], 0, 0) + if err != nil { + return (err) + } + tc.nums[k[0]] = int(u) + } else { + tc.bools[val] = true + } + } + return nil +} + +// LoadTerminfo creates a Terminfo by for named terminal by attempting to parse +// the output from infocmp. This returns the terminfo entry, a description of +// the terminal, and either nil or an error. +func LoadTerminfo(name string) (*terminfo.Terminfo, string, error) { + var tc termcap + if err := tc.setupterm(name); err != nil { + return nil, "", err + } + t := &terminfo.Terminfo{} + t.Name = tc.name + t.Aliases = tc.aliases + t.Colors = tc.getnum("colors") + t.Columns = tc.getnum("cols") + t.Lines = tc.getnum("lines") + t.Bell = tc.getstr("bel") + t.Clear = tc.getstr("clear") + t.EnterCA = tc.getstr("smcup") + t.ExitCA = tc.getstr("rmcup") + t.ShowCursor = tc.getstr("cnorm") + t.HideCursor = tc.getstr("civis") + t.AttrOff = tc.getstr("sgr0") + t.Underline = tc.getstr("smul") + t.Bold = tc.getstr("bold") + t.Blink = tc.getstr("blink") + t.Dim = tc.getstr("dim") + t.Italic = tc.getstr("sitm") + t.Reverse = tc.getstr("rev") + t.EnterKeypad = tc.getstr("smkx") + t.ExitKeypad = tc.getstr("rmkx") + t.SetFg = tc.getstr("setaf") + t.SetBg = tc.getstr("setab") + t.SetCursor = tc.getstr("cup") + t.CursorBack1 = tc.getstr("cub1") + t.CursorUp1 = tc.getstr("cuu1") + t.KeyF1 = tc.getstr("kf1") + t.KeyF2 = tc.getstr("kf2") + t.KeyF3 = tc.getstr("kf3") + t.KeyF4 = tc.getstr("kf4") + t.KeyF5 = tc.getstr("kf5") + t.KeyF6 = tc.getstr("kf6") + t.KeyF7 = tc.getstr("kf7") + t.KeyF8 = tc.getstr("kf8") + t.KeyF9 = tc.getstr("kf9") + t.KeyF10 = tc.getstr("kf10") + t.KeyF11 = tc.getstr("kf11") + t.KeyF12 = tc.getstr("kf12") + t.KeyF13 = tc.getstr("kf13") + t.KeyF14 = tc.getstr("kf14") + t.KeyF15 = tc.getstr("kf15") + t.KeyF16 = tc.getstr("kf16") + t.KeyF17 = tc.getstr("kf17") + t.KeyF18 = tc.getstr("kf18") + t.KeyF19 = tc.getstr("kf19") + t.KeyF20 = tc.getstr("kf20") + t.KeyF21 = tc.getstr("kf21") + t.KeyF22 = tc.getstr("kf22") + t.KeyF23 = tc.getstr("kf23") + t.KeyF24 = tc.getstr("kf24") + t.KeyF25 = tc.getstr("kf25") + t.KeyF26 = tc.getstr("kf26") + t.KeyF27 = tc.getstr("kf27") + t.KeyF28 = tc.getstr("kf28") + t.KeyF29 = tc.getstr("kf29") + t.KeyF30 = tc.getstr("kf30") + t.KeyF31 = tc.getstr("kf31") + t.KeyF32 = tc.getstr("kf32") + t.KeyF33 = tc.getstr("kf33") + t.KeyF34 = tc.getstr("kf34") + t.KeyF35 = tc.getstr("kf35") + t.KeyF36 = tc.getstr("kf36") + t.KeyF37 = tc.getstr("kf37") + t.KeyF38 = tc.getstr("kf38") + t.KeyF39 = tc.getstr("kf39") + t.KeyF40 = tc.getstr("kf40") + t.KeyF41 = tc.getstr("kf41") + t.KeyF42 = tc.getstr("kf42") + t.KeyF43 = tc.getstr("kf43") + t.KeyF44 = tc.getstr("kf44") + t.KeyF45 = tc.getstr("kf45") + t.KeyF46 = tc.getstr("kf46") + t.KeyF47 = tc.getstr("kf47") + t.KeyF48 = tc.getstr("kf48") + t.KeyF49 = tc.getstr("kf49") + t.KeyF50 = tc.getstr("kf50") + t.KeyF51 = tc.getstr("kf51") + t.KeyF52 = tc.getstr("kf52") + t.KeyF53 = tc.getstr("kf53") + t.KeyF54 = tc.getstr("kf54") + t.KeyF55 = tc.getstr("kf55") + t.KeyF56 = tc.getstr("kf56") + t.KeyF57 = tc.getstr("kf57") + t.KeyF58 = tc.getstr("kf58") + t.KeyF59 = tc.getstr("kf59") + t.KeyF60 = tc.getstr("kf60") + t.KeyF61 = tc.getstr("kf61") + t.KeyF62 = tc.getstr("kf62") + t.KeyF63 = tc.getstr("kf63") + t.KeyF64 = tc.getstr("kf64") + t.KeyInsert = tc.getstr("kich1") + t.KeyDelete = tc.getstr("kdch1") + t.KeyBackspace = tc.getstr("kbs") + t.KeyHome = tc.getstr("khome") + t.KeyEnd = tc.getstr("kend") + t.KeyUp = tc.getstr("kcuu1") + t.KeyDown = tc.getstr("kcud1") + t.KeyRight = tc.getstr("kcuf1") + t.KeyLeft = tc.getstr("kcub1") + t.KeyPgDn = tc.getstr("knp") + t.KeyPgUp = tc.getstr("kpp") + t.KeyBacktab = tc.getstr("kcbt") + t.KeyExit = tc.getstr("kext") + t.KeyCancel = tc.getstr("kcan") + t.KeyPrint = tc.getstr("kprt") + t.KeyHelp = tc.getstr("khlp") + t.KeyClear = tc.getstr("kclr") + t.AltChars = tc.getstr("acsc") + t.EnterAcs = tc.getstr("smacs") + t.ExitAcs = tc.getstr("rmacs") + t.EnableAcs = tc.getstr("enacs") + t.Mouse = tc.getstr("kmous") + t.KeyShfRight = tc.getstr("kRIT") + t.KeyShfLeft = tc.getstr("kLFT") + t.KeyShfHome = tc.getstr("kHOM") + t.KeyShfEnd = tc.getstr("kEND") + + // Terminfo lacks descriptions for a bunch of modified keys, + // but modern XTerm and emulators often have them. Let's add them, + // if the shifted right and left arrows are defined. + if t.KeyShfRight == "\x1b[1;2C" && t.KeyShfLeft == "\x1b[1;2D" { + t.Modifiers = terminfo.ModifiersXTerm + + t.KeyShfUp = "\x1b[1;2A" + t.KeyShfDown = "\x1b[1;2B" + t.KeyMetaUp = "\x1b[1;9A" + t.KeyMetaDown = "\x1b[1;9B" + t.KeyMetaRight = "\x1b[1;9C" + t.KeyMetaLeft = "\x1b[1;9D" + t.KeyAltUp = "\x1b[1;3A" + t.KeyAltDown = "\x1b[1;3B" + t.KeyAltRight = "\x1b[1;3C" + t.KeyAltLeft = "\x1b[1;3D" + t.KeyCtrlUp = "\x1b[1;5A" + t.KeyCtrlDown = "\x1b[1;5B" + t.KeyCtrlRight = "\x1b[1;5C" + t.KeyCtrlLeft = "\x1b[1;5D" + t.KeyAltShfUp = "\x1b[1;4A" + t.KeyAltShfDown = "\x1b[1;4B" + t.KeyAltShfRight = "\x1b[1;4C" + t.KeyAltShfLeft = "\x1b[1;4D" + + t.KeyMetaShfUp = "\x1b[1;10A" + t.KeyMetaShfDown = "\x1b[1;10B" + t.KeyMetaShfRight = "\x1b[1;10C" + t.KeyMetaShfLeft = "\x1b[1;10D" + + t.KeyCtrlShfUp = "\x1b[1;6A" + t.KeyCtrlShfDown = "\x1b[1;6B" + t.KeyCtrlShfRight = "\x1b[1;6C" + t.KeyCtrlShfLeft = "\x1b[1;6D" + + t.KeyShfPgUp = "\x1b[5;2~" + t.KeyShfPgDn = "\x1b[6;2~" + } + // And also for Home and End + if t.KeyShfHome == "\x1b[1;2H" && t.KeyShfEnd == "\x1b[1;2F" { + t.KeyCtrlHome = "\x1b[1;5H" + t.KeyCtrlEnd = "\x1b[1;5F" + t.KeyAltHome = "\x1b[1;9H" + t.KeyAltEnd = "\x1b[1;9F" + t.KeyCtrlShfHome = "\x1b[1;6H" + t.KeyCtrlShfEnd = "\x1b[1;6F" + t.KeyAltShfHome = "\x1b[1;4H" + t.KeyAltShfEnd = "\x1b[1;4F" + t.KeyMetaShfHome = "\x1b[1;10H" + t.KeyMetaShfEnd = "\x1b[1;10F" + } + + // And the same thing for rxvt and workalikes (Eterm, aterm, etc.) + // It seems that urxvt at least send escaped as ALT prefix for these, + // although some places seem to indicate a separate ALT key sesquence. + if t.KeyShfRight == "\x1b[c" && t.KeyShfLeft == "\x1b[d" { + t.KeyShfUp = "\x1b[a" + t.KeyShfDown = "\x1b[b" + t.KeyCtrlUp = "\x1b[Oa" + t.KeyCtrlDown = "\x1b[Ob" + t.KeyCtrlRight = "\x1b[Oc" + t.KeyCtrlLeft = "\x1b[Od" + } + if t.KeyShfHome == "\x1b[7$" && t.KeyShfEnd == "\x1b[8$" { + t.KeyCtrlHome = "\x1b[7^" + t.KeyCtrlEnd = "\x1b[8^" + } + + // Technically the RGB flag that is provided for xterm-direct is not + // quite right. The problem is that the -direct flag that was introduced + // with ncurses 6.1 requires a parsing for the parameters that we lack. + // For this case we'll just assume it's XTerm compatible. Someday this + // may be incorrect, but right now it is correct, and nobody uses it + // anyway. + if tc.getflag("Tc") { + // This presumes XTerm 24-bit true color. + t.TrueColor = true + } else if tc.getflag("RGB") { + // This is for xterm-direct, which uses a different scheme entirely. + // (ncurses went a very different direction from everyone else, and + // so it's unlikely anything is using this definition.) + t.TrueColor = true + t.SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m" + t.SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m" + } + + // We only support colors in ANSI 8 or 256 color mode. + if t.Colors < 8 || t.SetFg == "" { + t.Colors = 0 + } + if t.SetCursor == "" { + return nil, "", errNotAddressable + } + + // For padding, we lookup the pad char. If that isn't present, + // and npc is *not* set, then we assume a null byte. + t.PadChar = tc.getstr("pad") + if t.PadChar == "" { + if !tc.getflag("npc") { + t.PadChar = "\u0000" + } + } + + // For terminals that use "standard" SGR sequences, lets combine the + // foreground and background together. + if strings.HasPrefix(t.SetFg, "\x1b[") && + strings.HasPrefix(t.SetBg, "\x1b[") && + strings.HasSuffix(t.SetFg, "m") && + strings.HasSuffix(t.SetBg, "m") { + fg := t.SetFg[:len(t.SetFg)-1] + r := regexp.MustCompile("%p1") + bg := r.ReplaceAllString(t.SetBg[2:], "%p2") + t.SetFgBg = fg + ";" + bg + } + + return t, tc.desc, nil +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/e/emacs/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/e/emacs/term.go new file mode 100644 index 0000000..f6d078d --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/e/emacs/term.go @@ -0,0 +1,65 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package emacs + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // GNU Emacs term.el terminal emulation + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "eterm", + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + AttrOff: "\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Reverse: "\x1b[7m", + PadChar: "\x00", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + AutoMargin: true, + }) + + // Emacs term.el terminal emulator term-protocol-version 0.96 + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "eterm-color", + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + AttrOff: "\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + SetFg: "\x1b[%p1%{30}%+%dm", + SetBg: "\x1b[%p1%'('%+%dm", + SetFgBg: "\x1b[%p1%{30}%+%d;%p2%'('%+%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[1~", + KeyEnd: "\x1b[4~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go b/vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go new file mode 100644 index 0000000..7459cf3 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go @@ -0,0 +1,56 @@ +// Copyright 2024 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package extended contains an extended set of terminal descriptions. +// Applications desiring to have a better chance of Just Working by +// default should include this package. This will significantly increase +// the size of the program. +package extended + +import ( + // The following imports just register themselves -- + // these are the terminal types we aggregate in this package. + _ "github.com/gdamore/tcell/v2/terminfo/a/aixterm" + _ "github.com/gdamore/tcell/v2/terminfo/a/alacritty" + _ "github.com/gdamore/tcell/v2/terminfo/a/ansi" + _ "github.com/gdamore/tcell/v2/terminfo/b/beterm" + _ "github.com/gdamore/tcell/v2/terminfo/c/cygwin" + _ "github.com/gdamore/tcell/v2/terminfo/d/dtterm" + _ "github.com/gdamore/tcell/v2/terminfo/e/emacs" + _ "github.com/gdamore/tcell/v2/terminfo/f/foot" + _ "github.com/gdamore/tcell/v2/terminfo/g/gnome" + _ "github.com/gdamore/tcell/v2/terminfo/h/hpterm" + _ "github.com/gdamore/tcell/v2/terminfo/k/konsole" + _ "github.com/gdamore/tcell/v2/terminfo/k/kterm" + _ "github.com/gdamore/tcell/v2/terminfo/l/linux" + _ "github.com/gdamore/tcell/v2/terminfo/p/pcansi" + _ "github.com/gdamore/tcell/v2/terminfo/r/rxvt" + _ "github.com/gdamore/tcell/v2/terminfo/s/screen" + _ "github.com/gdamore/tcell/v2/terminfo/s/simpleterm" + _ "github.com/gdamore/tcell/v2/terminfo/s/sun" + _ "github.com/gdamore/tcell/v2/terminfo/t/tmux" + _ "github.com/gdamore/tcell/v2/terminfo/v/vt100" + _ "github.com/gdamore/tcell/v2/terminfo/v/vt102" + _ "github.com/gdamore/tcell/v2/terminfo/v/vt220" + _ "github.com/gdamore/tcell/v2/terminfo/v/vt320" + _ "github.com/gdamore/tcell/v2/terminfo/v/vt400" + _ "github.com/gdamore/tcell/v2/terminfo/v/vt420" + _ "github.com/gdamore/tcell/v2/terminfo/v/vt52" + _ "github.com/gdamore/tcell/v2/terminfo/w/wy50" + _ "github.com/gdamore/tcell/v2/terminfo/w/wy60" + _ "github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi" + _ "github.com/gdamore/tcell/v2/terminfo/x/xfce" + _ "github.com/gdamore/tcell/v2/terminfo/x/xterm" + _ "github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty" +) diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/f/foot/foot.go b/vendor/github.com/gdamore/tcell/v2/terminfo/f/foot/foot.go new file mode 100644 index 0000000..5daa3c8 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/f/foot/foot.go @@ -0,0 +1,70 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package foot + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // foot terminal emulator + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "foot", + Aliases: []string{"foot-extra"}, + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h\x1b[22;0;0t", + ExitCA: "\x1b[?1049l\x1b[23;0;0t", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b(B\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48:5:%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48:5:%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\u007f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/g/gnome/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/g/gnome/term.go new file mode 100644 index 0000000..a7af10c --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/g/gnome/term.go @@ -0,0 +1,134 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package gnome + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // GNOME Terminal + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "gnome", + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[0m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) + + // GNOME Terminal with xterm 256-colors + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "gnome-256color", + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[0m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/gen.sh b/vendor/github.com/gdamore/tcell/v2/terminfo/gen.sh new file mode 100644 index 0000000..851175a --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/gen.sh @@ -0,0 +1,19 @@ +#!/bin/bash +while read line +do + case "$line" in + *'|'*) + alias=${line#*|} + line=${line%|*} + ;; + *) + alias=${line%%,*} + ;; + esac + + alias=${alias//-/_} + direc=${alias:0:1} + + mkdir -p ${direc}/${alias} + go run mkinfo.go -P ${alias} -go ${direc}/${alias}/term.go ${line//,/ } +done < models.txt diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/h/hpterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/h/hpterm/term.go new file mode 100644 index 0000000..56a0fb7 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/h/hpterm/term.go @@ -0,0 +1,51 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package hpterm + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // HP X11 terminal emulator (old) + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "hpterm", + Aliases: []string{"X-hpterm"}, + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b&a0y0C\x1bJ", + AttrOff: "\x1b&d@\x0f", + Underline: "\x1b&dD", + Bold: "\x1b&dB", + Dim: "\x1b&dH", + Reverse: "\x1b&dB", + EnterKeypad: "\x1b&s1A", + ExitKeypad: "\x1b&s0A", + PadChar: "\x00", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + SetCursor: "\x1b&a%p1%dy%p2%dC", + CursorBack1: "\b", + CursorUp1: "\x1bA", + KeyUp: "\x1bA", + KeyDown: "\x1bB", + KeyRight: "\x1bC", + KeyLeft: "\x1bD", + KeyInsert: "\x1bQ", + KeyDelete: "\x1bP", + KeyBackspace: "\b", + KeyHome: "\x1bh", + KeyPgUp: "\x1bV", + KeyPgDn: "\x1bU", + KeyF1: "\x1bp", + KeyF2: "\x1bq", + KeyF3: "\x1br", + KeyF4: "\x1bs", + KeyF5: "\x1bt", + KeyF6: "\x1bu", + KeyF7: "\x1bv", + KeyF8: "\x1bw", + KeyClear: "\x1bJ", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/k/konsole/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/k/konsole/term.go new file mode 100644 index 0000000..c32de96 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/k/konsole/term.go @@ -0,0 +1,136 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package konsole + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // KDE console window + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "konsole", + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[0m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[<", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) + + // KDE console window with xterm 256-colors + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "konsole-256color", + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[0m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[<", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/k/kterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/k/kterm/term.go new file mode 100644 index 0000000..3430680 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/k/kterm/term.go @@ -0,0 +1,70 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package kterm + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // kterm kanji terminal emulator (X window system) + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "kterm", + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + AttrOff: "\x1b[m\x1b(B", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "``aajjkkllmmnnooppqqrrssttuuvvwwxx~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1b[11~", + KeyF2: "\x1b[12~", + KeyF3: "\x1b[13~", + KeyF4: "\x1b[14~", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF15: "\x1b[28~", + KeyF16: "\x1b[29~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/l/linux/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/l/linux/term.go new file mode 100644 index 0000000..8975bb3 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/l/linux/term.go @@ -0,0 +1,73 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package linux + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // Linux console + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "linux", + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + ShowCursor: "\x1b[?25h\x1b[?0c", + HideCursor: "\x1b[?25l\x1b[?1c", + AttrOff: "\x1b[m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[1~", + KeyEnd: "\x1b[4~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1b[[A", + KeyF2: "\x1b[[B", + KeyF3: "\x1b[[C", + KeyF4: "\x1b[[D", + KeyF5: "\x1b[[E", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF15: "\x1b[28~", + KeyF16: "\x1b[29~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + KeyBacktab: "\x1b\t", + AutoMargin: true, + InsertChar: "\x1b[@", + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/models.txt b/vendor/github.com/gdamore/tcell/v2/terminfo/models.txt new file mode 100644 index 0000000..feea5e2 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/models.txt @@ -0,0 +1,30 @@ +aixterm +alacritty +ansi +beterm +cygwin +dtterm +eterm,eterm-color|emacs +gnome,gnome-256color +hpterm +konsole,konsole-256color +kterm +linux +pcansi +rxvt,rxvt-256color,rxvt-88color,rxvt-unicode,rxvt-unicode-256color +screen,screen-256color +st,st-256color|simpleterm +tmux +vt52 +vt100 +vt102 +vt220 +vt320 +vt400 +vt420 +wy50 +wy60 +wy99-ansi,wy99a-ansi +xfce +xterm,xterm-88color,xterm-256color +xterm-kitty diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/p/pcansi/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/p/pcansi/term.go new file mode 100644 index 0000000..aadc871 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/p/pcansi/term.go @@ -0,0 +1,41 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package pcansi + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // ibm-pc terminal programs claiming to be ANSI + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "pcansi", + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + AttrOff: "\x1b[0;10m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[37;40m", + PadChar: "\x00", + AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe", + EnterAcs: "\x1b[12m", + ExitAcs: "\x1b[10m", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\x1b[D", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyBackspace: "\b", + KeyHome: "\x1b[H", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/r/rxvt/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/r/rxvt/term.go new file mode 100644 index 0000000..94169e7 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/r/rxvt/term.go @@ -0,0 +1,490 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package rxvt + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // rxvt terminal emulator (X Window System) + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "rxvt", + Aliases: []string{"rxvt-color"}, + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b=", + ExitKeypad: "\x1b>", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b(B\x1b)0", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[7~", + KeyEnd: "\x1b[8~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1b[11~", + KeyF2: "\x1b[12~", + KeyF3: "\x1b[13~", + KeyF4: "\x1b[14~", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF15: "\x1b[28~", + KeyF16: "\x1b[29~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + KeyF21: "\x1b[23$", + KeyF22: "\x1b[24$", + KeyF23: "\x1b[11^", + KeyF24: "\x1b[12^", + KeyF25: "\x1b[13^", + KeyF26: "\x1b[14^", + KeyF27: "\x1b[15^", + KeyF28: "\x1b[17^", + KeyF29: "\x1b[18^", + KeyF30: "\x1b[19^", + KeyF31: "\x1b[20^", + KeyF32: "\x1b[21^", + KeyF33: "\x1b[23^", + KeyF34: "\x1b[24^", + KeyF35: "\x1b[25^", + KeyF36: "\x1b[26^", + KeyF37: "\x1b[28^", + KeyF38: "\x1b[29^", + KeyF39: "\x1b[31^", + KeyF40: "\x1b[32^", + KeyF41: "\x1b[33^", + KeyF42: "\x1b[34^", + KeyF43: "\x1b[23@", + KeyF44: "\x1b[24@", + KeyBacktab: "\x1b[Z", + KeyShfLeft: "\x1b[d", + KeyShfRight: "\x1b[c", + KeyShfUp: "\x1b[a", + KeyShfDown: "\x1b[b", + KeyShfHome: "\x1b[7$", + KeyShfEnd: "\x1b[8$", + KeyShfInsert: "\x1b[2$", + KeyShfDelete: "\x1b[3$", + KeyCtrlUp: "\x1b[Oa", + KeyCtrlDown: "\x1b[Ob", + KeyCtrlRight: "\x1b[Oc", + KeyCtrlLeft: "\x1b[Od", + KeyCtrlHome: "\x1b[7^", + KeyCtrlEnd: "\x1b[8^", + AutoMargin: true, + }) + + // rxvt 2.7.9 with xterm 256-colors + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "rxvt-256color", + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b=", + ExitKeypad: "\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b(B\x1b)0", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[7~", + KeyEnd: "\x1b[8~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1b[11~", + KeyF2: "\x1b[12~", + KeyF3: "\x1b[13~", + KeyF4: "\x1b[14~", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF15: "\x1b[28~", + KeyF16: "\x1b[29~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + KeyF21: "\x1b[23$", + KeyF22: "\x1b[24$", + KeyF23: "\x1b[11^", + KeyF24: "\x1b[12^", + KeyF25: "\x1b[13^", + KeyF26: "\x1b[14^", + KeyF27: "\x1b[15^", + KeyF28: "\x1b[17^", + KeyF29: "\x1b[18^", + KeyF30: "\x1b[19^", + KeyF31: "\x1b[20^", + KeyF32: "\x1b[21^", + KeyF33: "\x1b[23^", + KeyF34: "\x1b[24^", + KeyF35: "\x1b[25^", + KeyF36: "\x1b[26^", + KeyF37: "\x1b[28^", + KeyF38: "\x1b[29^", + KeyF39: "\x1b[31^", + KeyF40: "\x1b[32^", + KeyF41: "\x1b[33^", + KeyF42: "\x1b[34^", + KeyF43: "\x1b[23@", + KeyF44: "\x1b[24@", + KeyBacktab: "\x1b[Z", + KeyShfLeft: "\x1b[d", + KeyShfRight: "\x1b[c", + KeyShfUp: "\x1b[a", + KeyShfDown: "\x1b[b", + KeyShfHome: "\x1b[7$", + KeyShfEnd: "\x1b[8$", + KeyShfInsert: "\x1b[2$", + KeyShfDelete: "\x1b[3$", + KeyCtrlUp: "\x1b[Oa", + KeyCtrlDown: "\x1b[Ob", + KeyCtrlRight: "\x1b[Oc", + KeyCtrlLeft: "\x1b[Od", + KeyCtrlHome: "\x1b[7^", + KeyCtrlEnd: "\x1b[8^", + AutoMargin: true, + }) + + // rxvt 2.7.9 with xterm 88-colors + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "rxvt-88color", + Columns: 80, + Lines: 24, + Colors: 88, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b=", + ExitKeypad: "\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b(B\x1b)0", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[7~", + KeyEnd: "\x1b[8~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1b[11~", + KeyF2: "\x1b[12~", + KeyF3: "\x1b[13~", + KeyF4: "\x1b[14~", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF15: "\x1b[28~", + KeyF16: "\x1b[29~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + KeyF21: "\x1b[23$", + KeyF22: "\x1b[24$", + KeyF23: "\x1b[11^", + KeyF24: "\x1b[12^", + KeyF25: "\x1b[13^", + KeyF26: "\x1b[14^", + KeyF27: "\x1b[15^", + KeyF28: "\x1b[17^", + KeyF29: "\x1b[18^", + KeyF30: "\x1b[19^", + KeyF31: "\x1b[20^", + KeyF32: "\x1b[21^", + KeyF33: "\x1b[23^", + KeyF34: "\x1b[24^", + KeyF35: "\x1b[25^", + KeyF36: "\x1b[26^", + KeyF37: "\x1b[28^", + KeyF38: "\x1b[29^", + KeyF39: "\x1b[31^", + KeyF40: "\x1b[32^", + KeyF41: "\x1b[33^", + KeyF42: "\x1b[34^", + KeyF43: "\x1b[23@", + KeyF44: "\x1b[24@", + KeyBacktab: "\x1b[Z", + KeyShfLeft: "\x1b[d", + KeyShfRight: "\x1b[c", + KeyShfUp: "\x1b[a", + KeyShfDown: "\x1b[b", + KeyShfHome: "\x1b[7$", + KeyShfEnd: "\x1b[8$", + KeyShfInsert: "\x1b[2$", + KeyShfDelete: "\x1b[3$", + KeyCtrlUp: "\x1b[Oa", + KeyCtrlDown: "\x1b[Ob", + KeyCtrlRight: "\x1b[Oc", + KeyCtrlLeft: "\x1b[Od", + KeyCtrlHome: "\x1b[7^", + KeyCtrlEnd: "\x1b[8^", + AutoMargin: true, + }) + + // rxvt-unicode terminal (X Window System) + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "rxvt-unicode", + Columns: 80, + Lines: 24, + Colors: 88, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h", + ExitCA: "\x1b[r\x1b[?1049l", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x1b(B", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b=", + ExitKeypad: "\x1b>", + SetFg: "\x1b[38;5;%p1%dm", + SetBg: "\x1b[48;5;%p1%dm", + SetFgBg: "\x1b[38;5;%p1%d;48;5;%p2%dm", + ResetFgBg: "\x1b[39;49m", + AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[7~", + KeyEnd: "\x1b[8~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1b[11~", + KeyF2: "\x1b[12~", + KeyF3: "\x1b[13~", + KeyF4: "\x1b[14~", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF15: "\x1b[28~", + KeyF16: "\x1b[29~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + KeyBacktab: "\x1b[Z", + KeyShfLeft: "\x1b[d", + KeyShfRight: "\x1b[c", + KeyShfUp: "\x1b[a", + KeyShfDown: "\x1b[b", + KeyShfHome: "\x1b[7$", + KeyShfEnd: "\x1b[8$", + KeyShfInsert: "\x1b[2$", + KeyShfDelete: "\x1b[3$", + KeyCtrlUp: "\x1b[Oa", + KeyCtrlDown: "\x1b[Ob", + KeyCtrlRight: "\x1b[Oc", + KeyCtrlLeft: "\x1b[Od", + KeyCtrlHome: "\x1b[7^", + KeyCtrlEnd: "\x1b[8^", + AutoMargin: true, + InsertChar: "\x1b[@", + }) + + // rxvt-unicode terminal with 256 colors (X Window System) + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "rxvt-unicode-256color", + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h", + ExitCA: "\x1b[r\x1b[?1049l", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x1b(B", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b=", + ExitKeypad: "\x1b>", + SetFg: "\x1b[38;5;%p1%dm", + SetBg: "\x1b[48;5;%p1%dm", + SetFgBg: "\x1b[38;5;%p1%d;48;5;%p2%dm", + ResetFgBg: "\x1b[39;49m", + AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[7~", + KeyEnd: "\x1b[8~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1b[11~", + KeyF2: "\x1b[12~", + KeyF3: "\x1b[13~", + KeyF4: "\x1b[14~", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF15: "\x1b[28~", + KeyF16: "\x1b[29~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + KeyBacktab: "\x1b[Z", + KeyShfLeft: "\x1b[d", + KeyShfRight: "\x1b[c", + KeyShfUp: "\x1b[a", + KeyShfDown: "\x1b[b", + KeyShfHome: "\x1b[7$", + KeyShfEnd: "\x1b[8$", + KeyShfInsert: "\x1b[2$", + KeyShfDelete: "\x1b[3$", + KeyCtrlUp: "\x1b[Oa", + KeyCtrlDown: "\x1b[Ob", + KeyCtrlRight: "\x1b[Oc", + KeyCtrlLeft: "\x1b[Od", + KeyCtrlHome: "\x1b[7^", + KeyCtrlEnd: "\x1b[8^", + AutoMargin: true, + InsertChar: "\x1b[@", + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/s/screen/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/s/screen/term.go new file mode 100644 index 0000000..b859529 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/s/screen/term.go @@ -0,0 +1,128 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package screen + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // VT 100/ANSI X3.64 virtual terminal + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "screen", + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + EnterCA: "\x1b[?1049h", + ExitCA: "\x1b[?1049l", + ShowCursor: "\x1b[34h\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b(B\x1b)0", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1bM", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[1~", + KeyEnd: "\x1b[4~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + AutoMargin: true, + }) + + // GNU Screen with 256 colors + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "screen-256color", + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + EnterCA: "\x1b[?1049h", + ExitCA: "\x1b[?1049l", + ShowCursor: "\x1b[34h\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b(B\x1b)0", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1bM", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[1~", + KeyEnd: "\x1b[4~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/s/simpleterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/s/simpleterm/term.go new file mode 100644 index 0000000..e14b265 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/s/simpleterm/term.go @@ -0,0 +1,134 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package simpleterm + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // aka simpleterm + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "st", + Aliases: []string{"stterm"}, + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h", + ExitCA: "\x1b[?1049l", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[0m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAcs: "\x1b)0", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[1~", + KeyEnd: "\x1b[4~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyClear: "\x1b[3;5~", + Modifiers: 1, + AutoMargin: true, + }) + + // simpleterm with 256 colors + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "st-256color", + Aliases: []string{"stterm-256color"}, + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h", + ExitCA: "\x1b[?1049l", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[0m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAcs: "\x1b)0", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[1~", + KeyEnd: "\x1b[4~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyClear: "\x1b[3;5~", + Modifiers: 1, + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/s/sun/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/s/sun/term.go new file mode 100644 index 0000000..16cb96c --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/s/sun/term.go @@ -0,0 +1,112 @@ +// Copyright 2021 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This terminal definition is hand-coded, as the default terminfo for +// this terminal is busted with respect to color. Unlike pretty much every +// other ANSI compliant terminal, this terminal cannot combine foreground and +// background escapes. The default terminfo also only provides escapes for +// 16-bit color. + +package sun + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // Sun Microsystems Inc. workstation console + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "sun", + Aliases: []string{"sun1", "sun2"}, + Columns: 80, + Lines: 34, + Bell: "\a", + Clear: "\f", + AttrOff: "\x1b[m", + Reverse: "\x1b[7m", + PadChar: "\x00", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[247z", + KeyDelete: "\u007f", + KeyBackspace: "\b", + KeyHome: "\x1b[214z", + KeyEnd: "\x1b[220z", + KeyPgUp: "\x1b[216z", + KeyPgDn: "\x1b[222z", + KeyF1: "\x1b[224z", + KeyF2: "\x1b[225z", + KeyF3: "\x1b[226z", + KeyF4: "\x1b[227z", + KeyF5: "\x1b[228z", + KeyF6: "\x1b[229z", + KeyF7: "\x1b[230z", + KeyF8: "\x1b[231z", + KeyF9: "\x1b[232z", + KeyF10: "\x1b[233z", + KeyF11: "\x1b[234z", + KeyF12: "\x1b[235z", + AutoMargin: true, + InsertChar: "\x1b[@", + }) + + // Sun Microsystems Workstation console with color support (IA systems) + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "sun-color", + Columns: 80, + Lines: 34, + Colors: 256, + Bell: "\a", + Clear: "\f", + AttrOff: "\x1b[m", + Bold: "\x1b[1m", + Reverse: "\x1b[7m", + SetFg: "\x1b[38;5;%p1%dm", + SetBg: "\x1b[48;5;%p1%dm", + ResetFgBg: "\x1b[0m", + PadChar: "\x00", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[247z", + KeyDelete: "\u007f", + KeyBackspace: "\b", + KeyHome: "\x1b[214z", + KeyEnd: "\x1b[220z", + KeyPgUp: "\x1b[216z", + KeyPgDn: "\x1b[222z", + KeyF1: "\x1b[224z", + KeyF2: "\x1b[225z", + KeyF3: "\x1b[226z", + KeyF4: "\x1b[227z", + KeyF5: "\x1b[228z", + KeyF6: "\x1b[229z", + KeyF7: "\x1b[230z", + KeyF8: "\x1b[231z", + KeyF9: "\x1b[232z", + KeyF10: "\x1b[233z", + KeyF11: "\x1b[234z", + KeyF12: "\x1b[235z", + AutoMargin: true, + InsertChar: "\x1b[@", + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/t/tmux/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/t/tmux/term.go new file mode 100644 index 0000000..5ecac38 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/t/tmux/term.go @@ -0,0 +1,71 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package tmux + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // tmux terminal multiplexer + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "tmux", + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + EnterCA: "\x1b[?1049h", + ExitCA: "\x1b[?1049l", + ShowCursor: "\x1b[34h\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b(B\x1b)0", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1bM", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[1~", + KeyEnd: "\x1b[4~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/terminfo.go b/vendor/github.com/gdamore/tcell/v2/terminfo/terminfo.go new file mode 100644 index 0000000..34c0eef --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/terminfo.go @@ -0,0 +1,769 @@ +// Copyright 2024 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package terminfo + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "strconv" + "strings" + "sync" + "time" +) + +var ( + // ErrTermNotFound indicates that a suitable terminal entry could + // not be found. This can result from either not having TERM set, + // or from the TERM failing to support certain minimal functionality, + // in particular absolute cursor addressability (the cup capability) + // is required. For example, legacy "adm3" lacks this capability, + // whereas the slightly newer "adm3a" supports it. This failure + // occurs most often with "dumb". + ErrTermNotFound = errors.New("terminal entry not found") +) + +// Terminfo represents a terminfo entry. Note that we use friendly names +// in Go, but when we write out JSON, we use the same names as terminfo. +// The name, aliases and smous, rmous fields do not come from terminfo directly. +type Terminfo struct { + Name string + Aliases []string + Columns int // cols + Lines int // lines + Colors int // colors + Bell string // bell + Clear string // clear + EnterCA string // smcup + ExitCA string // rmcup + ShowCursor string // cnorm + HideCursor string // civis + AttrOff string // sgr0 + Underline string // smul + Bold string // bold + Blink string // blink + Reverse string // rev + Dim string // dim + Italic string // sitm + EnterKeypad string // smkx + ExitKeypad string // rmkx + SetFg string // setaf + SetBg string // setab + ResetFgBg string // op + SetCursor string // cup + CursorBack1 string // cub1 + CursorUp1 string // cuu1 + PadChar string // pad + KeyBackspace string // kbs + KeyF1 string // kf1 + KeyF2 string // kf2 + KeyF3 string // kf3 + KeyF4 string // kf4 + KeyF5 string // kf5 + KeyF6 string // kf6 + KeyF7 string // kf7 + KeyF8 string // kf8 + KeyF9 string // kf9 + KeyF10 string // kf10 + KeyF11 string // kf11 + KeyF12 string // kf12 + KeyF13 string // kf13 + KeyF14 string // kf14 + KeyF15 string // kf15 + KeyF16 string // kf16 + KeyF17 string // kf17 + KeyF18 string // kf18 + KeyF19 string // kf19 + KeyF20 string // kf20 + KeyF21 string // kf21 + KeyF22 string // kf22 + KeyF23 string // kf23 + KeyF24 string // kf24 + KeyF25 string // kf25 + KeyF26 string // kf26 + KeyF27 string // kf27 + KeyF28 string // kf28 + KeyF29 string // kf29 + KeyF30 string // kf30 + KeyF31 string // kf31 + KeyF32 string // kf32 + KeyF33 string // kf33 + KeyF34 string // kf34 + KeyF35 string // kf35 + KeyF36 string // kf36 + KeyF37 string // kf37 + KeyF38 string // kf38 + KeyF39 string // kf39 + KeyF40 string // kf40 + KeyF41 string // kf41 + KeyF42 string // kf42 + KeyF43 string // kf43 + KeyF44 string // kf44 + KeyF45 string // kf45 + KeyF46 string // kf46 + KeyF47 string // kf47 + KeyF48 string // kf48 + KeyF49 string // kf49 + KeyF50 string // kf50 + KeyF51 string // kf51 + KeyF52 string // kf52 + KeyF53 string // kf53 + KeyF54 string // kf54 + KeyF55 string // kf55 + KeyF56 string // kf56 + KeyF57 string // kf57 + KeyF58 string // kf58 + KeyF59 string // kf59 + KeyF60 string // kf60 + KeyF61 string // kf61 + KeyF62 string // kf62 + KeyF63 string // kf63 + KeyF64 string // kf64 + KeyInsert string // kich1 + KeyDelete string // kdch1 + KeyHome string // khome + KeyEnd string // kend + KeyHelp string // khlp + KeyPgUp string // kpp + KeyPgDn string // knp + KeyUp string // kcuu1 + KeyDown string // kcud1 + KeyLeft string // kcub1 + KeyRight string // kcuf1 + KeyBacktab string // kcbt + KeyExit string // kext + KeyClear string // kclr + KeyPrint string // kprt + KeyCancel string // kcan + Mouse string // kmous + AltChars string // acsc + EnterAcs string // smacs + ExitAcs string // rmacs + EnableAcs string // enacs + KeyShfRight string // kRIT + KeyShfLeft string // kLFT + KeyShfHome string // kHOM + KeyShfEnd string // kEND + KeyShfInsert string // kIC + KeyShfDelete string // kDC + + // These are non-standard extensions to terminfo. This includes + // true color support, and some additional keys. Its kind of bizarre + // that shifted variants of left and right exist, but not up and down. + // Terminal support for these are going to vary amongst XTerm + // emulations, so don't depend too much on them in your application. + + StrikeThrough string // smxx + SetFgBg string // setfgbg + SetFgBgRGB string // setfgbgrgb + SetFgRGB string // setfrgb + SetBgRGB string // setbrgb + KeyShfUp string // shift-up + KeyShfDown string // shift-down + KeyShfPgUp string // shift-kpp + KeyShfPgDn string // shift-knp + KeyCtrlUp string // ctrl-up + KeyCtrlDown string // ctrl-left + KeyCtrlRight string // ctrl-right + KeyCtrlLeft string // ctrl-left + KeyMetaUp string // meta-up + KeyMetaDown string // meta-left + KeyMetaRight string // meta-right + KeyMetaLeft string // meta-left + KeyAltUp string // alt-up + KeyAltDown string // alt-left + KeyAltRight string // alt-right + KeyAltLeft string // alt-left + KeyCtrlHome string + KeyCtrlEnd string + KeyMetaHome string + KeyMetaEnd string + KeyAltHome string + KeyAltEnd string + KeyAltShfUp string + KeyAltShfDown string + KeyAltShfLeft string + KeyAltShfRight string + KeyMetaShfUp string + KeyMetaShfDown string + KeyMetaShfLeft string + KeyMetaShfRight string + KeyCtrlShfUp string + KeyCtrlShfDown string + KeyCtrlShfLeft string + KeyCtrlShfRight string + KeyCtrlShfHome string + KeyCtrlShfEnd string + KeyAltShfHome string + KeyAltShfEnd string + KeyMetaShfHome string + KeyMetaShfEnd string + EnablePaste string // bracketed paste mode + DisablePaste string + PasteStart string + PasteEnd string + Modifiers int + InsertChar string // string to insert a character (ich1) + AutoMargin bool // true if writing to last cell in line advances + TrueColor bool // true if the terminal supports direct color + CursorDefault string + CursorBlinkingBlock string + CursorSteadyBlock string + CursorBlinkingUnderline string + CursorSteadyUnderline string + CursorBlinkingBar string + CursorSteadyBar string + EnterUrl string + ExitUrl string + SetWindowSize string + EnableFocusReporting string + DisableFocusReporting string + DisableAutoMargin string // smam + EnableAutoMargin string // rmam +} + +const ( + ModifiersNone = 0 + ModifiersXTerm = 1 +) + +type stack []interface{} + +func (st stack) Push(v interface{}) stack { + if b, ok := v.(bool); ok { + if b { + return append(st, 1) + } else { + return append(st, 0) + } + } + return append(st, v) +} + +func (st stack) PopString() (string, stack) { + if len(st) > 0 { + e := st[len(st)-1] + var s string + switch v := e.(type) { + case int: + s = strconv.Itoa(v) + case string: + s = v + } + return s, st[:len(st)-1] + } + return "", st + +} +func (st stack) PopInt() (int, stack) { + if len(st) > 0 { + e := st[len(st)-1] + var i int + switch v := e.(type) { + case int: + i = v + case string: + i, _ = strconv.Atoi(v) + } + return i, st[:len(st)-1] + } + return 0, st +} + +// static vars +var svars [26]string + +type paramsBuffer struct { + out bytes.Buffer + buf bytes.Buffer +} + +// Start initializes the params buffer with the initial string data. +// It also locks the paramsBuffer. The caller must call End() when +// finished. +func (pb *paramsBuffer) Start(s string) { + pb.out.Reset() + pb.buf.Reset() + pb.buf.WriteString(s) +} + +// End returns the final output from TParam, but it also releases the lock. +func (pb *paramsBuffer) End() string { + s := pb.out.String() + return s +} + +// NextCh returns the next input character to the expander. +func (pb *paramsBuffer) NextCh() (byte, error) { + return pb.buf.ReadByte() +} + +// PutCh "emits" (rather schedules for output) a single byte character. +func (pb *paramsBuffer) PutCh(ch byte) { + pb.out.WriteByte(ch) +} + +// PutString schedules a string for output. +func (pb *paramsBuffer) PutString(s string) { + pb.out.WriteString(s) +} + +// TParm takes a terminfo parameterized string, such as setaf or cup, and +// evaluates the string, and returns the result with the parameter +// applied. +func (t *Terminfo) TParm(s string, p ...interface{}) string { + var stk stack + var a string + var ai, bi int + var dvars [26]string + var params [9]interface{} + var pb = ¶msBuffer{} + + pb.Start(s) + + // make sure we always have 9 parameters -- makes it easier + // later to skip checks + for i := 0; i < len(params) && i < len(p); i++ { + params[i] = p[i] + } + + const ( + emit = iota + toEnd + toElse + ) + + skip := emit + + for { + + ch, err := pb.NextCh() + if err != nil { + break + } + + if ch != '%' { + if skip == emit { + pb.PutCh(ch) + } + continue + } + + ch, err = pb.NextCh() + if err != nil { + // XXX Error + break + } + if skip == toEnd { + if ch == ';' { + skip = emit + } + continue + } else if skip == toElse { + if ch == 'e' || ch == ';' { + skip = emit + } + continue + } + + switch ch { + case '%': // quoted % + pb.PutCh(ch) + + case 'i': // increment both parameters (ANSI cup support) + if i, ok := params[0].(int); ok { + params[0] = i + 1 + } + if i, ok := params[1].(int); ok { + params[1] = i + 1 + } + + case 's': + // NB: 's', 'c', and 'd' below are special cased for + // efficiency. They could be handled by the richer + // format support below, less efficiently. + a, stk = stk.PopString() + pb.PutString(a) + + case 'c': + // Integer as special character. + ai, stk = stk.PopInt() + pb.PutCh(byte(ai)) + + case 'd': + ai, stk = stk.PopInt() + pb.PutString(strconv.Itoa(ai)) + + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'x', 'X', 'o', ':': + // This is pretty suboptimal, but this is rarely used. + // None of the mainstream terminals use any of this, + // and it would surprise me if this code is ever + // executed outside test cases. + f := "%" + if ch == ':' { + ch, _ = pb.NextCh() + } + f += string(ch) + for ch == '+' || ch == '-' || ch == '#' || ch == ' ' { + ch, _ = pb.NextCh() + f += string(ch) + } + for (ch >= '0' && ch <= '9') || ch == '.' { + ch, _ = pb.NextCh() + f += string(ch) + } + switch ch { + case 'd', 'x', 'X', 'o': + ai, stk = stk.PopInt() + pb.PutString(fmt.Sprintf(f, ai)) + case 's': + a, stk = stk.PopString() + pb.PutString(fmt.Sprintf(f, a)) + case 'c': + ai, stk = stk.PopInt() + pb.PutString(fmt.Sprintf(f, ai)) + } + + case 'p': // push parameter + ch, _ = pb.NextCh() + ai = int(ch - '1') + if ai >= 0 && ai < len(params) { + stk = stk.Push(params[ai]) + } else { + stk = stk.Push(0) + } + + case 'P': // pop & store variable + ch, _ = pb.NextCh() + if ch >= 'A' && ch <= 'Z' { + svars[int(ch-'A')], stk = stk.PopString() + } else if ch >= 'a' && ch <= 'z' { + dvars[int(ch-'a')], stk = stk.PopString() + } + + case 'g': // recall & push variable + ch, _ = pb.NextCh() + if ch >= 'A' && ch <= 'Z' { + stk = stk.Push(svars[int(ch-'A')]) + } else if ch >= 'a' && ch <= 'z' { + stk = stk.Push(dvars[int(ch-'a')]) + } + + case '\'': // push(char) - the integer value of it + ch, _ = pb.NextCh() + _, _ = pb.NextCh() // must be ' but we don't check + stk = stk.Push(int(ch)) + + case '{': // push(int) + ai = 0 + ch, _ = pb.NextCh() + for ch >= '0' && ch <= '9' { + ai *= 10 + ai += int(ch - '0') + ch, _ = pb.NextCh() + } + // ch must be '}' but no verification + stk = stk.Push(ai) + + case 'l': // push(strlen(pop)) + a, stk = stk.PopString() + stk = stk.Push(len(a)) + + case '+': + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + stk = stk.Push(ai + bi) + + case '-': + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + stk = stk.Push(ai - bi) + + case '*': + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + stk = stk.Push(ai * bi) + + case '/': + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + if bi != 0 { + stk = stk.Push(ai / bi) + } else { + stk = stk.Push(0) + } + + case 'm': // push(pop mod pop) + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + if bi != 0 { + stk = stk.Push(ai % bi) + } else { + stk = stk.Push(0) + } + + case '&': // AND + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + stk = stk.Push(ai & bi) + + case '|': // OR + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + stk = stk.Push(ai | bi) + + case '^': // XOR + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + stk = stk.Push(ai ^ bi) + + case '~': // bit complement + ai, stk = stk.PopInt() + stk = stk.Push(ai ^ -1) + + case '!': // logical NOT + ai, stk = stk.PopInt() + stk = stk.Push(ai == 0) + + case '=': // numeric compare + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + stk = stk.Push(ai == bi) + + case '>': // greater than, numeric + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + stk = stk.Push(ai > bi) + + case '<': // less than, numeric + bi, stk = stk.PopInt() + ai, stk = stk.PopInt() + stk = stk.Push(ai < bi) + + case '?': // start conditional + + case ';': + skip = emit + + case 't': + ai, stk = stk.PopInt() + if ai == 0 { + skip = toElse + } + + case 'e': + skip = toEnd + + default: + pb.PutString("%" + string(ch)) + } + } + + return pb.End() +} + +// TPuts emits the string to the writer, but expands inline padding +// indications (of the form $<[delay]> where [delay] is msec) to +// a suitable time (unless the terminfo string indicates this isn't needed +// by specifying npc - no padding). All Terminfo based strings should be +// emitted using this function. +func (t *Terminfo) TPuts(w io.Writer, s string) { + for { + beg := strings.Index(s, "$<") + if beg < 0 { + // Most strings don't need padding, which is good news! + _, _ = io.WriteString(w, s) + return + } + _, _ = io.WriteString(w, s[:beg]) + s = s[beg+2:] + end := strings.Index(s, ">") + if end < 0 { + // unterminated.. just emit bytes unadulterated + _, _ = io.WriteString(w, "$<"+s) + return + } + val := s[:end] + s = s[end+1:] + padus := 0 + unit := time.Millisecond + dot := false + loop: + for i := range val { + switch val[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + padus *= 10 + padus += int(val[i] - '0') + if dot { + unit /= 10 + } + case '.': + if !dot { + dot = true + } else { + break loop + } + default: + break loop + } + } + + // Curses historically uses padding to achieve "fine grained" + // delays. We have much better clocks these days, and so we + // do not rely on padding but simply sleep a bit. + if len(t.PadChar) > 0 { + time.Sleep(unit * time.Duration(padus)) + } + } +} + +// TGoto returns a string suitable for addressing the cursor at the given +// row and column. The origin 0, 0 is in the upper left corner of the screen. +func (t *Terminfo) TGoto(col, row int) string { + return t.TParm(t.SetCursor, row, col) +} + +// TColor returns a string corresponding to the given foreground and background +// colors. Either fg or bg can be set to -1 to elide. +func (t *Terminfo) TColor(fi, bi int) string { + rv := "" + // As a special case, we map bright colors to lower versions if the + // color table only holds 8. For the remaining 240 colors, the user + // is out of luck. Someday we could create a mapping table, but its + // not worth it. + if t.Colors == 8 { + if fi > 7 && fi < 16 { + fi -= 8 + } + if bi > 7 && bi < 16 { + bi -= 8 + } + } + if t.Colors > fi && fi >= 0 { + rv += t.TParm(t.SetFg, fi) + } + if t.Colors > bi && bi >= 0 { + rv += t.TParm(t.SetBg, bi) + } + return rv +} + +var ( + dblock sync.Mutex + terminfos = make(map[string]*Terminfo) +) + +// AddTerminfo can be called to register a new Terminfo entry. +func AddTerminfo(t *Terminfo) { + dblock.Lock() + terminfos[t.Name] = t + for _, x := range t.Aliases { + terminfos[x] = t + } + dblock.Unlock() +} + +// LookupTerminfo attempts to find a definition for the named $TERM. +func LookupTerminfo(name string) (*Terminfo, error) { + if name == "" { + // else on windows: index out of bounds + // on the name[0] reference below + return nil, ErrTermNotFound + } + + addtruecolor := false + add256color := false + switch os.Getenv("COLORTERM") { + case "truecolor", "24bit", "24-bit": + addtruecolor = true + } + dblock.Lock() + t := terminfos[name] + dblock.Unlock() + + // If the name ends in -truecolor, then fabricate an entry + // from the corresponding -256color, -color, or bare terminal. + if t != nil && t.TrueColor { + addtruecolor = true + } else if t == nil && strings.HasSuffix(name, "-truecolor") { + + suffixes := []string{ + "-256color", + "-88color", + "-color", + "", + } + base := name[:len(name)-len("-truecolor")] + for _, s := range suffixes { + if t, _ = LookupTerminfo(base + s); t != nil { + addtruecolor = true + break + } + } + } + + // If the name ends in -256color, maybe fabricate using the xterm 256 color sequences + if t == nil && strings.HasSuffix(name, "-256color") { + suffixes := []string{ + "-88color", + "-color", + } + base := name[:len(name)-len("-256color")] + for _, s := range suffixes { + if t, _ = LookupTerminfo(base + s); t != nil { + add256color = true + break + } + } + } + + if t == nil { + return nil, ErrTermNotFound + } + + switch os.Getenv("TCELL_TRUECOLOR") { + case "": + case "disable": + addtruecolor = false + default: + addtruecolor = true + } + + // If the user has requested 24-bit color with $COLORTERM, then + // amend the value (unless already present). This means we don't + // need to have a value present. + if addtruecolor && + t.SetFgBgRGB == "" && + t.SetFgRGB == "" && + t.SetBgRGB == "" { + + // Supply vanilla ISO 8613-6:1994 24-bit color sequences. + t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm" + t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm" + t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" + + "48;2;%p4%d;%p5%d;%p6%dm" + } + + if add256color { + t.Colors = 256 + t.SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m" + t.SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m" + t.SetFgBg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m" + t.ResetFgBg = "\x1b[39;49m" + } + return t, nil +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt100/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt100/term.go new file mode 100644 index 0000000..2bad42e --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt100/term.go @@ -0,0 +1,51 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package vt100 + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // DEC VT100 (w/advanced video) + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "vt100", + Aliases: []string{"vt100-am"}, + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b[H\x1b[J$<50>", + AttrOff: "\x1b[m\x0f$<2>", + Underline: "\x1b[4m$<2>", + Bold: "\x1b[1m$<2>", + Blink: "\x1b[5m$<2>", + Reverse: "\x1b[7m$<2>", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b(B\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + SetCursor: "\x1b[%i%p1%d;%p2%dH$<5>", + CursorBack1: "\b", + CursorUp1: "\x1b[A$<2>", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyBackspace: "\b", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1bOt", + KeyF6: "\x1bOu", + KeyF7: "\x1bOv", + KeyF8: "\x1bOl", + KeyF9: "\x1bOw", + KeyF10: "\x1bOx", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt102/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt102/term.go new file mode 100644 index 0000000..1269b5b --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt102/term.go @@ -0,0 +1,50 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package vt102 + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // DEC VT102 + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "vt102", + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b[H\x1b[J$<50>", + AttrOff: "\x1b[m\x0f$<2>", + Underline: "\x1b[4m$<2>", + Bold: "\x1b[1m$<2>", + Blink: "\x1b[5m$<2>", + Reverse: "\x1b[7m$<2>", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b(B\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + SetCursor: "\x1b[%i%p1%d;%p2%dH$<5>", + CursorBack1: "\b", + CursorUp1: "\x1b[A$<2>", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyBackspace: "\b", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1bOt", + KeyF6: "\x1bOu", + KeyF7: "\x1bOv", + KeyF8: "\x1bOl", + KeyF9: "\x1bOw", + KeyF10: "\x1bOx", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt220/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt220/term.go new file mode 100644 index 0000000..a637677 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt220/term.go @@ -0,0 +1,63 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package vt220 + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // DEC VT220 + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "vt220", + Aliases: []string{"vt200"}, + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x1b(B", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0$<2>", + ExitAcs: "\x1b(B$<4>", + EnableAcs: "\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\b", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + KeyHelp: "\x1b[28~", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt320/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt320/term.go new file mode 100644 index 0000000..e929ed4 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt320/term.go @@ -0,0 +1,66 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package vt320 + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // DEC VT320 7 bit terminal + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "vt320", + Aliases: []string{"vt300"}, + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x1b(B", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1b[1~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[25~", + KeyF14: "\x1b[26~", + KeyF15: "\x1b[28~", + KeyF16: "\x1b[29~", + KeyF17: "\x1b[31~", + KeyF18: "\x1b[32~", + KeyF19: "\x1b[33~", + KeyF20: "\x1b[34~", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt400/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt400/term.go new file mode 100644 index 0000000..0540656 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt400/term.go @@ -0,0 +1,50 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package vt400 + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // DEC VT400 24x80 column autowrap + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "vt400", + Aliases: []string{"vt400-24", "dec-vt400"}, + Columns: 80, + Lines: 24, + Clear: "\x1b[H\x1b[J$<10/>", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x1b(B", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyBackspace: "\b", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + AutoMargin: true, + InsertChar: "\x1b[@", + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt420/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt420/term.go new file mode 100644 index 0000000..4c56f1e --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt420/term.go @@ -0,0 +1,56 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package vt420 + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // DEC VT420 + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "vt420", + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b[H\x1b[2J$<50>", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x1b(B$<2>", + Underline: "\x1b[4m", + Bold: "\x1b[1m$<2>", + Blink: "\x1b[5m$<2>", + Reverse: "\x1b[7m$<2>", + EnterKeypad: "\x1b=", + ExitKeypad: "\x1b>", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0$<2>", + ExitAcs: "\x1b(B$<4>", + EnableAcs: "\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + SetCursor: "\x1b[%i%p1%d;%p2%dH$<10>", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\b", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[17~", + KeyF6: "\x1b[18~", + KeyF7: "\x1b[19~", + KeyF8: "\x1b[20~", + KeyF9: "\x1b[21~", + KeyF10: "\x1b[29~", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt52/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt52/term.go new file mode 100644 index 0000000..5d193ed --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt52/term.go @@ -0,0 +1,39 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package vt52 + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // DEC VT52 + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "vt52", + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1bH\x1bJ", + EnterKeypad: "\x1b=", + ExitKeypad: "\x1b>", + PadChar: "\x00", + AltChars: "+h.k0affggolpnqprrss", + EnterAcs: "\x1bF", + ExitAcs: "\x1bG", + SetCursor: "\x1bY%p1%' '%+%c%p2%' '%+%c", + CursorBack1: "\x1bD", + CursorUp1: "\x1bA", + KeyUp: "\x1bA", + KeyDown: "\x1bB", + KeyRight: "\x1bC", + KeyLeft: "\x1bD", + KeyBackspace: "\b", + KeyF1: "\x1bP", + KeyF2: "\x1bQ", + KeyF3: "\x1bR", + KeyF5: "\x1b?t", + KeyF6: "\x1b?u", + KeyF7: "\x1b?v", + KeyF8: "\x1b?w", + KeyF9: "\x1b?x", + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy50/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy50/term.go new file mode 100644 index 0000000..beced62 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy50/term.go @@ -0,0 +1,60 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package wy50 + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // Wyse 50 + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "wy50", + Aliases: []string{"wyse50"}, + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b+$<20>", + ShowCursor: "\x1b`1", + HideCursor: "\x1b`0", + AttrOff: "\x1b(\x1bH\x03", + Dim: "\x1b`7\x1b)", + Reverse: "\x1b`6\x1b)", + PadChar: "\x00", + AltChars: "a;j5k3l2m1n8q:t4u9v=w0x6", + EnterAcs: "\x1bH\x02", + ExitAcs: "\x1bH\x03", + SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c", + CursorBack1: "\b", + CursorUp1: "\v", + KeyUp: "\v", + KeyDown: "\n", + KeyRight: "\f", + KeyLeft: "\b", + KeyInsert: "\x1bQ", + KeyDelete: "\x1bW", + KeyBackspace: "\b", + KeyHome: "\x1e", + KeyPgUp: "\x1bJ", + KeyPgDn: "\x1bK", + KeyF1: "\x01@\r", + KeyF2: "\x01A\r", + KeyF3: "\x01B\r", + KeyF4: "\x01C\r", + KeyF5: "\x01D\r", + KeyF6: "\x01E\r", + KeyF7: "\x01F\r", + KeyF8: "\x01G\r", + KeyF9: "\x01H\r", + KeyF10: "\x01I\r", + KeyF11: "\x01J\r", + KeyF12: "\x01K\r", + KeyF13: "\x01L\r", + KeyF14: "\x01M\r", + KeyF15: "\x01N\r", + KeyF16: "\x01O\r", + KeyPrint: "\x1bP", + KeyBacktab: "\x1bI", + KeyShfHome: "\x1b{", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy60/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy60/term.go new file mode 100644 index 0000000..27705f2 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy60/term.go @@ -0,0 +1,66 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package wy60 + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // Wyse 60 + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "wy60", + Aliases: []string{"wyse60"}, + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b+$<100>", + EnterCA: "\x1bw0", + ExitCA: "\x1bw1", + ShowCursor: "\x1b`1", + HideCursor: "\x1b`0", + AttrOff: "\x1b(\x1bH\x03\x1bG0\x1bcD", + Underline: "\x1bG8", + Dim: "\x1bGp", + Blink: "\x1bG2", + Reverse: "\x1bG4", + PadChar: "\x00", + AltChars: "+/,.0[a2fxgqh1ihjYk?lZm@nEqDtCu4vAwBx3yszr{c~~", + EnterAcs: "\x1bcE", + ExitAcs: "\x1bcD", + EnableAutoMargin: "\x1bd/", + DisableAutoMargin: "\x1bd.", + SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c", + CursorBack1: "\b", + CursorUp1: "\v", + KeyUp: "\v", + KeyDown: "\n", + KeyRight: "\f", + KeyLeft: "\b", + KeyInsert: "\x1bQ", + KeyDelete: "\x1bW", + KeyBackspace: "\b", + KeyHome: "\x1e", + KeyPgUp: "\x1bJ", + KeyPgDn: "\x1bK", + KeyF1: "\x01@\r", + KeyF2: "\x01A\r", + KeyF3: "\x01B\r", + KeyF4: "\x01C\r", + KeyF5: "\x01D\r", + KeyF6: "\x01E\r", + KeyF7: "\x01F\r", + KeyF8: "\x01G\r", + KeyF9: "\x01H\r", + KeyF10: "\x01I\r", + KeyF11: "\x01J\r", + KeyF12: "\x01K\r", + KeyF13: "\x01L\r", + KeyF14: "\x01M\r", + KeyF15: "\x01N\r", + KeyF16: "\x01O\r", + KeyPrint: "\x1bP", + KeyBacktab: "\x1bI", + KeyShfHome: "\x1b{", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi/term.go new file mode 100644 index 0000000..9b5cd7e --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi/term.go @@ -0,0 +1,120 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package wy99_ansi + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // Wyse WY-99GT in ANSI mode (int'l PC keyboard) + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "wy99-ansi", + Columns: 80, + Lines: 25, + Bell: "\a", + Clear: "\x1b[H\x1b[J$<200>", + ShowCursor: "\x1b[34h\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x0f\x1b[\"q", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h", + ExitKeypad: "\x1b[?1l", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooqqssttuuvvwwxx{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b$<1>", + CursorUp1: "\x1bM", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyBackspace: "\b", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[M", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF17: "\x1b[K", + KeyF18: "\x1b[31~", + KeyF19: "\x1b[32~", + KeyF20: "\x1b[33~", + KeyF21: "\x1b[34~", + KeyF22: "\x1b[35~", + KeyF23: "\x1b[1~", + KeyF24: "\x1b[2~", + KeyBacktab: "\x1b[z", + AutoMargin: true, + }) + + // Wyse WY-99GT in ANSI mode (US PC keyboard) + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "wy99a-ansi", + Columns: 80, + Lines: 25, + Bell: "\a", + Clear: "\x1b[H\x1b[J$<200>", + ShowCursor: "\x1b[34h\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x0f\x1b[\"q", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h", + ExitKeypad: "\x1b[?1l", + PadChar: "\x00", + AltChars: "``aaffggjjkkllmmnnooqqssttuuvvwwxx{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b$<1>", + CursorUp1: "\x1bM", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyBackspace: "\b", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[M", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF17: "\x1b[K", + KeyF18: "\x1b[31~", + KeyF19: "\x1b[32~", + KeyF20: "\x1b[33~", + KeyF21: "\x1b[34~", + KeyF22: "\x1b[35~", + KeyF23: "\x1b[1~", + KeyF24: "\x1b[2~", + KeyBacktab: "\x1b[z", + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xfce/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xfce/term.go new file mode 100644 index 0000000..4f7e825 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xfce/term.go @@ -0,0 +1,69 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package xfce + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // Xfce Terminal + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "xfce", + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[0m\x0f", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + PadChar: "\x00", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b)0", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/direct.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/direct.go new file mode 100644 index 0000000..358ebae --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/direct.go @@ -0,0 +1,92 @@ +// Copyright 2021 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This terminal definition is derived from the xterm-256color definition, but +// makes use of the RGB property these terminals have to support direct color. +// The terminfo entry for this uses a new format for the color handling introduced +// by ncurses 6.1 (and used by nobody else), so this override ensures we get +// good handling even in the face of this. + +package xterm + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // derived from xterm-256color, but adds full RGB support + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "xterm-direct", + Aliases: []string{"xterm-truecolor"}, + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h\x1b[22;0;0t", + ExitCA: "\x1b[?1049l\x1b[23;0;0t", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b(B\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + SetFgRGB: "\x1b[38;2;%p1%d;%p2%d;%p3%dm", + SetBgRGB: "\x1b[48;2;%p1%d;%p2%d;%p3%dm", + SetFgBgRGB: "\x1b[38;2;%p1%d;%p2%d;%p3%d;48;2;%p4%d;%p5%d;%p6%dm", + ResetFgBg: "\x1b[39;49m", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\u007f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + TrueColor: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/term.go new file mode 100644 index 0000000..fb9c758 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/term.go @@ -0,0 +1,198 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package xterm + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // xterm terminal emulator (X Window System) + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "xterm", + Aliases: []string{"xterm-debian"}, + Columns: 80, + Lines: 24, + Colors: 8, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h\x1b[22;0;0t", + ExitCA: "\x1b[?1049l\x1b[23;0;0t", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b(B\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[3%p1%dm", + SetBg: "\x1b[4%p1%dm", + SetFgBg: "\x1b[3%p1%d;4%p2%dm", + ResetFgBg: "\x1b[39;49m", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[<", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) + + // xterm with 88 colors + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "xterm-88color", + Columns: 80, + Lines: 24, + Colors: 88, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h\x1b[22;0;0t", + ExitCA: "\x1b[?1049l\x1b[23;0;0t", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b(B\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[<", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) + + // xterm with 256 colors + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "xterm-256color", + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h\x1b[22;0;0t", + ExitCA: "\x1b[?1049l\x1b[23;0;0t", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b(B\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[<", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty/term.go new file mode 100644 index 0000000..ac815a1 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty/term.go @@ -0,0 +1,71 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package xterm_kitty + +import "github.com/gdamore/tcell/v2/terminfo" + +func init() { + + // KovIdTTY + terminfo.AddTerminfo(&terminfo.Terminfo{ + Name: "xterm-kitty", + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h", + ExitCA: "\x1b[?1049l", + ShowCursor: "\x1b[?12h\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b(B\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Italic: "\x1b[3m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h", + ExitKeypad: "\x1b[?1l", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + ResetFgBg: "\x1b[39;49m", + AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAutoMargin: "\x1b[?7h", + DisableAutoMargin: "\x1b[?7l", + StrikeThrough: "\x1b[9m", + Mouse: "\x1b[M", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\x7f", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyBacktab: "\x1b[Z", + Modifiers: 1, + TrueColor: true, + AutoMargin: true, + }) +} diff --git a/vendor/github.com/gdamore/tcell/v2/terms_default.go b/vendor/github.com/gdamore/tcell/v2/terms_default.go new file mode 100644 index 0000000..fefcf89 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terms_default.go @@ -0,0 +1,24 @@ +//go:build !tcell_minimal +// +build !tcell_minimal + +// Copyright 2019 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + // This imports the default terminal entries. To disable, use the + // tcell_minimal build tag. + _ "github.com/gdamore/tcell/v2/terminfo/extended" +) diff --git a/vendor/github.com/gdamore/tcell/v2/terms_dynamic.go b/vendor/github.com/gdamore/tcell/v2/terms_dynamic.go new file mode 100644 index 0000000..f552b0e --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terms_dynamic.go @@ -0,0 +1,38 @@ +//go:build !tcell_minimal && !nacl && !js && !zos && !plan9 && !windows && !android +// +build !tcell_minimal,!nacl,!js,!zos,!plan9,!windows,!android + +// Copyright 2019 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + // This imports a dynamic version of the terminal database, which + // is built using infocmp. This relies on a working installation + // of infocmp (typically supplied with ncurses). We only do this + // for systems likely to have that -- i.e. UNIX based hosts. We + // also don't support Android here, because you really don't want + // to run external programs there. Generally the android terminals + // will be automatically included anyway. + "github.com/gdamore/tcell/v2/terminfo" + "github.com/gdamore/tcell/v2/terminfo/dynamic" +) + +func loadDynamicTerminfo(term string) (*terminfo.Terminfo, error) { + ti, _, e := dynamic.LoadTerminfo(term) + if e != nil { + return nil, e + } + return ti, nil +} diff --git a/vendor/github.com/gdamore/tcell/v2/terms_static.go b/vendor/github.com/gdamore/tcell/v2/terms_static.go new file mode 100644 index 0000000..6d725cb --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/terms_static.go @@ -0,0 +1,28 @@ +//go:build tcell_minimal || nacl || zos || plan9 || windows || android || js +// +build tcell_minimal nacl zos plan9 windows android js + +// Copyright 2019 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import ( + "errors" + + "github.com/gdamore/tcell/v2/terminfo" +) + +func loadDynamicTerminfo(_ string) (*terminfo.Terminfo, error) { + return nil, errors.New("terminal type unsupported") +} diff --git a/vendor/github.com/gdamore/tcell/v2/tscreen.go b/vendor/github.com/gdamore/tcell/v2/tscreen.go new file mode 100644 index 0000000..498f744 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/tscreen.go @@ -0,0 +1,1911 @@ +// Copyright 2024 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !(js && wasm) +// +build !js !wasm + +package tcell + +import ( + "bytes" + "errors" + "io" + "os" + "strconv" + "strings" + "sync" + "time" + "unicode/utf8" + + "golang.org/x/term" + "golang.org/x/text/transform" + + "github.com/gdamore/tcell/v2/terminfo" + + // import the stock terminals + _ "github.com/gdamore/tcell/v2/terminfo/base" +) + +// NewTerminfoScreen returns a Screen that uses the stock TTY interface +// and POSIX terminal control, combined with a terminfo description taken from +// the $TERM environment variable. It returns an error if the terminal +// is not supported for any reason. +// +// For terminals that do not support dynamic resize events, the $LINES +// $COLUMNS environment variables can be set to the actual window size, +// otherwise defaults taken from the terminal database are used. +func NewTerminfoScreen() (Screen, error) { + return NewTerminfoScreenFromTty(nil) +} + +// LookupTerminfo attempts to find a definition for the named $TERM falling +// back to attempting to parse the output from infocmp. +func LookupTerminfo(name string) (ti *terminfo.Terminfo, e error) { + ti, e = terminfo.LookupTerminfo(name) + if e != nil { + ti, e = loadDynamicTerminfo(name) + if e != nil { + return nil, e + } + terminfo.AddTerminfo(ti) + } + + return +} + +// NewTerminfoScreenFromTtyTerminfo returns a Screen using a custom Tty +// implementation and custom terminfo specification. +// If the passed in tty is nil, then a reasonable default (typically /dev/tty) +// is presumed, at least on UNIX hosts. (Windows hosts will typically fail this +// call altogether.) +// If passed terminfo is nil, then TERM environment variable is queried for +// terminal specification. +func NewTerminfoScreenFromTtyTerminfo(tty Tty, ti *terminfo.Terminfo) (s Screen, e error) { + if ti == nil { + ti, e = LookupTerminfo(os.Getenv("TERM")) + if e != nil { + return + } + } + + t := &tScreen{ti: ti, tty: tty} + + t.keyexist = make(map[Key]bool) + t.keycodes = make(map[string]*tKeyCode) + if len(ti.Mouse) > 0 { + t.mouse = []byte(ti.Mouse) + } + t.prepareKeys() + t.buildAcsMap() + t.resizeQ = make(chan bool, 1) + t.fallback = make(map[rune]string) + for k, v := range RuneFallbacks { + t.fallback[k] = v + } + + return &baseScreen{screenImpl: t}, nil +} + +// NewTerminfoScreenFromTty returns a Screen using a custom Tty implementation. +// If the passed in tty is nil, then a reasonable default (typically /dev/tty) +// is presumed, at least on UNIX hosts. (Windows hosts will typically fail this +// call altogether.) +func NewTerminfoScreenFromTty(tty Tty) (Screen, error) { + return NewTerminfoScreenFromTtyTerminfo(tty, nil) +} + +// tKeyCode represents a combination of a key code and modifiers. +type tKeyCode struct { + key Key + mod ModMask +} + +// tScreen represents a screen backed by a terminfo implementation. +type tScreen struct { + ti *terminfo.Terminfo + tty Tty + h int + w int + fini bool + cells CellBuffer + buffering bool // true if we are collecting writes to buf instead of sending directly to out + buf bytes.Buffer + curstyle Style + style Style + resizeQ chan bool + quit chan struct{} + keyexist map[Key]bool + keycodes map[string]*tKeyCode + keychan chan []byte + keytimer *time.Timer + keyexpire time.Time + cx int + cy int + mouse []byte + clear bool + cursorx int + cursory int + acs map[rune]string + charset string + encoder transform.Transformer + decoder transform.Transformer + fallback map[rune]string + colors map[Color]Color + palette []Color + truecolor bool + escaped bool + buttondn bool + finiOnce sync.Once + enablePaste string + disablePaste string + enterUrl string + exitUrl string + setWinSize string + enableFocus string + disableFocus string + cursorStyles map[CursorStyle]string + cursorStyle CursorStyle + saved *term.State + stopQ chan struct{} + eventQ chan Event + running bool + wg sync.WaitGroup + mouseFlags MouseFlags + pasteEnabled bool + focusEnabled bool + + sync.Mutex +} + +func (t *tScreen) Init() error { + if e := t.initialize(); e != nil { + return e + } + + t.keychan = make(chan []byte, 10) + t.keytimer = time.NewTimer(time.Millisecond * 50) + t.charset = "UTF-8" + + t.charset = getCharset() + if enc := GetEncoding(t.charset); enc != nil { + t.encoder = enc.NewEncoder() + t.decoder = enc.NewDecoder() + } else { + return ErrNoCharset + } + ti := t.ti + + // environment overrides + w := ti.Columns + h := ti.Lines + if i, _ := strconv.Atoi(os.Getenv("LINES")); i != 0 { + h = i + } + if i, _ := strconv.Atoi(os.Getenv("COLUMNS")); i != 0 { + w = i + } + if t.ti.SetFgBgRGB != "" || t.ti.SetFgRGB != "" || t.ti.SetBgRGB != "" { + t.truecolor = true + } + // A user who wants to have his themes honored can + // set this environment variable. + if os.Getenv("TCELL_TRUECOLOR") == "disable" { + t.truecolor = false + } + nColors := t.nColors() + if nColors > 256 { + nColors = 256 // clip to reasonable limits + } + t.colors = make(map[Color]Color, nColors) + t.palette = make([]Color, nColors) + for i := 0; i < nColors; i++ { + t.palette[i] = Color(i) | ColorValid + // identity map for our builtin colors + t.colors[Color(i)|ColorValid] = Color(i) | ColorValid + } + + t.quit = make(chan struct{}) + t.eventQ = make(chan Event, 10) + + t.Lock() + t.cx = -1 + t.cy = -1 + t.style = StyleDefault + t.cells.Resize(w, h) + t.cursorx = -1 + t.cursory = -1 + t.resize() + t.Unlock() + + if err := t.engage(); err != nil { + return err + } + + return nil +} + +func (t *tScreen) prepareKeyMod(key Key, mod ModMask, val string) { + if val != "" { + // Do not override codes that already exist + if _, exist := t.keycodes[val]; !exist { + t.keyexist[key] = true + t.keycodes[val] = &tKeyCode{key: key, mod: mod} + } + } +} + +func (t *tScreen) prepareKeyModReplace(key Key, replace Key, mod ModMask, val string) { + if val != "" { + // Do not override codes that already exist + if old, exist := t.keycodes[val]; !exist || old.key == replace { + t.keyexist[key] = true + t.keycodes[val] = &tKeyCode{key: key, mod: mod} + } + } +} + +func (t *tScreen) prepareKeyModXTerm(key Key, val string) { + + if strings.HasPrefix(val, "\x1b[") && strings.HasSuffix(val, "~") { + + // Drop the trailing ~ + val = val[:len(val)-1] + + // These suffixes are calculated assuming Xterm style modifier suffixes. + // Please see https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf for + // more information (specifically "PC-Style Function Keys"). + t.prepareKeyModReplace(key, key+12, ModShift, val+";2~") + t.prepareKeyModReplace(key, key+48, ModAlt, val+";3~") + t.prepareKeyModReplace(key, key+60, ModAlt|ModShift, val+";4~") + t.prepareKeyModReplace(key, key+24, ModCtrl, val+";5~") + t.prepareKeyModReplace(key, key+36, ModCtrl|ModShift, val+";6~") + t.prepareKeyMod(key, ModAlt|ModCtrl, val+";7~") + t.prepareKeyMod(key, ModShift|ModAlt|ModCtrl, val+";8~") + t.prepareKeyMod(key, ModMeta, val+";9~") + t.prepareKeyMod(key, ModMeta|ModShift, val+";10~") + t.prepareKeyMod(key, ModMeta|ModAlt, val+";11~") + t.prepareKeyMod(key, ModMeta|ModAlt|ModShift, val+";12~") + t.prepareKeyMod(key, ModMeta|ModCtrl, val+";13~") + t.prepareKeyMod(key, ModMeta|ModCtrl|ModShift, val+";14~") + t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt, val+";15~") + t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt|ModShift, val+";16~") + } else if strings.HasPrefix(val, "\x1bO") && len(val) == 3 { + val = val[2:] + t.prepareKeyModReplace(key, key+12, ModShift, "\x1b[1;2"+val) + t.prepareKeyModReplace(key, key+48, ModAlt, "\x1b[1;3"+val) + t.prepareKeyModReplace(key, key+24, ModCtrl, "\x1b[1;5"+val) + t.prepareKeyModReplace(key, key+36, ModCtrl|ModShift, "\x1b[1;6"+val) + t.prepareKeyModReplace(key, key+60, ModAlt|ModShift, "\x1b[1;4"+val) + t.prepareKeyMod(key, ModAlt|ModCtrl, "\x1b[1;7"+val) + t.prepareKeyMod(key, ModShift|ModAlt|ModCtrl, "\x1b[1;8"+val) + t.prepareKeyMod(key, ModMeta, "\x1b[1;9"+val) + t.prepareKeyMod(key, ModMeta|ModShift, "\x1b[1;10"+val) + t.prepareKeyMod(key, ModMeta|ModAlt, "\x1b[1;11"+val) + t.prepareKeyMod(key, ModMeta|ModAlt|ModShift, "\x1b[1;12"+val) + t.prepareKeyMod(key, ModMeta|ModCtrl, "\x1b[1;13"+val) + t.prepareKeyMod(key, ModMeta|ModCtrl|ModShift, "\x1b[1;14"+val) + t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt, "\x1b[1;15"+val) + t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt|ModShift, "\x1b[1;16"+val) + } +} + +func (t *tScreen) prepareXtermModifiers() { + if t.ti.Modifiers != terminfo.ModifiersXTerm { + return + } + t.prepareKeyModXTerm(KeyRight, t.ti.KeyRight) + t.prepareKeyModXTerm(KeyLeft, t.ti.KeyLeft) + t.prepareKeyModXTerm(KeyUp, t.ti.KeyUp) + t.prepareKeyModXTerm(KeyDown, t.ti.KeyDown) + t.prepareKeyModXTerm(KeyInsert, t.ti.KeyInsert) + t.prepareKeyModXTerm(KeyDelete, t.ti.KeyDelete) + t.prepareKeyModXTerm(KeyPgUp, t.ti.KeyPgUp) + t.prepareKeyModXTerm(KeyPgDn, t.ti.KeyPgDn) + t.prepareKeyModXTerm(KeyHome, t.ti.KeyHome) + t.prepareKeyModXTerm(KeyEnd, t.ti.KeyEnd) + t.prepareKeyModXTerm(KeyF1, t.ti.KeyF1) + t.prepareKeyModXTerm(KeyF2, t.ti.KeyF2) + t.prepareKeyModXTerm(KeyF3, t.ti.KeyF3) + t.prepareKeyModXTerm(KeyF4, t.ti.KeyF4) + t.prepareKeyModXTerm(KeyF5, t.ti.KeyF5) + t.prepareKeyModXTerm(KeyF6, t.ti.KeyF6) + t.prepareKeyModXTerm(KeyF7, t.ti.KeyF7) + t.prepareKeyModXTerm(KeyF8, t.ti.KeyF8) + t.prepareKeyModXTerm(KeyF9, t.ti.KeyF9) + t.prepareKeyModXTerm(KeyF10, t.ti.KeyF10) + t.prepareKeyModXTerm(KeyF11, t.ti.KeyF11) + t.prepareKeyModXTerm(KeyF12, t.ti.KeyF12) +} + +func (t *tScreen) prepareBracketedPaste() { + // Another workaround for lack of reporting in terminfo. + // We assume if the terminal has a mouse entry, that it + // offers bracketed paste. But we allow specific overrides + // via our terminal database. + if t.ti.EnablePaste != "" { + t.enablePaste = t.ti.EnablePaste + t.disablePaste = t.ti.DisablePaste + t.prepareKey(keyPasteStart, t.ti.PasteStart) + t.prepareKey(keyPasteEnd, t.ti.PasteEnd) + } else if t.ti.Mouse != "" { + t.enablePaste = "\x1b[?2004h" + t.disablePaste = "\x1b[?2004l" + t.prepareKey(keyPasteStart, "\x1b[200~") + t.prepareKey(keyPasteEnd, "\x1b[201~") + } +} + +func (t *tScreen) prepareExtendedOSC() { + // Linux is a special beast - because it has a mouse entry, but does + // not swallow these OSC commands properly. + if strings.Contains(t.ti.Name, "linux") { + return + } + // More stuff for limits in terminfo. This time we are applying + // the most common OSC (operating system commands). Generally + // terminals that don't understand these will ignore them. + // Again, we condition this based on mouse capabilities. + if t.ti.EnterUrl != "" { + t.enterUrl = t.ti.EnterUrl + t.exitUrl = t.ti.ExitUrl + } else if t.ti.Mouse != "" { + t.enterUrl = "\x1b]8;%p2%s;%p1%s\x1b\\" + t.exitUrl = "\x1b]8;;\x1b\\" + } + + if t.ti.SetWindowSize != "" { + t.setWinSize = t.ti.SetWindowSize + } else if t.ti.Mouse != "" { + t.setWinSize = "\x1b[8;%p1%p2%d;%dt" + } + + if t.ti.EnableFocusReporting != "" { + t.enableFocus = t.ti.EnableFocusReporting + } else if t.ti.Mouse != "" { + t.enableFocus = "\x1b[?1004h" + } + if t.ti.DisableFocusReporting != "" { + t.disableFocus = t.ti.DisableFocusReporting + } else if t.ti.Mouse != "" { + t.disableFocus = "\x1b[?1004l" + } +} + +func (t *tScreen) prepareCursorStyles() { + // Another workaround for lack of reporting in terminfo. + // We assume if the terminal has a mouse entry, that it + // offers bracketed paste. But we allow specific overrides + // via our terminal database. + if t.ti.CursorDefault != "" { + t.cursorStyles = map[CursorStyle]string{ + CursorStyleDefault: t.ti.CursorDefault, + CursorStyleBlinkingBlock: t.ti.CursorBlinkingBlock, + CursorStyleSteadyBlock: t.ti.CursorSteadyBlock, + CursorStyleBlinkingUnderline: t.ti.CursorBlinkingUnderline, + CursorStyleSteadyUnderline: t.ti.CursorSteadyUnderline, + CursorStyleBlinkingBar: t.ti.CursorBlinkingBar, + CursorStyleSteadyBar: t.ti.CursorSteadyBar, + } + } else if t.ti.Mouse != "" { + t.cursorStyles = map[CursorStyle]string{ + CursorStyleDefault: "\x1b[0 q", + CursorStyleBlinkingBlock: "\x1b[1 q", + CursorStyleSteadyBlock: "\x1b[2 q", + CursorStyleBlinkingUnderline: "\x1b[3 q", + CursorStyleSteadyUnderline: "\x1b[4 q", + CursorStyleBlinkingBar: "\x1b[5 q", + CursorStyleSteadyBar: "\x1b[6 q", + } + } +} + +func (t *tScreen) prepareKey(key Key, val string) { + t.prepareKeyMod(key, ModNone, val) +} + +func (t *tScreen) prepareKeys() { + ti := t.ti + t.prepareKey(KeyBackspace, ti.KeyBackspace) + t.prepareKey(KeyF1, ti.KeyF1) + t.prepareKey(KeyF2, ti.KeyF2) + t.prepareKey(KeyF3, ti.KeyF3) + t.prepareKey(KeyF4, ti.KeyF4) + t.prepareKey(KeyF5, ti.KeyF5) + t.prepareKey(KeyF6, ti.KeyF6) + t.prepareKey(KeyF7, ti.KeyF7) + t.prepareKey(KeyF8, ti.KeyF8) + t.prepareKey(KeyF9, ti.KeyF9) + t.prepareKey(KeyF10, ti.KeyF10) + t.prepareKey(KeyF11, ti.KeyF11) + t.prepareKey(KeyF12, ti.KeyF12) + t.prepareKey(KeyF13, ti.KeyF13) + t.prepareKey(KeyF14, ti.KeyF14) + t.prepareKey(KeyF15, ti.KeyF15) + t.prepareKey(KeyF16, ti.KeyF16) + t.prepareKey(KeyF17, ti.KeyF17) + t.prepareKey(KeyF18, ti.KeyF18) + t.prepareKey(KeyF19, ti.KeyF19) + t.prepareKey(KeyF20, ti.KeyF20) + t.prepareKey(KeyF21, ti.KeyF21) + t.prepareKey(KeyF22, ti.KeyF22) + t.prepareKey(KeyF23, ti.KeyF23) + t.prepareKey(KeyF24, ti.KeyF24) + t.prepareKey(KeyF25, ti.KeyF25) + t.prepareKey(KeyF26, ti.KeyF26) + t.prepareKey(KeyF27, ti.KeyF27) + t.prepareKey(KeyF28, ti.KeyF28) + t.prepareKey(KeyF29, ti.KeyF29) + t.prepareKey(KeyF30, ti.KeyF30) + t.prepareKey(KeyF31, ti.KeyF31) + t.prepareKey(KeyF32, ti.KeyF32) + t.prepareKey(KeyF33, ti.KeyF33) + t.prepareKey(KeyF34, ti.KeyF34) + t.prepareKey(KeyF35, ti.KeyF35) + t.prepareKey(KeyF36, ti.KeyF36) + t.prepareKey(KeyF37, ti.KeyF37) + t.prepareKey(KeyF38, ti.KeyF38) + t.prepareKey(KeyF39, ti.KeyF39) + t.prepareKey(KeyF40, ti.KeyF40) + t.prepareKey(KeyF41, ti.KeyF41) + t.prepareKey(KeyF42, ti.KeyF42) + t.prepareKey(KeyF43, ti.KeyF43) + t.prepareKey(KeyF44, ti.KeyF44) + t.prepareKey(KeyF45, ti.KeyF45) + t.prepareKey(KeyF46, ti.KeyF46) + t.prepareKey(KeyF47, ti.KeyF47) + t.prepareKey(KeyF48, ti.KeyF48) + t.prepareKey(KeyF49, ti.KeyF49) + t.prepareKey(KeyF50, ti.KeyF50) + t.prepareKey(KeyF51, ti.KeyF51) + t.prepareKey(KeyF52, ti.KeyF52) + t.prepareKey(KeyF53, ti.KeyF53) + t.prepareKey(KeyF54, ti.KeyF54) + t.prepareKey(KeyF55, ti.KeyF55) + t.prepareKey(KeyF56, ti.KeyF56) + t.prepareKey(KeyF57, ti.KeyF57) + t.prepareKey(KeyF58, ti.KeyF58) + t.prepareKey(KeyF59, ti.KeyF59) + t.prepareKey(KeyF60, ti.KeyF60) + t.prepareKey(KeyF61, ti.KeyF61) + t.prepareKey(KeyF62, ti.KeyF62) + t.prepareKey(KeyF63, ti.KeyF63) + t.prepareKey(KeyF64, ti.KeyF64) + t.prepareKey(KeyInsert, ti.KeyInsert) + t.prepareKey(KeyDelete, ti.KeyDelete) + t.prepareKey(KeyHome, ti.KeyHome) + t.prepareKey(KeyEnd, ti.KeyEnd) + t.prepareKey(KeyUp, ti.KeyUp) + t.prepareKey(KeyDown, ti.KeyDown) + t.prepareKey(KeyLeft, ti.KeyLeft) + t.prepareKey(KeyRight, ti.KeyRight) + t.prepareKey(KeyPgUp, ti.KeyPgUp) + t.prepareKey(KeyPgDn, ti.KeyPgDn) + t.prepareKey(KeyHelp, ti.KeyHelp) + t.prepareKey(KeyPrint, ti.KeyPrint) + t.prepareKey(KeyCancel, ti.KeyCancel) + t.prepareKey(KeyExit, ti.KeyExit) + t.prepareKey(KeyBacktab, ti.KeyBacktab) + + t.prepareKeyMod(KeyRight, ModShift, ti.KeyShfRight) + t.prepareKeyMod(KeyLeft, ModShift, ti.KeyShfLeft) + t.prepareKeyMod(KeyUp, ModShift, ti.KeyShfUp) + t.prepareKeyMod(KeyDown, ModShift, ti.KeyShfDown) + t.prepareKeyMod(KeyHome, ModShift, ti.KeyShfHome) + t.prepareKeyMod(KeyEnd, ModShift, ti.KeyShfEnd) + t.prepareKeyMod(KeyPgUp, ModShift, ti.KeyShfPgUp) + t.prepareKeyMod(KeyPgDn, ModShift, ti.KeyShfPgDn) + + t.prepareKeyMod(KeyRight, ModCtrl, ti.KeyCtrlRight) + t.prepareKeyMod(KeyLeft, ModCtrl, ti.KeyCtrlLeft) + t.prepareKeyMod(KeyUp, ModCtrl, ti.KeyCtrlUp) + t.prepareKeyMod(KeyDown, ModCtrl, ti.KeyCtrlDown) + t.prepareKeyMod(KeyHome, ModCtrl, ti.KeyCtrlHome) + t.prepareKeyMod(KeyEnd, ModCtrl, ti.KeyCtrlEnd) + + // Sadly, xterm handling of keycodes is somewhat erratic. In + // particular, different codes are sent depending on application + // mode is in use or not, and the entries for many of these are + // simply absent from terminfo on many systems. So we insert + // a number of escape sequences if they are not already used, in + // order to have the widest correct usage. Note that prepareKey + // will not inject codes if the escape sequence is already known. + // We also only do this for terminals that have the application + // mode present. + + // Cursor mode + if ti.EnterKeypad != "" { + t.prepareKey(KeyUp, "\x1b[A") + t.prepareKey(KeyDown, "\x1b[B") + t.prepareKey(KeyRight, "\x1b[C") + t.prepareKey(KeyLeft, "\x1b[D") + t.prepareKey(KeyEnd, "\x1b[F") + t.prepareKey(KeyHome, "\x1b[H") + t.prepareKey(KeyDelete, "\x1b[3~") + t.prepareKey(KeyHome, "\x1b[1~") + t.prepareKey(KeyEnd, "\x1b[4~") + t.prepareKey(KeyPgUp, "\x1b[5~") + t.prepareKey(KeyPgDn, "\x1b[6~") + + // Application mode + t.prepareKey(KeyUp, "\x1bOA") + t.prepareKey(KeyDown, "\x1bOB") + t.prepareKey(KeyRight, "\x1bOC") + t.prepareKey(KeyLeft, "\x1bOD") + t.prepareKey(KeyHome, "\x1bOH") + } + + t.prepareKey(keyPasteStart, ti.PasteStart) + t.prepareKey(keyPasteEnd, ti.PasteEnd) + t.prepareXtermModifiers() + t.prepareBracketedPaste() + t.prepareCursorStyles() + t.prepareExtendedOSC() + +outer: + // Add key mappings for control keys. + for i := 0; i < ' '; i++ { + // Do not insert direct key codes for ambiguous keys. + // For example, ESC is used for lots of other keys, so + // when parsing this we don't want to fast path handling + // of it, but instead wait a bit before parsing it as in + // isolation. + for esc := range t.keycodes { + if []byte(esc)[0] == byte(i) { + continue outer + } + } + + t.keyexist[Key(i)] = true + + mod := ModCtrl + switch Key(i) { + case KeyBS, KeyTAB, KeyESC, KeyCR: + // directly type-able- no control sequence + mod = ModNone + } + t.keycodes[string(rune(i))] = &tKeyCode{key: Key(i), mod: mod} + } +} + +func (t *tScreen) Fini() { + t.finiOnce.Do(t.finish) +} + +func (t *tScreen) finish() { + close(t.quit) + t.finalize() +} + +func (t *tScreen) SetStyle(style Style) { + t.Lock() + if !t.fini { + t.style = style + } + t.Unlock() +} + +func (t *tScreen) encodeRune(r rune, buf []byte) []byte { + + nb := make([]byte, 6) + ob := make([]byte, 6) + num := utf8.EncodeRune(ob, r) + ob = ob[:num] + dst := 0 + var err error + if enc := t.encoder; enc != nil { + enc.Reset() + dst, _, err = enc.Transform(nb, ob, true) + } + if err != nil || dst == 0 || nb[0] == '\x1a' { + // Combining characters are elided + if len(buf) == 0 { + if acs, ok := t.acs[r]; ok { + buf = append(buf, []byte(acs)...) + } else if fb, ok := t.fallback[r]; ok { + buf = append(buf, []byte(fb)...) + } else { + buf = append(buf, '?') + } + } + } else { + buf = append(buf, nb[:dst]...) + } + + return buf +} + +func (t *tScreen) sendFgBg(fg Color, bg Color, attr AttrMask) AttrMask { + ti := t.ti + if ti.Colors == 0 { + // foreground vs background, we calculate luminance + // and possibly do a reverse video + if !fg.Valid() { + return attr + } + v, ok := t.colors[fg] + if !ok { + v = FindColor(fg, []Color{ColorBlack, ColorWhite}) + t.colors[fg] = v + } + switch v { + case ColorWhite: + return attr + case ColorBlack: + return attr ^ AttrReverse + } + } + + if fg == ColorReset || bg == ColorReset { + t.TPuts(ti.ResetFgBg) + } + if t.truecolor { + if ti.SetFgBgRGB != "" && fg.IsRGB() && bg.IsRGB() { + r1, g1, b1 := fg.RGB() + r2, g2, b2 := bg.RGB() + t.TPuts(ti.TParm(ti.SetFgBgRGB, + int(r1), int(g1), int(b1), + int(r2), int(g2), int(b2))) + return attr + } + + if fg.IsRGB() && ti.SetFgRGB != "" { + r, g, b := fg.RGB() + t.TPuts(ti.TParm(ti.SetFgRGB, int(r), int(g), int(b))) + fg = ColorDefault + } + + if bg.IsRGB() && ti.SetBgRGB != "" { + r, g, b := bg.RGB() + t.TPuts(ti.TParm(ti.SetBgRGB, + int(r), int(g), int(b))) + bg = ColorDefault + } + } + + if fg.Valid() { + if v, ok := t.colors[fg]; ok { + fg = v + } else { + v = FindColor(fg, t.palette) + t.colors[fg] = v + fg = v + } + } + + if bg.Valid() { + if v, ok := t.colors[bg]; ok { + bg = v + } else { + v = FindColor(bg, t.palette) + t.colors[bg] = v + bg = v + } + } + + if fg.Valid() && bg.Valid() && ti.SetFgBg != "" { + t.TPuts(ti.TParm(ti.SetFgBg, int(fg&0xff), int(bg&0xff))) + } else { + if fg.Valid() && ti.SetFg != "" { + t.TPuts(ti.TParm(ti.SetFg, int(fg&0xff))) + } + if bg.Valid() && ti.SetBg != "" { + t.TPuts(ti.TParm(ti.SetBg, int(bg&0xff))) + } + } + return attr +} + +func (t *tScreen) drawCell(x, y int) int { + + ti := t.ti + + mainc, combc, style, width := t.cells.GetContent(x, y) + if !t.cells.Dirty(x, y) { + return width + } + + if y == t.h-1 && x == t.w-1 && t.ti.AutoMargin && ti.DisableAutoMargin == "" && ti.InsertChar != "" { + // our solution is somewhat goofy. + // we write to the second to the last cell what we want in the last cell, then we + // insert a character at that 2nd to last position to shift the last column into + // place, then we rewrite that 2nd to last cell. Old terminals suck. + t.TPuts(ti.TGoto(x-1, y)) + defer func() { + t.TPuts(ti.TGoto(x-1, y)) + t.TPuts(ti.InsertChar) + t.cy = y + t.cx = x - 1 + t.cells.SetDirty(x-1, y, true) + _ = t.drawCell(x-1, y) + t.TPuts(t.ti.TGoto(0, 0)) + t.cy = 0 + t.cx = 0 + }() + } else if t.cy != y || t.cx != x { + t.TPuts(ti.TGoto(x, y)) + t.cx = x + t.cy = y + } + + if style == StyleDefault { + style = t.style + } + if style != t.curstyle { + fg, bg, attrs := style.Decompose() + + t.TPuts(ti.AttrOff) + + attrs = t.sendFgBg(fg, bg, attrs) + if attrs&AttrBold != 0 { + t.TPuts(ti.Bold) + } + if attrs&AttrUnderline != 0 { + t.TPuts(ti.Underline) + } + if attrs&AttrReverse != 0 { + t.TPuts(ti.Reverse) + } + if attrs&AttrBlink != 0 { + t.TPuts(ti.Blink) + } + if attrs&AttrDim != 0 { + t.TPuts(ti.Dim) + } + if attrs&AttrItalic != 0 { + t.TPuts(ti.Italic) + } + if attrs&AttrStrikeThrough != 0 { + t.TPuts(ti.StrikeThrough) + } + + // URL string can be long, so don't send it unless we really need to + if t.enterUrl != "" && t.curstyle != style { + if style.url != "" { + t.TPuts(ti.TParm(t.enterUrl, style.url, style.urlId)) + } else { + t.TPuts(t.exitUrl) + } + } + + t.curstyle = style + } + + // now emit runes - taking care to not overrun width with a + // wide character, and to ensure that we emit exactly one regular + // character followed up by any residual combing characters + + if width < 1 { + width = 1 + } + + var str string + + buf := make([]byte, 0, 6) + + buf = t.encodeRune(mainc, buf) + for _, r := range combc { + buf = t.encodeRune(r, buf) + } + + str = string(buf) + if width > 1 && str == "?" { + // No FullWidth character support + str = "? " + t.cx = -1 + } + + if x > t.w-width { + // too wide to fit; emit a single space instead + width = 1 + str = " " + } + t.writeString(str) + t.cx += width + t.cells.SetDirty(x, y, false) + if width > 1 { + t.cx = -1 + } + + return width +} + +func (t *tScreen) ShowCursor(x, y int) { + t.Lock() + t.cursorx = x + t.cursory = y + t.Unlock() +} + +func (t *tScreen) SetCursorStyle(cs CursorStyle) { + t.Lock() + t.cursorStyle = cs + t.Unlock() +} + +func (t *tScreen) HideCursor() { + t.ShowCursor(-1, -1) +} + +func (t *tScreen) showCursor() { + + x, y := t.cursorx, t.cursory + w, h := t.cells.Size() + if x < 0 || y < 0 || x >= w || y >= h { + t.hideCursor() + return + } + t.TPuts(t.ti.TGoto(x, y)) + t.TPuts(t.ti.ShowCursor) + if t.cursorStyles != nil { + if esc, ok := t.cursorStyles[t.cursorStyle]; ok { + t.TPuts(esc) + } + } + t.cx = x + t.cy = y +} + +// writeString sends a string to the terminal. The string is sent as-is and +// this function does not expand inline padding indications (of the form +// $<[delay]> where [delay] is msec). In order to have these expanded, use +// TPuts. If the screen is "buffering", the string is collected in a buffer, +// with the intention that the entire buffer be sent to the terminal in one +// write operation at some point later. +func (t *tScreen) writeString(s string) { + if t.buffering { + _, _ = io.WriteString(&t.buf, s) + } else { + _, _ = io.WriteString(t.tty, s) + } +} + +func (t *tScreen) TPuts(s string) { + if t.buffering { + t.ti.TPuts(&t.buf, s) + } else { + t.ti.TPuts(t.tty, s) + } +} + +func (t *tScreen) Show() { + t.Lock() + if !t.fini { + t.resize() + t.draw() + } + t.Unlock() +} + +func (t *tScreen) clearScreen() { + t.TPuts(t.ti.AttrOff) + t.TPuts(t.exitUrl) + fg, bg, _ := t.style.Decompose() + _ = t.sendFgBg(fg, bg, AttrNone) + t.TPuts(t.ti.Clear) + t.clear = false +} + +func (t *tScreen) hideCursor() { + // does not update cursor position + if t.ti.HideCursor != "" { + t.TPuts(t.ti.HideCursor) + } else { + // No way to hide cursor, stick it + // at bottom right of screen + t.cx, t.cy = t.cells.Size() + t.TPuts(t.ti.TGoto(t.cx, t.cy)) + } +} + +func (t *tScreen) draw() { + // clobber cursor position, because we're going to change it all + t.cx = -1 + t.cy = -1 + // make no style assumptions + t.curstyle = styleInvalid + + t.buf.Reset() + t.buffering = true + defer func() { + t.buffering = false + }() + + // hide the cursor while we move stuff around + t.hideCursor() + + if t.clear { + t.clearScreen() + } + + for y := 0; y < t.h; y++ { + for x := 0; x < t.w; x++ { + width := t.drawCell(x, y) + if width > 1 { + if x+1 < t.w { + // this is necessary so that if we ever + // go back to drawing that cell, we + // actually will *draw* it. + t.cells.SetDirty(x+1, y, true) + } + } + x += width - 1 + } + } + + // restore the cursor + t.showCursor() + + _, _ = t.buf.WriteTo(t.tty) +} + +func (t *tScreen) EnableMouse(flags ...MouseFlags) { + var f MouseFlags + flagsPresent := false + for _, flag := range flags { + f |= flag + flagsPresent = true + } + if !flagsPresent { + f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents + } + + t.Lock() + t.mouseFlags = f + t.enableMouse(f) + t.Unlock() +} + +func (t *tScreen) enableMouse(f MouseFlags) { + // Rather than using terminfo to find mouse escape sequences, we rely on the fact that + // pretty much *every* terminal that supports mouse tracking follows the + // XTerm standards (the modern ones). + if len(t.mouse) != 0 { + // start by disabling all tracking. + t.TPuts("\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l") + if f&MouseButtonEvents != 0 { + t.TPuts("\x1b[?1000h") + } + if f&MouseDragEvents != 0 { + t.TPuts("\x1b[?1002h") + } + if f&MouseMotionEvents != 0 { + t.TPuts("\x1b[?1003h") + } + if f&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) != 0 { + t.TPuts("\x1b[?1006h") + } + } + +} + +func (t *tScreen) DisableMouse() { + t.Lock() + t.mouseFlags = 0 + t.enableMouse(0) + t.Unlock() +} + +func (t *tScreen) EnablePaste() { + t.Lock() + t.pasteEnabled = true + t.enablePasting(true) + t.Unlock() +} + +func (t *tScreen) DisablePaste() { + t.Lock() + t.pasteEnabled = false + t.enablePasting(false) + t.Unlock() +} + +func (t *tScreen) enablePasting(on bool) { + var s string + if on { + s = t.enablePaste + } else { + s = t.disablePaste + } + if s != "" { + t.TPuts(s) + } +} + +func (t *tScreen) EnableFocus() { + t.Lock() + t.focusEnabled = true + t.enableFocusReporting() + t.Unlock() +} + +func (t *tScreen) DisableFocus() { + t.Lock() + t.focusEnabled = false + t.disableFocusReporting() + t.Unlock() +} + +func (t *tScreen) enableFocusReporting() { + if t.enableFocus != "" { + t.TPuts(t.enableFocus) + } +} + +func (t *tScreen) disableFocusReporting() { + if t.disableFocus != "" { + t.TPuts(t.disableFocus) + } +} + +func (t *tScreen) Size() (int, int) { + t.Lock() + w, h := t.w, t.h + t.Unlock() + return w, h +} + +func (t *tScreen) resize() { + ws, err := t.tty.WindowSize() + if err != nil { + return + } + if ws.Width == t.w && ws.Height == t.h { + return + } + t.cx = -1 + t.cy = -1 + + t.cells.Resize(ws.Width, ws.Height) + t.cells.Invalidate() + t.h = ws.Height + t.w = ws.Width + ev := &EventResize{t: time.Now(), ws: ws} + select { + case t.eventQ <- ev: + default: + } +} + +func (t *tScreen) Colors() int { + // this doesn't change, no need for lock + if t.truecolor { + return 1 << 24 + } + return t.ti.Colors +} + +// nColors returns the size of the built-in palette. +// This is distinct from Colors(), as it will generally +// always be a small number. (<= 256) +func (t *tScreen) nColors() int { + return t.ti.Colors +} + +// vtACSNames is a map of bytes defined by terminfo that are used in +// the terminals Alternate Character Set to represent other glyphs. +// For example, the upper left corner of the box drawing set can be +// displayed by printing "l" while in the alternate character set. +// It's not quite that simple, since the "l" is the terminfo name, +// and it may be necessary to use a different character based on +// the terminal implementation (or the terminal may lack support for +// this altogether). See buildAcsMap below for detail. +var vtACSNames = map[byte]rune{ + '+': RuneRArrow, + ',': RuneLArrow, + '-': RuneUArrow, + '.': RuneDArrow, + '0': RuneBlock, + '`': RuneDiamond, + 'a': RuneCkBoard, + 'b': '␉', // VT100, Not defined by terminfo + 'c': '␌', // VT100, Not defined by terminfo + 'd': '␋', // VT100, Not defined by terminfo + 'e': '␊', // VT100, Not defined by terminfo + 'f': RuneDegree, + 'g': RunePlMinus, + 'h': RuneBoard, + 'i': RuneLantern, + 'j': RuneLRCorner, + 'k': RuneURCorner, + 'l': RuneULCorner, + 'm': RuneLLCorner, + 'n': RunePlus, + 'o': RuneS1, + 'p': RuneS3, + 'q': RuneHLine, + 'r': RuneS7, + 's': RuneS9, + 't': RuneLTee, + 'u': RuneRTee, + 'v': RuneBTee, + 'w': RuneTTee, + 'x': RuneVLine, + 'y': RuneLEqual, + 'z': RuneGEqual, + '{': RunePi, + '|': RuneNEqual, + '}': RuneSterling, + '~': RuneBullet, +} + +// buildAcsMap builds a map of characters that we translate from Unicode to +// alternate character encodings. To do this, we use the standard VT100 ACS +// maps. This is only done if the terminal lacks support for Unicode; we +// always prefer to emit Unicode glyphs when we are able. +func (t *tScreen) buildAcsMap() { + acsstr := t.ti.AltChars + t.acs = make(map[rune]string) + for len(acsstr) > 2 { + srcv := acsstr[0] + dstv := string(acsstr[1]) + if r, ok := vtACSNames[srcv]; ok { + t.acs[r] = t.ti.EnterAcs + dstv + t.ti.ExitAcs + } + acsstr = acsstr[2:] + } +} + +func (t *tScreen) clip(x, y int) (int, int) { + w, h := t.cells.Size() + if x < 0 { + x = 0 + } + if y < 0 { + y = 0 + } + if x > w-1 { + x = w - 1 + } + if y > h-1 { + y = h - 1 + } + return x, y +} + +// buildMouseEvent returns an event based on the supplied coordinates and button +// state. Note that the screen's mouse button state is updated based on the +// input to this function (i.e. it mutates the receiver). +func (t *tScreen) buildMouseEvent(x, y, btn int) *EventMouse { + + // XTerm mouse events only report at most one button at a time, + // which may include a wheel button. Wheel motion events are + // reported as single impulses, while other button events are reported + // as separate press & release events. + + button := ButtonNone + mod := ModNone + + // Mouse wheel has bit 6 set, no release events. It should be noted + // that wheel events are sometimes misdelivered as mouse button events + // during a click-drag, so we debounce these, considering them to be + // button press events unless we see an intervening release event. + switch btn & 0x43 { + case 0: + button = Button1 + case 1: + button = Button3 // Note we prefer to treat right as button 2 + case 2: + button = Button2 // And the middle button as button 3 + case 3: + button = ButtonNone + case 0x40: + button = WheelUp + case 0x41: + button = WheelDown + } + + if btn&0x4 != 0 { + mod |= ModShift + } + if btn&0x8 != 0 { + mod |= ModAlt + } + if btn&0x10 != 0 { + mod |= ModCtrl + } + + // Some terminals will report mouse coordinates outside the + // screen, especially with click-drag events. Clip the coordinates + // to the screen in that case. + x, y = t.clip(x, y) + + return NewEventMouse(x, y, button, mod) +} + +// parseSgrMouse attempts to locate an SGR mouse record at the start of the +// buffer. It returns true, true if it found one, and the associated bytes +// be removed from the buffer. It returns true, false if the buffer might +// contain such an event, but more bytes are necessary (partial match), and +// false, false if the content is definitely *not* an SGR mouse record. +func (t *tScreen) parseSgrMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) { + + b := buf.Bytes() + + var x, y, btn, state int + dig := false + neg := false + motion := false + scroll := false + i := 0 + val := 0 + + for i = range b { + switch b[i] { + case '\x1b': + if state != 0 { + return false, false + } + state = 1 + + case '\x9b': + if state != 0 { + return false, false + } + state = 2 + + case '[': + if state != 1 { + return false, false + } + state = 2 + + case '<': + if state != 2 { + return false, false + } + val = 0 + dig = false + neg = false + state = 3 + + case '-': + if state != 3 && state != 4 && state != 5 { + return false, false + } + if dig || neg { + return false, false + } + neg = true // stay in state + + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + if state != 3 && state != 4 && state != 5 { + return false, false + } + val *= 10 + val += int(b[i] - '0') + dig = true // stay in state + + case ';': + if neg { + val = -val + } + switch state { + case 3: + btn, val = val, 0 + neg, dig, state = false, false, 4 + case 4: + x, val = val-1, 0 + neg, dig, state = false, false, 5 + default: + return false, false + } + + case 'm', 'M': + if state != 5 { + return false, false + } + if neg { + val = -val + } + y = val - 1 + + motion = (btn & 32) != 0 + scroll = (btn & 0x42) == 0x40 + btn &^= 32 + if b[i] == 'm' { + // mouse release, clear all buttons + btn |= 3 + btn &^= 0x40 + t.buttondn = false + } else if motion { + /* + * Some broken terminals appear to send + * mouse button one motion events, instead of + * encoding 35 (no buttons) into these events. + * We resolve these by looking for a non-motion + * event first. + */ + if !t.buttondn { + btn |= 3 + btn &^= 0x40 + } + } else if !scroll { + t.buttondn = true + } + // consume the event bytes + for i >= 0 { + _, _ = buf.ReadByte() + i-- + } + *evs = append(*evs, t.buildMouseEvent(x, y, btn)) + return true, true + } + } + + // incomplete & inconclusive at this point + return true, false +} + +func (t *tScreen) parseFocus(buf *bytes.Buffer, evs *[]Event) (bool, bool) { + state := 0 + b := buf.Bytes() + for i := range b { + switch state { + case 0: + if b[i] != '\x1b' { + return false, false + } + state = 1 + case 1: + if b[i] != '[' { + return false, false + } + state = 2 + case 2: + if b[i] != 'I' && b[i] != 'O' { + return false, false + } + *evs = append(*evs, NewEventFocus(b[i] == 'I')) + _, _ = buf.ReadByte() + _, _ = buf.ReadByte() + _, _ = buf.ReadByte() + return true, true + } + } + return true, false +} + +// parseXtermMouse is like parseSgrMouse, but it parses a legacy +// X11 mouse record. +func (t *tScreen) parseXtermMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) { + + b := buf.Bytes() + + state := 0 + btn := 0 + x := 0 + y := 0 + + for i := range b { + switch state { + case 0: + switch b[i] { + case '\x1b': + state = 1 + case '\x9b': + state = 2 + default: + return false, false + } + case 1: + if b[i] != '[' { + return false, false + } + state = 2 + case 2: + if b[i] != 'M' { + return false, false + } + state++ + case 3: + btn = int(b[i]) + state++ + case 4: + x = int(b[i]) - 32 - 1 + state++ + case 5: + y = int(b[i]) - 32 - 1 + for i >= 0 { + _, _ = buf.ReadByte() + i-- + } + *evs = append(*evs, t.buildMouseEvent(x, y, btn)) + return true, true + } + } + return true, false +} + +func (t *tScreen) parseFunctionKey(buf *bytes.Buffer, evs *[]Event) (bool, bool) { + b := buf.Bytes() + partial := false + for e, k := range t.keycodes { + esc := []byte(e) + if (len(esc) == 1) && (esc[0] == '\x1b') { + continue + } + if bytes.HasPrefix(b, esc) { + // matched + var r rune + if len(esc) == 1 { + r = rune(b[0]) + } + mod := k.mod + if t.escaped { + mod |= ModAlt + t.escaped = false + } + switch k.key { + case keyPasteStart: + *evs = append(*evs, NewEventPaste(true)) + case keyPasteEnd: + *evs = append(*evs, NewEventPaste(false)) + default: + *evs = append(*evs, NewEventKey(k.key, r, mod)) + } + for i := 0; i < len(esc); i++ { + _, _ = buf.ReadByte() + } + return true, true + } + if bytes.HasPrefix(esc, b) { + partial = true + } + } + return partial, false +} + +func (t *tScreen) parseRune(buf *bytes.Buffer, evs *[]Event) (bool, bool) { + b := buf.Bytes() + if b[0] >= ' ' && b[0] <= 0x7F { + // printable ASCII easy to deal with -- no encodings + mod := ModNone + if t.escaped { + mod = ModAlt + t.escaped = false + } + *evs = append(*evs, NewEventKey(KeyRune, rune(b[0]), mod)) + _, _ = buf.ReadByte() + return true, true + } + + if b[0] < 0x80 { + // Low numbered values are control keys, not runes. + return false, false + } + + utf := make([]byte, 12) + for l := 1; l <= len(b); l++ { + t.decoder.Reset() + nOut, nIn, e := t.decoder.Transform(utf, b[:l], true) + if e == transform.ErrShortSrc { + continue + } + if nOut != 0 { + r, _ := utf8.DecodeRune(utf[:nOut]) + if r != utf8.RuneError { + mod := ModNone + if t.escaped { + mod = ModAlt + t.escaped = false + } + *evs = append(*evs, NewEventKey(KeyRune, r, mod)) + } + for nIn > 0 { + _, _ = buf.ReadByte() + nIn-- + } + return true, true + } + } + // Looks like potential escape + return true, false +} + +func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) { + evs := t.collectEventsFromInput(buf, expire) + + for _, ev := range evs { + select { + case t.eventQ <- ev: + case <-t.quit: + return + } + } +} + +// Return an array of Events extracted from the supplied buffer. This is done +// while holding the screen's lock - the events can then be queued for +// application processing with the lock released. +func (t *tScreen) collectEventsFromInput(buf *bytes.Buffer, expire bool) []Event { + + res := make([]Event, 0, 20) + + t.Lock() + defer t.Unlock() + + for { + b := buf.Bytes() + if len(b) == 0 { + buf.Reset() + return res + } + + partials := 0 + + if part, comp := t.parseRune(buf, &res); comp { + continue + } else if part { + partials++ + } + + if part, comp := t.parseFunctionKey(buf, &res); comp { + continue + } else if part { + partials++ + } + + if part, comp := t.parseFocus(buf, &res); comp { + continue + } else if part { + partials++ + } + + // Only parse mouse records if this term claims to have + // mouse support + + if t.ti.Mouse != "" { + if part, comp := t.parseXtermMouse(buf, &res); comp { + continue + } else if part { + partials++ + } + + if part, comp := t.parseSgrMouse(buf, &res); comp { + continue + } else if part { + partials++ + } + } + + if partials == 0 || expire { + if b[0] == '\x1b' { + if len(b) == 1 { + res = append(res, NewEventKey(KeyEsc, 0, ModNone)) + t.escaped = false + } else { + t.escaped = true + } + _, _ = buf.ReadByte() + continue + } + // Nothing was going to match, or we timed-out + // waiting for more data -- just deliver the characters + // to the app & let them sort it out. Possibly we + // should only do this for control characters like ESC. + by, _ := buf.ReadByte() + mod := ModNone + if t.escaped { + t.escaped = false + mod = ModAlt + } + res = append(res, NewEventKey(KeyRune, rune(by), mod)) + continue + } + + // well we have some partial data, wait until we get + // some more + break + } + + return res +} + +func (t *tScreen) mainLoop(stopQ chan struct{}) { + defer t.wg.Done() + buf := &bytes.Buffer{} + for { + select { + case <-stopQ: + return + case <-t.quit: + return + case <-t.resizeQ: + t.Lock() + t.cx = -1 + t.cy = -1 + t.resize() + t.cells.Invalidate() + t.draw() + t.Unlock() + continue + case <-t.keytimer.C: + // If the timer fired, and the current time + // is after the expiration of the escape sequence, + // then we assume the escape sequence reached its + // conclusion, and process the chunk independently. + // This lets us detect conflicts such as a lone ESC. + if buf.Len() > 0 { + if time.Now().After(t.keyexpire) { + t.scanInput(buf, true) + } + } + if buf.Len() > 0 { + if !t.keytimer.Stop() { + select { + case <-t.keytimer.C: + default: + } + } + t.keytimer.Reset(time.Millisecond * 50) + } + case chunk := <-t.keychan: + buf.Write(chunk) + t.keyexpire = time.Now().Add(time.Millisecond * 50) + t.scanInput(buf, false) + if !t.keytimer.Stop() { + select { + case <-t.keytimer.C: + default: + } + } + if buf.Len() > 0 { + t.keytimer.Reset(time.Millisecond * 50) + } + } + } +} + +func (t *tScreen) inputLoop(stopQ chan struct{}) { + + defer t.wg.Done() + for { + select { + case <-stopQ: + return + default: + } + chunk := make([]byte, 128) + n, e := t.tty.Read(chunk) + switch e { + case nil: + default: + t.Lock() + running := t.running + t.Unlock() + if running { + select { + case t.eventQ <- NewEventError(e): + case <-t.quit: + } + } + return + } + if n > 0 { + t.keychan <- chunk[:n] + } + } +} + +func (t *tScreen) Sync() { + t.Lock() + t.cx = -1 + t.cy = -1 + if !t.fini { + t.resize() + t.clear = true + t.cells.Invalidate() + t.draw() + } + t.Unlock() +} + +func (t *tScreen) CharacterSet() string { + return t.charset +} + +func (t *tScreen) RegisterRuneFallback(orig rune, fallback string) { + t.Lock() + t.fallback[orig] = fallback + t.Unlock() +} + +func (t *tScreen) UnregisterRuneFallback(orig rune) { + t.Lock() + delete(t.fallback, orig) + t.Unlock() +} + +func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool { + + if enc := t.encoder; enc != nil { + nb := make([]byte, 6) + ob := make([]byte, 6) + num := utf8.EncodeRune(ob, r) + + enc.Reset() + dst, _, err := enc.Transform(nb, ob[:num], true) + if dst != 0 && err == nil && nb[0] != '\x1A' { + return true + } + } + // Terminal fallbacks always permitted, since we assume they are + // basically nearly perfect renditions. + if _, ok := t.acs[r]; ok { + return true + } + if !checkFallbacks { + return false + } + if _, ok := t.fallback[r]; ok { + return true + } + return false +} + +func (t *tScreen) HasMouse() bool { + return len(t.mouse) != 0 +} + +func (t *tScreen) HasKey(k Key) bool { + if k == KeyRune { + return true + } + return t.keyexist[k] +} + +func (t *tScreen) SetSize(w, h int) { + if t.setWinSize != "" { + t.TPuts(t.ti.TParm(t.setWinSize, w, h)) + } + t.cells.Invalidate() + t.resize() +} + +func (t *tScreen) Resize(int, int, int, int) {} + +func (t *tScreen) Suspend() error { + t.disengage() + return nil +} + +func (t *tScreen) Resume() error { + return t.engage() +} + +func (t *tScreen) Tty() (Tty, bool) { + return t.tty, true +} + +// engage is used to place the terminal in raw mode and establish screen size, etc. +// Think of this is as tcell "engaging" the clutch, as it's going to be driving the +// terminal interface. +func (t *tScreen) engage() error { + t.Lock() + defer t.Unlock() + if t.tty == nil { + return ErrNoScreen + } + t.tty.NotifyResize(func() { + select { + case t.resizeQ <- true: + default: + } + }) + if t.running { + return errors.New("already engaged") + } + if err := t.tty.Start(); err != nil { + return err + } + t.running = true + if ws, err := t.tty.WindowSize(); err == nil && ws.Width != 0 && ws.Height != 0 { + t.cells.Resize(ws.Width, ws.Height) + } + stopQ := make(chan struct{}) + t.stopQ = stopQ + t.enableMouse(t.mouseFlags) + t.enablePasting(t.pasteEnabled) + if t.focusEnabled { + t.enableFocusReporting() + } + + ti := t.ti + if os.Getenv("TCELL_ALTSCREEN") != "disable" { + // Technically this may not be right, but every terminal we know about + // (even Wyse 60) uses this to enter the alternate screen buffer, and + // possibly save and restore the window title and/or icon. + // (In theory there could be terminals that don't support X,Y cursor + // positions without a setup command, but we don't support them.) + t.TPuts(ti.EnterCA) + } + t.TPuts(ti.EnterKeypad) + t.TPuts(ti.HideCursor) + t.TPuts(ti.EnableAcs) + t.TPuts(ti.DisableAutoMargin) + t.TPuts(ti.Clear) + + t.wg.Add(2) + go t.inputLoop(stopQ) + go t.mainLoop(stopQ) + return nil +} + +// disengage is used to release the terminal back to support from the caller. +// Think of this as tcell disengaging the clutch, so that another application +// can take over the terminal interface. This restores the TTY mode that was +// present when the application was first started. +func (t *tScreen) disengage() { + + t.Lock() + if !t.running { + t.Unlock() + return + } + t.running = false + stopQ := t.stopQ + close(stopQ) + _ = t.tty.Drain() + t.Unlock() + + t.tty.NotifyResize(nil) + // wait for everything to shut down + t.wg.Wait() + + // shutdown the screen and disable special modes (e.g. mouse and bracketed paste) + ti := t.ti + t.cells.Resize(0, 0) + t.TPuts(ti.ShowCursor) + if t.cursorStyles != nil && t.cursorStyle != CursorStyleDefault { + t.TPuts(t.cursorStyles[CursorStyleDefault]) + } + t.TPuts(ti.ResetFgBg) + t.TPuts(ti.AttrOff) + t.TPuts(ti.ExitKeypad) + t.TPuts(ti.EnableAutoMargin) + if os.Getenv("TCELL_ALTSCREEN") != "disable" { + t.TPuts(ti.Clear) // only needed if ExitCA is empty + t.TPuts(ti.ExitCA) + } + t.enableMouse(0) + t.enablePasting(false) + t.disableFocusReporting() + + _ = t.tty.Stop() +} + +// Beep emits a beep to the terminal. +func (t *tScreen) Beep() error { + t.writeString(string(byte(7))) + return nil +} + +// finalize is used to at application shutdown, and restores the terminal +// to it's initial state. It should not be called more than once. +func (t *tScreen) finalize() { + t.disengage() + _ = t.tty.Close() +} + +func (t *tScreen) StopQ() <-chan struct{} { + return t.quit +} + +func (t *tScreen) EventQ() chan Event { + return t.eventQ +} + +func (t *tScreen) GetCells() *CellBuffer { + return &t.cells +} diff --git a/vendor/github.com/gdamore/tcell/v2/tscreen_stub.go b/vendor/github.com/gdamore/tcell/v2/tscreen_stub.go new file mode 100644 index 0000000..0e4deae --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/tscreen_stub.go @@ -0,0 +1,32 @@ +//go:build plan9 || windows +// +build plan9 windows + +// Copyright 2022 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +// NB: We might someday wish to move Windows to this model. However, +// that would probably mean sacrificing some of the richer key reporting +// that we can obtain with the console API present on Windows. + +func (t *tScreen) initialize() error { + if t.tty == nil { + return ErrNoScreen + } + // If a tty was supplied (custom), it should work. + // Custom screen implementations will need to provide a TTY + // implementation that we can use. + return nil +} diff --git a/vendor/github.com/gdamore/tcell/v2/tscreen_unix.go b/vendor/github.com/gdamore/tcell/v2/tscreen_unix.go new file mode 100644 index 0000000..84727f8 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/tscreen_unix.go @@ -0,0 +1,32 @@ +// Copyright 2021 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos + +package tcell + +// initialize is used at application startup, and sets up the initial values +// including file descriptors used for terminals and saving the initial state +// so that it can be restored when the application terminates. +func (t *tScreen) initialize() error { + var err error + if t.tty == nil { + t.tty, err = NewDevTty() + if err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/gdamore/tcell/v2/tty.go b/vendor/github.com/gdamore/tcell/v2/tty.go new file mode 100644 index 0000000..8bb1ac5 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/tty.go @@ -0,0 +1,56 @@ +// Copyright 2021 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +import "io" + +// Tty is an abstraction of a tty (traditionally "teletype"). This allows applications to +// provide for alternate backends, as there are situations where the traditional /dev/tty +// does not work, or where more flexible handling is required. This interface is for use +// with the terminfo-style based API. It extends the io.ReadWriter API. It is reasonable +// that the implementation might choose to use different underlying files for the Reader +// and Writer sides of this API, as part of it's internal implementation. +type Tty interface { + // Start is used to activate the Tty for use. Upon return the terminal should be + // in raw mode, non-blocking, etc. The implementation should take care of saving + // any state that is required so that it may be restored when Stop is called. + Start() error + + // Stop is used to stop using this Tty instance. This may be a suspend, so that other + // terminal based applications can run in the foreground. Implementations should + // restore any state collected at Start(), and return to ordinary blocking mode, etc. + // Drain is called first to drain the input. Once this is called, no more Read + // or Write calls will be made until Start is called again. + Stop() error + + // Drain is called before Stop, and ensures that the reader will wake up appropriately + // if it was blocked. This workaround is required for /dev/tty on certain UNIX systems + // to ensure that Read() does not block forever. This typically arranges for the tty driver + // to send data immediately (e.g. VMIN and VTIME both set zero) and sets a deadline on input. + // Implementations may reasonably make this a no-op. There will still be control sequences + // emitted between the time this is called, and when Stop is called. + Drain() error + + // NotifyResize is used register a callback when the tty thinks the dimensions have + // changed. The standard UNIX implementation links this to a handler for SIGWINCH. + // If the supplied callback is nil, then any handler should be unregistered. + NotifyResize(cb func()) + + // WindowSize is called to determine the terminal dimensions. This might be determined + // by an ioctl or other means. + WindowSize() (WindowSize, error) + + io.ReadWriteCloser +} diff --git a/vendor/github.com/gdamore/tcell/v2/tty_unix.go b/vendor/github.com/gdamore/tcell/v2/tty_unix.go new file mode 100644 index 0000000..ca82d83 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/tty_unix.go @@ -0,0 +1,198 @@ +// Copyright 2021 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos + +package tcell + +import ( + "errors" + "fmt" + "os" + "os/signal" + "strconv" + "sync" + "syscall" + "time" + + "golang.org/x/sys/unix" + "golang.org/x/term" +) + +// devTty is an implementation of the Tty API based upon /dev/tty. +type devTty struct { + fd int + f *os.File + of *os.File // the first open of /dev/tty + saved *term.State + sig chan os.Signal + cb func() + stopQ chan struct{} + dev string + wg sync.WaitGroup + l sync.Mutex +} + +func (tty *devTty) Read(b []byte) (int, error) { + return tty.f.Read(b) +} + +func (tty *devTty) Write(b []byte) (int, error) { + return tty.f.Write(b) +} + +func (tty *devTty) Close() error { + return tty.f.Close() +} + +func (tty *devTty) Start() error { + tty.l.Lock() + defer tty.l.Unlock() + + // We open another copy of /dev/tty. This is a workaround for unusual behavior + // observed in macOS, apparently caused when a subshell (for example) closes our + // own tty device (when it exits for example). Getting a fresh new one seems to + // resolve the problem. (We believe this is a bug in the macOS tty driver that + // fails to account for dup() references to the same file before applying close() + // related behaviors to the tty.) We're also holding the original copy we opened + // since closing that might have deleterious effects as well. The upshot is that + // we will have up to two separate file handles open on /dev/tty. (Note that when + // using stdin/stdout instead of /dev/tty this problem is not observed.) + var err error + if tty.f, err = os.OpenFile(tty.dev, os.O_RDWR, 0); err != nil { + return err + } + + if !term.IsTerminal(tty.fd) { + return errors.New("device is not a terminal") + } + + _ = tty.f.SetReadDeadline(time.Time{}) + saved, err := term.MakeRaw(tty.fd) // also sets vMin and vTime + if err != nil { + return err + } + tty.saved = saved + + tty.stopQ = make(chan struct{}) + tty.wg.Add(1) + go func(stopQ chan struct{}) { + defer tty.wg.Done() + for { + select { + case <-tty.sig: + tty.l.Lock() + cb := tty.cb + tty.l.Unlock() + if cb != nil { + cb() + } + case <-stopQ: + return + } + } + }(tty.stopQ) + + signal.Notify(tty.sig, syscall.SIGWINCH) + return nil +} + +func (tty *devTty) Drain() error { + _ = tty.f.SetReadDeadline(time.Now()) + if err := tcSetBufParams(tty.fd, 0, 0); err != nil { + return err + } + return nil +} + +func (tty *devTty) Stop() error { + tty.l.Lock() + if err := term.Restore(tty.fd, tty.saved); err != nil { + tty.l.Unlock() + return err + } + _ = tty.f.SetReadDeadline(time.Now()) + + signal.Stop(tty.sig) + close(tty.stopQ) + tty.l.Unlock() + + tty.wg.Wait() + + // close our tty device -- we'll get another one if we Start again later. + _ = tty.f.Close() + + return nil +} + +func (tty *devTty) WindowSize() (WindowSize, error) { + size := WindowSize{} + ws, err := unix.IoctlGetWinsize(tty.fd, unix.TIOCGWINSZ) + if err != nil { + return size, err + } + w := int(ws.Col) + h := int(ws.Row) + if w == 0 { + w, _ = strconv.Atoi(os.Getenv("COLUMNS")) + } + if w == 0 { + w = 80 // default + } + if h == 0 { + h, _ = strconv.Atoi(os.Getenv("LINES")) + } + if h == 0 { + h = 25 // default + } + size.Width = w + size.Height = h + size.PixelWidth = int(ws.Xpixel) + size.PixelHeight = int(ws.Ypixel) + return size, nil +} + +func (tty *devTty) NotifyResize(cb func()) { + tty.l.Lock() + tty.cb = cb + tty.l.Unlock() +} + +// NewDevTty opens a /dev/tty based Tty. +func NewDevTty() (Tty, error) { + return NewDevTtyFromDev("/dev/tty") +} + +// NewDevTtyFromDev opens a tty device given a path. This can be useful to bind to other nodes. +func NewDevTtyFromDev(dev string) (Tty, error) { + tty := &devTty{ + dev: dev, + sig: make(chan os.Signal), + } + var err error + if tty.of, err = os.OpenFile(dev, os.O_RDWR, 0); err != nil { + return nil, err + } + tty.fd = int(tty.of.Fd()) + if !term.IsTerminal(tty.fd) { + _ = tty.f.Close() + return nil, errors.New("not a terminal") + } + if tty.saved, err = term.GetState(tty.fd); err != nil { + _ = tty.f.Close() + return nil, fmt.Errorf("failed to get state: %w", err) + } + return tty, nil +} diff --git a/vendor/github.com/gdamore/tcell/v2/wscreen.go b/vendor/github.com/gdamore/tcell/v2/wscreen.go new file mode 100644 index 0000000..137968c --- /dev/null +++ b/vendor/github.com/gdamore/tcell/v2/wscreen.go @@ -0,0 +1,649 @@ +// Copyright 2023 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build js && wasm +// +build js,wasm + +package tcell + +import ( + "errors" + "github.com/gdamore/tcell/v2/terminfo" + "strings" + "sync" + "syscall/js" + "unicode/utf8" +) + +func NewTerminfoScreen() (Screen, error) { + t := &wScreen{} + t.fallback = make(map[rune]string) + + return &baseScreen{screenImpl: t}, nil +} + +type wScreen struct { + w, h int + style Style + cells CellBuffer + + running bool + clear bool + flagsPresent bool + pasteEnabled bool + mouseFlags MouseFlags + + cursorStyle CursorStyle + + quit chan struct{} + evch chan Event + fallback map[rune]string + finiOnce sync.Once + + sync.Mutex +} + +func (t *wScreen) Init() error { + t.w, t.h = 80, 24 // default for html as of now + t.evch = make(chan Event, 10) + t.quit = make(chan struct{}) + + t.Lock() + t.running = true + t.style = StyleDefault + t.cells.Resize(t.w, t.h) + t.Unlock() + + js.Global().Set("onKeyEvent", js.FuncOf(t.onKeyEvent)) + + return nil +} + +func (t *wScreen) Fini() { + t.finiOnce.Do(func() { + close(t.quit) + }) +} + +func (t *wScreen) SetStyle(style Style) { + t.Lock() + t.style = style + t.Unlock() +} + +// paletteColor gives a more natural palette color actually matching +// typical XTerm. We might in the future want to permit styling these +// via CSS. + +var palette = map[Color]int32{ + ColorBlack: 0x000000, + ColorMaroon: 0xcd0000, + ColorGreen: 0x00cd00, + ColorOlive: 0xcdcd00, + ColorNavy: 0x0000ee, + ColorPurple: 0xcd00cd, + ColorTeal: 0x00cdcd, + ColorSilver: 0xe5e5e5, + ColorGray: 0x7f7f7f, + ColorRed: 0xff0000, + ColorLime: 0x00ff00, + ColorYellow: 0xffff00, + ColorBlue: 0x5c5cff, + ColorFuchsia: 0xff00ff, + ColorAqua: 0x00ffff, + ColorWhite: 0xffffff, +} + +func paletteColor(c Color) int32 { + if c.IsRGB() { + return int32(c & 0xffffff) + } + if c >= ColorBlack && c <= ColorWhite { + return palette[c] + } + return c.Hex() +} + +func (t *wScreen) drawCell(x, y int) int { + mainc, combc, style, width := t.cells.GetContent(x, y) + + if !t.cells.Dirty(x, y) { + return width + } + + if style == StyleDefault { + style = t.style + } + + fg, bg := paletteColor(style.fg), paletteColor(style.bg) + if fg == -1 { + fg = 0xe5e5e5 + } + if bg == -1 { + bg = 0x000000 + } + + var combcarr []interface{} = make([]interface{}, len(combc)) + for i, c := range combc { + combcarr[i] = c + } + + t.cells.SetDirty(x, y, false) + js.Global().Call("drawCell", x, y, mainc, combcarr, fg, bg, int(style.attrs)) + + return width +} + +func (t *wScreen) ShowCursor(x, y int) { + t.Lock() + js.Global().Call("showCursor", x, y) + t.Unlock() +} + +func (t *wScreen) SetCursorStyle(cs CursorStyle) { + t.Lock() + js.Global().Call("setCursorStyle", curStyleClasses[cs]) + t.Unlock() +} + +func (t *wScreen) HideCursor() { + t.ShowCursor(-1, -1) +} + +func (t *wScreen) Show() { + t.Lock() + t.resize() + t.draw() + t.Unlock() +} + +func (t *wScreen) clearScreen() { + js.Global().Call("clearScreen", t.style.fg.Hex(), t.style.bg.Hex()) + t.clear = false +} + +func (t *wScreen) draw() { + if t.clear { + t.clearScreen() + } + + for y := 0; y < t.h; y++ { + for x := 0; x < t.w; x++ { + width := t.drawCell(x, y) + x += width - 1 + } + } + + js.Global().Call("show") +} + +func (t *wScreen) EnableMouse(flags ...MouseFlags) { + var f MouseFlags + flagsPresent := false + for _, flag := range flags { + f |= flag + flagsPresent = true + } + if !flagsPresent { + f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents + } + + t.Lock() + t.mouseFlags = f + t.enableMouse(f) + t.Unlock() +} + +func (t *wScreen) enableMouse(f MouseFlags) { + if f&MouseButtonEvents != 0 { + js.Global().Set("onMouseClick", js.FuncOf(t.onMouseEvent)) + } else { + js.Global().Set("onMouseClick", js.FuncOf(t.unset)) + } + + if f&MouseDragEvents != 0 || f&MouseMotionEvents != 0 { + js.Global().Set("onMouseMove", js.FuncOf(t.onMouseEvent)) + } else { + js.Global().Set("onMouseMove", js.FuncOf(t.unset)) + } +} + +func (t *wScreen) DisableMouse() { + t.Lock() + t.mouseFlags = 0 + t.enableMouse(0) + t.Unlock() +} + +func (t *wScreen) EnablePaste() { + t.Lock() + t.pasteEnabled = true + t.enablePasting(true) + t.Unlock() +} + +func (t *wScreen) DisablePaste() { + t.Lock() + t.pasteEnabled = false + t.enablePasting(false) + t.Unlock() +} + +func (t *wScreen) enablePasting(on bool) { + if on { + js.Global().Set("onPaste", js.FuncOf(t.onPaste)) + } else { + js.Global().Set("onPaste", js.FuncOf(t.unset)) + } +} + +func (t *wScreen) EnableFocus() { + t.Lock() + js.Global().Set("onFocus", js.FuncOf(t.onFocus)) + t.Unlock() +} + +func (t *wScreen) DisableFocus() { + t.Lock() + js.Global().Set("onFocus", js.FuncOf(t.unset)) + t.Unlock() +} + +func (t *wScreen) Size() (int, int) { + t.Lock() + w, h := t.w, t.h + t.Unlock() + return w, h +} + +// resize does nothing, as asking the web window to resize +// without a specified width or height will cause no change. +func (t *wScreen) resize() {} + +func (t *wScreen) Colors() int { + return 16777216 // 256 ^ 3 +} + +func (t *wScreen) clip(x, y int) (int, int) { + w, h := t.cells.Size() + if x < 0 { + x = 0 + } + if y < 0 { + y = 0 + } + if x > w-1 { + x = w - 1 + } + if y > h-1 { + y = h - 1 + } + return x, y +} + +func (t *wScreen) postEvent(ev Event) { + select { + case t.evch <- ev: + case <-t.quit: + } +} + +func (t *wScreen) onMouseEvent(this js.Value, args []js.Value) interface{} { + mod := ModNone + button := ButtonNone + + switch args[2].Int() { + case 0: + if t.mouseFlags&MouseMotionEvents == 0 { + // don't want this event! is a mouse motion event, but user has asked not. + return nil + } + button = ButtonNone + case 1: + button = Button1 + case 2: + button = Button3 // Note we prefer to treat right as button 2 + case 3: + button = Button2 // And the middle button as button 3 + } + + if args[3].Bool() { // mod shift + mod |= ModShift + } + + if args[4].Bool() { // mod alt + mod |= ModAlt + } + + if args[5].Bool() { // mod ctrl + mod |= ModCtrl + } + + t.postEvent(NewEventMouse(args[0].Int(), args[1].Int(), button, mod)) + return nil +} + +func (t *wScreen) onKeyEvent(this js.Value, args []js.Value) interface{} { + key := args[0].String() + + // don't accept any modifier keys as their own + if key == "Control" || key == "Alt" || key == "Meta" || key == "Shift" { + return nil + } + + mod := ModNone + if args[1].Bool() { // mod shift + mod |= ModShift + } + + if args[2].Bool() { // mod alt + mod |= ModAlt + } + + if args[3].Bool() { // mod ctrl + mod |= ModCtrl + } + + if args[4].Bool() { // mod meta + mod |= ModMeta + } + + // check for special case of Ctrl + key + if mod == ModCtrl { + if k, ok := WebKeyNames["Ctrl-"+strings.ToLower(key)]; ok { + t.postEvent(NewEventKey(k, 0, mod)) + return nil + } + } + + // next try function keys + if k, ok := WebKeyNames[key]; ok { + t.postEvent(NewEventKey(k, 0, mod)) + return nil + } + + // finally try normal, printable chars + r, _ := utf8.DecodeRuneInString(key) + t.postEvent(NewEventKey(KeyRune, r, mod)) + return nil +} + +func (t *wScreen) onPaste(this js.Value, args []js.Value) interface{} { + t.postEvent(NewEventPaste(args[0].Bool())) + return nil +} + +func (t *wScreen) onFocus(this js.Value, args []js.Value) interface{} { + t.postEvent(NewEventFocus(args[0].Bool())) + return nil +} + +// unset is a dummy function for js when we want nothing to +// happen when javascript calls a function (for example, when +// mouse input is disabled, when onMouseEvent() is called from +// js, it redirects here and does nothing). +func (t *wScreen) unset(this js.Value, args []js.Value) interface{} { + return nil +} + +func (t *wScreen) Sync() { + t.Lock() + t.resize() + t.clear = true + t.cells.Invalidate() + t.draw() + t.Unlock() +} + +func (t *wScreen) CharacterSet() string { + return "UTF-8" +} + +func (t *wScreen) RegisterRuneFallback(orig rune, fallback string) { + t.Lock() + t.fallback[orig] = fallback + t.Unlock() +} + +func (t *wScreen) UnregisterRuneFallback(orig rune) { + t.Lock() + delete(t.fallback, orig) + t.Unlock() +} + +func (t *wScreen) CanDisplay(r rune, checkFallbacks bool) bool { + if utf8.ValidRune(r) { + return true + } + if !checkFallbacks { + return false + } + if _, ok := t.fallback[r]; ok { + return true + } + return false +} + +func (t *wScreen) HasMouse() bool { + return true +} + +func (t *wScreen) HasKey(k Key) bool { + return true +} + +func (t *wScreen) SetSize(w, h int) { + if w == t.w && h == t.h { + return + } + + t.cells.Invalidate() + t.cells.Resize(w, h) + js.Global().Call("resize", w, h) + t.w, t.h = w, h + t.postEvent(NewEventResize(w, h)) +} + +func (t *wScreen) Resize(int, int, int, int) {} + +// Suspend simply pauses all input and output, and clears the screen. +// There isn't a "default terminal" to go back to. +func (t *wScreen) Suspend() error { + t.Lock() + if !t.running { + t.Unlock() + return nil + } + t.running = false + t.clearScreen() + t.enableMouse(0) + t.enablePasting(false) + js.Global().Set("onKeyEvent", js.FuncOf(t.unset)) // stop keypresses + return nil +} + +func (t *wScreen) Resume() error { + t.Lock() + + if t.running { + return errors.New("already engaged") + } + t.running = true + + t.enableMouse(t.mouseFlags) + t.enablePasting(t.pasteEnabled) + + js.Global().Set("onKeyEvent", js.FuncOf(t.onKeyEvent)) + + t.Unlock() + return nil +} + +func (t *wScreen) Beep() error { + js.Global().Call("beep") + return nil +} + +func (t *wScreen) Tty() (Tty, bool) { + return nil, false +} + +func (t *wScreen) GetCells() *CellBuffer { + return &t.cells +} + +func (t *wScreen) EventQ() chan Event { + return t.evch +} + +func (t *wScreen) StopQ() <-chan struct{} { + return t.quit +} + +// WebKeyNames maps string names reported from HTML +// (KeyboardEvent.key) to tcell accepted keys. +var WebKeyNames = map[string]Key{ + "Enter": KeyEnter, + "Backspace": KeyBackspace, + "Tab": KeyTab, + "Backtab": KeyBacktab, + "Escape": KeyEsc, + "Backspace2": KeyBackspace2, + "Delete": KeyDelete, + "Insert": KeyInsert, + "ArrowUp": KeyUp, + "ArrowDown": KeyDown, + "ArrowLeft": KeyLeft, + "ArrowRight": KeyRight, + "Home": KeyHome, + "End": KeyEnd, + "UpLeft": KeyUpLeft, // not supported by HTML + "UpRight": KeyUpRight, // not supported by HTML + "DownLeft": KeyDownLeft, // not supported by HTML + "DownRight": KeyDownRight, // not supported by HTML + "Center": KeyCenter, + "PgDn": KeyPgDn, + "PgUp": KeyPgUp, + "Clear": KeyClear, + "Exit": KeyExit, + "Cancel": KeyCancel, + "Pause": KeyPause, + "Print": KeyPrint, + "F1": KeyF1, + "F2": KeyF2, + "F3": KeyF3, + "F4": KeyF4, + "F5": KeyF5, + "F6": KeyF6, + "F7": KeyF7, + "F8": KeyF8, + "F9": KeyF9, + "F10": KeyF10, + "F11": KeyF11, + "F12": KeyF12, + "F13": KeyF13, + "F14": KeyF14, + "F15": KeyF15, + "F16": KeyF16, + "F17": KeyF17, + "F18": KeyF18, + "F19": KeyF19, + "F20": KeyF20, + "F21": KeyF21, + "F22": KeyF22, + "F23": KeyF23, + "F24": KeyF24, + "F25": KeyF25, + "F26": KeyF26, + "F27": KeyF27, + "F28": KeyF28, + "F29": KeyF29, + "F30": KeyF30, + "F31": KeyF31, + "F32": KeyF32, + "F33": KeyF33, + "F34": KeyF34, + "F35": KeyF35, + "F36": KeyF36, + "F37": KeyF37, + "F38": KeyF38, + "F39": KeyF39, + "F40": KeyF40, + "F41": KeyF41, + "F42": KeyF42, + "F43": KeyF43, + "F44": KeyF44, + "F45": KeyF45, + "F46": KeyF46, + "F47": KeyF47, + "F48": KeyF48, + "F49": KeyF49, + "F50": KeyF50, + "F51": KeyF51, + "F52": KeyF52, + "F53": KeyF53, + "F54": KeyF54, + "F55": KeyF55, + "F56": KeyF56, + "F57": KeyF57, + "F58": KeyF58, + "F59": KeyF59, + "F60": KeyF60, + "F61": KeyF61, + "F62": KeyF62, + "F63": KeyF63, + "F64": KeyF64, + "Ctrl-a": KeyCtrlA, // not reported by HTML- need to do special check + "Ctrl-b": KeyCtrlB, // not reported by HTML- need to do special check + "Ctrl-c": KeyCtrlC, // not reported by HTML- need to do special check + "Ctrl-d": KeyCtrlD, // not reported by HTML- need to do special check + "Ctrl-e": KeyCtrlE, // not reported by HTML- need to do special check + "Ctrl-f": KeyCtrlF, // not reported by HTML- need to do special check + "Ctrl-g": KeyCtrlG, // not reported by HTML- need to do special check + "Ctrl-j": KeyCtrlJ, // not reported by HTML- need to do special check + "Ctrl-k": KeyCtrlK, // not reported by HTML- need to do special check + "Ctrl-l": KeyCtrlL, // not reported by HTML- need to do special check + "Ctrl-n": KeyCtrlN, // not reported by HTML- need to do special check + "Ctrl-o": KeyCtrlO, // not reported by HTML- need to do special check + "Ctrl-p": KeyCtrlP, // not reported by HTML- need to do special check + "Ctrl-q": KeyCtrlQ, // not reported by HTML- need to do special check + "Ctrl-r": KeyCtrlR, // not reported by HTML- need to do special check + "Ctrl-s": KeyCtrlS, // not reported by HTML- need to do special check + "Ctrl-t": KeyCtrlT, // not reported by HTML- need to do special check + "Ctrl-u": KeyCtrlU, // not reported by HTML- need to do special check + "Ctrl-v": KeyCtrlV, // not reported by HTML- need to do special check + "Ctrl-w": KeyCtrlW, // not reported by HTML- need to do special check + "Ctrl-x": KeyCtrlX, // not reported by HTML- need to do special check + "Ctrl-y": KeyCtrlY, // not reported by HTML- need to do special check + "Ctrl-z": KeyCtrlZ, // not reported by HTML- need to do special check + "Ctrl- ": KeyCtrlSpace, // not reported by HTML- need to do special check + "Ctrl-_": KeyCtrlUnderscore, // not reported by HTML- need to do special check + "Ctrl-]": KeyCtrlRightSq, // not reported by HTML- need to do special check + "Ctrl-\\": KeyCtrlBackslash, // not reported by HTML- need to do special check + "Ctrl-^": KeyCtrlCarat, // not reported by HTML- need to do special check +} + +var curStyleClasses = map[CursorStyle]string{ + CursorStyleDefault: "cursor-blinking-block", + CursorStyleBlinkingBlock: "cursor-blinking-block", + CursorStyleSteadyBlock: "cursor-steady-block", + CursorStyleBlinkingUnderline: "cursor-blinking-underline", + CursorStyleSteadyUnderline: "cursor-steady-underline", + CursorStyleBlinkingBar: "cursor-blinking-bar", + CursorStyleSteadyBar: "cursor-steady-bar", +} + +func LookupTerminfo(name string) (ti *terminfo.Terminfo, e error) { + return nil, errors.New("LookupTermInfo not supported") +} diff --git a/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md new file mode 100644 index 0000000..c88f9b2 --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# How to Contribute + +## Getting Started + +- Fork the repository on GitHub +- Read the [README](README.markdown) for build and test instructions +- Play with the project, submit bugs, submit patches! + +## Contribution Flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where you want to base your work (usually master). +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Make sure the tests pass, and add any new tests as appropriate. +- Submit a pull request to the original repository. + +Thanks for your contributions! + +### Format of the Commit Message + +We follow a rough convention for commit messages that is designed to answer two +questions: what changed and why. The subject line should feature the what and +the body of the commit should describe the why. + +``` +scripts: add the test-cluster command + +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +