├── .clang-format ├── .github └── workflows │ └── main.yml ├── AUTHORS ├── COMMITMENT ├── COPYING ├── MAINTAINERS ├── NEWS ├── README.md ├── RELEASE.md ├── contrib ├── ci │ ├── Dockerfile-debian │ ├── Dockerfile-fedora │ └── build_and_test.sh ├── generate-version-script.py ├── libgusb.spec.in └── org.freedesktop.GUsb.metainfo.xml ├── docs ├── libgusb.toml.in ├── meson.build └── urlmap_libgusb.js ├── gusb ├── gusb-bos-descriptor-private.h ├── gusb-bos-descriptor.c ├── gusb-bos-descriptor.h ├── gusb-context-private.h ├── gusb-context.c ├── gusb-context.h ├── gusb-device-event-private.h ├── gusb-device-event.c ├── gusb-device-event.h ├── gusb-device-list.c ├── gusb-device-list.h ├── gusb-device-private.h ├── gusb-device.c ├── gusb-device.h ├── gusb-endpoint-private.h ├── gusb-endpoint.c ├── gusb-endpoint.h ├── gusb-interface-private.h ├── gusb-interface.c ├── gusb-interface.h ├── gusb-json-common.c ├── gusb-json-common.h ├── gusb-private.h ├── gusb-self-test.c ├── gusb-source.c ├── gusb-source.h ├── gusb-umockdev-test.c ├── gusb-util.c ├── gusb-util.h ├── gusb-version.c ├── gusb-version.h.in ├── gusb.h ├── libgusb.ver ├── libgusb_meson.ver └── meson.build ├── meson.build ├── meson_options.txt └── tools ├── gusb-main.c └── meson.build /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: 'Align' 3 | AlignConsecutiveAssignments: 'false' 4 | AlignConsecutiveDeclarations: 'false' 5 | AlignConsecutiveMacros: 'true' 6 | AlignOperands: 'true' 7 | AlignTrailingComments: 'true' 8 | AllowAllArgumentsOnNextLine: 'false' 9 | AllowAllParametersOfDeclarationOnNextLine: 'false' 10 | AllowShortBlocksOnASingleLine: 'false' 11 | AllowShortCaseLabelsOnASingleLine: 'false' 12 | AllowShortFunctionsOnASingleLine: 'Inline' 13 | AllowShortIfStatementsOnASingleLine: 'false' 14 | AlwaysBreakAfterReturnType: 'All' 15 | BinPackParameters: 'false' 16 | BinPackArguments: 'false' 17 | BreakBeforeBraces: 'Linux' 18 | ColumnLimit: '100' 19 | DerivePointerAlignment: 'false' 20 | IndentCaseLabels: 'false' 21 | IndentWidth: '8' 22 | IncludeBlocks: 'Regroup' 23 | KeepEmptyLinesAtTheStartOfBlocks: 'false' 24 | MaxEmptyLinesToKeep: '1' 25 | PointerAlignment: 'Right' 26 | SortIncludes: 'true' 27 | SpaceAfterCStyleCast: 'false' 28 | SpaceBeforeAssignmentOperators : 'true' 29 | SpaceBeforeParens: 'ControlStatements' 30 | SpaceInEmptyParentheses: 'false' 31 | SpacesInSquareBrackets: 'false' 32 | TabWidth: '8' 33 | UseTab: 'Always' 34 | PenaltyBreakAssignment: '3' 35 | PenaltyBreakBeforeFirstCallParameter: '15' 36 | --- 37 | Language: 'Proto' 38 | --- 39 | Language: 'Cpp' 40 | IncludeCategories: 41 | - Regex: '^"config.h"$' 42 | Priority: '0' 43 | - Regex: '^<' 44 | Priority: '2' 45 | - Regex: '.*' 46 | Priority: '4' 47 | ... 48 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build-linux: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | distro: 16 | - fedora 17 | - debian 18 | fail-fast: false 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - run: docker build -t libgusb-${{ matrix.distro }} -f contrib/ci/Dockerfile-${{ matrix.distro }} . 23 | - run: docker run -t -v `pwd`:/build libgusb-${{ matrix.distro }} ./contrib/ci/build_and_test.sh -Dtests=false 24 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Richard Hughes 2 | Hans de Goede 3 | -------------------------------------------------------------------------------- /COMMITMENT: -------------------------------------------------------------------------------- 1 | Common Cure Rights Commitment, version 1.0 2 | 3 | Before filing or continuing to prosecute any legal proceeding or claim 4 | (other than a Defensive Action) arising from termination of a Covered 5 | License, we commit to extend to the person or entity ('you') accused 6 | of violating the Covered License the following provisions regarding 7 | cure and reinstatement, taken from GPL version 3. As used here, the 8 | term 'this License' refers to the specific Covered License being 9 | enforced. 10 | 11 | However, if you cease all violation of this License, then your 12 | license from a particular copyright holder is reinstated (a) 13 | provisionally, unless and until the copyright holder explicitly 14 | and finally terminates your license, and (b) permanently, if the 15 | copyright holder fails to notify you of the violation by some 16 | reasonable means prior to 60 days after the cessation. 17 | 18 | Moreover, your license from a particular copyright holder is 19 | reinstated permanently if the copyright holder notifies you of the 20 | violation by some reasonable means, this is the first time you 21 | have received notice of violation of this License (for any work) 22 | from that copyright holder, and you cure the violation prior to 30 23 | days after your receipt of the notice. 24 | 25 | We intend this Commitment to be irrevocable, and binding and 26 | enforceable against us and assignees of or successors to our 27 | copyrights. 28 | 29 | Definitions 30 | 31 | 'Covered License' means the GNU General Public License, version 2 32 | (GPLv2), the GNU Lesser General Public License, version 2.1 33 | (LGPLv2.1), or the GNU Library General Public License, version 2 34 | (LGPLv2), all as published by the Free Software Foundation. 35 | 36 | 'Defensive Action' means a legal proceeding or claim that We bring 37 | against you in response to a prior proceeding or claim initiated by 38 | you or your affiliate. 39 | 40 | 'We' means each contributor to this repository as of the date of 41 | inclusion of this file, including subsidiaries of a corporate 42 | contributor. 43 | 44 | This work is available under a Creative Commons Attribution-ShareAlike 45 | 4.0 International license. 46 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | GNU LESSER GENERAL PUBLIC LICENSE 3 | Version 2.1, February 1999 4 | 5 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 6 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | [This is the first released version of the Lesser GPL. It also counts 11 | as the successor of the GNU Library Public License, version 2, hence 12 | the version number 2.1.] 13 | 14 | Preamble 15 | 16 | The licenses for most software are designed to take away your 17 | freedom to share and change it. By contrast, the GNU General Public 18 | Licenses are intended to guarantee your freedom to share and change 19 | free software--to make sure the software is free for all its users. 20 | 21 | This license, the Lesser General Public License, applies to some 22 | specially designated software packages--typically libraries--of the 23 | Free Software Foundation and other authors who decide to use it. You 24 | can use it too, but we suggest you first think carefully about whether 25 | this license or the ordinary General Public License is the better 26 | strategy to use in any particular case, based on the explanations 27 | below. 28 | 29 | When we speak of free software, we are referring to freedom of use, 30 | not price. Our General Public Licenses are designed to make sure that 31 | you have the freedom to distribute copies of free software (and charge 32 | for this service if you wish); that you receive source code or can get 33 | it if you want it; that you can change the software and use pieces of 34 | it in new free programs; and that you are informed that you can do 35 | these things. 36 | 37 | To protect your rights, we need to make restrictions that forbid 38 | distributors to deny you these rights or to ask you to surrender these 39 | rights. These restrictions translate to certain responsibilities for 40 | you if you distribute copies of the library or if you modify it. 41 | 42 | For example, if you distribute copies of the library, whether gratis 43 | or for a fee, you must give the recipients all the rights that we gave 44 | you. You must make sure that they, too, receive or can get the source 45 | code. If you link other code with the library, you must provide 46 | complete object files to the recipients, so that they can relink them 47 | with the library after making changes to the library and recompiling 48 | it. And you must show them these terms so they know their rights. 49 | 50 | We protect your rights with a two-step method: (1) we copyright the 51 | library, and (2) we offer you this license, which gives you legal 52 | permission to copy, distribute and/or modify the library. 53 | 54 | To protect each distributor, we want to make it very clear that 55 | there is no warranty for the free library. Also, if the library is 56 | modified by someone else and passed on, the recipients should know 57 | that what they have is not the original version, so that the original 58 | author's reputation will not be affected by problems that might be 59 | introduced by others. 60 | 61 | Finally, software patents pose a constant threat to the existence of 62 | any free program. We wish to make sure that a company cannot 63 | effectively restrict the users of a free program by obtaining a 64 | restrictive license from a patent holder. Therefore, we insist that 65 | any patent license obtained for a version of the library must be 66 | consistent with the full freedom of use specified in this license. 67 | 68 | Most GNU software, including some libraries, is covered by the 69 | ordinary GNU General Public License. This license, the GNU Lesser 70 | General Public License, applies to certain designated libraries, and 71 | is quite different from the ordinary General Public License. We use 72 | this license for certain libraries in order to permit linking those 73 | libraries into non-free programs. 74 | 75 | When a program is linked with a library, whether statically or using 76 | a shared library, the combination of the two is legally speaking a 77 | combined work, a derivative of the original library. The ordinary 78 | General Public License therefore permits such linking only if the 79 | entire combination fits its criteria of freedom. The Lesser General 80 | Public License permits more lax criteria for linking other code with 81 | the library. 82 | 83 | We call this license the "Lesser" General Public License because it 84 | does Less to protect the user's freedom than the ordinary General 85 | Public License. It also provides other free software developers Less 86 | of an advantage over competing non-free programs. These disadvantages 87 | are the reason we use the ordinary General Public License for many 88 | libraries. However, the Lesser license provides advantages in certain 89 | special circumstances. 90 | 91 | For example, on rare occasions, there may be a special need to 92 | encourage the widest possible use of a certain library, so that it 93 | becomes a de-facto standard. To achieve this, non-free programs must 94 | be allowed to use the library. A more frequent case is that a free 95 | library does the same job as widely used non-free libraries. In this 96 | case, there is little to gain by limiting the free library to free 97 | software only, so we use the Lesser General Public License. 98 | 99 | In other cases, permission to use a particular library in non-free 100 | programs enables a greater number of people to use a large body of 101 | free software. For example, permission to use the GNU C Library in 102 | non-free programs enables many more people to use the whole GNU 103 | operating system, as well as its variant, the GNU/Linux operating 104 | system. 105 | 106 | Although the Lesser General Public License is Less protective of the 107 | users' freedom, it does ensure that the user of a program that is 108 | linked with the Library has the freedom and the wherewithal to run 109 | that program using a modified version of the Library. 110 | 111 | The precise terms and conditions for copying, distribution and 112 | modification follow. Pay close attention to the difference between a 113 | "work based on the library" and a "work that uses the library". The 114 | former contains code derived from the library, whereas the latter must 115 | be combined with the library in order to run. 116 | 117 | GNU LESSER GENERAL PUBLIC LICENSE 118 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 119 | 120 | 0. This License Agreement applies to any software library or other 121 | program which contains a notice placed by the copyright holder or 122 | other authorized party saying it may be distributed under the terms of 123 | this Lesser General Public License (also called "this License"). 124 | Each licensee is addressed as "you". 125 | 126 | A "library" means a collection of software functions and/or data 127 | prepared so as to be conveniently linked with application programs 128 | (which use some of those functions and data) to form executables. 129 | 130 | The "Library", below, refers to any such software library or work 131 | which has been distributed under these terms. A "work based on the 132 | Library" means either the Library or any derivative work under 133 | copyright law: that is to say, a work containing the Library or a 134 | portion of it, either verbatim or with modifications and/or translated 135 | straightforwardly into another language. (Hereinafter, translation is 136 | included without limitation in the term "modification".) 137 | 138 | "Source code" for a work means the preferred form of the work for 139 | making modifications to it. For a library, complete source code means 140 | all the source code for all modules it contains, plus any associated 141 | interface definition files, plus the scripts used to control 142 | compilation and installation of the library. 143 | 144 | Activities other than copying, distribution and modification are not 145 | covered by this License; they are outside its scope. The act of 146 | running a program using the Library is not restricted, and output from 147 | such a program is covered only if its contents constitute a work based 148 | on the Library (independent of the use of the Library in a tool for 149 | writing it). Whether that is true depends on what the Library does 150 | and what the program that uses the Library does. 151 | 152 | 1. You may copy and distribute verbatim copies of the Library's 153 | complete source code as you receive it, in any medium, provided that 154 | you conspicuously and appropriately publish on each copy an 155 | appropriate copyright notice and disclaimer of warranty; keep intact 156 | all the notices that refer to this License and to the absence of any 157 | warranty; and distribute a copy of this License along with the 158 | Library. 159 | 160 | You may charge a fee for the physical act of transferring a copy, 161 | and you may at your option offer warranty protection in exchange for a 162 | fee. 163 | 164 | 2. You may modify your copy or copies of the Library or any portion 165 | of it, thus forming a work based on the Library, and copy and 166 | distribute such modifications or work under the terms of Section 1 167 | above, provided that you also meet all of these conditions: 168 | 169 | a) The modified work must itself be a software library. 170 | 171 | b) You must cause the files modified to carry prominent notices 172 | stating that you changed the files and the date of any change. 173 | 174 | c) You must cause the whole of the work to be licensed at no 175 | charge to all third parties under the terms of this License. 176 | 177 | d) If a facility in the modified Library refers to a function or a 178 | table of data to be supplied by an application program that uses 179 | the facility, other than as an argument passed when the facility 180 | is invoked, then you must make a good faith effort to ensure that, 181 | in the event an application does not supply such function or 182 | table, the facility still operates, and performs whatever part of 183 | its purpose remains meaningful. 184 | 185 | (For example, a function in a library to compute square roots has 186 | a purpose that is entirely well-defined independent of the 187 | application. Therefore, Subsection 2d requires that any 188 | application-supplied function or table used by this function must 189 | be optional: if the application does not supply it, the square 190 | root function must still compute square roots.) 191 | 192 | These requirements apply to the modified work as a whole. If 193 | identifiable sections of that work are not derived from the Library, 194 | and can be reasonably considered independent and separate works in 195 | themselves, then this License, and its terms, do not apply to those 196 | sections when you distribute them as separate works. But when you 197 | distribute the same sections as part of a whole which is a work based 198 | on the Library, the distribution of the whole must be on the terms of 199 | this License, whose permissions for other licensees extend to the 200 | entire whole, and thus to each and every part regardless of who wrote 201 | it. 202 | 203 | Thus, it is not the intent of this section to claim rights or contest 204 | your rights to work written entirely by you; rather, the intent is to 205 | exercise the right to control the distribution of derivative or 206 | collective works based on the Library. 207 | 208 | In addition, mere aggregation of another work not based on the Library 209 | with the Library (or with a work based on the Library) on a volume of 210 | a storage or distribution medium does not bring the other work under 211 | the scope of this License. 212 | 213 | 3. You may opt to apply the terms of the ordinary GNU General Public 214 | License instead of this License to a given copy of the Library. To do 215 | this, you must alter all the notices that refer to this License, so 216 | that they refer to the ordinary GNU General Public License, version 2, 217 | instead of to this License. (If a newer version than version 2 of the 218 | ordinary GNU General Public License has appeared, then you can specify 219 | that version instead if you wish.) Do not make any other change in 220 | these notices. 221 | 222 | Once this change is made in a given copy, it is irreversible for 223 | that copy, so the ordinary GNU General Public License applies to all 224 | subsequent copies and derivative works made from that copy. 225 | 226 | This option is useful when you wish to copy part of the code of 227 | the Library into a program that is not a library. 228 | 229 | 4. You may copy and distribute the Library (or a portion or 230 | derivative of it, under Section 2) in object code or executable form 231 | under the terms of Sections 1 and 2 above provided that you accompany 232 | it with the complete corresponding machine-readable source code, which 233 | must be distributed under the terms of Sections 1 and 2 above on a 234 | medium customarily used for software interchange. 235 | 236 | If distribution of object code is made by offering access to copy 237 | from a designated place, then offering equivalent access to copy the 238 | source code from the same place satisfies the requirement to 239 | distribute the source code, even though third parties are not 240 | compelled to copy the source along with the object code. 241 | 242 | 5. A program that contains no derivative of any portion of the 243 | Library, but is designed to work with the Library by being compiled or 244 | linked with it, is called a "work that uses the Library". Such a 245 | work, in isolation, is not a derivative work of the Library, and 246 | therefore falls outside the scope of this License. 247 | 248 | However, linking a "work that uses the Library" with the Library 249 | creates an executable that is a derivative of the Library (because it 250 | contains portions of the Library), rather than a "work that uses the 251 | library". The executable is therefore covered by this License. 252 | Section 6 states terms for distribution of such executables. 253 | 254 | When a "work that uses the Library" uses material from a header file 255 | that is part of the Library, the object code for the work may be a 256 | derivative work of the Library even though the source code is not. 257 | Whether this is true is especially significant if the work can be 258 | linked without the Library, or if the work is itself a library. The 259 | threshold for this to be true is not precisely defined by law. 260 | 261 | If such an object file uses only numerical parameters, data 262 | structure layouts and accessors, and small macros and small inline 263 | functions (ten lines or less in length), then the use of the object 264 | file is unrestricted, regardless of whether it is legally a derivative 265 | work. (Executables containing this object code plus portions of the 266 | Library will still fall under Section 6.) 267 | 268 | Otherwise, if the work is a derivative of the Library, you may 269 | distribute the object code for the work under the terms of Section 6. 270 | Any executables containing that work also fall under Section 6, 271 | whether or not they are linked directly with the Library itself. 272 | 273 | 6. As an exception to the Sections above, you may also combine or 274 | link a "work that uses the Library" with the Library to produce a 275 | work containing portions of the Library, and distribute that work 276 | under terms of your choice, provided that the terms permit 277 | modification of the work for the customer's own use and reverse 278 | engineering for debugging such modifications. 279 | 280 | You must give prominent notice with each copy of the work that the 281 | Library is used in it and that the Library and its use are covered by 282 | this License. You must supply a copy of this License. If the work 283 | during execution displays copyright notices, you must include the 284 | copyright notice for the Library among them, as well as a reference 285 | directing the user to the copy of this License. Also, you must do one 286 | of these things: 287 | 288 | a) Accompany the work with the complete corresponding 289 | machine-readable source code for the Library including whatever 290 | changes were used in the work (which must be distributed under 291 | Sections 1 and 2 above); and, if the work is an executable linked 292 | with the Library, with the complete machine-readable "work that 293 | uses the Library", as object code and/or source code, so that the 294 | user can modify the Library and then relink to produce a modified 295 | executable containing the modified Library. (It is understood 296 | that the user who changes the contents of definitions files in the 297 | Library will not necessarily be able to recompile the application 298 | to use the modified definitions.) 299 | 300 | b) Use a suitable shared library mechanism for linking with the 301 | Library. A suitable mechanism is one that (1) uses at run time a 302 | copy of the library already present on the user's computer system, 303 | rather than copying library functions into the executable, and (2) 304 | will operate properly with a modified version of the library, if 305 | the user installs one, as long as the modified version is 306 | interface-compatible with the version that the work was made with. 307 | 308 | c) Accompany the work with a written offer, valid for at least 309 | three years, to give the same user the materials specified in 310 | Subsection 6a, above, for a charge no more than the cost of 311 | performing this distribution. 312 | 313 | d) If distribution of the work is made by offering access to copy 314 | from a designated place, offer equivalent access to copy the above 315 | specified materials from the same place. 316 | 317 | e) Verify that the user has already received a copy of these 318 | materials or that you have already sent this user a copy. 319 | 320 | For an executable, the required form of the "work that uses the 321 | Library" must include any data and utility programs needed for 322 | reproducing the executable from it. However, as a special exception, 323 | the materials to be distributed need not include anything that is 324 | normally distributed (in either source or binary form) with the major 325 | components (compiler, kernel, and so on) of the operating system on 326 | which the executable runs, unless that component itself accompanies 327 | the executable. 328 | 329 | It may happen that this requirement contradicts the license 330 | restrictions of other proprietary libraries that do not normally 331 | accompany the operating system. Such a contradiction means you cannot 332 | use both them and the Library together in an executable that you 333 | distribute. 334 | 335 | 7. You may place library facilities that are a work based on the 336 | Library side-by-side in a single library together with other library 337 | facilities not covered by this License, and distribute such a combined 338 | library, provided that the separate distribution of the work based on 339 | the Library and of the other library facilities is otherwise 340 | permitted, and provided that you do these two things: 341 | 342 | a) Accompany the combined library with a copy of the same work 343 | based on the Library, uncombined with any other library 344 | facilities. This must be distributed under the terms of the 345 | Sections above. 346 | 347 | b) Give prominent notice with the combined library of the fact 348 | that part of it is a work based on the Library, and explaining 349 | where to find the accompanying uncombined form of the same work. 350 | 351 | 8. You may not copy, modify, sublicense, link with, or distribute 352 | the Library except as expressly provided under this License. Any 353 | attempt otherwise to copy, modify, sublicense, link with, or 354 | distribute the Library is void, and will automatically terminate your 355 | rights under this License. However, parties who have received copies, 356 | or rights, from you under this License will not have their licenses 357 | terminated so long as such parties remain in full compliance. 358 | 359 | 9. You are not required to accept this License, since you have not 360 | signed it. However, nothing else grants you permission to modify or 361 | distribute the Library or its derivative works. These actions are 362 | prohibited by law if you do not accept this License. Therefore, by 363 | modifying or distributing the Library (or any work based on the 364 | Library), you indicate your acceptance of this License to do so, and 365 | all its terms and conditions for copying, distributing or modifying 366 | the Library or works based on it. 367 | 368 | 10. Each time you redistribute the Library (or any work based on the 369 | Library), the recipient automatically receives a license from the 370 | original licensor to copy, distribute, link with or modify the Library 371 | subject to these terms and conditions. You may not impose any further 372 | restrictions on the recipients' exercise of the rights granted herein. 373 | You are not responsible for enforcing compliance by third parties with 374 | this License. 375 | 376 | 11. If, as a consequence of a court judgment or allegation of patent 377 | infringement or for any other reason (not limited to patent issues), 378 | conditions are imposed on you (whether by court order, agreement or 379 | otherwise) that contradict the conditions of this License, they do not 380 | excuse you from the conditions of this License. If you cannot 381 | distribute so as to satisfy simultaneously your obligations under this 382 | License and any other pertinent obligations, then as a consequence you 383 | may not distribute the Library at all. For example, if a patent 384 | license would not permit royalty-free redistribution of the Library by 385 | all those who receive copies directly or indirectly through you, then 386 | the only way you could satisfy both it and this License would be to 387 | refrain entirely from distribution of the Library. 388 | 389 | If any portion of this section is held invalid or unenforceable under 390 | any particular circumstance, the balance of the section is intended to 391 | apply, and the section as a whole is intended to apply in other 392 | circumstances. 393 | 394 | It is not the purpose of this section to induce you to infringe any 395 | patents or other property right claims or to contest validity of any 396 | such claims; this section has the sole purpose of protecting the 397 | integrity of the free software distribution system which is 398 | implemented by public license practices. Many people have made 399 | generous contributions to the wide range of software distributed 400 | through that system in reliance on consistent application of that 401 | system; it is up to the author/donor to decide if he or she is willing 402 | to distribute software through any other system and a licensee cannot 403 | impose that choice. 404 | 405 | This section is intended to make thoroughly clear what is believed to 406 | be a consequence of the rest of this License. 407 | 408 | 12. If the distribution and/or use of the Library is restricted in 409 | certain countries either by patents or by copyrighted interfaces, the 410 | original copyright holder who places the Library under this License 411 | may add an explicit geographical distribution limitation excluding those 412 | countries, so that distribution is permitted only in or among 413 | countries not thus excluded. In such case, this License incorporates 414 | the limitation as if written in the body of this License. 415 | 416 | 13. The Free Software Foundation may publish revised and/or new 417 | versions of the Lesser General Public License from time to time. 418 | Such new versions will be similar in spirit to the present version, 419 | but may differ in detail to address new problems or concerns. 420 | 421 | Each version is given a distinguishing version number. If the Library 422 | specifies a version number of this License which applies to it and 423 | "any later version", you have the option of following the terms and 424 | conditions either of that version or of any later version published by 425 | the Free Software Foundation. If the Library does not specify a 426 | license version number, you may choose any version ever published by 427 | the Free Software Foundation. 428 | 429 | 14. If you wish to incorporate parts of the Library into other free 430 | programs whose distribution conditions are incompatible with these, 431 | write to the author to ask for permission. For software which is 432 | copyrighted by the Free Software Foundation, write to the Free 433 | Software Foundation; we sometimes make exceptions for this. Our 434 | decision will be guided by the two goals of preserving the free status 435 | of all derivatives of our free software and of promoting the sharing 436 | and reuse of software generally. 437 | 438 | NO WARRANTY 439 | 440 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 441 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 442 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 443 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 444 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 445 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 446 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 447 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 448 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 449 | 450 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 451 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 452 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 453 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 454 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 455 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 456 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 457 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 458 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 459 | DAMAGES. 460 | 461 | END OF TERMS AND CONDITIONS 462 | 463 | How to Apply These Terms to Your New Libraries 464 | 465 | If you develop a new library, and you want it to be of the greatest 466 | possible use to the public, we recommend making it free software that 467 | everyone can redistribute and change. You can do so by permitting 468 | redistribution under these terms (or, alternatively, under the terms 469 | of the ordinary General Public License). 470 | 471 | To apply these terms, attach the following notices to the library. 472 | It is safest to attach them to the start of each source file to most 473 | effectively convey the exclusion of warranty; and each file should 474 | have at least the "copyright" line and a pointer to where the full 475 | notice is found. 476 | 477 | 478 | 479 | Copyright (C) 480 | 481 | This library is free software; you can redistribute it and/or 482 | modify it under the terms of the GNU Lesser General Public 483 | License as published by the Free Software Foundation; either 484 | version 2.1 of the License, or (at your option) any later version. 485 | 486 | This library is distributed in the hope that it will be useful, 487 | but WITHOUT ANY WARRANTY; without even the implied warranty of 488 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 489 | Lesser General Public License for more details. 490 | 491 | You should have received a copy of the GNU Lesser General Public 492 | License along with this library; if not, write to the Free Software 493 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 494 | 495 | Also add information on how to contact you by electronic and paper mail. 496 | 497 | You should also get your employer (if you work as a programmer) or 498 | your school, if any, to sign a "copyright disclaimer" for the library, 499 | if necessary. Here is a sample; alter the names: 500 | 501 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 502 | library `Frob' (a library for tweaking knobs) written by James 503 | Random Hacker. 504 | 505 | , 1 April 1990 506 | Ty Coon, President of Vice 507 | 508 | That's all there is to it! 509 | 510 | 511 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Richard Hughes 2 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Version 0.4.9 2 | ============= 3 | Released: 2024-04-22 4 | 5 | This release fixes the following bugs: 6 | * Allow reading HID descriptors larger than 255 bytes 7 | * Close the libusb handle if returning open failure 8 | * Fix a buffer over-read when emulating devices 9 | * Only request the BOS descriptor when bcdUSB is new enough 10 | 11 | Version 0.4.8 12 | ============= 13 | Released: 2023-11-07 14 | 15 | This release adds the following feature: 16 | * Add an device error code for 'busy' 17 | 18 | Version 0.4.7 19 | ============= 20 | Released: 2023-10-09 21 | 22 | This release adds the following features: 23 | * Add new API to get the HID descriptor data 24 | 25 | This release fixes the following bugs: 26 | * Add calling convention to libusb callback functions 27 | 28 | Version 0.4.6 29 | ============= 30 | Released: 2023-06-08 31 | 32 | This release fixes the following bugs: 33 | * Add JSON compatibility helpers for old json-glib versions 34 | * Fix emulation of devices using out-of-order BulkTransfer replies 35 | * Use project_source_root to fix building as a subproject 36 | 37 | Version 0.4.5 38 | ============= 39 | Released: 2023-02-08 40 | 41 | This release adds the following features: 42 | * Add new API to better detect emulated replug 43 | 44 | This release fixes the following bugs: 45 | * Do not emit spurious ::device-removed events when emulating 46 | * Record zero-sized device events 47 | * Reset the in-order event index when clearing the event array 48 | * Save the libusb error if libusb_submit_transfer() fails 49 | 50 | Version 0.4.4 51 | ============= 52 | Released: 2023-01-30 53 | 54 | This release adds the following features: 55 | * Add new API to make emulation easier to use 56 | 57 | This release fixes the following bugs: 58 | * Emit remove devices with matching tags when loading contexts 59 | * Fix a critical warning when finalizing devices without endpoints 60 | * Reduce the debugging level when emulating firmware 61 | 62 | Version 0.4.3 63 | ============= 64 | Released: 2022-12-20 65 | 66 | This release fixes the following bugs: 67 | * Export g_usb_device_has_tag() 68 | * Work around a libusb issue which causes devices to appear twice 69 | 70 | Version 0.4.2 71 | ============= 72 | Released: 2022-10-18 73 | 74 | This release fixes the following bugs: 75 | * Close the GUsbDevice objects in gusbcmd 76 | * Do not call the hotplug callback with the mutex held 77 | 78 | Version 0.4.1 79 | ============= 80 | Released: 2022-10-01 81 | 82 | This release adds the following features: 83 | * Allow loading and saving only devices with specific tags 84 | * Include removed devices in the JSON dump 85 | 86 | This release fixes the following bug: 87 | * Port from gtkdoc to gi-docgen 88 | 89 | Version 0.4.0 90 | ============= 91 | Released: 2022-09-12 92 | 93 | The JSON format used to serialising devices is not yet stable, and might 94 | change. 95 | 96 | This release adds the following features: 97 | * Add support for getting BOS descriptors 98 | * Allow emulating devices by saving and replaying device events 99 | * Cache the list of interfaces and descriptors 100 | * Serialize the GUsbDevice to and from JSON format 101 | 102 | This release fixes the following bugs: 103 | * Ignore usb.ids classes, subclasses and protocols 104 | * Never dispatch idle events after the context has been destroyed 105 | * Skip some functionality when emulating 106 | 107 | Version 0.3.10 108 | ============== 109 | Released: 2021-01-10 110 | 111 | This release adds the following feature: 112 | * Add API to change the hotplug poll interval 113 | 114 | Version 0.3.9 115 | ============= 116 | Released: 2021-12-07 117 | 118 | This release fixes the following bugs: 119 | * Don't hard-code the list of OS supporting --version-script 120 | * Fix hotplug after threaded device removal 121 | * Fix version script check for GNU BFD linker on FreeBSD 122 | 123 | Version 0.3.8 124 | ============= 125 | Released: 2021-10-06 126 | 127 | This release adds the following feature: 128 | * Add g_usb_device_get_string_descriptor_bytes_full 129 | 130 | This release fixes the following bugs: 131 | * Add the GUsbEndpoint methods to the docs 132 | * Fix hotplug handling and signal emission during enumerate 133 | 134 | Version 0.3.7 135 | ============= 136 | Released: 2021-05-24 137 | 138 | This release adds the following feature: 139 | * Add a FreeBSD CI target 140 | 141 | This release fixes the following bugs: 142 | * Do not depend on libusb_get_parent() to fix FreeBSD compile 143 | * Do not depend on libusb_get_port_number() to fix DragonFlyBSD compile 144 | * Do not double-reference USB devices 145 | * Do not run the tests on FreeBSD 146 | * Require *any* python3 to fix FreeBSD build 147 | 148 | Version 0.3.6 149 | ============= 150 | Released: 2021-03-12 151 | 152 | This release adds the following feature: 153 | * Add g_usb_device_get_string_descriptor_bytes() 154 | 155 | This release fixes the following bugs: 156 | * Properly set dylib versions on darwin 157 | * Fix cancellation if cancellable is already cancelled 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GUsb 2 | ==== 3 | 4 | GUsb is a GObject wrapper for libusb1 that makes it easy to do 5 | asynchronous control, bulk and interrupt transfers with proper 6 | cancellation and integration into a mainloop. 7 | This makes it easy to integrate low level USB transfers with your 8 | high-level application or system daemon. 9 | 10 | Not everything you can do in libusb1 is wrapped, although we'll accept 11 | feature requests (with patches) if it makes sense. 12 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | GUsb Release Notes 2 | ================== 3 | 4 | git log --format="%s" --cherry-pick --right-only 0.4.8... | grep -i -v trivial | grep -v Merge | sort | uniq 5 | Add any user visible changes into ../contrib/org.freedesktop.GUsb.metainfo.xml 6 | appstream-util appdata-to-news ../contrib/org.freedesktop.GUsb.metainfo.xml > ../NEWS 7 | 8 | Update library version if new ABI or API in `meson.build`, commit, and build tarball: 9 | 10 | # MAKE SURE THIS IS CORRECT 11 | export release_ver="0.4.9" 12 | 13 | git commit -a -m "Release version ${release_ver}" 14 | git tag -s -f -m "Release ${release_ver}" "${release_ver}" 15 | ninja dist 16 | git push --tags 17 | git push 18 | gpg -b -a meson-dist/libgusb-${release_ver}.tar.xz 19 | 20 | Upload tarball and GPG signatures to https://github.com/hughsie/libgusb 21 | 22 | Do post release version bump in `meson.build` and commit changes: 23 | 24 | git commit -a -m "trivial: post release version bump" 25 | git push 26 | -------------------------------------------------------------------------------- /contrib/ci/Dockerfile-debian: -------------------------------------------------------------------------------- 1 | FROM debian:testing 2 | 3 | RUN echo "deb-src http://deb.debian.org/debian/ testing main" >> /etc/apt/sources.list 4 | RUN apt-get update -qq 5 | RUN apt-get install -yq --no-install-recommends meson libjson-glib-dev gi-docgen 6 | RUN apt-get build-dep --allow-unauthenticated -yq libgusb 7 | 8 | RUN mkdir /build 9 | WORKDIR /build 10 | -------------------------------------------------------------------------------- /contrib/ci/Dockerfile-fedora: -------------------------------------------------------------------------------- 1 | FROM fedora:37 2 | 3 | RUN dnf -y update 4 | RUN dnf -y install \ 5 | diffutils \ 6 | glib2-devel \ 7 | json-glib-devel \ 8 | gobject-introspection-devel \ 9 | gi-docgen \ 10 | libusb1-devel \ 11 | meson \ 12 | redhat-rpm-config \ 13 | rpm-devel \ 14 | vala-devel \ 15 | vala 16 | RUN mkdir /build 17 | WORKDIR /build 18 | -------------------------------------------------------------------------------- /contrib/ci/build_and_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p build && cd build 5 | meson .. $@ 6 | ninja -v 7 | ninja test -v 8 | DESTDIR=/tmp/install-ninja ninja install 9 | cd .. 10 | -------------------------------------------------------------------------------- /contrib/generate-version-script.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # pylint: disable=invalid-name,missing-docstring 3 | # 4 | # Copyright (C) 2017 Richard Hughes 5 | # 6 | # SPDX-License-Identifier: LGPL-2.1-or-later 7 | 8 | import sys 9 | import xml.etree.ElementTree as ET 10 | 11 | from pkg_resources import parse_version 12 | 13 | XMLNS = '{http://www.gtk.org/introspection/core/1.0}' 14 | XMLNS_C = '{http://www.gtk.org/introspection/c/1.0}' 15 | 16 | def usage(return_code): 17 | """ print usage and exit with the supplied return code """ 18 | if return_code == 0: 19 | out = sys.stdout 20 | else: 21 | out = sys.stderr 22 | out.write("usage: %s \n" % sys.argv[0]) 23 | sys.exit(return_code) 24 | 25 | class LdVersionScript: 26 | """ Rasterize some text """ 27 | 28 | def __init__(self, library_name): 29 | self.library_name = library_name 30 | self.releases = {} 31 | 32 | def _add_node(self, node): 33 | identifier = node.attrib[XMLNS_C + 'identifier'] 34 | if 'version' not in node.attrib: 35 | print('No version for', identifier) 36 | sys.exit(1) 37 | version = node.attrib['version'] 38 | if version not in self.releases: 39 | self.releases[version] = [] 40 | release = self.releases[version] 41 | if identifier not in release: 42 | release.append(identifier) 43 | return version 44 | 45 | def _add_cls(self, cls): 46 | 47 | # add all class functions 48 | for node in cls.findall(XMLNS + 'function'): 49 | self._add_node(node) 50 | 51 | # choose the lowest version method for the _get_type symbol 52 | version_lowest = None 53 | 54 | # add all class methods 55 | for node in cls.findall(XMLNS + 'method'): 56 | version_tmp = self._add_node(node) 57 | if version_tmp: 58 | if not version_lowest or parse_version(version_tmp) < parse_version(version_lowest): 59 | version_lowest = version_tmp 60 | 61 | # add the constructor 62 | for node in cls.findall(XMLNS + 'constructor'): 63 | version_tmp = self._add_node(node) 64 | if version_tmp: 65 | if not version_lowest or parse_version(version_tmp) < parse_version(version_lowest): 66 | version_lowest = version_tmp 67 | 68 | if '{http://www.gtk.org/introspection/glib/1.0}get-type' not in cls.attrib: 69 | return 70 | type_name = cls.attrib['{http://www.gtk.org/introspection/glib/1.0}get-type'] 71 | 72 | # finally add the get_type symbol 73 | if version_lowest: 74 | self.releases[version_lowest].append(type_name) 75 | 76 | def import_gir(self, filename): 77 | tree = ET.parse(filename) 78 | root = tree.getroot() 79 | for ns in root.findall(XMLNS + 'namespace'): 80 | for node in ns.findall(XMLNS + 'function'): 81 | self._add_node(node) 82 | for cls in ns.findall(XMLNS + 'record'): 83 | self._add_cls(cls) 84 | for cls in ns.findall(XMLNS + 'class'): 85 | self._add_cls(cls) 86 | 87 | def render(self): 88 | 89 | # get a sorted list of all the versions 90 | versions = [] 91 | for version in self.releases: 92 | versions.append(version) 93 | 94 | # output the version data to a file 95 | verout = '# generated automatically, do not edit!\n' 96 | oldversion = None 97 | for version in sorted(versions, key=parse_version): 98 | symbols = sorted(self.releases[version]) 99 | verout += '\n%s_%s {\n' % (self.library_name, version) 100 | verout += ' global:\n' 101 | for symbol in symbols: 102 | verout += ' %s;\n' % symbol 103 | verout += ' local: *;\n' 104 | if oldversion: 105 | verout += '} %s_%s;\n' % (self.library_name, oldversion) 106 | else: 107 | verout += '};\n' 108 | oldversion = version 109 | return verout 110 | 111 | if __name__ == '__main__': 112 | if {'-?', '--help', '--usage'}.intersection(set(sys.argv)): 113 | usage(0) 114 | if len(sys.argv) != 4: 115 | usage(1) 116 | 117 | ld = LdVersionScript(library_name=sys.argv[1]) 118 | ld.import_gir(sys.argv[2]) 119 | open(sys.argv[3], 'w').write(ld.render()) 120 | -------------------------------------------------------------------------------- /contrib/libgusb.spec.in: -------------------------------------------------------------------------------- 1 | %define alphatag #ALPHATAG# 2 | 3 | Summary: GLib wrapper around libusb1 4 | Name: libgusb 5 | Version: #VERSION# 6 | Release: 0.#BUILD#%{?alphatag}%{?dist} 7 | License: LGPLv2+ 8 | URL: https://github.com/hughsie/libgusb 9 | Source0: https://github.com/hughsie/libgusb/releases/download/%{version}/%{name}-%{version}.tar.xz 10 | 11 | BuildRequires: glib2-devel >= 2.38.0 12 | BuildRequires: json-glib-devel 13 | BuildRequires: gobject-introspection-devel 14 | BuildRequires: gi-docgen 15 | BuildRequires: libusb1-devel >= 1.0.19 16 | BuildRequires: umockdev-devel 17 | BuildRequires: meson 18 | BuildRequires: vala 19 | 20 | %description 21 | GUsb is a GObject wrapper for libusb1 that makes it easy to do 22 | asynchronous control, bulk and interrupt transfers with proper 23 | cancellation and integration into a mainloop. 24 | 25 | %package devel 26 | Summary: Libraries and headers for gusb 27 | Requires: %{name} = %{version}-%{release} 28 | 29 | %description devel 30 | GLib headers and libraries for gusb. 31 | 32 | %prep 33 | %setup -q 34 | 35 | %build 36 | %meson -Dvapi=true -Dtests=true 37 | 38 | %meson_build 39 | 40 | %install 41 | %meson_install 42 | 43 | %ldconfig_scriptlets 44 | 45 | %files 46 | %doc README.md AUTHORS NEWS COPYING 47 | %{_libdir}/libgusb.so.? 48 | %{_libdir}/libgusb.so.?.0.* 49 | %{_libdir}/girepository-1.0/GUsb-1.0.typelib 50 | 51 | %files devel 52 | %{_includedir}/gusb-1 53 | %{_bindir}/gusbcmd 54 | %{_libdir}/libgusb.so 55 | %{_libdir}/pkgconfig/gusb.pc 56 | %{_datadir}/doc/libgusb 57 | %{_datadir}/gir-1.0/GUsb-1.0.gir 58 | %{_datadir}/vala/vapi/gusb.deps 59 | %{_datadir}/vala/vapi/gusb.vapi 60 | 61 | %changelog 62 | %autochangelog 63 | -------------------------------------------------------------------------------- /contrib/org.freedesktop.GUsb.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | org.freedesktop.GUsb 5 | CC0-1.0 6 | LGPL-2.0+ 7 | GUsb 8 | A GObject wrapper for libusb1 9 | 10 |

11 | GUsb is a GObject wrapper for libusb1 that makes it easy to do asynchronous control, 12 | bulk and interrupt transfers with proper cancellation and integration into a mainloop. 13 | This makes it easy to integrate low level USB transfers with your high-level application or 14 | system daemon. 15 |

16 |
17 | https://github.com/hughsie/libgusb/issues 18 | https://github.com/hughsie/libgusb 19 | 20 | 21 | gusbcmd 22 | 23 | 24 | 25 | 26 |

This release fixes the following bugs:

27 |
    28 |
  • Allow reading HID descriptors larger than 255 bytes
  • 29 |
  • Close the libusb handle if returning open failure
  • 30 |
  • Fix a buffer over-read when emulating devices
  • 31 |
  • Only request the BOS descriptor when bcdUSB is new enough
  • 32 |
33 |
34 |
35 | 36 | 37 |

This release adds the following feature:

38 |
    39 |
  • Add an device error code for 'busy'
  • 40 |
41 |
42 |
43 | 44 | 45 |

This release adds the following features:

46 |
    47 |
  • Add new API to get the HID descriptor data
  • 48 |
49 |

This release fixes the following bugs:

50 |
    51 |
  • Add calling convention to libusb callback functions
  • 52 |
53 |
54 |
55 | 56 | 57 |

This release fixes the following bugs:

58 |
    59 |
  • Add JSON compatibility helpers for old json-glib versions
  • 60 |
  • Fix emulation of devices using out-of-order BulkTransfer replies
  • 61 |
  • Use project_source_root to fix building as a subproject
  • 62 |
63 |
64 |
65 | 66 | 67 |

This release adds the following features:

68 |
    69 |
  • Add new API to better detect emulated replug
  • 70 |
71 |

This release fixes the following bugs:

72 |
    73 |
  • Do not emit spurious ::device-removed events when emulating
  • 74 |
  • Record zero-sized device events
  • 75 |
  • Reset the in-order event index when clearing the event array
  • 76 |
  • Save the libusb error if libusb_submit_transfer() fails
  • 77 |
78 |
79 |
80 | 81 | 82 |

This release adds the following features:

83 |
    84 |
  • Add new API to make emulation easier to use
  • 85 |
86 |

This release fixes the following bugs:

87 |
    88 |
  • Emit remove devices with matching tags when loading contexts
  • 89 |
  • Fix a critical warning when finalizing devices without endpoints
  • 90 |
  • Reduce the debugging level when emulating firmware
  • 91 |
92 |
93 |
94 | 95 | 96 |

This release fixes the following bugs:

97 |
    98 |
  • Export g_usb_device_has_tag()
  • 99 |
  • Work around a libusb issue which causes devices to appear twice
  • 100 |
101 |
102 |
103 | 104 | 105 |

This release fixes the following bugs:

106 |
    107 |
  • Close the GUsbDevice objects in gusbcmd
  • 108 |
  • Do not call the hotplug callback with the mutex held
  • 109 |
110 |
111 |
112 | 113 | 114 |

This release adds the following features:

115 |
    116 |
  • Allow loading and saving only devices with specific tags
  • 117 |
  • Include removed devices in the JSON dump
  • 118 |
119 |

This release fixes the following bug:

120 |
    121 |
  • Port from gtkdoc to gi-docgen
  • 122 |
123 |
124 |
125 | 126 | 127 |

128 | The JSON format used to serialising devices is not yet stable, and 129 | might change. 130 |

131 |

This release adds the following features:

132 |
    133 |
  • Add support for getting BOS descriptors
  • 134 |
  • Allow emulating devices by saving and replaying device events
  • 135 |
  • Cache the list of interfaces and descriptors
  • 136 |
  • Serialize the GUsbDevice to and from JSON format
  • 137 |
138 |

This release fixes the following bugs:

139 |
    140 |
  • Ignore usb.ids classes, subclasses and protocols
  • 141 |
  • Never dispatch idle events after the context has been destroyed
  • 142 |
  • Skip some functionality when emulating
  • 143 |
144 |
145 |
146 | 147 | 148 |

This release adds the following feature:

149 |
    150 |
  • Add API to change the hotplug poll interval
  • 151 |
152 |
153 |
154 | 155 | 156 |

This release fixes the following bugs:

157 |
    158 |
  • Don't hard-code the list of OS supporting --version-script
  • 159 |
  • Fix hotplug after threaded device removal
  • 160 |
  • Fix version script check for GNU BFD linker on FreeBSD
  • 161 |
162 |
163 |
164 | 165 | 166 |

This release adds the following feature:

167 |
    168 |
  • Add g_usb_device_get_string_descriptor_bytes_full
  • 169 |
170 |

This release fixes the following bugs:

171 |
    172 |
  • Add the GUsbEndpoint methods to the docs
  • 173 |
  • Fix hotplug handling and signal emission during enumerate
  • 174 |
175 |
176 |
177 | 178 | 179 |

This release adds the following feature:

180 |
    181 |
  • Add a FreeBSD CI target
  • 182 |
183 |

This release fixes the following bugs:

184 |
    185 |
  • Do not depend on libusb_get_parent() to fix FreeBSD compile
  • 186 |
  • Do not depend on libusb_get_port_number() to fix DragonFlyBSD compile
  • 187 |
  • Do not double-reference USB devices
  • 188 |
  • Do not run the tests on FreeBSD
  • 189 |
  • Require *any* python3 to fix FreeBSD build
  • 190 |
191 |
192 |
193 | 194 | 195 |

This release adds the following feature:

196 |
    197 |
  • Add g_usb_device_get_string_descriptor_bytes()
  • 198 |
199 |

This release fixes the following bugs:

200 |
    201 |
  • Properly set dylib versions on darwin
  • 202 |
  • Fix cancellation if cancellable is already cancelled
  • 203 |
204 |
205 |
206 |
207 |
208 | -------------------------------------------------------------------------------- /docs/libgusb.toml.in: -------------------------------------------------------------------------------- 1 | [library] 2 | version = "@version@" 3 | browse_url = "https://github.com/hughsie/libgusb" 4 | repository_url = "https://github.com/hughsie/libgusb.git" 5 | website_url = "https://github.com/hughsie/libgusb" 6 | authors = "GUsb Development Team" 7 | license = "LGPL-2.1-or-later" 8 | description = "GUsb is a GLib wrapper for libusb1" 9 | dependencies = [ 10 | "GObject-2.0", 11 | "Gio-2.0", 12 | "Json-1.0" 13 | ] 14 | devhelp = true 15 | search_index = true 16 | 17 | [dependencies."GObject-2.0"] 18 | name = "GObject" 19 | description = "The base type system library" 20 | docs_url = "https://docs.gtk.org/gobject/" 21 | 22 | [dependencies."Gio-2.0"] 23 | name = "Gio" 24 | description = "A modern, easy-to-use VFS API" 25 | docs_url = "https://docs.gtk.org/gio/" 26 | 27 | [dependencies."Json-1.0"] 28 | name = "Json" 29 | description = "API for efficient parsing and writing of JSON (JavaScript Object Notation) streams" 30 | docs_url = "https://gnome.pages.gitlab.gnome.org/json-glib/" 31 | 32 | [theme] 33 | name = "basic" 34 | show_index_summary = true 35 | show_class_hierarchy = true 36 | 37 | [extra] 38 | urlmap_file = "urlmap_libgusb.js" 39 | -------------------------------------------------------------------------------- /docs/meson.build: -------------------------------------------------------------------------------- 1 | gidocgen_dep = dependency('gi-docgen', 2 | version: '>= 2021.1', 3 | native: true, 4 | required: false, 5 | ) 6 | gidocgen_app = find_program('gi-docgen') 7 | 8 | if gidocgen_dep.found() and gidocgen_app.found() and get_option('introspection') 9 | toml_conf = configuration_data() 10 | toml_conf.set('version', meson.project_version()) 11 | 12 | libgusb_toml = configure_file( 13 | input: 'libgusb.toml.in', 14 | output: 'libgusb.toml', 15 | configuration: toml_conf 16 | ) 17 | 18 | custom_target('doc-libgusb', 19 | input: [ 20 | libgusb_toml, 21 | libgusb_girtarget[0], 22 | ], 23 | output: 'libgusb', 24 | command: [ 25 | gidocgen_app, 26 | 'generate', 27 | '--quiet', 28 | '--add-include-path=@0@'.format(meson.current_build_dir() / '../libgusb'), 29 | '--config=@INPUT0@', 30 | '--output-dir=@OUTPUT@', 31 | '--no-namespace-dir', 32 | '--content-dir=@0@'.format(meson.current_source_dir()), 33 | '@INPUT1@', 34 | ], 35 | depends: [ 36 | libgusb_girtarget[0], 37 | ], 38 | build_by_default: true, 39 | install: true, 40 | install_dir: join_paths(datadir, 'doc'), 41 | ) 42 | install_data(['urlmap_libgusb.js'], 43 | install_dir: join_paths(datadir, 'doc', 'libgusb') 44 | ) 45 | endif 46 | -------------------------------------------------------------------------------- /docs/urlmap_libgusb.js: -------------------------------------------------------------------------------- 1 | baseURLs = [ 2 | [ 'Gio', 'https://people.gnome.org/~ebassi/docs/_build/Gio/' ], 3 | [ 'GObject', 'https://people.gnome.org/~ebassi/docs/_build/GObject/' ], 4 | ] 5 | -------------------------------------------------------------------------------- /gusb/gusb-bos-descriptor-private.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2022 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | G_BEGIN_DECLS 15 | 16 | GUsbBosDescriptor * 17 | _g_usb_bos_descriptor_new(const struct libusb_bos_dev_capability_descriptor *bos_cap); 18 | 19 | gboolean 20 | _g_usb_bos_descriptor_load(GUsbBosDescriptor *self, JsonObject *json_object, GError **error); 21 | gboolean 22 | _g_usb_bos_descriptor_save(GUsbBosDescriptor *self, JsonBuilder *json_builder, GError **error); 23 | 24 | G_END_DECLS 25 | -------------------------------------------------------------------------------- /gusb/gusb-bos-descriptor.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2022 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | /** 9 | * SECTION:gusb-bos-descriptor 10 | * @short_description: GLib wrapper around a USB BOS descriptor. 11 | * 12 | * This object is a thin glib wrapper around a `libusb_bos_dev_capability_descriptor`. 13 | * 14 | * All the data is copied when the object is created and the original descriptor can be destroyed 15 | * at any point. 16 | */ 17 | 18 | #include "config.h" 19 | 20 | #include 21 | 22 | #include "gusb-bos-descriptor-private.h" 23 | #include "gusb-json-common.h" 24 | 25 | struct _GUsbBosDescriptor { 26 | GObject parent_instance; 27 | 28 | struct libusb_bos_dev_capability_descriptor bos_cap; 29 | GBytes *extra; 30 | }; 31 | 32 | G_DEFINE_TYPE(GUsbBosDescriptor, g_usb_bos_descriptor, G_TYPE_OBJECT) 33 | 34 | static void 35 | g_usb_bos_descriptor_finalize(GObject *object) 36 | { 37 | GUsbBosDescriptor *self = G_USB_BOS_DESCRIPTOR(object); 38 | 39 | g_bytes_unref(self->extra); 40 | 41 | G_OBJECT_CLASS(g_usb_bos_descriptor_parent_class)->finalize(object); 42 | } 43 | 44 | static void 45 | g_usb_bos_descriptor_class_init(GUsbBosDescriptorClass *klass) 46 | { 47 | GObjectClass *object_class = G_OBJECT_CLASS(klass); 48 | 49 | object_class->finalize = g_usb_bos_descriptor_finalize; 50 | } 51 | 52 | static void 53 | g_usb_bos_descriptor_init(GUsbBosDescriptor *self) 54 | { 55 | } 56 | 57 | gboolean 58 | _g_usb_bos_descriptor_load(GUsbBosDescriptor *self, JsonObject *json_object, GError **error) 59 | { 60 | const gchar *str; 61 | 62 | g_return_val_if_fail(G_USB_IS_BOS_DESCRIPTOR(self), FALSE); 63 | g_return_val_if_fail(json_object != NULL, FALSE); 64 | g_return_val_if_fail(error == NULL || *error == NULL, -1); 65 | 66 | /* optional properties */ 67 | self->bos_cap.bDevCapabilityType = 68 | json_object_get_int_member_with_default(json_object, "DevCapabilityType", 0x0); 69 | 70 | /* extra data */ 71 | str = json_object_get_string_member_with_default(json_object, "ExtraData", NULL); 72 | if (str != NULL) { 73 | gsize bufsz = 0; 74 | g_autofree guchar *buf = g_base64_decode(str, &bufsz); 75 | if (self->extra != NULL) 76 | g_bytes_unref(self->extra); 77 | self->extra = g_bytes_new_take(g_steal_pointer(&buf), bufsz); 78 | } 79 | 80 | /* success */ 81 | return TRUE; 82 | } 83 | 84 | gboolean 85 | _g_usb_bos_descriptor_save(GUsbBosDescriptor *self, JsonBuilder *json_builder, GError **error) 86 | { 87 | g_return_val_if_fail(G_USB_IS_BOS_DESCRIPTOR(self), FALSE); 88 | g_return_val_if_fail(json_builder != NULL, FALSE); 89 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 90 | 91 | /* start */ 92 | json_builder_begin_object(json_builder); 93 | 94 | /* optional properties */ 95 | if (self->bos_cap.bDevCapabilityType != 0) { 96 | json_builder_set_member_name(json_builder, "DevCapabilityType"); 97 | json_builder_add_int_value(json_builder, self->bos_cap.bDevCapabilityType); 98 | } 99 | 100 | /* extra data */ 101 | if (self->extra != NULL && g_bytes_get_size(self->extra) > 0) { 102 | g_autofree gchar *str = g_base64_encode(g_bytes_get_data(self->extra, NULL), 103 | g_bytes_get_size(self->extra)); 104 | json_builder_set_member_name(json_builder, "ExtraData"); 105 | json_builder_add_string_value(json_builder, str); 106 | } 107 | 108 | /* success */ 109 | json_builder_end_object(json_builder); 110 | return TRUE; 111 | } 112 | 113 | /** 114 | * _g_usb_bos_descriptor_new: 115 | * 116 | * Return value: a new #GUsbBosDescriptor object. 117 | * 118 | * Since: 0.4.0 119 | **/ 120 | GUsbBosDescriptor * 121 | _g_usb_bos_descriptor_new(const struct libusb_bos_dev_capability_descriptor *bos_cap) 122 | { 123 | GUsbBosDescriptor *self; 124 | self = g_object_new(G_USB_TYPE_BOS_DESCRIPTOR, NULL); 125 | 126 | /* copy the data */ 127 | memcpy(&self->bos_cap, bos_cap, sizeof(*bos_cap)); 128 | self->extra = g_bytes_new(bos_cap->dev_capability_data, bos_cap->bLength - 0x03); 129 | 130 | return G_USB_BOS_DESCRIPTOR(self); 131 | } 132 | 133 | /** 134 | * g_usb_bos_descriptor_get_capability: 135 | * @self: a #GUsbBosDescriptor 136 | * 137 | * Gets the BOS descriptor capability. 138 | * 139 | * Return value: capability 140 | * 141 | * Since: 0.4.0 142 | **/ 143 | guint8 144 | g_usb_bos_descriptor_get_capability(GUsbBosDescriptor *self) 145 | { 146 | g_return_val_if_fail(G_USB_IS_BOS_DESCRIPTOR(self), 0); 147 | return self->bos_cap.bDevCapabilityType; 148 | } 149 | 150 | /** 151 | * g_usb_bos_descriptor_get_extra: 152 | * @self: a #GUsbBosDescriptor 153 | * 154 | * Gets any extra data from the BOS descriptor. 155 | * 156 | * Return value: (transfer none): a #GBytes, or %NULL for failure 157 | * 158 | * Since: 0.4.0 159 | **/ 160 | GBytes * 161 | g_usb_bos_descriptor_get_extra(GUsbBosDescriptor *self) 162 | { 163 | g_return_val_if_fail(G_USB_IS_BOS_DESCRIPTOR(self), NULL); 164 | return self->extra; 165 | } 166 | -------------------------------------------------------------------------------- /gusb/gusb-bos-descriptor.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2022 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | G_BEGIN_DECLS 13 | 14 | #define G_USB_TYPE_BOS_DESCRIPTOR (g_usb_bos_descriptor_get_type()) 15 | G_DECLARE_FINAL_TYPE(GUsbBosDescriptor, g_usb_bos_descriptor, G_USB, BOS_DESCRIPTOR, GObject) 16 | 17 | guint8 18 | g_usb_bos_descriptor_get_capability(GUsbBosDescriptor *self); 19 | GBytes * 20 | g_usb_bos_descriptor_get_extra(GUsbBosDescriptor *self); 21 | 22 | G_END_DECLS 23 | -------------------------------------------------------------------------------- /gusb/gusb-context-private.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2011 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | G_BEGIN_DECLS 14 | 15 | libusb_context * 16 | _g_usb_context_get_context(GUsbContext *self); 17 | 18 | const gchar * 19 | _g_usb_context_lookup_vendor(GUsbContext *self, guint16 vid, GError **error); 20 | const gchar * 21 | _g_usb_context_lookup_product(GUsbContext *self, guint16 vid, guint16 pid, GError **error); 22 | gboolean 23 | _g_usb_context_has_flag(GUsbContext *self, GUsbContextFlags flags); 24 | 25 | G_END_DECLS 26 | -------------------------------------------------------------------------------- /gusb/gusb-context.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2011 Richard Hughes 4 | * Copyright (C) 2011 Hans de Goede 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | G_BEGIN_DECLS 15 | 16 | #define G_USB_TYPE_CONTEXT (g_usb_context_get_type()) 17 | #define G_USB_CONTEXT_ERROR (g_usb_context_error_quark()) 18 | 19 | G_DECLARE_DERIVABLE_TYPE(GUsbContext, g_usb_context, G_USB, CONTEXT, GObject) 20 | 21 | struct _GUsbContextClass { 22 | GObjectClass parent_class; 23 | void (*device_added)(GUsbContext *self, GUsbDevice *device); 24 | void (*device_removed)(GUsbContext *self, GUsbDevice *device); 25 | void (*device_changed)(GUsbContext *self, GUsbDevice *device); 26 | /*< private >*/ 27 | /* 28 | * If adding fields to this struct, remove corresponding 29 | * amount of padding to avoid changing overall struct size 30 | */ 31 | gchar _gusb_reserved[61]; 32 | }; 33 | 34 | typedef enum { G_USB_CONTEXT_ERROR_INTERNAL } GUsbContextError; 35 | 36 | /** 37 | * GUsbContextFlags: 38 | * 39 | * The flags to use for the context. 40 | **/ 41 | typedef enum { 42 | G_USB_CONTEXT_FLAGS_NONE = 0, 43 | G_USB_CONTEXT_FLAGS_AUTO_OPEN_DEVICES = 1 << 0, 44 | G_USB_CONTEXT_FLAGS_SAVE_EVENTS = 1 << 1, 45 | G_USB_CONTEXT_FLAGS_SAVE_REMOVED_DEVICES = 1 << 2, 46 | G_USB_CONTEXT_FLAGS_DEBUG = 1 << 3, 47 | /*< private >*/ 48 | G_USB_CONTEXT_FLAGS_LAST 49 | } GUsbContextFlags; 50 | 51 | GQuark 52 | g_usb_context_error_quark(void); 53 | 54 | GUsbContext * 55 | g_usb_context_new(GError **error); 56 | 57 | void 58 | g_usb_context_set_flags(GUsbContext *self, GUsbContextFlags flags); 59 | GUsbContextFlags 60 | g_usb_context_get_flags(GUsbContext *self); 61 | 62 | G_DEPRECATED 63 | GUsbSource * 64 | g_usb_context_get_source(GUsbContext *self, GMainContext *main_ctx); 65 | GMainContext * 66 | g_usb_context_get_main_context(GUsbContext *self); 67 | void 68 | g_usb_context_set_main_context(GUsbContext *self, GMainContext *main_ctx); 69 | guint 70 | g_usb_context_get_hotplug_poll_interval(GUsbContext *self); 71 | void 72 | g_usb_context_set_hotplug_poll_interval(GUsbContext *self, guint hotplug_poll_interval); 73 | 74 | void 75 | g_usb_context_enumerate(GUsbContext *self); 76 | 77 | gboolean 78 | g_usb_context_load(GUsbContext *self, JsonObject *json_object, GError **error); 79 | gboolean 80 | g_usb_context_load_with_tag(GUsbContext *self, 81 | JsonObject *json_object, 82 | const gchar *tag, 83 | GError **error); 84 | gboolean 85 | g_usb_context_save(GUsbContext *self, JsonBuilder *json_builder, GError **error); 86 | gboolean 87 | g_usb_context_save_with_tag(GUsbContext *self, 88 | JsonBuilder *json_builder, 89 | const gchar *tag, 90 | GError **error); 91 | 92 | void 93 | g_usb_context_set_debug(GUsbContext *self, GLogLevelFlags flags); 94 | GPtrArray * 95 | g_usb_context_get_devices(GUsbContext *self); 96 | 97 | GUsbDevice * 98 | g_usb_context_find_by_bus_address(GUsbContext *self, guint8 bus, guint8 address, GError **error); 99 | 100 | GUsbDevice * 101 | g_usb_context_find_by_vid_pid(GUsbContext *self, guint16 vid, guint16 pid, GError **error); 102 | GUsbDevice * 103 | g_usb_context_find_by_platform_id(GUsbContext *self, const gchar *platform_id, GError **error); 104 | 105 | GUsbDevice * 106 | g_usb_context_wait_for_replug(GUsbContext *self, 107 | GUsbDevice *device, 108 | guint timeout_ms, 109 | GError **error); 110 | 111 | G_END_DECLS 112 | -------------------------------------------------------------------------------- /gusb/gusb-device-event-private.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2022 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | G_BEGIN_DECLS 15 | 16 | GUsbDeviceEvent * 17 | _g_usb_device_event_new(const gchar *id); 18 | void 19 | _g_usb_device_event_set_bytes_raw(GUsbDeviceEvent *self, gconstpointer buf, gsize bufsz); 20 | void 21 | _g_usb_device_event_set_status(GUsbDeviceEvent *self, gint status); 22 | void 23 | _g_usb_device_event_set_rc(GUsbDeviceEvent *self, gint rc); 24 | 25 | gboolean 26 | _g_usb_device_event_load(GUsbDeviceEvent *self, JsonObject *json_object, GError **error); 27 | gboolean 28 | _g_usb_device_event_save(GUsbDeviceEvent *self, JsonBuilder *json_builder, GError **error); 29 | 30 | G_END_DECLS 31 | -------------------------------------------------------------------------------- /gusb/gusb-device-event.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2022 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | /** 9 | * SECTION:gusb-device-event 10 | * @short_description: An event that happened to a GUsbDevice. 11 | */ 12 | 13 | #include "config.h" 14 | 15 | #include "gusb-device-event-private.h" 16 | #include "gusb-json-common.h" 17 | 18 | struct _GUsbDeviceEvent { 19 | GObject parent_instance; 20 | gchar *id; 21 | gint status; 22 | gint rc; 23 | GBytes *bytes; 24 | }; 25 | 26 | G_DEFINE_TYPE(GUsbDeviceEvent, g_usb_device_event, G_TYPE_OBJECT) 27 | 28 | static void 29 | g_usb_device_event_finalize(GObject *object) 30 | { 31 | GUsbDeviceEvent *self = G_USB_DEVICE_EVENT(object); 32 | 33 | g_free(self->id); 34 | if (self->bytes != NULL) 35 | g_bytes_unref(self->bytes); 36 | 37 | G_OBJECT_CLASS(g_usb_device_event_parent_class)->finalize(object); 38 | } 39 | 40 | static void 41 | g_usb_device_event_class_init(GUsbDeviceEventClass *klass) 42 | { 43 | GObjectClass *object_class = G_OBJECT_CLASS(klass); 44 | object_class->finalize = g_usb_device_event_finalize; 45 | } 46 | 47 | static void 48 | g_usb_device_event_init(GUsbDeviceEvent *self) 49 | { 50 | } 51 | 52 | gboolean 53 | _g_usb_device_event_load(GUsbDeviceEvent *self, JsonObject *json_object, GError **error) 54 | { 55 | const gchar *str; 56 | 57 | g_return_val_if_fail(G_USB_IS_DEVICE_EVENT(self), FALSE); 58 | g_return_val_if_fail(json_object != NULL, FALSE); 59 | g_return_val_if_fail(error == NULL || *error == NULL, -1); 60 | 61 | /* optional properties */ 62 | self->id = g_strdup(json_object_get_string_member_with_default(json_object, "Id", NULL)); 63 | self->status = json_object_get_int_member_with_default(json_object, 64 | "Status", 65 | LIBUSB_TRANSFER_COMPLETED); 66 | self->rc = json_object_get_int_member_with_default(json_object, "Error", LIBUSB_SUCCESS); 67 | 68 | /* extra data */ 69 | str = json_object_get_string_member_with_default(json_object, "Data", NULL); 70 | if (str != NULL) { 71 | gsize bufsz = 0; 72 | g_autofree guchar *buf = g_base64_decode(str, &bufsz); 73 | self->bytes = g_bytes_new_take(g_steal_pointer(&buf), bufsz); 74 | } 75 | 76 | /* success */ 77 | return TRUE; 78 | } 79 | 80 | gboolean 81 | _g_usb_device_event_save(GUsbDeviceEvent *self, JsonBuilder *json_builder, GError **error) 82 | { 83 | g_return_val_if_fail(G_USB_IS_DEVICE_EVENT(self), FALSE); 84 | g_return_val_if_fail(json_builder != NULL, FALSE); 85 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 86 | 87 | /* start */ 88 | json_builder_begin_object(json_builder); 89 | 90 | if (self->id != NULL) { 91 | json_builder_set_member_name(json_builder, "Id"); 92 | json_builder_add_string_value(json_builder, self->id); 93 | } 94 | if (self->status != LIBUSB_TRANSFER_COMPLETED) { 95 | json_builder_set_member_name(json_builder, "Status"); 96 | json_builder_add_int_value(json_builder, self->status); 97 | } 98 | if (self->rc != LIBUSB_SUCCESS) { 99 | json_builder_set_member_name(json_builder, "Error"); 100 | json_builder_add_int_value(json_builder, self->rc); 101 | } 102 | if (self->bytes != NULL) { 103 | g_autofree gchar *str = g_base64_encode(g_bytes_get_data(self->bytes, NULL), 104 | g_bytes_get_size(self->bytes)); 105 | json_builder_set_member_name(json_builder, "Data"); 106 | json_builder_add_string_value(json_builder, str); 107 | } 108 | 109 | /* success */ 110 | json_builder_end_object(json_builder); 111 | return TRUE; 112 | } 113 | 114 | /** 115 | * _g_usb_device_event_new: 116 | * @id: a cache key 117 | * 118 | * Return value: a new #GUsbDeviceEvent object. 119 | * 120 | * Since: 0.4.0 121 | **/ 122 | GUsbDeviceEvent * 123 | _g_usb_device_event_new(const gchar *id) 124 | { 125 | GUsbDeviceEvent *self; 126 | self = g_object_new(G_USB_TYPE_DEVICE_EVENT, NULL); 127 | self->id = g_strdup(id); 128 | return G_USB_DEVICE_EVENT(self); 129 | } 130 | 131 | /** 132 | * g_usb_device_event_get_id: 133 | * @self: a #GUsbDeviceEvent 134 | * 135 | * Gets the event ID. 136 | * 137 | * Return value: string, or %NULL 138 | * 139 | * Since: 0.4.0 140 | **/ 141 | const gchar * 142 | g_usb_device_event_get_id(GUsbDeviceEvent *self) 143 | { 144 | g_return_val_if_fail(G_USB_IS_DEVICE_EVENT(self), NULL); 145 | return self->id; 146 | } 147 | 148 | /** 149 | * g_usb_device_event_get_status: 150 | * @self: a #GUsbDeviceEvent 151 | * 152 | * Gets any status data from the event. 153 | * 154 | * Return value: a `enum libusb_transfer_status`, or -1 for failure 155 | * 156 | * Since: 0.4.0 157 | **/ 158 | gint 159 | g_usb_device_event_get_status(GUsbDeviceEvent *self) 160 | { 161 | g_return_val_if_fail(G_USB_IS_DEVICE_EVENT(self), -1); 162 | return self->status; 163 | } 164 | 165 | /** 166 | * _g_usb_device_event_set_status: 167 | * @self: a #GUsbDeviceEvent 168 | * @status: `enum libusb_transfer_status` 169 | * 170 | * Set the status of the event, e.g. `LIBUSB_TRANSFER_COMPLETED`. 171 | * 172 | * Since: 0.4.0 173 | **/ 174 | void 175 | _g_usb_device_event_set_status(GUsbDeviceEvent *self, gint status) 176 | { 177 | g_return_if_fail(G_USB_IS_DEVICE_EVENT(self)); 178 | self->status = status; 179 | } 180 | 181 | /** 182 | * g_usb_device_event_get_rc: 183 | * @self: a #GUsbDeviceEvent 184 | * 185 | * Gets any return code from the event. 186 | * 187 | * Return value: a `enum libusb_error` 188 | * 189 | * Since: 0.4.5 190 | **/ 191 | gint 192 | g_usb_device_event_get_rc(GUsbDeviceEvent *self) 193 | { 194 | g_return_val_if_fail(G_USB_IS_DEVICE_EVENT(self), LIBUSB_ERROR_OTHER); 195 | return self->rc; 196 | } 197 | 198 | /** 199 | * _g_usb_device_event_set_rc: 200 | * @self: a #GUsbDeviceEvent 201 | * @status: `enum libusb_error` 202 | * 203 | * Set the return code of the event, e.g. `LIBUSB_ERROR_TIMEOUT`. 204 | * 205 | * Since: 0.4.5 206 | **/ 207 | void 208 | _g_usb_device_event_set_rc(GUsbDeviceEvent *self, gint rc) 209 | { 210 | g_return_if_fail(G_USB_IS_DEVICE_EVENT(self)); 211 | g_return_if_fail(rc <= 0); 212 | self->rc = rc; 213 | } 214 | 215 | /** 216 | * g_usb_device_event_get_bytes: 217 | * @self: a #GUsbDeviceEvent 218 | * 219 | * Gets any bytes data from the event. 220 | * 221 | * Return value: (transfer none): a #GBytes, or %NULL 222 | * 223 | * Since: 0.4.0 224 | **/ 225 | GBytes * 226 | g_usb_device_event_get_bytes(GUsbDeviceEvent *self) 227 | { 228 | g_return_val_if_fail(G_USB_IS_DEVICE_EVENT(self), NULL); 229 | return self->bytes; 230 | } 231 | 232 | /** 233 | * g_usb_device_event_set_bytes: 234 | * @self: a #GUsbDeviceEvent 235 | * @bytes: a #GBytes 236 | * 237 | * Set the bytes data to the event. 238 | * 239 | * Since: 0.4.0 240 | **/ 241 | void 242 | g_usb_device_event_set_bytes(GUsbDeviceEvent *self, GBytes *bytes) 243 | { 244 | g_return_if_fail(G_USB_IS_DEVICE_EVENT(self)); 245 | g_return_if_fail(bytes != NULL); 246 | if (self->bytes != NULL) 247 | g_bytes_unref(self->bytes); 248 | self->bytes = g_bytes_ref(bytes); 249 | } 250 | 251 | void 252 | _g_usb_device_event_set_bytes_raw(GUsbDeviceEvent *self, gconstpointer buf, gsize bufsz) 253 | { 254 | g_return_if_fail(G_USB_IS_DEVICE_EVENT(self)); 255 | g_return_if_fail(buf != NULL); 256 | if (self->bytes != NULL) 257 | g_bytes_unref(self->bytes); 258 | self->bytes = g_bytes_new(buf, bufsz); 259 | } 260 | -------------------------------------------------------------------------------- /gusb/gusb-device-event.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2022 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | G_BEGIN_DECLS 13 | 14 | #define G_USB_TYPE_DEVICE_EVENT (g_usb_device_event_get_type()) 15 | G_DECLARE_FINAL_TYPE(GUsbDeviceEvent, g_usb_device_event, G_USB, DEVICE_EVENT, GObject) 16 | 17 | const gchar * 18 | g_usb_device_event_get_id(GUsbDeviceEvent *self); 19 | GBytes * 20 | g_usb_device_event_get_bytes(GUsbDeviceEvent *self); 21 | gint 22 | g_usb_device_event_get_status(GUsbDeviceEvent *self); 23 | gint 24 | g_usb_device_event_get_rc(GUsbDeviceEvent *self); 25 | void 26 | g_usb_device_event_set_bytes(GUsbDeviceEvent *self, GBytes *bytes); 27 | 28 | G_END_DECLS 29 | -------------------------------------------------------------------------------- /gusb/gusb-device-list.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2011 Hans de Goede 4 | * Copyright (C) 2011 Richard Hughes 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | /** 10 | * SECTION:gusb-device-list 11 | * @short_description: A device list 12 | * 13 | * A device list that is updated as devices are pluged in and unplugged. 14 | */ 15 | 16 | #include "config.h" 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "gusb-context-private.h" 23 | #include "gusb-device-list.h" 24 | #include "gusb-device-private.h" 25 | 26 | enum { PROP_0, PROP_CONTEXT }; 27 | 28 | enum { DEVICE_ADDED_SIGNAL, DEVICE_REMOVED_SIGNAL, LAST_SIGNAL }; 29 | 30 | typedef struct { 31 | GUsbContext *context; 32 | } GUsbDeviceListPrivate; 33 | 34 | static guint signals[LAST_SIGNAL] = {0}; 35 | 36 | G_DEFINE_TYPE_WITH_PRIVATE(GUsbDeviceList, g_usb_device_list, G_TYPE_OBJECT); 37 | 38 | #define GET_PRIVATE(o) (g_usb_device_list_get_instance_private(o)) 39 | 40 | static void 41 | g_usb_device_list_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) 42 | { 43 | GUsbDeviceList *self = G_USB_DEVICE_LIST(object); 44 | GUsbDeviceListPrivate *priv = GET_PRIVATE(self); 45 | 46 | switch (prop_id) { 47 | case PROP_CONTEXT: 48 | g_value_set_object(value, priv->context); 49 | break; 50 | default: 51 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); 52 | break; 53 | } 54 | } 55 | 56 | static void 57 | g_usb_device_added_cb(GUsbContext *context, GUsbDevice *device, GUsbDeviceList *self) 58 | { 59 | g_signal_emit(self, signals[DEVICE_ADDED_SIGNAL], 0, device); 60 | } 61 | 62 | static void 63 | g_usb_device_removed_cb(GUsbContext *context, GUsbDevice *device, GUsbDeviceList *self) 64 | { 65 | g_signal_emit(self, signals[DEVICE_REMOVED_SIGNAL], 0, device); 66 | } 67 | 68 | static void 69 | g_usb_device_list_set_property(GObject *object, 70 | guint prop_id, 71 | const GValue *value, 72 | GParamSpec *pspec) 73 | { 74 | GUsbDeviceList *self = G_USB_DEVICE_LIST(object); 75 | GUsbDeviceListPrivate *priv = GET_PRIVATE(self); 76 | 77 | switch (prop_id) { 78 | case PROP_CONTEXT: 79 | priv->context = g_value_get_object(value); 80 | g_signal_connect(priv->context, 81 | "device-added", 82 | G_CALLBACK(g_usb_device_added_cb), 83 | self); 84 | g_signal_connect(priv->context, 85 | "device-removed", 86 | G_CALLBACK(g_usb_device_removed_cb), 87 | self); 88 | break; 89 | default: 90 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); 91 | break; 92 | } 93 | } 94 | 95 | static void 96 | g_usb_device_list_class_init(GUsbDeviceListClass *klass) 97 | { 98 | GParamSpec *pspec; 99 | GObjectClass *object_class = G_OBJECT_CLASS(klass); 100 | 101 | object_class->get_property = g_usb_device_list_get_property; 102 | object_class->set_property = g_usb_device_list_set_property; 103 | 104 | /** 105 | * GUsbDeviceList:context: 106 | */ 107 | pspec = g_param_spec_object("context", 108 | NULL, 109 | NULL, 110 | G_USB_TYPE_CONTEXT, 111 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); 112 | g_object_class_install_property(object_class, PROP_CONTEXT, pspec); 113 | 114 | /** 115 | * GUsbDeviceList::device-added: 116 | * @self: the #GUsbDeviceList instance that emitted the signal 117 | * @device: A #GUsbDevice 118 | * 119 | * This signal is emitted when a USB device is added. 120 | **/ 121 | signals[DEVICE_ADDED_SIGNAL] = 122 | g_signal_new("device-added", 123 | G_TYPE_FROM_CLASS(klass), 124 | G_SIGNAL_RUN_LAST, 125 | G_STRUCT_OFFSET(GUsbDeviceListClass, device_added), 126 | NULL, 127 | NULL, 128 | g_cclosure_marshal_VOID__OBJECT, 129 | G_TYPE_NONE, 130 | 1, 131 | G_USB_TYPE_DEVICE); 132 | 133 | /** 134 | * GUsbDeviceList::device-removed: 135 | * @self: the #GUsbDeviceList instance that emitted the signal 136 | * @device: A #GUsbDevice 137 | * 138 | * This signal is emitted when a USB device is removed. 139 | **/ 140 | signals[DEVICE_REMOVED_SIGNAL] = 141 | g_signal_new("device-removed", 142 | G_TYPE_FROM_CLASS(klass), 143 | G_SIGNAL_RUN_LAST, 144 | G_STRUCT_OFFSET(GUsbDeviceListClass, device_removed), 145 | NULL, 146 | NULL, 147 | g_cclosure_marshal_VOID__OBJECT, 148 | G_TYPE_NONE, 149 | 1, 150 | G_USB_TYPE_DEVICE); 151 | } 152 | 153 | static void 154 | g_usb_device_list_init(GUsbDeviceList *self) 155 | { 156 | } 157 | 158 | /** 159 | * g_usb_device_list_get_devices: 160 | * @self: a #GUsbDeviceList 161 | * 162 | * Return value: (transfer full) (element-type GUsbDevice): a new #GPtrArray of #GUsbDevice's. 163 | * 164 | * Since: 0.1.0 165 | **/ 166 | GPtrArray * 167 | g_usb_device_list_get_devices(GUsbDeviceList *self) 168 | { 169 | GUsbDeviceListPrivate *priv = GET_PRIVATE(self); 170 | g_return_val_if_fail(G_USB_IS_DEVICE_LIST(self), NULL); 171 | return g_usb_context_get_devices(priv->context); 172 | } 173 | 174 | /** 175 | * g_usb_device_list_coldplug: 176 | * @self: a #GUsbDeviceList 177 | * 178 | * This function does nothing. 179 | * 180 | * Since: 0.1.0 181 | **/ 182 | void 183 | g_usb_device_list_coldplug(GUsbDeviceList *self) 184 | { 185 | return; 186 | } 187 | 188 | /** 189 | * g_usb_device_list_find_by_bus_address: 190 | * @self: a #GUsbDeviceList 191 | * @bus: a bus number 192 | * @address: a bus address 193 | * @error: A #GError or %NULL 194 | * 195 | * Finds a device based on its bus and address values. 196 | * 197 | * Return value: (transfer full): a new #GUsbDevice, or %NULL if not found. 198 | * 199 | * Since: 0.1.0 200 | **/ 201 | GUsbDevice * 202 | g_usb_device_list_find_by_bus_address(GUsbDeviceList *self, 203 | guint8 bus, 204 | guint8 address, 205 | GError **error) 206 | { 207 | GUsbDeviceListPrivate *priv = GET_PRIVATE(self); 208 | g_return_val_if_fail(G_USB_IS_DEVICE_LIST(self), NULL); 209 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); 210 | return g_usb_context_find_by_bus_address(priv->context, bus, address, error); 211 | } 212 | 213 | /** 214 | * g_usb_device_list_find_by_vid_pid: 215 | * @self: a #GUsbDeviceList 216 | * @vid: a vendor ID 217 | * @pid: a product ID 218 | * @error: A #GError or %NULL 219 | * 220 | * Finds a device based on its bus and address values. 221 | * 222 | * Return value: (transfer full): a new #GUsbDevice, or %NULL if not found. 223 | * 224 | * Since: 0.1.0 225 | **/ 226 | GUsbDevice * 227 | g_usb_device_list_find_by_vid_pid(GUsbDeviceList *self, guint16 vid, guint16 pid, GError **error) 228 | { 229 | GUsbDeviceListPrivate *priv = GET_PRIVATE(self); 230 | g_return_val_if_fail(G_USB_IS_DEVICE_LIST(self), NULL); 231 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); 232 | return g_usb_context_find_by_vid_pid(priv->context, vid, pid, error); 233 | } 234 | 235 | /** 236 | * g_usb_device_list_new: 237 | * @context: a #GUsbContext 238 | * 239 | * Creates a new device list. 240 | * 241 | * You will need to call g_usb_device_list_coldplug() to coldplug the 242 | * list of devices after creating a device list. 243 | * 244 | * Return value: a new #GUsbDeviceList 245 | * 246 | * Since: 0.1.0 247 | **/ 248 | GUsbDeviceList * 249 | g_usb_device_list_new(GUsbContext *context) 250 | { 251 | return g_object_new(G_USB_TYPE_DEVICE_LIST, "context", context, NULL); 252 | } 253 | -------------------------------------------------------------------------------- /gusb/gusb-device-list.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2011 Hans de Goede 4 | * Copyright (C) 2011 Richard Hughes 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | G_BEGIN_DECLS 14 | 15 | #define G_USB_TYPE_DEVICE_LIST (g_usb_device_list_get_type()) 16 | 17 | G_DECLARE_DERIVABLE_TYPE(GUsbDeviceList, g_usb_device_list, G_USB, DEVICE_LIST, GObject) 18 | 19 | struct _GUsbDeviceListClass { 20 | GObjectClass parent_class; 21 | /* Signals */ 22 | void (*device_added)(GUsbDeviceList *self, GUsbDevice *device); 23 | void (*device_removed)(GUsbDeviceList *self, GUsbDevice *device); 24 | /*< private >*/ 25 | /* 26 | * If adding fields to this struct, remove corresponding 27 | * amount of padding to avoid changing overall struct size 28 | */ 29 | gchar _gusb_reserved[64]; 30 | }; 31 | 32 | G_DEPRECATED_FOR(g_usb_context_new) 33 | GUsbDeviceList * 34 | g_usb_device_list_new(GUsbContext *context); 35 | 36 | G_DEPRECATED 37 | void 38 | g_usb_device_list_coldplug(GUsbDeviceList *self); 39 | 40 | G_DEPRECATED_FOR(g_usb_context_get_devices) 41 | GPtrArray * 42 | g_usb_device_list_get_devices(GUsbDeviceList *self); 43 | 44 | G_DEPRECATED_FOR(g_usb_context_find_by_bus_address) 45 | GUsbDevice * 46 | g_usb_device_list_find_by_bus_address(GUsbDeviceList *self, 47 | guint8 bus, 48 | guint8 address, 49 | GError **error); 50 | 51 | G_DEPRECATED_FOR(g_usb_context_find_by_vid_pid) 52 | GUsbDevice * 53 | g_usb_device_list_find_by_vid_pid(GUsbDeviceList *self, guint16 vid, guint16 pid, GError **error); 54 | 55 | G_END_DECLS 56 | -------------------------------------------------------------------------------- /gusb/gusb-device-private.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2011 Hans de Goede 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | G_BEGIN_DECLS 14 | 15 | GUsbDevice * 16 | _g_usb_device_new(GUsbContext *context, libusb_device *device, GError **error); 17 | gboolean 18 | _g_usb_device_load(GUsbDevice *self, JsonObject *json_object, GError **error); 19 | gboolean 20 | _g_usb_device_save(GUsbDevice *self, JsonBuilder *json_builder, GError **error); 21 | void 22 | _g_usb_device_add_event(GUsbDevice *self, GUsbDeviceEvent *event); 23 | 24 | libusb_device * 25 | _g_usb_device_get_device(GUsbDevice *self); 26 | gboolean 27 | _g_usb_device_open_internal(GUsbDevice *self, GError **error); 28 | 29 | G_END_DECLS 30 | -------------------------------------------------------------------------------- /gusb/gusb-device.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2010 Richard Hughes 4 | * Copyright (C) 2011 Hans de Goede 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | G_BEGIN_DECLS 18 | 19 | #define G_USB_TYPE_DEVICE (g_usb_device_get_type()) 20 | #define G_USB_DEVICE_ERROR (g_usb_device_error_quark()) 21 | 22 | G_DECLARE_DERIVABLE_TYPE(GUsbDevice, g_usb_device, G_USB, DEVICE, GObject) 23 | 24 | /** 25 | * GUsbDeviceDirection: 26 | * 27 | * The message direction. 28 | **/ 29 | typedef enum { 30 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, /* IN */ 31 | G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE /* OUT */ 32 | } GUsbDeviceDirection; 33 | 34 | /** 35 | * GUsbDeviceRequestType: 36 | * 37 | * The message request type. 38 | **/ 39 | typedef enum { 40 | G_USB_DEVICE_REQUEST_TYPE_STANDARD, 41 | G_USB_DEVICE_REQUEST_TYPE_CLASS, 42 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, 43 | G_USB_DEVICE_REQUEST_TYPE_RESERVED 44 | } GUsbDeviceRequestType; 45 | 46 | /** 47 | * GUsbDeviceRecipient: 48 | * 49 | * The message recipient. 50 | **/ 51 | typedef enum { 52 | G_USB_DEVICE_RECIPIENT_DEVICE, 53 | G_USB_DEVICE_RECIPIENT_INTERFACE, 54 | G_USB_DEVICE_RECIPIENT_ENDPOINT, 55 | G_USB_DEVICE_RECIPIENT_OTHER 56 | } GUsbDeviceRecipient; 57 | 58 | /** 59 | * GUsbDeviceError: 60 | * @G_USB_DEVICE_ERROR_INTERNAL: Internal error 61 | * @G_USB_DEVICE_ERROR_IO: IO error 62 | * @G_USB_DEVICE_ERROR_TIMED_OUT: Operation timed out 63 | * @G_USB_DEVICE_ERROR_NOT_SUPPORTED: Operation not supported 64 | * @G_USB_DEVICE_ERROR_NO_DEVICE: No device found 65 | * @G_USB_DEVICE_ERROR_NOT_OPEN: Device is not open 66 | * @G_USB_DEVICE_ERROR_ALREADY_OPEN: Device is already open 67 | * @G_USB_DEVICE_ERROR_CANCELLED: Operation was cancelled 68 | * @G_USB_DEVICE_ERROR_FAILED: Operation failed 69 | * @G_USB_DEVICE_ERROR_PERMISSION_DENIED: Permission denied 70 | * @G_USB_DEVICE_ERROR_BUSY: Device was busy 71 | * 72 | * The error code. 73 | **/ 74 | typedef enum { 75 | G_USB_DEVICE_ERROR_INTERNAL, 76 | G_USB_DEVICE_ERROR_IO, 77 | G_USB_DEVICE_ERROR_TIMED_OUT, 78 | G_USB_DEVICE_ERROR_NOT_SUPPORTED, 79 | G_USB_DEVICE_ERROR_NO_DEVICE, 80 | G_USB_DEVICE_ERROR_NOT_OPEN, 81 | G_USB_DEVICE_ERROR_ALREADY_OPEN, 82 | G_USB_DEVICE_ERROR_CANCELLED, 83 | G_USB_DEVICE_ERROR_FAILED, 84 | G_USB_DEVICE_ERROR_PERMISSION_DENIED, 85 | G_USB_DEVICE_ERROR_BUSY, 86 | /*< private >*/ 87 | G_USB_DEVICE_ERROR_LAST 88 | } GUsbDeviceError; 89 | 90 | /** 91 | * GUsbDeviceClaimInterfaceFlags: 92 | * 93 | * Flags for the g_usb_device_claim_interface and 94 | * g_usb_device_release_interface methods flags parameters. 95 | **/ 96 | typedef enum { 97 | G_USB_DEVICE_CLAIM_INTERFACE_NONE = 0, 98 | G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER = 1 << 0, 99 | } GUsbDeviceClaimInterfaceFlags; 100 | 101 | /** 102 | * GUsbDeviceClassCode: 103 | * 104 | * The USB device class. 105 | **/ 106 | typedef enum { 107 | G_USB_DEVICE_CLASS_INTERFACE_DESC = 0x00, 108 | G_USB_DEVICE_CLASS_AUDIO = 0x01, 109 | G_USB_DEVICE_CLASS_COMMUNICATIONS = 0x02, 110 | G_USB_DEVICE_CLASS_HID = 0x03, 111 | G_USB_DEVICE_CLASS_PHYSICAL = 0x05, 112 | G_USB_DEVICE_CLASS_IMAGE = 0x06, 113 | G_USB_DEVICE_CLASS_PRINTER = 0x07, 114 | G_USB_DEVICE_CLASS_MASS_STORAGE = 0x08, 115 | G_USB_DEVICE_CLASS_HUB = 0x09, 116 | G_USB_DEVICE_CLASS_CDC_DATA = 0x0a, 117 | G_USB_DEVICE_CLASS_SMART_CARD = 0x0b, 118 | G_USB_DEVICE_CLASS_CONTENT_SECURITY = 0x0d, 119 | G_USB_DEVICE_CLASS_VIDEO = 0x0e, 120 | G_USB_DEVICE_CLASS_PERSONAL_HEALTHCARE = 0x0f, 121 | G_USB_DEVICE_CLASS_AUDIO_VIDEO = 0x10, 122 | G_USB_DEVICE_CLASS_BILLBOARD = 0x11, 123 | G_USB_DEVICE_CLASS_DIAGNOSTIC = 0xdc, 124 | G_USB_DEVICE_CLASS_WIRELESS_CONTROLLER = 0xe0, 125 | G_USB_DEVICE_CLASS_MISCELLANEOUS = 0xef, 126 | G_USB_DEVICE_CLASS_APPLICATION_SPECIFIC = 0xfe, 127 | G_USB_DEVICE_CLASS_VENDOR_SPECIFIC = 0xff 128 | } GUsbDeviceClassCode; 129 | 130 | /** 131 | * GUsbDeviceLangid: 132 | * 133 | * The USB language ID. 134 | **/ 135 | typedef enum { 136 | G_USB_DEVICE_LANGID_INVALID = 0x0000, 137 | G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES = 0x0409, 138 | } GUsbDeviceLangid; 139 | 140 | struct _GUsbDeviceClass { 141 | GObjectClass parent_class; 142 | /*< private >*/ 143 | /* 144 | * If adding fields to this struct, remove corresponding 145 | * amount of padding to avoid changing overall struct size 146 | */ 147 | gchar _gusb_reserved[64]; 148 | }; 149 | 150 | GQuark 151 | g_usb_device_error_quark(void); 152 | 153 | const gchar * 154 | g_usb_device_get_platform_id(GUsbDevice *self); 155 | gboolean 156 | g_usb_device_is_emulated(GUsbDevice *self); 157 | GDateTime * 158 | g_usb_device_get_created(GUsbDevice *self); 159 | GUsbDevice * 160 | g_usb_device_get_parent(GUsbDevice *self); 161 | GPtrArray * 162 | g_usb_device_get_children(GUsbDevice *self); 163 | 164 | guint8 165 | g_usb_device_get_bus(GUsbDevice *self); 166 | guint8 167 | g_usb_device_get_address(GUsbDevice *self); 168 | guint8 169 | g_usb_device_get_port_number(GUsbDevice *self); 170 | 171 | guint16 172 | g_usb_device_get_vid(GUsbDevice *self); 173 | guint16 174 | g_usb_device_get_pid(GUsbDevice *self); 175 | guint16 176 | g_usb_device_get_release(GUsbDevice *self); 177 | guint16 178 | g_usb_device_get_spec(GUsbDevice *self); 179 | const gchar * 180 | g_usb_device_get_vid_as_str(GUsbDevice *self); 181 | const gchar * 182 | g_usb_device_get_pid_as_str(GUsbDevice *self); 183 | guint8 184 | g_usb_device_get_device_class(GUsbDevice *self); 185 | guint8 186 | g_usb_device_get_device_subclass(GUsbDevice *self); 187 | guint8 188 | g_usb_device_get_device_protocol(GUsbDevice *self); 189 | 190 | void 191 | g_usb_device_add_tag(GUsbDevice *self, const gchar *tag); 192 | void 193 | g_usb_device_remove_tag(GUsbDevice *self, const gchar *tag); 194 | gboolean 195 | g_usb_device_has_tag(GUsbDevice *self, const gchar *tag); 196 | GPtrArray * 197 | g_usb_device_get_tags(GUsbDevice *self); 198 | 199 | guint8 200 | g_usb_device_get_configuration_index(GUsbDevice *self); 201 | guint8 202 | g_usb_device_get_manufacturer_index(GUsbDevice *self); 203 | guint8 204 | g_usb_device_get_product_index(GUsbDevice *self); 205 | guint8 206 | g_usb_device_get_serial_number_index(GUsbDevice *self); 207 | guint8 208 | g_usb_device_get_custom_index(GUsbDevice *self, 209 | guint8 class_id, 210 | guint8 subclass_id, 211 | guint8 protocol_id, 212 | GError **error); 213 | 214 | GUsbInterface * 215 | g_usb_device_get_interface(GUsbDevice *self, 216 | guint8 class_id, 217 | guint8 subclass_id, 218 | guint8 protocol_id, 219 | GError **error); 220 | GPtrArray * 221 | g_usb_device_get_interfaces(GUsbDevice *self, GError **error); 222 | 223 | GPtrArray * 224 | g_usb_device_get_events(GUsbDevice *self); 225 | void 226 | g_usb_device_clear_events(GUsbDevice *self); 227 | 228 | GPtrArray * 229 | g_usb_device_get_hid_descriptors(GUsbDevice *self, GError **error); 230 | GBytes * 231 | g_usb_device_get_hid_descriptor_default(GUsbDevice *self, GError **error); 232 | 233 | GPtrArray * 234 | g_usb_device_get_bos_descriptors(GUsbDevice *self, GError **error); 235 | GUsbBosDescriptor * 236 | g_usb_device_get_bos_descriptor(GUsbDevice *self, guint8 capability, GError **error); 237 | 238 | gboolean 239 | g_usb_device_open(GUsbDevice *self, GError **error); 240 | gboolean 241 | g_usb_device_close(GUsbDevice *self, GError **error); 242 | 243 | gboolean 244 | g_usb_device_reset(GUsbDevice *self, GError **error); 245 | void 246 | g_usb_device_invalidate(GUsbDevice *self); 247 | 248 | gint 249 | g_usb_device_get_configuration(GUsbDevice *self, GError **error); 250 | gboolean 251 | g_usb_device_set_configuration(GUsbDevice *self, gint configuration, GError **error); 252 | 253 | gboolean 254 | g_usb_device_claim_interface(GUsbDevice *self, 255 | gint iface, 256 | GUsbDeviceClaimInterfaceFlags flags, 257 | GError **error); 258 | gboolean 259 | g_usb_device_release_interface(GUsbDevice *self, 260 | gint iface, 261 | GUsbDeviceClaimInterfaceFlags flags, 262 | GError **error); 263 | gboolean 264 | g_usb_device_set_interface_alt(GUsbDevice *self, gint iface, guint8 alt, GError **error); 265 | 266 | gchar * 267 | g_usb_device_get_string_descriptor(GUsbDevice *self, guint8 desc_index, GError **error); 268 | GBytes * 269 | g_usb_device_get_string_descriptor_bytes(GUsbDevice *self, 270 | guint8 desc_index, 271 | guint16 langid, 272 | GError **error); 273 | GBytes * 274 | g_usb_device_get_string_descriptor_bytes_full(GUsbDevice *self, 275 | guint8 desc_index, 276 | guint16 langid, 277 | gsize length, 278 | GError **error); 279 | 280 | /* sync -- TODO: use GCancellable and GUsbSource */ 281 | gboolean 282 | g_usb_device_control_transfer(GUsbDevice *self, 283 | GUsbDeviceDirection direction, 284 | GUsbDeviceRequestType request_type, 285 | GUsbDeviceRecipient recipient, 286 | guint8 request, 287 | guint16 value, 288 | guint16 idx, 289 | guint8 *data, 290 | gsize length, 291 | gsize *actual_length, 292 | guint timeout, 293 | GCancellable *cancellable, 294 | GError **error); 295 | 296 | gboolean 297 | g_usb_device_bulk_transfer(GUsbDevice *self, 298 | guint8 endpoint, 299 | guint8 *data, 300 | gsize length, 301 | gsize *actual_length, 302 | guint timeout, 303 | GCancellable *cancellable, 304 | GError **error); 305 | 306 | gboolean 307 | g_usb_device_interrupt_transfer(GUsbDevice *self, 308 | guint8 endpoint, 309 | guint8 *data, 310 | gsize length, 311 | gsize *actual_length, 312 | guint timeout, 313 | GCancellable *cancellable, 314 | GError **error); 315 | 316 | /* async */ 317 | 318 | void 319 | g_usb_device_control_transfer_async(GUsbDevice *self, 320 | GUsbDeviceDirection direction, 321 | GUsbDeviceRequestType request_type, 322 | GUsbDeviceRecipient recipient, 323 | guint8 request, 324 | guint16 value, 325 | guint16 idx, 326 | guint8 *data, 327 | gsize length, 328 | guint timeout, 329 | GCancellable *cancellable, 330 | GAsyncReadyCallback callback, 331 | gpointer user_data); 332 | gssize 333 | g_usb_device_control_transfer_finish(GUsbDevice *self, GAsyncResult *res, GError **error); 334 | 335 | void 336 | g_usb_device_bulk_transfer_async(GUsbDevice *self, 337 | guint8 endpoint, 338 | guint8 *data, 339 | gsize length, 340 | guint timeout, 341 | GCancellable *cancellable, 342 | GAsyncReadyCallback callback, 343 | gpointer user_data); 344 | gssize 345 | g_usb_device_bulk_transfer_finish(GUsbDevice *self, GAsyncResult *res, GError **error); 346 | 347 | void 348 | g_usb_device_interrupt_transfer_async(GUsbDevice *self, 349 | guint8 endpoint, 350 | guint8 *data, 351 | gsize length, 352 | guint timeout, 353 | GCancellable *cancellable, 354 | GAsyncReadyCallback callback, 355 | gpointer user_data); 356 | gssize 357 | g_usb_device_interrupt_transfer_finish(GUsbDevice *self, GAsyncResult *res, GError **error); 358 | 359 | G_END_DECLS 360 | -------------------------------------------------------------------------------- /gusb/gusb-endpoint-private.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2020 Emmanuel Pacaud 4 | * Copyright (C) 2022 Richard Hughes 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | G_BEGIN_DECLS 16 | 17 | GUsbEndpoint * 18 | _g_usb_endpoint_new(const struct libusb_endpoint_descriptor *endpoint); 19 | 20 | gboolean 21 | _g_usb_endpoint_load(GUsbEndpoint *self, JsonObject *json_object, GError **error); 22 | gboolean 23 | _g_usb_endpoint_save(GUsbEndpoint *self, JsonBuilder *json_builder, GError **error); 24 | 25 | G_END_DECLS 26 | -------------------------------------------------------------------------------- /gusb/gusb-endpoint.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2020 Emmanuel Pacaud 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | /** 9 | * SECTION:gusb-endpoint 10 | * @short_description: GLib wrapper around a USB endpoint. 11 | * 12 | * This object is a thin glib wrapper around a libusb_endpoint_descriptor. 13 | * 14 | * All the data is copied when the object is created and the original 15 | * descriptor can be destroyed any at point. 16 | */ 17 | 18 | #include "config.h" 19 | 20 | #include 21 | 22 | #include "gusb-endpoint-private.h" 23 | #include "gusb-json-common.h" 24 | 25 | struct _GUsbEndpoint { 26 | GObject parent_instance; 27 | 28 | struct libusb_endpoint_descriptor endpoint_descriptor; 29 | GBytes *extra; 30 | }; 31 | 32 | G_DEFINE_TYPE(GUsbEndpoint, g_usb_endpoint, G_TYPE_OBJECT) 33 | 34 | static void 35 | g_usb_endpoint_finalize(GObject *object) 36 | { 37 | GUsbEndpoint *self = G_USB_ENDPOINT(object); 38 | 39 | g_bytes_unref(self->extra); 40 | 41 | G_OBJECT_CLASS(g_usb_endpoint_parent_class)->finalize(object); 42 | } 43 | 44 | static void 45 | g_usb_endpoint_class_init(GUsbEndpointClass *klass) 46 | { 47 | GObjectClass *object_class = G_OBJECT_CLASS(klass); 48 | 49 | object_class->finalize = g_usb_endpoint_finalize; 50 | } 51 | 52 | static void 53 | g_usb_endpoint_init(GUsbEndpoint *self) 54 | { 55 | } 56 | 57 | gboolean 58 | _g_usb_endpoint_load(GUsbEndpoint *self, JsonObject *json_object, GError **error) 59 | { 60 | const gchar *str; 61 | 62 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), FALSE); 63 | g_return_val_if_fail(json_object != NULL, FALSE); 64 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 65 | 66 | /* optional properties */ 67 | self->endpoint_descriptor.bDescriptorType = 68 | json_object_get_int_member_with_default(json_object, "DescriptorType", 0x0); 69 | self->endpoint_descriptor.bEndpointAddress = 70 | json_object_get_int_member_with_default(json_object, "EndpointAddress", 0x0); 71 | self->endpoint_descriptor.bRefresh = 72 | json_object_get_int_member_with_default(json_object, "Refresh", 0x0); 73 | self->endpoint_descriptor.bInterval = 74 | json_object_get_int_member_with_default(json_object, "Interval", 0x0); 75 | self->endpoint_descriptor.bSynchAddress = 76 | json_object_get_int_member_with_default(json_object, "SynchAddress", 0x0); 77 | self->endpoint_descriptor.wMaxPacketSize = 78 | json_object_get_int_member_with_default(json_object, "MaxPacketSize", 0x0); 79 | 80 | /* extra data */ 81 | str = json_object_get_string_member_with_default(json_object, "ExtraData", NULL); 82 | if (str != NULL) { 83 | gsize bufsz = 0; 84 | g_autofree guchar *buf = g_base64_decode(str, &bufsz); 85 | if (self->extra != NULL) 86 | g_bytes_unref(self->extra); 87 | self->extra = g_bytes_new_take(g_steal_pointer(&buf), bufsz); 88 | } 89 | 90 | /* success */ 91 | return TRUE; 92 | } 93 | 94 | gboolean 95 | _g_usb_endpoint_save(GUsbEndpoint *self, JsonBuilder *json_builder, GError **error) 96 | { 97 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), FALSE); 98 | g_return_val_if_fail(json_builder != NULL, FALSE); 99 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 100 | 101 | /* start */ 102 | json_builder_begin_object(json_builder); 103 | 104 | /* optional properties */ 105 | if (self->endpoint_descriptor.bDescriptorType != 0) { 106 | json_builder_set_member_name(json_builder, "DescriptorType"); 107 | json_builder_add_int_value(json_builder, self->endpoint_descriptor.bDescriptorType); 108 | } 109 | if (self->endpoint_descriptor.bEndpointAddress != 0) { 110 | json_builder_set_member_name(json_builder, "EndpointAddress"); 111 | json_builder_add_int_value(json_builder, 112 | self->endpoint_descriptor.bEndpointAddress); 113 | } 114 | if (self->endpoint_descriptor.bRefresh != 0) { 115 | json_builder_set_member_name(json_builder, "Refresh"); 116 | json_builder_add_int_value(json_builder, self->endpoint_descriptor.bRefresh); 117 | } 118 | if (self->endpoint_descriptor.bInterval != 0) { 119 | json_builder_set_member_name(json_builder, "Interval"); 120 | json_builder_add_int_value(json_builder, self->endpoint_descriptor.bInterval); 121 | } 122 | if (self->endpoint_descriptor.bSynchAddress != 0) { 123 | json_builder_set_member_name(json_builder, "SynchAddress"); 124 | json_builder_add_int_value(json_builder, self->endpoint_descriptor.bSynchAddress); 125 | } 126 | if (self->endpoint_descriptor.wMaxPacketSize != 0) { 127 | json_builder_set_member_name(json_builder, "MaxPacketSize"); 128 | json_builder_add_int_value(json_builder, self->endpoint_descriptor.wMaxPacketSize); 129 | } 130 | 131 | /* extra data */ 132 | if (self->extra != NULL && g_bytes_get_size(self->extra) > 0) { 133 | g_autofree gchar *str = g_base64_encode(g_bytes_get_data(self->extra, NULL), 134 | g_bytes_get_size(self->extra)); 135 | json_builder_set_member_name(json_builder, "ExtraData"); 136 | json_builder_add_string_value(json_builder, str); 137 | } 138 | 139 | /* success */ 140 | json_builder_end_object(json_builder); 141 | return TRUE; 142 | } 143 | 144 | /** 145 | * _g_usb_endpoint_new: 146 | * 147 | * Return value: a new #GUsbEndpoint object. 148 | * 149 | * Since: 0.3.3 150 | **/ 151 | GUsbEndpoint * 152 | _g_usb_endpoint_new(const struct libusb_endpoint_descriptor *endpoint_descriptor) 153 | { 154 | GUsbEndpoint *self; 155 | self = g_object_new(G_USB_TYPE_ENDPOINT, NULL); 156 | 157 | /* copy the data */ 158 | memcpy(&self->endpoint_descriptor, 159 | endpoint_descriptor, 160 | sizeof(struct libusb_endpoint_descriptor)); 161 | self->extra = g_bytes_new(endpoint_descriptor->extra, endpoint_descriptor->extra_length); 162 | 163 | return G_USB_ENDPOINT(self); 164 | } 165 | 166 | /** 167 | * g_usb_endpoint_get_kind: 168 | * @self: a #GUsbEndpoint 169 | * 170 | * Gets the type of endpoint. 171 | * 172 | * Return value: The 8-bit type 173 | * 174 | * Since: 0.3.3 175 | **/ 176 | guint8 177 | g_usb_endpoint_get_kind(GUsbEndpoint *self) 178 | { 179 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), 0); 180 | return self->endpoint_descriptor.bDescriptorType; 181 | } 182 | 183 | /** 184 | * g_usb_endpoint_get_maximum_packet_size: 185 | * @self: a #GUsbEndpoint 186 | * 187 | * Gets the maximum packet size this endpoint is capable of sending/receiving. 188 | * 189 | * Return value: The maximum packet size 190 | * 191 | * Since: 0.3.3 192 | **/ 193 | guint16 194 | g_usb_endpoint_get_maximum_packet_size(GUsbEndpoint *self) 195 | { 196 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), 0); 197 | return self->endpoint_descriptor.wMaxPacketSize; 198 | } 199 | 200 | /** 201 | * g_usb_endpoint_get_polling_interval: 202 | * @self: a #GUsbEndpoint 203 | * 204 | * Gets the endpoint polling interval. 205 | * 206 | * Return value: The endpoint polling interval 207 | * 208 | * Since: 0.3.3 209 | **/ 210 | guint8 211 | g_usb_endpoint_get_polling_interval(GUsbEndpoint *self) 212 | { 213 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), 0); 214 | return self->endpoint_descriptor.bInterval; 215 | } 216 | 217 | /** 218 | * g_usb_endpoint_get_refresh: 219 | * @self: a #GUsbEndpoint 220 | * 221 | * Gets the rate at which synchronization feedback is provided, for audio device only. 222 | * 223 | * Return value: The endpoint refresh 224 | * 225 | * Since: 0.3.3 226 | **/ 227 | guint8 228 | g_usb_endpoint_get_refresh(GUsbEndpoint *self) 229 | { 230 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), 0); 231 | return self->endpoint_descriptor.bRefresh; 232 | } 233 | 234 | /** 235 | * g_usb_endpoint_get_synch_address: 236 | * @self: a #GUsbEndpoint 237 | * 238 | * Gets the address if the synch endpoint, for audio device only. 239 | * 240 | * Return value: The synch endpoint address 241 | * 242 | * Since: 0.3.3 243 | **/ 244 | guint8 245 | g_usb_endpoint_get_synch_address(GUsbEndpoint *self) 246 | { 247 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), 0); 248 | return self->endpoint_descriptor.bSynchAddress; 249 | } 250 | 251 | /** 252 | * g_usb_endpoint_get_address: 253 | * @self: a #GUsbEndpoint 254 | * 255 | * Gets the address of the endpoint. 256 | * 257 | * Return value: The 4-bit endpoint address 258 | * 259 | * Since: 0.3.3 260 | **/ 261 | guint8 262 | g_usb_endpoint_get_address(GUsbEndpoint *self) 263 | { 264 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), 0); 265 | return self->endpoint_descriptor.bEndpointAddress; 266 | } 267 | 268 | /** 269 | * g_usb_endpoint_get_number: 270 | * @self: a #GUsbEndpoint 271 | * 272 | * Gets the number part of endpoint address. 273 | * 274 | * Return value: The lower 4-bit of endpoint address 275 | * 276 | * Since: 0.3.3 277 | **/ 278 | guint8 279 | g_usb_endpoint_get_number(GUsbEndpoint *self) 280 | { 281 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), 0); 282 | return (self->endpoint_descriptor.bEndpointAddress) & 0xf; 283 | } 284 | 285 | /** 286 | * g_usb_endpoint_get_direction: 287 | * @self: a #GUsbEndpoint 288 | * 289 | * Gets the direction of the endpoint. 290 | * 291 | * Return value: The endpoint direction 292 | * 293 | * Since: 0.3.3 294 | **/ 295 | GUsbDeviceDirection 296 | g_usb_endpoint_get_direction(GUsbEndpoint *self) 297 | { 298 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), 0); 299 | return (self->endpoint_descriptor.bEndpointAddress & 0x80) 300 | ? G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST 301 | : G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE; 302 | } 303 | 304 | /** 305 | * g_usb_endpoint_get_extra: 306 | * @self: a #GUsbEndpoint 307 | * 308 | * Gets any extra data from the endpoint. 309 | * 310 | * Return value: (transfer none): a #GBytes, or %NULL for failure 311 | * 312 | * Since: 0.3.3 313 | **/ 314 | GBytes * 315 | g_usb_endpoint_get_extra(GUsbEndpoint *self) 316 | { 317 | g_return_val_if_fail(G_USB_IS_ENDPOINT(self), NULL); 318 | return self->extra; 319 | } 320 | -------------------------------------------------------------------------------- /gusb/gusb-endpoint.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2020 Emmanuel Pacaud 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | G_BEGIN_DECLS 13 | 14 | #define G_USB_TYPE_ENDPOINT (g_usb_endpoint_get_type()) 15 | G_DECLARE_FINAL_TYPE(GUsbEndpoint, g_usb_endpoint, G_USB, ENDPOINT, GObject) 16 | 17 | guint8 18 | g_usb_endpoint_get_kind(GUsbEndpoint *self); 19 | guint16 20 | g_usb_endpoint_get_maximum_packet_size(GUsbEndpoint *self); 21 | guint8 22 | g_usb_endpoint_get_polling_interval(GUsbEndpoint *self); 23 | guint8 24 | g_usb_endpoint_get_refresh(GUsbEndpoint *self); 25 | guint8 26 | g_usb_endpoint_get_synch_address(GUsbEndpoint *self); 27 | guint8 28 | g_usb_endpoint_get_address(GUsbEndpoint *self); 29 | guint8 30 | g_usb_endpoint_get_number(GUsbEndpoint *self); 31 | GUsbDeviceDirection 32 | g_usb_endpoint_get_direction(GUsbEndpoint *self); 33 | GBytes * 34 | g_usb_endpoint_get_extra(GUsbEndpoint *self); 35 | 36 | G_END_DECLS 37 | -------------------------------------------------------------------------------- /gusb/gusb-interface-private.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2015 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | G_BEGIN_DECLS 15 | 16 | GUsbInterface * 17 | _g_usb_interface_new(const struct libusb_interface_descriptor *iface); 18 | 19 | gboolean 20 | _g_usb_interface_load(GUsbInterface *self, JsonObject *json_object, GError **error); 21 | gboolean 22 | _g_usb_interface_save(GUsbInterface *self, JsonBuilder *json_builder, GError **error); 23 | 24 | G_END_DECLS 25 | -------------------------------------------------------------------------------- /gusb/gusb-interface.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2015 Richard Hughes 4 | * Copyright (C) 2020 Emmanuel Pacaud 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | /** 10 | * SECTION:gusb-interface 11 | * @short_description: GLib wrapper around a USB interface. 12 | * 13 | * This object is a thin glib wrapper around a libusb_interface_descriptor. 14 | * 15 | * All the data is copied when the object is created and the original 16 | * descriptor can be destroyed any at point. 17 | */ 18 | 19 | #include "config.h" 20 | 21 | #include 22 | 23 | #include "gusb-endpoint-private.h" 24 | #include "gusb-interface-private.h" 25 | #include "gusb-json-common.h" 26 | 27 | struct _GUsbInterface { 28 | GObject parent_instance; 29 | 30 | struct libusb_interface_descriptor iface; 31 | GBytes *extra; 32 | 33 | GPtrArray *endpoints; 34 | }; 35 | 36 | G_DEFINE_TYPE(GUsbInterface, g_usb_interface, G_TYPE_OBJECT) 37 | 38 | static void 39 | g_usb_interface_finalize(GObject *object) 40 | { 41 | GUsbInterface *self = G_USB_INTERFACE(object); 42 | 43 | if (self->extra != NULL) 44 | g_bytes_unref(self->extra); 45 | if (self->endpoints != NULL) 46 | g_ptr_array_unref(self->endpoints); 47 | 48 | G_OBJECT_CLASS(g_usb_interface_parent_class)->finalize(object); 49 | } 50 | 51 | static void 52 | g_usb_interface_class_init(GUsbInterfaceClass *klass) 53 | { 54 | GObjectClass *object_class = G_OBJECT_CLASS(klass); 55 | 56 | object_class->finalize = g_usb_interface_finalize; 57 | } 58 | 59 | static void 60 | g_usb_interface_init(GUsbInterface *self) 61 | { 62 | } 63 | 64 | gboolean 65 | _g_usb_interface_load(GUsbInterface *self, JsonObject *json_object, GError **error) 66 | { 67 | const gchar *str; 68 | 69 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), FALSE); 70 | g_return_val_if_fail(json_object != NULL, FALSE); 71 | g_return_val_if_fail(error == NULL || *error == NULL, -1); 72 | 73 | /* optional properties */ 74 | self->iface.bLength = json_object_get_int_member_with_default(json_object, "Length", 0x0); 75 | self->iface.bDescriptorType = 76 | json_object_get_int_member_with_default(json_object, "DescriptorType", 0x0); 77 | self->iface.bInterfaceNumber = 78 | json_object_get_int_member_with_default(json_object, "InterfaceNumber", 0x0); 79 | self->iface.bAlternateSetting = 80 | json_object_get_int_member_with_default(json_object, "AlternateSetting", 0x0); 81 | self->iface.bInterfaceClass = 82 | json_object_get_int_member_with_default(json_object, "InterfaceClass", 0x0); 83 | self->iface.bInterfaceSubClass = 84 | json_object_get_int_member_with_default(json_object, "InterfaceSubClass", 0x0); 85 | self->iface.bInterfaceProtocol = 86 | json_object_get_int_member_with_default(json_object, "InterfaceProtocol", 0x0); 87 | self->iface.iInterface = 88 | json_object_get_int_member_with_default(json_object, "Interface", 0x0); 89 | 90 | /* array of endpoints */ 91 | if (json_object_has_member(json_object, "UsbEndpoints")) { 92 | JsonArray *json_array = json_object_get_array_member(json_object, "UsbEndpoints"); 93 | self->endpoints = g_ptr_array_new_with_free_func(g_object_unref); 94 | for (guint i = 0; i < json_array_get_length(json_array); i++) { 95 | JsonNode *node_tmp = json_array_get_element(json_array, i); 96 | JsonObject *obj_tmp = json_node_get_object(node_tmp); 97 | g_autoptr(GUsbEndpoint) endpoint = g_object_new(G_USB_TYPE_ENDPOINT, NULL); 98 | if (!_g_usb_endpoint_load(endpoint, obj_tmp, error)) 99 | return FALSE; 100 | g_ptr_array_add(self->endpoints, g_object_ref(endpoint)); 101 | } 102 | } 103 | 104 | /* extra data */ 105 | str = json_object_get_string_member_with_default(json_object, "ExtraData", NULL); 106 | if (str != NULL) { 107 | gsize bufsz = 0; 108 | g_autofree guchar *buf = g_base64_decode(str, &bufsz); 109 | if (self->extra != NULL) 110 | g_bytes_unref(self->extra); 111 | self->extra = g_bytes_new_take(g_steal_pointer(&buf), bufsz); 112 | } 113 | 114 | /* success */ 115 | return TRUE; 116 | } 117 | 118 | gboolean 119 | _g_usb_interface_save(GUsbInterface *self, JsonBuilder *json_builder, GError **error) 120 | { 121 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), FALSE); 122 | g_return_val_if_fail(json_builder != NULL, FALSE); 123 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 124 | 125 | /* start */ 126 | json_builder_begin_object(json_builder); 127 | 128 | /* optional properties */ 129 | if (self->iface.bLength != 0) { 130 | json_builder_set_member_name(json_builder, "Length"); 131 | json_builder_add_int_value(json_builder, self->iface.bLength); 132 | } 133 | if (self->iface.bDescriptorType != 0) { 134 | json_builder_set_member_name(json_builder, "DescriptorType"); 135 | json_builder_add_int_value(json_builder, self->iface.bDescriptorType); 136 | } 137 | if (self->iface.bInterfaceNumber != 0) { 138 | json_builder_set_member_name(json_builder, "InterfaceNumber"); 139 | json_builder_add_int_value(json_builder, self->iface.bInterfaceNumber); 140 | } 141 | if (self->iface.bAlternateSetting != 0) { 142 | json_builder_set_member_name(json_builder, "AlternateSetting"); 143 | json_builder_add_int_value(json_builder, self->iface.bAlternateSetting); 144 | } 145 | if (self->iface.bInterfaceClass != 0) { 146 | json_builder_set_member_name(json_builder, "InterfaceClass"); 147 | json_builder_add_int_value(json_builder, self->iface.bInterfaceClass); 148 | } 149 | if (self->iface.bInterfaceSubClass != 0) { 150 | json_builder_set_member_name(json_builder, "InterfaceSubClass"); 151 | json_builder_add_int_value(json_builder, self->iface.bInterfaceSubClass); 152 | } 153 | if (self->iface.bInterfaceProtocol != 0) { 154 | json_builder_set_member_name(json_builder, "InterfaceProtocol"); 155 | json_builder_add_int_value(json_builder, self->iface.bInterfaceProtocol); 156 | } 157 | if (self->iface.iInterface != 0) { 158 | json_builder_set_member_name(json_builder, "Interface"); 159 | json_builder_add_int_value(json_builder, self->iface.iInterface); 160 | } 161 | 162 | /* array of endpoints */ 163 | if (self->endpoints != NULL && self->endpoints->len > 0) { 164 | json_builder_set_member_name(json_builder, "UsbEndpoints"); 165 | json_builder_begin_array(json_builder); 166 | for (guint i = 0; i < self->endpoints->len; i++) { 167 | GUsbEndpoint *endpoint = g_ptr_array_index(self->endpoints, i); 168 | if (!_g_usb_endpoint_save(endpoint, json_builder, error)) 169 | return FALSE; 170 | } 171 | json_builder_end_array(json_builder); 172 | } 173 | 174 | /* extra data */ 175 | if (self->extra != NULL && g_bytes_get_size(self->extra) > 0) { 176 | g_autofree gchar *str = g_base64_encode(g_bytes_get_data(self->extra, NULL), 177 | g_bytes_get_size(self->extra)); 178 | json_builder_set_member_name(json_builder, "ExtraData"); 179 | json_builder_add_string_value(json_builder, str); 180 | } 181 | 182 | /* success */ 183 | json_builder_end_object(json_builder); 184 | return TRUE; 185 | } 186 | 187 | /** 188 | * _g_usb_interface_new: 189 | * 190 | * Return value: a new #GUsbInterface object. 191 | * 192 | * Since: 0.2.8 193 | **/ 194 | GUsbInterface * 195 | _g_usb_interface_new(const struct libusb_interface_descriptor *iface) 196 | { 197 | GUsbInterface *self; 198 | self = g_object_new(G_USB_TYPE_INTERFACE, NULL); 199 | 200 | /* copy the data */ 201 | memcpy(&self->iface, iface, sizeof(struct libusb_interface_descriptor)); 202 | self->extra = g_bytes_new(iface->extra, iface->extra_length); 203 | 204 | self->endpoints = g_ptr_array_new_with_free_func(g_object_unref); 205 | for (guint i = 0; i < iface->bNumEndpoints; i++) 206 | g_ptr_array_add(self->endpoints, _g_usb_endpoint_new(&iface->endpoint[i])); 207 | 208 | return G_USB_INTERFACE(self); 209 | } 210 | 211 | /** 212 | * g_usb_interface_get_length: 213 | * @self: a #GUsbInterface 214 | * 215 | * Gets the USB bus number for the interface. 216 | * 217 | * Return value: The 8-bit bus number 218 | * 219 | * Since: 0.2.8 220 | **/ 221 | guint8 222 | g_usb_interface_get_length(GUsbInterface *self) 223 | { 224 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), 0); 225 | return self->iface.bLength; 226 | } 227 | 228 | /** 229 | * g_usb_interface_get_kind: 230 | * @self: a #GUsbInterface 231 | * 232 | * Gets the type of interface. 233 | * 234 | * Return value: The 8-bit address 235 | * 236 | * Since: 0.2.8 237 | **/ 238 | guint8 239 | g_usb_interface_get_kind(GUsbInterface *self) 240 | { 241 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), 0); 242 | return self->iface.bDescriptorType; 243 | } 244 | 245 | /** 246 | * g_usb_interface_get_number: 247 | * @self: a #GUsbInterface 248 | * 249 | * Gets the interface number. 250 | * 251 | * Return value: The interface ID 252 | * 253 | * Since: 0.2.8 254 | **/ 255 | guint8 256 | g_usb_interface_get_number(GUsbInterface *self) 257 | { 258 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), 0); 259 | return self->iface.bInterfaceNumber; 260 | } 261 | 262 | /** 263 | * g_usb_interface_get_alternate: 264 | * @self: a #GUsbInterface 265 | * 266 | * Gets the alternate setting for the interface. 267 | * 268 | * Return value: alt setting, typically zero. 269 | * 270 | * Since: 0.2.8 271 | **/ 272 | guint8 273 | g_usb_interface_get_alternate(GUsbInterface *self) 274 | { 275 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), 0); 276 | return self->iface.bAlternateSetting; 277 | } 278 | 279 | /** 280 | * g_usb_interface_get_class: 281 | * @self: a #GUsbInterface 282 | * 283 | * Gets the interface class, typically a #GUsbInterfaceClassCode. 284 | * 285 | * Return value: a interface class number, e.g. 0x09 is a USB hub. 286 | * 287 | * Since: 0.2.8 288 | **/ 289 | guint8 290 | g_usb_interface_get_class(GUsbInterface *self) 291 | { 292 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), 0); 293 | return self->iface.bInterfaceClass; 294 | } 295 | 296 | /** 297 | * g_usb_interface_get_subclass: 298 | * @self: a #GUsbInterface 299 | * 300 | * Gets the interface subclass qualified by the class number. 301 | * See g_usb_interface_get_class(). 302 | * 303 | * Return value: a interface subclass number. 304 | * 305 | * Since: 0.2.8 306 | **/ 307 | guint8 308 | g_usb_interface_get_subclass(GUsbInterface *self) 309 | { 310 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), 0); 311 | return self->iface.bInterfaceSubClass; 312 | } 313 | 314 | /** 315 | * g_usb_interface_get_protocol: 316 | * @self: a #GUsbInterface 317 | * 318 | * Gets the interface protocol qualified by the class and subclass numbers. 319 | * See g_usb_interface_get_class() and g_usb_interface_get_subclass(). 320 | * 321 | * Return value: a interface protocol number. 322 | * 323 | * Since: 0.2.8 324 | **/ 325 | guint8 326 | g_usb_interface_get_protocol(GUsbInterface *self) 327 | { 328 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), 0); 329 | return self->iface.bInterfaceProtocol; 330 | } 331 | 332 | /** 333 | * g_usb_interface_get_index: 334 | * @self: a #GUsbInterface 335 | * 336 | * Gets the index for the string descriptor. 337 | * 338 | * Return value: a string descriptor index. 339 | * 340 | * Since: 0.2.8 341 | **/ 342 | guint8 343 | g_usb_interface_get_index(GUsbInterface *self) 344 | { 345 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), 0); 346 | return self->iface.iInterface; 347 | } 348 | 349 | /** 350 | * g_usb_interface_get_extra: 351 | * @self: a #GUsbInterface 352 | * 353 | * Gets any extra data from the interface. 354 | * 355 | * Return value: (transfer none): a #GBytes, or %NULL for failure 356 | * 357 | * Since: 0.2.8 358 | **/ 359 | GBytes * 360 | g_usb_interface_get_extra(GUsbInterface *self) 361 | { 362 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), NULL); 363 | return self->extra; 364 | } 365 | 366 | /** 367 | * g_usb_interface_get_endpoints: 368 | * @self: a #GUsbInterface 369 | * 370 | * Gets interface endpoints. 371 | * 372 | * Return value: (transfer container) (element-type GUsbEndpoint): an array of endpoints, 373 | * or %NULL on failure. 374 | * 375 | * Since: 0.3.3 376 | **/ 377 | GPtrArray * 378 | g_usb_interface_get_endpoints(GUsbInterface *self) 379 | { 380 | g_return_val_if_fail(G_USB_IS_INTERFACE(self), NULL); 381 | if (self->endpoints == NULL) 382 | return NULL; 383 | return g_ptr_array_ref(self->endpoints); 384 | } 385 | -------------------------------------------------------------------------------- /gusb/gusb-interface.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2015 Richard Hughes 4 | * Copyright (C) 2020 Emmanuel Pacaud 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | G_BEGIN_DECLS 14 | 15 | #define G_USB_TYPE_INTERFACE (g_usb_interface_get_type()) 16 | G_DECLARE_FINAL_TYPE(GUsbInterface, g_usb_interface, G_USB, INTERFACE, GObject) 17 | 18 | guint8 19 | g_usb_interface_get_length(GUsbInterface *self); 20 | guint8 21 | g_usb_interface_get_kind(GUsbInterface *self); 22 | guint8 23 | g_usb_interface_get_number(GUsbInterface *self); 24 | guint8 25 | g_usb_interface_get_alternate(GUsbInterface *self); 26 | guint8 27 | g_usb_interface_get_class(GUsbInterface *self); 28 | guint8 29 | g_usb_interface_get_subclass(GUsbInterface *self); 30 | guint8 31 | g_usb_interface_get_protocol(GUsbInterface *self); 32 | guint8 33 | g_usb_interface_get_index(GUsbInterface *self); 34 | GBytes * 35 | g_usb_interface_get_extra(GUsbInterface *self); 36 | GPtrArray * 37 | g_usb_interface_get_endpoints(GUsbInterface *self); 38 | 39 | G_END_DECLS 40 | -------------------------------------------------------------------------------- /gusb/gusb-json-common.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2023 Collabora Ltd. 4 | * @author Frédéric Danis 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | #include "config.h" 10 | 11 | #include "gusb-json-common.h" 12 | 13 | #if !JSON_CHECK_VERSION(1, 6, 0) 14 | const char * 15 | json_object_get_string_member_with_default(JsonObject *json_object, 16 | const char *member_name, 17 | const char *default_value) 18 | { 19 | if (!json_object_has_member(json_object, member_name)) 20 | return default_value; 21 | return json_object_get_string_member(json_object, member_name); 22 | } 23 | 24 | gint64 25 | json_object_get_int_member_with_default(JsonObject *json_object, 26 | const char *member_name, 27 | gint64 default_value) 28 | { 29 | if (!json_object_has_member(json_object, member_name)) 30 | return default_value; 31 | return json_object_get_int_member(json_object, member_name); 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /gusb/gusb-json-common.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2023 Collabora Ltd. 4 | * @author Frédéric Danis 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | G_BEGIN_DECLS 15 | 16 | #if !JSON_CHECK_VERSION(1, 6, 0) 17 | const char * 18 | json_object_get_string_member_with_default(JsonObject *json_object, 19 | const char *member_name, 20 | const char *default_value); 21 | gint64 22 | json_object_get_int_member_with_default(JsonObject *json_object, 23 | const char *member_name, 24 | gint64 default_value); 25 | #endif 26 | 27 | G_END_DECLS 28 | -------------------------------------------------------------------------------- /gusb/gusb-private.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2011 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #define __GUSB_INSIDE__ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #undef __GUSB_INSIDE__ 17 | -------------------------------------------------------------------------------- /gusb/gusb-self-test.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2011 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #include "config.h" 9 | 10 | #include "gusb-context-private.h" 11 | 12 | static void 13 | gusb_device_func(void) 14 | { 15 | GUsbDevice *device; 16 | g_autoptr(GError) error = NULL; 17 | g_autoptr(GPtrArray) array = NULL; 18 | g_autoptr(GUsbContext) ctx = NULL; 19 | 20 | #ifdef __FreeBSD__ 21 | g_test_skip("Root hubs on FreeBSD have vid and pid set to zero"); 22 | return; 23 | #endif 24 | 25 | ctx = g_usb_context_new(&error); 26 | g_assert_no_error(error); 27 | g_assert(ctx != NULL); 28 | 29 | g_usb_context_set_debug(ctx, G_LOG_LEVEL_ERROR); 30 | 31 | array = g_usb_context_get_devices(ctx); 32 | g_assert(array != NULL); 33 | g_assert_cmpint(array->len, >, 0); 34 | device = G_USB_DEVICE(g_ptr_array_index(array, 0)); 35 | 36 | g_assert_cmpint(g_usb_device_get_vid(device), >, 0x0000); 37 | g_assert_cmpint(g_usb_device_get_pid(device), >, 0x0000); 38 | 39 | g_assert_false(g_usb_device_has_tag(device, "foobar")); 40 | g_usb_device_add_tag(device, "foobar"); 41 | g_usb_device_add_tag(device, "foobar"); 42 | g_assert_true(g_usb_device_has_tag(device, "foobar")); 43 | g_usb_device_remove_tag(device, "foobar"); 44 | g_assert_false(g_usb_device_has_tag(device, "foobar")); 45 | } 46 | 47 | static void 48 | gusb_context_lookup_func(void) 49 | { 50 | GError *error = NULL; 51 | const gchar *tmp; 52 | g_autoptr(GUsbContext) ctx = NULL; 53 | 54 | ctx = g_usb_context_new(&error); 55 | g_assert_no_error(error); 56 | g_assert(ctx != NULL); 57 | 58 | tmp = _g_usb_context_lookup_vendor(ctx, 0x04d8, &error); 59 | g_assert_no_error(error); 60 | g_assert_cmpstr(tmp, ==, "Microchip Technology, Inc."); 61 | tmp = _g_usb_context_lookup_product(ctx, 0x04d8, 0xf8da, &error); 62 | g_assert_no_error(error); 63 | g_assert_cmpstr(tmp, ==, "Hughski Ltd. ColorHug"); 64 | } 65 | 66 | static void 67 | gusb_context_func(void) 68 | { 69 | GPtrArray *array; 70 | guint old_number_of_devices; 71 | guint8 bus, address; 72 | GUsbDevice *device; 73 | g_autoptr(GError) error = NULL; 74 | g_autoptr(GUsbContext) ctx = NULL; 75 | 76 | #ifdef __FreeBSD__ 77 | g_test_skip("Root hubs on FreeBSD have vid and pid set to zero"); 78 | return; 79 | #endif 80 | 81 | ctx = g_usb_context_new(&error); 82 | g_assert_no_error(error); 83 | g_assert(ctx != NULL); 84 | 85 | g_usb_context_set_debug(ctx, G_LOG_LEVEL_ERROR); 86 | 87 | /* coldplug, and ensure we got some devices */ 88 | array = g_usb_context_get_devices(ctx); 89 | g_assert(array != NULL); 90 | g_assert_cmpint(array->len, >, 0); 91 | old_number_of_devices = array->len; 92 | 93 | /* Print a list (also exercising various bits of g_usb_device) */ 94 | g_print("\n"); 95 | for (guint i = 0; i < array->len; i++) { 96 | g_autofree gchar *manufacturer = NULL; 97 | g_autofree gchar *product = NULL; 98 | 99 | device = G_USB_DEVICE(g_ptr_array_index(array, i)); 100 | 101 | g_assert_cmpint(g_usb_device_get_vid(device), >, 0x0000); 102 | g_assert_cmpint(g_usb_device_get_pid(device), >, 0x0000); 103 | 104 | /* Needed for g_usb_device_get_string_descriptor below, 105 | not error checked to allow running basic tests without 106 | needing r/w rights on /dev/bus/usb nodes */ 107 | g_usb_context_set_debug(ctx, 0); 108 | g_usb_device_open(device, NULL); 109 | g_usb_context_set_debug(ctx, G_LOG_LEVEL_ERROR); 110 | 111 | /* We don't error check these as not all devices have these 112 | (and the device_open may have failed). */ 113 | manufacturer = 114 | g_usb_device_get_string_descriptor(device, 115 | g_usb_device_get_manufacturer_index(device), 116 | NULL); 117 | product = g_usb_device_get_string_descriptor(device, 118 | g_usb_device_get_product_index(device), 119 | NULL); 120 | 121 | g_usb_device_close(device, NULL); 122 | 123 | g_print("Found %04x:%04x, %s %s\n", 124 | g_usb_device_get_vid(device), 125 | g_usb_device_get_pid(device), 126 | manufacturer ? manufacturer : "", 127 | product ? product : ""); 128 | } 129 | g_ptr_array_unref(array); 130 | 131 | /* coldplug again, and ensure we did not duplicate devices */ 132 | array = g_usb_context_get_devices(ctx); 133 | g_assert(array != NULL); 134 | g_assert_cmpint(array->len, ==, old_number_of_devices); 135 | device = G_USB_DEVICE(g_ptr_array_index(array, 0)); 136 | bus = g_usb_device_get_bus(device); 137 | address = g_usb_device_get_address(device); 138 | g_ptr_array_unref(array); 139 | 140 | /* ensure we can search for the same device */ 141 | device = g_usb_context_find_by_bus_address(ctx, bus, address, &error); 142 | g_assert_no_error(error); 143 | g_assert(device != NULL); 144 | g_assert_cmpint(bus, ==, g_usb_device_get_bus(device)); 145 | g_assert_cmpint(address, ==, g_usb_device_get_address(device)); 146 | g_object_unref(device); 147 | 148 | /* get a device that can't exist */ 149 | device = g_usb_context_find_by_vid_pid(ctx, 0xffff, 0xffff, &error); 150 | g_assert_error(error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE); 151 | g_assert(device == NULL); 152 | } 153 | 154 | static void 155 | gusb_device_huey_func(void) 156 | { 157 | gboolean ret; 158 | GCancellable *cancellable = NULL; 159 | const gchar request[8] = {0x0e, 'G', 'r', 'M', 'b', 'k', 'e', 'd'}; 160 | g_autoptr(GError) error = NULL; 161 | g_autoptr(GUsbContext) ctx = NULL; 162 | g_autoptr(GUsbDevice) device = NULL; 163 | 164 | ctx = g_usb_context_new(&error); 165 | g_assert_no_error(error); 166 | g_assert(ctx != NULL); 167 | 168 | g_usb_context_set_debug(ctx, G_LOG_LEVEL_ERROR); 169 | 170 | /* coldplug, and get the huey */ 171 | device = g_usb_context_find_by_vid_pid(ctx, 0x0971, 0x2005, &error); 172 | if (device == NULL && error->domain == G_USB_DEVICE_ERROR && 173 | error->code == G_USB_DEVICE_ERROR_NO_DEVICE) { 174 | g_print("No device detected!\n"); 175 | return; 176 | } 177 | g_assert_no_error(error); 178 | g_assert(device != NULL); 179 | 180 | /* close not opened */ 181 | ret = g_usb_device_close(device, &error); 182 | g_assert_error(error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_OPEN); 183 | g_assert(!ret); 184 | g_clear_error(&error); 185 | 186 | /* open */ 187 | ret = g_usb_device_open(device, &error); 188 | g_assert_no_error(error); 189 | g_assert(ret); 190 | 191 | /* open opened */ 192 | ret = g_usb_device_open(device, &error); 193 | g_assert_error(error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_ALREADY_OPEN); 194 | g_assert(!ret); 195 | g_clear_error(&error); 196 | 197 | /* claim interface 0 */ 198 | ret = g_usb_device_claim_interface(device, 199 | 0x00, 200 | G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, 201 | &error); 202 | g_assert_no_error(error); 203 | g_assert(ret); 204 | 205 | /* do a request (unlock) */ 206 | ret = g_usb_device_control_transfer(device, 207 | G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, 208 | G_USB_DEVICE_REQUEST_TYPE_CLASS, 209 | G_USB_DEVICE_RECIPIENT_INTERFACE, 210 | 0x09, /* request */ 211 | 0x0200, /* value */ 212 | 0, /* index */ 213 | (guint8 *)request, 214 | 8, 215 | NULL, 216 | 2000, 217 | cancellable, 218 | &error); 219 | g_assert_no_error(error); 220 | g_assert(ret); 221 | 222 | /* do a request we know is going to fail */ 223 | ret = g_usb_device_control_transfer(device, 224 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, 225 | G_USB_DEVICE_REQUEST_TYPE_CLASS, 226 | G_USB_DEVICE_RECIPIENT_INTERFACE, 227 | 0x09, /* request */ 228 | 0x0200, /* value */ 229 | 0, /* index */ 230 | (guint8 *)request, 231 | 8, 232 | NULL, 233 | 2000, 234 | cancellable, 235 | &error); 236 | g_assert_error(error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_SUPPORTED); 237 | g_assert(!ret); 238 | g_clear_error(&error); 239 | 240 | /* release interface 0 */ 241 | ret = g_usb_device_release_interface(device, 242 | 0x00, 243 | G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, 244 | &error); 245 | g_assert_no_error(error); 246 | g_assert(ret); 247 | 248 | /* close */ 249 | ret = g_usb_device_close(device, &error); 250 | g_assert_no_error(error); 251 | g_assert(ret); 252 | } 253 | 254 | typedef struct { 255 | guint8 *buffer; 256 | guint buffer_len; 257 | GMainLoop *loop; 258 | } GUsbDeviceAsyncHelper; 259 | 260 | static void 261 | g_usb_device_print_data(const gchar *title, const guchar *data, gsize length) 262 | { 263 | if (g_strcmp0(title, "request") == 0) 264 | g_print("%c[%dm", 0x1B, 31); 265 | if (g_strcmp0(title, "reply") == 0) 266 | g_print("%c[%dm", 0x1B, 34); 267 | g_print("%s\t", title); 268 | 269 | for (guint i = 0; i < length; i++) { 270 | g_print("%02x [%c]\t", data[i], g_ascii_isprint(data[i]) ? data[i] : '?'); 271 | } 272 | g_print("%c[%dm\n", 0x1B, 0); 273 | } 274 | 275 | static void 276 | g_usb_test_button_pressed_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) 277 | { 278 | gssize rc; 279 | GUsbDeviceAsyncHelper *helper = (GUsbDeviceAsyncHelper *)user_data; 280 | g_autoptr(GError) error = NULL; 281 | 282 | rc = g_usb_device_interrupt_transfer_finish(G_USB_DEVICE(source_object), res, &error); 283 | if (rc < 0) { 284 | g_error("%s", error->message); 285 | return; 286 | } 287 | 288 | g_usb_device_print_data("button press", helper->buffer, helper->buffer_len); 289 | g_main_loop_quit(helper->loop); 290 | g_free(helper->buffer); 291 | } 292 | 293 | static void 294 | gusb_device_munki_func(void) 295 | { 296 | gboolean ret; 297 | GCancellable *cancellable = NULL; 298 | guint8 request[24]; 299 | GUsbDeviceAsyncHelper *helper; 300 | g_autoptr(GError) error = NULL; 301 | g_autoptr(GUsbContext) ctx = NULL; 302 | g_autoptr(GUsbDevice) device = NULL; 303 | 304 | ctx = g_usb_context_new(&error); 305 | g_assert_no_error(error); 306 | g_assert(ctx != NULL); 307 | 308 | g_usb_context_set_debug(ctx, G_LOG_LEVEL_ERROR); 309 | 310 | /* coldplug, and get the ColorMunki */ 311 | device = g_usb_context_find_by_vid_pid(ctx, 0x0971, 0x2007, &error); 312 | if (device == NULL && error->domain == G_USB_DEVICE_ERROR && 313 | error->code == G_USB_DEVICE_ERROR_NO_DEVICE) { 314 | g_print("No device detected!\n"); 315 | return; 316 | } 317 | g_assert_no_error(error); 318 | g_assert(device != NULL); 319 | 320 | /* close not opened */ 321 | ret = g_usb_device_close(device, &error); 322 | g_assert_error(error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_OPEN); 323 | g_assert(!ret); 324 | g_clear_error(&error); 325 | 326 | /* open */ 327 | ret = g_usb_device_open(device, &error); 328 | g_assert_no_error(error); 329 | g_assert(ret); 330 | 331 | /* open opened */ 332 | ret = g_usb_device_open(device, &error); 333 | g_assert_error(error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_ALREADY_OPEN); 334 | g_assert(!ret); 335 | g_clear_error(&error); 336 | 337 | /* claim interface 0 */ 338 | ret = g_usb_device_claim_interface(device, 339 | 0x00, 340 | G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, 341 | &error); 342 | g_assert_no_error(error); 343 | g_assert(ret); 344 | 345 | /* do a request (get chip id) */ 346 | ret = g_usb_device_control_transfer(device, 347 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, 348 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, 349 | G_USB_DEVICE_RECIPIENT_DEVICE, 350 | 0x86, /* request */ 351 | 0x0000, /* value */ 352 | 0, /* index */ 353 | (guint8 *)request, 354 | 24, 355 | NULL, 356 | 2000, 357 | cancellable, 358 | &error); 359 | g_assert_no_error(error); 360 | g_assert(ret); 361 | 362 | /* do a request we know is going to fail */ 363 | ret = g_usb_device_control_transfer(device, 364 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, 365 | G_USB_DEVICE_REQUEST_TYPE_CLASS, 366 | G_USB_DEVICE_RECIPIENT_INTERFACE, 367 | 0x00, /* request */ 368 | 0x0200, /* value */ 369 | 0, /* index */ 370 | (guint8 *)request, 371 | 8, 372 | NULL, 373 | 100, 374 | cancellable, 375 | &error); 376 | g_assert_error(error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT); 377 | g_assert(!ret); 378 | g_clear_error(&error); 379 | 380 | /* do async read of button event */ 381 | helper = g_slice_new0(GUsbDeviceAsyncHelper); 382 | helper->buffer_len = 8; 383 | helper->buffer = g_new0(guint8, helper->buffer_len); 384 | helper->loop = g_main_loop_new(NULL, FALSE); 385 | g_usb_device_interrupt_transfer_async(device, 386 | 0x83, 387 | helper->buffer, 388 | 8, 389 | 30 * 1000, 390 | cancellable, /* TODO; use GCancellable */ 391 | g_usb_test_button_pressed_cb, 392 | helper); 393 | g_main_loop_run(helper->loop); 394 | g_main_loop_unref(helper->loop); 395 | 396 | /* release interface 0 */ 397 | ret = g_usb_device_release_interface(device, 398 | 0x00, 399 | G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, 400 | &error); 401 | g_assert_no_error(error); 402 | g_assert(ret); 403 | 404 | /* close */ 405 | ret = g_usb_device_close(device, &error); 406 | g_assert_no_error(error); 407 | g_assert(ret); 408 | } 409 | 410 | static void 411 | _context_signal_count_cb(GUsbContext *ctx, GUsbDevice *usb_device, gpointer user_data) 412 | { 413 | guint *cnt = (guint *)user_data; 414 | (*cnt)++; 415 | } 416 | 417 | static gboolean 418 | _g_usb_context_load_json(GUsbContext *ctx, const gchar *json, GError **error) 419 | { 420 | JsonObject *json_obj; 421 | g_autoptr(JsonParser) parser = json_parser_new(); 422 | 423 | if (!json_parser_load_from_data(parser, json, -1, error)) 424 | return FALSE; 425 | json_obj = json_node_get_object(json_parser_get_root(parser)); 426 | return g_usb_context_load_with_tag(ctx, json_obj, "emulation", error); 427 | } 428 | 429 | static void 430 | gusb_device_json_func(void) 431 | { 432 | gboolean ret; 433 | guint8 idx; 434 | guint added_cnt = 0; 435 | guint changed_cnt = 0; 436 | guint removed_cnt = 0; 437 | g_autoptr(GUsbDevice) device = NULL; 438 | g_autoptr(GUsbDevice) device2 = NULL; 439 | g_autoptr(GUsbDevice) device3 = NULL; 440 | g_autofree gchar *tmp = NULL; 441 | g_autoptr(GUsbContext) ctx = NULL; 442 | g_autoptr(GError) error = NULL; 443 | const gchar *json = 444 | "{" 445 | " \"UsbDevices\" : [" 446 | " {" 447 | " \"PlatformId\" : \"usb:AA:AA:06\"," 448 | " \"Created\" : \"2023-02-01T16:35:03.302027Z\"," 449 | " \"Tags\" : [" 450 | " \"emulation\"" 451 | " ]," 452 | " \"IdVendor\" : 10047," 453 | " \"IdProduct\" : 4100," 454 | " \"Device\" : 2," 455 | " \"USB\" : 512," 456 | " \"Manufacturer\" : 1," 457 | " \"UsbInterfaces\" : [" 458 | " {" 459 | " \"Length\" : 9," 460 | " \"DescriptorType\" : 4," 461 | " \"InterfaceNumber\" : 1," 462 | " \"InterfaceClass\" : 255," 463 | " \"InterfaceSubClass\" : 70," 464 | " \"InterfaceProtocol\" : 87," 465 | " \"Interface\" : 3" 466 | " }," 467 | " {" 468 | " \"Length\" : 9," 469 | " \"DescriptorType\" : 4," 470 | " \"InterfaceNumber\" : 2," 471 | " \"InterfaceClass\" : 255," 472 | " \"InterfaceSubClass\" : 71," 473 | " \"InterfaceProtocol\" : 85," 474 | " \"Interface\" : 4" 475 | " }," 476 | " {" 477 | " \"Length\" : 9," 478 | " \"DescriptorType\" : 4," 479 | " \"InterfaceClass\" : 3," 480 | " \"UsbEndpoints\" : [" 481 | " {" 482 | " \"DescriptorType\" : 5," 483 | " \"EndpointAddress\" : 129," 484 | " \"Interval\" : 1," 485 | " \"MaxPacketSize\" : 64" 486 | " }," 487 | " {" 488 | " \"DescriptorType\" : 5," 489 | " \"EndpointAddress\" : 1," 490 | " \"Interval\" : 1," 491 | " \"MaxPacketSize\" : 64" 492 | " }" 493 | " ]," 494 | " \"ExtraData\" : \"CSERAQABIh0A\"" 495 | " }" 496 | " ]," 497 | " \"UsbEvents\" : [" 498 | " {" 499 | " \"Id\" : " 500 | "\"GetCustomIndex:ClassId=0xff,SubclassId=0x46,ProtocolId=0x57\"," 501 | " \"Data\" : \"Aw==\"" 502 | " }," 503 | " {" 504 | " \"Id\" : " 505 | "\"GetCustomIndex:ClassId=0xff,SubclassId=0x46,ProtocolId=0x57\"," 506 | " \"Data\" : \"Aw==\"" 507 | " }," 508 | " {" 509 | " \"Id\" : \"GetStringDescriptor:DescIndex=0x03\"," 510 | " \"Data\" : " 511 | "\"Mi4wLjcAAAAR3HzMiH8AAAAAAAAAAAAA8HBIAQAAAACQ6AsW/n8AADeVQAAAAAAAsOgLFv5/" 512 | "AABNkkAAVwAAAEboCxb/" 513 | "fwAAkAZLAQAAAAAAZkAAAAAAAwAAAAAAAAAAsPpJAQAAAAAwBksBAAAAAJAZTwEAAAAAAFZZJp63C7g=\"" 514 | " }" 515 | " ]" 516 | " }" 517 | " ]" 518 | "}"; 519 | const gchar *json_new = "{" 520 | " \"UsbDevices\" : [" 521 | " {" 522 | " \"PlatformId\" : \"usb:FF:FF:06\"," 523 | " \"Created\" : \"2099-02-01T16:35:03.302027Z\"," 524 | " \"Tags\" : [" 525 | " \"emulation\"" 526 | " ]," 527 | " \"IdVendor\" : 10047," 528 | " \"IdProduct\" : 4101," 529 | " \"Device\" : 2," 530 | " \"USB\" : 512," 531 | " \"Manufacturer\" : 1" 532 | " }" 533 | " ]" 534 | "}"; 535 | ctx = g_usb_context_new(&error); 536 | g_usb_context_set_flags(ctx, G_USB_CONTEXT_FLAGS_DEBUG); 537 | g_assert_no_error(error); 538 | g_assert(ctx != NULL); 539 | 540 | /* watch events */ 541 | g_usb_context_enumerate(ctx); 542 | g_signal_connect(G_USB_CONTEXT(ctx), 543 | "device-added", 544 | G_CALLBACK(_context_signal_count_cb), 545 | &added_cnt); 546 | g_signal_connect(G_USB_CONTEXT(ctx), 547 | "device-removed", 548 | G_CALLBACK(_context_signal_count_cb), 549 | &removed_cnt); 550 | g_signal_connect(G_USB_CONTEXT(ctx), 551 | "device-changed", 552 | G_CALLBACK(_context_signal_count_cb), 553 | &changed_cnt); 554 | 555 | /* parse */ 556 | ret = _g_usb_context_load_json(ctx, json, &error); 557 | g_assert_no_error(error); 558 | g_assert(ret); 559 | g_assert_cmpint(added_cnt, ==, 1); 560 | g_assert_cmpint(removed_cnt, ==, 0); 561 | g_assert_cmpint(changed_cnt, ==, 0); 562 | 563 | /* get vendor data */ 564 | device = g_usb_context_find_by_vid_pid(ctx, 0x273f, 0x1004, &error); 565 | g_assert_no_error(error); 566 | g_assert(device != NULL); 567 | g_assert_true(g_usb_device_has_tag(device, "emulation")); 568 | idx = g_usb_device_get_custom_index(device, 569 | G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, 570 | 'F', 571 | 'W', 572 | &error); 573 | g_assert_no_error(error); 574 | g_assert_cmpint(idx, ==, 3); 575 | 576 | /* in-order, repeat */ 577 | idx = g_usb_device_get_custom_index(device, 578 | G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, 579 | 'F', 580 | 'W', 581 | &error); 582 | g_assert_no_error(error); 583 | g_assert_cmpint(idx, ==, 3); 584 | 585 | /* out-of-order */ 586 | idx = g_usb_device_get_custom_index(device, 587 | G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, 588 | 'F', 589 | 'W', 590 | &error); 591 | g_assert_no_error(error); 592 | g_assert_cmpint(idx, ==, 3); 593 | 594 | /* get the firmware version */ 595 | tmp = g_usb_device_get_string_descriptor(device, idx, &error); 596 | g_assert_no_error(error); 597 | g_assert_cmpstr(tmp, ==, "2.0.7"); 598 | 599 | /* load the same data */ 600 | ret = _g_usb_context_load_json(ctx, json, &error); 601 | g_assert_no_error(error); 602 | g_assert(ret); 603 | g_assert_cmpint(added_cnt, ==, 1); 604 | g_assert_cmpint(removed_cnt, ==, 0); 605 | g_assert_cmpint(changed_cnt, ==, 1); 606 | device2 = g_usb_context_find_by_vid_pid(ctx, 0x273f, 0x1004, &error); 607 | g_assert_no_error(error); 608 | g_assert(device2 != NULL); 609 | g_assert_true(g_usb_device_has_tag(device2, "emulation")); 610 | 611 | /* load a different device */ 612 | ret = _g_usb_context_load_json(ctx, json_new, &error); 613 | g_assert_no_error(error); 614 | g_assert(ret); 615 | g_assert_cmpint(added_cnt, ==, 2); 616 | g_assert_cmpint(changed_cnt, ==, 1); 617 | g_assert_cmpint(removed_cnt, ==, 1); 618 | device3 = g_usb_context_find_by_vid_pid(ctx, 0x273f, 0x1005, &error); 619 | g_assert_no_error(error); 620 | g_assert(device3 != NULL); 621 | g_assert_true(g_usb_device_has_tag(device3, "emulation")); 622 | } 623 | 624 | static void 625 | gusb_device_ch2_func(void) 626 | { 627 | gboolean ret; 628 | guint8 idx; 629 | g_autofree gchar *data = NULL; 630 | g_autofree gchar *tmp = NULL; 631 | g_autoptr(GError) error = NULL; 632 | g_autoptr(GUsbContext) ctx = NULL; 633 | g_autoptr(GUsbDevice) device = NULL; 634 | g_autoptr(JsonBuilder) json_builder = json_builder_new(); 635 | g_autoptr(JsonGenerator) json_generator = NULL; 636 | g_autoptr(JsonNode) json_root = NULL; 637 | 638 | ctx = g_usb_context_new(&error); 639 | g_usb_context_set_flags(ctx, G_USB_CONTEXT_FLAGS_SAVE_EVENTS); 640 | g_assert_no_error(error); 641 | g_assert(ctx != NULL); 642 | 643 | g_usb_context_set_debug(ctx, G_LOG_LEVEL_ERROR); 644 | 645 | /* coldplug, and get the ColorHug */ 646 | device = g_usb_context_find_by_vid_pid(ctx, 0x273f, 0x1004, &error); 647 | if (device == NULL && error->domain == G_USB_DEVICE_ERROR && 648 | error->code == G_USB_DEVICE_ERROR_NO_DEVICE) { 649 | g_print("No device detected!\n"); 650 | return; 651 | } 652 | g_assert_no_error(error); 653 | g_assert(device != NULL); 654 | 655 | /* open */ 656 | ret = g_usb_device_open(device, &error); 657 | g_assert_no_error(error); 658 | g_assert(ret); 659 | 660 | /* get vendor data */ 661 | idx = g_usb_device_get_custom_index(device, 662 | G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, 663 | 'F', 664 | 'W', 665 | &error); 666 | g_assert_no_error(error); 667 | g_assert_cmpint(idx, ==, 3); 668 | 669 | /* get the firmware version */ 670 | tmp = g_usb_device_get_string_descriptor(device, idx, &error); 671 | g_assert_no_error(error); 672 | g_assert_cmpstr(tmp, ==, "2.0.7"); 673 | 674 | /* close */ 675 | ret = g_usb_device_close(device, &error); 676 | g_assert_no_error(error); 677 | g_assert(ret); 678 | 679 | /* save context */ 680 | ret = g_usb_context_save(ctx, json_builder, &error); 681 | g_assert_no_error(error); 682 | g_assert(ret); 683 | 684 | /* export as a string */ 685 | json_root = json_builder_get_root(json_builder); 686 | json_generator = json_generator_new(); 687 | json_generator_set_pretty(json_generator, TRUE); 688 | json_generator_set_root(json_generator, json_root); 689 | data = json_generator_to_data(json_generator, NULL); 690 | g_assert_nonnull(data); 691 | g_print("%s\n", data); 692 | } 693 | 694 | int 695 | main(int argc, char **argv) 696 | { 697 | g_test_init(&argc, &argv, NULL); 698 | 699 | /* only critical and error are fatal */ 700 | g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); 701 | 702 | /* tests go here */ 703 | g_test_add_func("/gusb/context", gusb_context_func); 704 | g_test_add_func("/gusb/context{lookup}", gusb_context_lookup_func); 705 | g_test_add_func("/gusb/device", gusb_device_func); 706 | g_test_add_func("/gusb/device[huey]", gusb_device_huey_func); 707 | g_test_add_func("/gusb/device[munki]", gusb_device_munki_func); 708 | g_test_add_func("/gusb/device[colorhug2]", gusb_device_ch2_func); 709 | g_test_add_func("/gusb/device[json]", gusb_device_json_func); 710 | 711 | return g_test_run(); 712 | } 713 | -------------------------------------------------------------------------------- /gusb/gusb-source.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2010 Richard Hughes 4 | * Copyright (C) 2011 Hans de Goede 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | /** 10 | * SECTION:gusb-source 11 | * @short_description: GSource integration for libusb 12 | * 13 | * This object used to integrate libusb into the GLib main loop before we used 14 | * a thread. It's now pretty much unused. 15 | */ 16 | 17 | #include "config.h" 18 | 19 | #include "gusb-source.h" 20 | 21 | /** 22 | * g_usb_source_error_quark: 23 | * 24 | * Return value: Our personal error quark. 25 | * 26 | * Since: 0.1.0 27 | **/ 28 | GQuark 29 | g_usb_source_error_quark(void) 30 | { 31 | static GQuark quark = 0; 32 | if (!quark) 33 | quark = g_quark_from_static_string("g_usb_source_error"); 34 | return quark; 35 | } 36 | 37 | /** 38 | * g_usb_source_set_callback: 39 | * @self: a #GUsbSource 40 | * @func: a function to call 41 | * @data: data to pass to @func 42 | * @notify: a #GDestroyNotify 43 | * 44 | * This function does nothing. 45 | * 46 | * Since: 0.1.0 47 | **/ 48 | void 49 | g_usb_source_set_callback(GUsbSource *self, GSourceFunc func, gpointer data, GDestroyNotify notify) 50 | { 51 | g_source_set_callback((GSource *)self, func, data, notify); 52 | } 53 | -------------------------------------------------------------------------------- /gusb/gusb-source.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2010 Richard Hughes 4 | * Copyright (C) 2011 Hans de Goede 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | G_BEGIN_DECLS 14 | 15 | #define G_USB_SOURCE_ERROR (g_usb_source_error_quark()) 16 | 17 | typedef struct _GUsbSource GUsbSource; 18 | 19 | /** 20 | * GUsbSourceError: 21 | * 22 | * The error code. 23 | **/ 24 | typedef enum { G_USB_SOURCE_ERROR_INTERNAL } GUsbSourceError; 25 | 26 | G_DEPRECATED_FOR(g_usb_context_error_quark) 27 | GQuark 28 | g_usb_source_error_quark(void); 29 | 30 | G_DEPRECATED 31 | void 32 | g_usb_source_set_callback(GUsbSource *self, GSourceFunc func, gpointer data, GDestroyNotify notify); 33 | 34 | G_END_DECLS 35 | -------------------------------------------------------------------------------- /gusb/gusb-umockdev-test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libusb umockdev based tests 3 | * 4 | * Copyright (C) 2022 Benjamin Berg 5 | * 6 | * SPDX-License-Identifier: LGPL-2.1-or-later 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "gusb.h" 14 | #include "umockdev.h" 15 | 16 | #define UNUSED_DATA __attribute__((unused)) gconstpointer unused_data 17 | 18 | /* avoid leak reports inside assertions; leaking stuff on assertion failures does not matter in 19 | * tests */ 20 | #if !defined(__clang__) 21 | #pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak" 22 | #pragma GCC diagnostic ignored "-Wanalyzer-file-leak" 23 | #endif 24 | 25 | typedef struct { 26 | UMockdevTestbed *testbed; 27 | GUsbContext *ctx; 28 | } UMockdevTestbedFixture; 29 | 30 | static void 31 | test_fixture_setup(UMockdevTestbedFixture *fixture, UNUSED_DATA) 32 | { 33 | fixture->testbed = umockdev_testbed_new(); 34 | g_assert(fixture->testbed != NULL); 35 | } 36 | 37 | static void 38 | test_fixture_setup_empty(UMockdevTestbedFixture *fixture, UNUSED_DATA) 39 | { 40 | test_fixture_setup(fixture, NULL); 41 | fixture->ctx = g_usb_context_new(NULL); 42 | } 43 | 44 | static void 45 | test_fixture_teardown(UMockdevTestbedFixture *fixture, UNUSED_DATA) 46 | { 47 | /* break context -> device -> context cycle */ 48 | if (fixture->ctx) 49 | g_object_run_dispose(G_OBJECT(fixture->ctx)); 50 | g_clear_object(&fixture->ctx); 51 | g_clear_object(&fixture->testbed); 52 | 53 | /* running the mainloop is needed to ensure everything is cleaned up */ 54 | while (g_main_context_iteration(NULL, FALSE)) { 55 | } 56 | } 57 | 58 | static void 59 | test_fixture_add_canon(UMockdevTestbedFixture *fixture) 60 | { 61 | /* NOTE: there is no device file, so cannot be opened */ 62 | 63 | /* NOTE: add_device would not create a file, needed for device emulation */ 64 | /* XXX: racy, see https://github.com/martinpitt/umockdev/issues/173 */ 65 | umockdev_testbed_add_from_string( 66 | fixture->testbed, 67 | "P: /devices/usb1\n" 68 | "N: bus/usb/001/001\n" 69 | "E: SUBSYSTEM=usb\n" 70 | "E: DRIVER=usb\n" 71 | "E: BUSNUM=001\n" 72 | "E: DEVNUM=001\n" 73 | "E: DEVNAME=/dev/bus/usb/001/001\n" 74 | "E: DEVTYPE=usb_device\n" 75 | "A: bConfigurationValue=1\\n\n" 76 | "A: busnum=1\\n\n" 77 | "A: devnum=1\\n\n" 78 | "A: bConfigurationValue=1\\n\n" 79 | "A: speed=480\\n\n" 80 | /* descriptor from a Canon PowerShot SX200; VID 04a9 PID 31c0 */ 81 | "H: descriptors=" 82 | "1201000200000040a904c03102000102" 83 | "030109022700010100c0010904000003" 84 | "06010100070581020002000705020200" 85 | "020007058303080009\n", 86 | NULL); 87 | } 88 | 89 | static void 90 | test_ctx_enumerate(UMockdevTestbedFixture *fixture, UNUSED_DATA) 91 | { 92 | g_autoptr(GPtrArray) devices = NULL; 93 | 94 | test_fixture_add_canon(fixture); 95 | 96 | g_usb_context_enumerate(fixture->ctx); 97 | 98 | devices = g_usb_context_get_devices(fixture->ctx); 99 | g_assert_cmpint(devices->len, ==, 1); 100 | } 101 | 102 | static void 103 | count_hotplug_event_cb(GUsbContext *context, GUsbDevice *device, gpointer user_data) 104 | { 105 | int *counter = user_data; 106 | 107 | *counter += 1; 108 | } 109 | 110 | static void 111 | test_ctx_hotplug(UMockdevTestbedFixture *fixture, UNUSED_DATA) 112 | { 113 | g_autoptr(GPtrArray) devices = NULL; 114 | gint events = 0; 115 | 116 | g_signal_connect(fixture->ctx, "device-added", G_CALLBACK(count_hotplug_event_cb), &events); 117 | 118 | g_usb_context_enumerate(fixture->ctx); 119 | 120 | devices = g_usb_context_get_devices(fixture->ctx); 121 | g_assert_cmpint(devices->len, ==, 0); 122 | g_assert_cmpint(events, ==, 0); 123 | g_clear_pointer(&devices, g_ptr_array_unref); 124 | 125 | test_fixture_add_canon(fixture); 126 | /* ensure the event was processed by helper thread */ 127 | g_usleep(G_USEC_PER_SEC / 2); 128 | 129 | /* still not returned (and no event fired) */ 130 | devices = g_usb_context_get_devices(fixture->ctx); 131 | g_assert_cmpint(devices->len, ==, 0); 132 | g_assert_cmpint(events, ==, 0); 133 | g_clear_pointer(&devices, g_ptr_array_unref); 134 | 135 | /* run mainloop, which causes the event to be processed */ 136 | while (g_main_context_iteration(NULL, FALSE)) { 137 | } 138 | 139 | devices = g_usb_context_get_devices(fixture->ctx); 140 | g_assert_cmpint(events, ==, 1); 141 | g_assert_cmpint(devices->len, ==, 1); 142 | g_clear_pointer(&devices, g_ptr_array_unref); 143 | } 144 | 145 | static void 146 | test_ctx_hotplug_dispose(UMockdevTestbedFixture *fixture, UNUSED_DATA) 147 | { 148 | g_autoptr(GPtrArray) devices = NULL; 149 | gint events = 0; 150 | 151 | g_signal_connect(fixture->ctx, "device-added", G_CALLBACK(count_hotplug_event_cb), &events); 152 | 153 | g_usb_context_enumerate(fixture->ctx); 154 | devices = g_usb_context_get_devices(fixture->ctx); 155 | g_assert_cmpint(devices->len, ==, 0); 156 | g_assert_cmpint(events, ==, 0); 157 | g_clear_pointer(&devices, g_ptr_array_unref); 158 | 159 | test_fixture_add_canon(fixture); 160 | /* ensure the event was processed by helper thread */ 161 | g_usleep(G_USEC_PER_SEC / 2); 162 | 163 | /* still not returned (and no event fired) */ 164 | g_usb_context_enumerate(fixture->ctx); 165 | devices = g_usb_context_get_devices(fixture->ctx); 166 | g_assert_cmpint(devices->len, ==, 0); 167 | g_assert_cmpint(events, ==, 0); 168 | g_clear_pointer(&devices, g_ptr_array_unref); 169 | 170 | /* idle handler is pending, we dispose our context reference */ 171 | g_object_run_dispose(G_OBJECT(fixture->ctx)); 172 | 173 | /* run mainloop, which causes the event to be processed */ 174 | while (g_main_context_iteration(NULL, FALSE)) { 175 | } 176 | 177 | /* but no signal is fired */ 178 | g_assert_cmpint(events, ==, 0); 179 | 180 | g_clear_object(&fixture->ctx); 181 | } 182 | 183 | int 184 | main(int argc, char **argv) 185 | { 186 | g_test_init(&argc, &argv, NULL); 187 | 188 | g_test_add("/gusb/ctx/enumerate", 189 | UMockdevTestbedFixture, 190 | NULL, 191 | test_fixture_setup_empty, 192 | test_ctx_enumerate, 193 | test_fixture_teardown); 194 | 195 | g_test_add("/gusb/ctx/hotplug", 196 | UMockdevTestbedFixture, 197 | NULL, 198 | test_fixture_setup_empty, 199 | test_ctx_hotplug, 200 | test_fixture_teardown); 201 | 202 | g_test_add("/gusb/ctx/hotplug-dispose", 203 | UMockdevTestbedFixture, 204 | NULL, 205 | test_fixture_setup_empty, 206 | test_ctx_hotplug_dispose, 207 | test_fixture_teardown); 208 | 209 | return g_test_run(); 210 | } 211 | -------------------------------------------------------------------------------- /gusb/gusb-util.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2011 Hans de Goede 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #include "config.h" 9 | 10 | #include 11 | 12 | #include "gusb-util.h" 13 | 14 | /** 15 | * g_usb_strerror: 16 | * @error_code: a libusb error code 17 | * 18 | * Converts the error code into a string 19 | * 20 | * Return value: String, or %NULL 21 | * 22 | * Since: 0.1.0 23 | **/ 24 | const gchar * 25 | g_usb_strerror(gint error_code) 26 | { 27 | return libusb_strerror(error_code); 28 | } 29 | -------------------------------------------------------------------------------- /gusb/gusb-util.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2011 Hans de Goede 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | G_BEGIN_DECLS 13 | 14 | const gchar * 15 | g_usb_strerror(gint error_code); 16 | 17 | G_END_DECLS 18 | -------------------------------------------------------------------------------- /gusb/gusb-version.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2018 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #include "config.h" 9 | 10 | #include "gusb-version.h" 11 | 12 | /** 13 | * g_usb_version_string: 14 | * 15 | * Gets the GUsb installed runtime version. 16 | * 17 | * Returns: a version number, e.g. "0.3.1" 18 | * 19 | * Since: 0.3.1 20 | **/ 21 | const gchar * 22 | g_usb_version_string(void) 23 | { 24 | return G_STRINGIFY(G_USB_MAJOR_VERSION) "." G_STRINGIFY( 25 | G_USB_MINOR_VERSION) "." G_STRINGIFY(G_USB_MICRO_VERSION); 26 | } 27 | -------------------------------------------------------------------------------- /gusb/gusb-version.h.in: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2010 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | /** 9 | * SECTION:gusb-version 10 | * @short_description: Obtains the version for the installed libgusb 11 | * 12 | * These compile time macros allow the user to enable parts of client code 13 | * depending on the version of libgusb installed. 14 | */ 15 | 16 | #if !defined (__GUSB_INSIDE__) && !defined (GUSB_COMPILATION) 17 | #error "Only can be included directly." 18 | #endif 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | /** 25 | * G_USB_MAJOR_VERSION: 26 | * 27 | * The compile-time major version 28 | */ 29 | #define G_USB_MAJOR_VERSION @G_USB_MAJOR_VERSION@ 30 | 31 | /** 32 | * G_USB_MINOR_VERSION: 33 | * 34 | * The compile-time minor version 35 | */ 36 | #define G_USB_MINOR_VERSION @G_USB_MINOR_VERSION@ 37 | 38 | /** 39 | * G_USB_MICRO_VERSION: 40 | * 41 | * The compile-time micro version 42 | */ 43 | #define G_USB_MICRO_VERSION @G_USB_MICRO_VERSION@ 44 | 45 | /** 46 | * G_USB_CHECK_VERSION: 47 | * 48 | * Check whether a gusb version equal to or greater than 49 | * major.minor.micro. 50 | */ 51 | #define G_USB_CHECK_VERSION(major,minor,micro) \ 52 | (G_USB_MAJOR_VERSION > (major) || \ 53 | (G_USB_MAJOR_VERSION == (major) && G_USB_MINOR_VERSION > (minor)) || \ 54 | (G_USB_MAJOR_VERSION == (major) && G_USB_MINOR_VERSION == (minor) && \ 55 | G_USB_MICRO_VERSION >= (micro))) 56 | 57 | const gchar *g_usb_version_string (void); 58 | -------------------------------------------------------------------------------- /gusb/gusb.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2010 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #define __GUSB_INSIDE__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #undef __GUSB_INSIDE__ 24 | -------------------------------------------------------------------------------- /gusb/libgusb.ver: -------------------------------------------------------------------------------- 1 | # generated automatically, do not edit! 2 | 3 | LIBGUSB_0.1.0 { 4 | global: 5 | g_usb_context_error_quark; 6 | g_usb_context_get_source; 7 | g_usb_context_get_type; 8 | g_usb_context_new; 9 | g_usb_context_set_debug; 10 | g_usb_device_bulk_transfer; 11 | g_usb_device_bulk_transfer_async; 12 | g_usb_device_bulk_transfer_finish; 13 | g_usb_device_claim_interface; 14 | g_usb_device_close; 15 | g_usb_device_control_transfer; 16 | g_usb_device_control_transfer_async; 17 | g_usb_device_control_transfer_finish; 18 | g_usb_device_error_quark; 19 | g_usb_device_get_address; 20 | g_usb_device_get_bus; 21 | g_usb_device_get_configuration; 22 | g_usb_device_get_manufacturer_index; 23 | g_usb_device_get_pid; 24 | g_usb_device_get_product_index; 25 | g_usb_device_get_serial_number_index; 26 | g_usb_device_get_string_descriptor; 27 | g_usb_device_get_type; 28 | g_usb_device_get_vid; 29 | g_usb_device_interrupt_transfer; 30 | g_usb_device_interrupt_transfer_async; 31 | g_usb_device_interrupt_transfer_finish; 32 | g_usb_device_list_coldplug; 33 | g_usb_device_list_find_by_bus_address; 34 | g_usb_device_list_find_by_vid_pid; 35 | g_usb_device_list_get_devices; 36 | g_usb_device_list_get_type; 37 | g_usb_device_list_new; 38 | g_usb_device_open; 39 | g_usb_device_release_interface; 40 | g_usb_device_reset; 41 | g_usb_device_set_configuration; 42 | g_usb_source_error_quark; 43 | g_usb_source_set_callback; 44 | g_usb_strerror; 45 | local: *; 46 | }; 47 | 48 | LIBGUSB_0.1.1 { 49 | global: 50 | g_usb_device_get_platform_id; 51 | local: *; 52 | } LIBGUSB_0.1.0; 53 | 54 | LIBGUSB_0.1.7 { 55 | global: 56 | g_usb_device_get_device_class; 57 | local: *; 58 | } LIBGUSB_0.1.1; 59 | 60 | LIBGUSB_0.2.2 { 61 | global: 62 | g_usb_context_enumerate; 63 | g_usb_context_find_by_bus_address; 64 | g_usb_context_find_by_vid_pid; 65 | g_usb_context_get_devices; 66 | local: *; 67 | } LIBGUSB_0.1.7; 68 | 69 | LIBGUSB_0.2.4 { 70 | global: 71 | g_usb_context_find_by_platform_id; 72 | g_usb_device_get_children; 73 | g_usb_device_get_device_protocol; 74 | g_usb_device_get_device_subclass; 75 | g_usb_device_get_parent; 76 | g_usb_device_get_pid_as_str; 77 | g_usb_device_get_port_number; 78 | g_usb_device_get_vid_as_str; 79 | local: *; 80 | } LIBGUSB_0.2.2; 81 | 82 | LIBGUSB_0.2.5 { 83 | global: 84 | g_usb_context_get_main_context; 85 | g_usb_context_set_main_context; 86 | g_usb_device_get_custom_index; 87 | local: *; 88 | } LIBGUSB_0.2.4; 89 | 90 | LIBGUSB_0.2.8 { 91 | global: 92 | g_usb_device_get_interface; 93 | g_usb_device_get_interfaces; 94 | g_usb_device_get_release; 95 | g_usb_device_set_interface_alt; 96 | g_usb_interface_get_alternate; 97 | g_usb_interface_get_class; 98 | g_usb_interface_get_extra; 99 | g_usb_interface_get_index; 100 | g_usb_interface_get_kind; 101 | g_usb_interface_get_length; 102 | g_usb_interface_get_number; 103 | g_usb_interface_get_protocol; 104 | g_usb_interface_get_subclass; 105 | g_usb_interface_get_type; 106 | local: *; 107 | } LIBGUSB_0.2.5; 108 | 109 | LIBGUSB_0.2.9 { 110 | global: 111 | g_usb_context_wait_for_replug; 112 | local: *; 113 | } LIBGUSB_0.2.8; 114 | 115 | LIBGUSB_0.2.11 { 116 | global: 117 | g_usb_context_get_flags; 118 | g_usb_context_set_flags; 119 | local: *; 120 | } LIBGUSB_0.2.9; 121 | 122 | LIBGUSB_0.3.1 { 123 | global: 124 | g_usb_device_get_spec; 125 | g_usb_version_string; 126 | local: *; 127 | } LIBGUSB_0.2.11; 128 | 129 | LIBGUSB_0.3.3 { 130 | global: 131 | g_usb_endpoint_get_address; 132 | g_usb_endpoint_get_direction; 133 | g_usb_endpoint_get_extra; 134 | g_usb_endpoint_get_kind; 135 | g_usb_endpoint_get_maximum_packet_size; 136 | g_usb_endpoint_get_number; 137 | g_usb_endpoint_get_polling_interval; 138 | g_usb_endpoint_get_refresh; 139 | g_usb_endpoint_get_synch_address; 140 | g_usb_endpoint_get_type; 141 | g_usb_interface_get_endpoints; 142 | local: *; 143 | } LIBGUSB_0.3.1; 144 | 145 | LIBGUSB_0.3.5 { 146 | global: 147 | g_usb_device_get_configuration_index; 148 | local: *; 149 | } LIBGUSB_0.3.3; 150 | 151 | LIBGUSB_0.3.6 { 152 | global: 153 | g_usb_device_get_string_descriptor_bytes; 154 | local: *; 155 | } LIBGUSB_0.3.5; 156 | 157 | LIBGUSB_0.3.8 { 158 | global: 159 | g_usb_device_get_string_descriptor_bytes_full; 160 | local: *; 161 | } LIBGUSB_0.3.6; 162 | 163 | LIBGUSB_0.3.10 { 164 | global: 165 | g_usb_context_get_hotplug_poll_interval; 166 | g_usb_context_set_hotplug_poll_interval; 167 | local: *; 168 | } LIBGUSB_0.3.8; 169 | 170 | LIBGUSB_0.4.0 { 171 | global: 172 | g_usb_bos_descriptor_get_capability; 173 | g_usb_bos_descriptor_get_extra; 174 | g_usb_bos_descriptor_get_type; 175 | g_usb_context_load; 176 | g_usb_context_save; 177 | g_usb_device_event_get_bytes; 178 | g_usb_device_event_get_id; 179 | g_usb_device_event_get_status; 180 | g_usb_device_event_get_type; 181 | g_usb_device_event_set_bytes; 182 | g_usb_device_get_bos_descriptor; 183 | g_usb_device_get_bos_descriptors; 184 | g_usb_device_get_events; 185 | g_usb_device_invalidate; 186 | local: *; 187 | } LIBGUSB_0.3.10; 188 | 189 | LIBGUSB_0.4.1 { 190 | global: 191 | g_usb_context_load_with_tag; 192 | g_usb_context_save_with_tag; 193 | g_usb_device_add_tag; 194 | local: *; 195 | } LIBGUSB_0.4.0; 196 | 197 | LIBGUSB_0.4.3 { 198 | global: 199 | g_usb_device_has_tag; 200 | local: *; 201 | } LIBGUSB_0.4.1; 202 | 203 | LIBGUSB_0.4.4 { 204 | global: 205 | g_usb_device_clear_events; 206 | g_usb_device_get_tags; 207 | g_usb_device_is_emulated; 208 | g_usb_device_remove_tag; 209 | local: *; 210 | } LIBGUSB_0.4.3; 211 | 212 | LIBGUSB_0.4.5 { 213 | global: 214 | g_usb_device_event_get_rc; 215 | g_usb_device_get_created; 216 | local: *; 217 | } LIBGUSB_0.4.4; 218 | 219 | LIBGUSB_0.4.7 { 220 | global: 221 | g_usb_device_get_hid_descriptor_default; 222 | g_usb_device_get_hid_descriptors; 223 | local: *; 224 | } LIBGUSB_0.4.5; 225 | -------------------------------------------------------------------------------- /gusb/libgusb_meson.ver: -------------------------------------------------------------------------------- 1 | LIBGUSB_0.1.0 { 2 | global: 3 | g_usb_*; 4 | environ; 5 | __progname; 6 | local: *; 7 | }; 8 | -------------------------------------------------------------------------------- /gusb/meson.build: -------------------------------------------------------------------------------- 1 | cargs = [ 2 | '-DG_LOG_DOMAIN="libgusb"', 3 | '-DUSB_IDS="' + get_option('usb_ids') + '"', 4 | ] 5 | 6 | gusb_source_dir = meson.current_source_dir() 7 | gusb_build_dir = meson.current_build_dir() 8 | 9 | con2 = configuration_data() 10 | con2.set('G_USB_MAJOR_VERSION', gusb_major_version) 11 | con2.set('G_USB_MINOR_VERSION', gusb_minor_version) 12 | con2.set('G_USB_MICRO_VERSION', gusb_micro_version) 13 | 14 | gusb_version_h = configure_file( 15 | input : 'gusb-version.h.in', 16 | output : 'gusb-version.h', 17 | configuration : con2 18 | ) 19 | 20 | install_headers( 21 | 'gusb.h', 22 | subdir : 'gusb-1', 23 | ) 24 | 25 | lib_incdir = include_directories('.') 26 | 27 | install_headers([ 28 | gusb_version_h, 29 | 'gusb-context.h', 30 | 'gusb-context-private.h', 31 | 'gusb-device.h', 32 | 'gusb-device-event.h', 33 | 'gusb-device-private.h', 34 | 'gusb-device-list.h', 35 | 'gusb-interface.h', 36 | 'gusb-interface-private.h', 37 | 'gusb-bos-descriptor.h', 38 | 'gusb-bos-descriptor-private.h', 39 | 'gusb-endpoint.h', 40 | 'gusb-endpoint-private.h', 41 | 'gusb-source.h', 42 | 'gusb-util.h', 43 | ], 44 | subdir : 'gusb-1/gusb', 45 | ) 46 | 47 | mapfile = 'libgusb.ver' 48 | mapfile_test = 'libgusb_meson.ver' 49 | vflag = [] 50 | vflag_test = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile_test) 51 | if cc.has_link_argument(vflag_test) 52 | vflag += '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) 53 | endif 54 | gusb = library( 55 | 'gusb', 56 | sources : [ 57 | 'gusb-context.c', 58 | 'gusb-device.c', 59 | 'gusb-device-event.c', 60 | 'gusb-device-list.c', 61 | 'gusb-interface.c', 62 | 'gusb-bos-descriptor.c', 63 | 'gusb-endpoint.c', 64 | 'gusb-source.c', 65 | 'gusb-util.c', 66 | 'gusb-version.c', 67 | 'gusb-json-common.c', 68 | ], 69 | soversion : lt_current, 70 | version : lt_version, 71 | darwin_versions: [lt_current + 1, '@0@.@1@.0'.format(lt_current + 1, lt_revision)], 72 | dependencies : [ 73 | libgio, 74 | libusb, 75 | libjsonglib, 76 | ], 77 | c_args : [ 78 | cargs, 79 | ], 80 | include_directories : [ 81 | root_incdir, 82 | lib_incdir, 83 | ], 84 | link_args : vflag, 85 | link_depends : mapfile, 86 | install : true 87 | ) 88 | 89 | gusb_dep = declare_dependency( 90 | link_with : gusb, 91 | include_directories : [ 92 | root_incdir, 93 | lib_incdir, 94 | ], 95 | dependencies : gusb_deps 96 | ) 97 | 98 | pkgg = import('pkgconfig') 99 | pkgg.generate(gusb, 100 | requires : [ 'gio-2.0', 'gobject-2.0', 'libusb-1.0', 'json-glib-1.0' ], 101 | subdirs : 'gusb-1', 102 | version : meson.project_version(), 103 | name : 'gusb', 104 | filebase : 'gusb', 105 | description : 'GUsb is a GLib binding for libusb1', 106 | ) 107 | 108 | if get_option('introspection') 109 | libgusb_girtarget = gnome.generate_gir(gusb, 110 | sources : [ 111 | gusb_version_h, 112 | 'gusb-context.c', 113 | 'gusb-context.h', 114 | 'gusb-context-private.h', 115 | 'gusb-device.c', 116 | 'gusb-device.h', 117 | 'gusb-device-event.c', 118 | 'gusb-device-event.h', 119 | 'gusb-device-event-private.h', 120 | 'gusb-device-list.c', 121 | 'gusb-device-list.h', 122 | 'gusb-device-private.h', 123 | 'gusb-interface.c', 124 | 'gusb-interface.h', 125 | 'gusb-interface-private.h', 126 | 'gusb-bos-descriptor.c', 127 | 'gusb-bos-descriptor.h', 128 | 'gusb-bos-descriptor-private.h', 129 | 'gusb-endpoint.c', 130 | 'gusb-endpoint.h', 131 | 'gusb-endpoint-private.h', 132 | 'gusb-source.c', 133 | 'gusb-source.h', 134 | 'gusb-util.c', 135 | 'gusb-util.h', 136 | 'gusb-version.c', 137 | ], 138 | nsversion : '1.0', 139 | namespace : 'GUsb', 140 | symbol_prefix : 'g_usb', 141 | identifier_prefix : 'GUsb', 142 | export_packages : 'gusb', 143 | extra_args : [ 144 | '--c-include=gusb.h', 145 | '-DGUSB_COMPILATION', 146 | ], 147 | link_with : gusb, 148 | dependencies : [ 149 | libgio, 150 | libusb, 151 | libjsonglib, 152 | ], 153 | includes : [ 154 | 'Gio-2.0', 155 | 'GObject-2.0', 156 | 'Json-1.0', 157 | ], 158 | install : true 159 | ) 160 | libgusb_gir = libgusb_girtarget[0] 161 | libgusb_typelib = libgusb_girtarget[1] 162 | 163 | pymod = import('python') 164 | py_installation = pymod.find_installation() 165 | 166 | # Verify the map file is correct -- note we can't actually use the generated 167 | # file for two reasons: 168 | # 169 | # 1. We don't hard depend on GObject Introspection 170 | # 2. The map file is required to build the lib that the GIR is built from 171 | # 172 | # To avoid the circular dep, and to ensure we don't change exported API 173 | # accidentally actually check in a version of the version script to git. 174 | mapfile_target = custom_target('gusb_mapfile', 175 | input: libgusb_girtarget[0], 176 | output: 'libgusb.ver', 177 | command: [ 178 | py_installation, 179 | join_paths(meson.project_source_root(), 'contrib', 'generate-version-script.py'), 180 | 'LIBGUSB', 181 | '@INPUT@', 182 | '@OUTPUT@', 183 | ], 184 | ) 185 | diffcmd = find_program('diff') 186 | test('gusb-exported-api', diffcmd, 187 | args : [ 188 | '-urNp', 189 | join_paths(meson.current_source_dir(), 'libgusb.ver'), 190 | mapfile_target, 191 | ], 192 | ) 193 | 194 | endif 195 | 196 | if get_option('vapi') 197 | gnome.generate_vapi('gusb', 198 | sources: libgusb_girtarget[0], 199 | packages: ['gio-2.0', 'json-glib-1.0'], 200 | install: true, 201 | ) 202 | endif 203 | 204 | if get_option('tests') 205 | e = executable( 206 | 'gusb-self-test', 207 | sources : [ 208 | 'gusb-context.c', 209 | 'gusb-device.c', 210 | 'gusb-device-event.c', 211 | 'gusb-device-list.c', 212 | 'gusb-interface.c', 213 | 'gusb-bos-descriptor.c', 214 | 'gusb-endpoint.c', 215 | 'gusb-self-test.c', 216 | 'gusb-source.c', 217 | 'gusb-util.c', 218 | 'gusb-json-common.c' 219 | ], 220 | include_directories : [ 221 | root_incdir, 222 | lib_incdir, 223 | ], 224 | dependencies : [ 225 | libgio, 226 | libusb, 227 | libjsonglib, 228 | ], 229 | c_args : [ 230 | cargs, 231 | ], 232 | ) 233 | test('gusb-self-test', e) 234 | 235 | # Umockdev based tests 236 | test_env = environment() 237 | test_env.prepend('LD_PRELOAD', 'libumockdev-preload.so.0') 238 | 239 | test_umockdev = executable( 240 | 'gusb-umockdev-test', 241 | 'gusb-umockdev-test.c', 242 | dependencies: [ umockdev, gusb_dep ]) 243 | 244 | test('gusb-umockdev-test', 245 | test_umockdev, 246 | env: test_env) 247 | endif 248 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('libgusb', 'c', 2 | version : '0.4.9', 3 | license : 'LGPL-2.1-or-later', 4 | meson_version : '>=0.56.0', 5 | default_options : ['c_std=c99'] 6 | ) 7 | 8 | gusb_version = meson.project_version() 9 | varr = gusb_version.split('.') 10 | gusb_major_version = varr[0] 11 | gusb_minor_version = varr[1] 12 | gusb_micro_version = varr[2] 13 | 14 | conf = configuration_data() 15 | conf.set_quoted('VERSION', gusb_version) 16 | 17 | # libtool versioning - this applies to libgusb 18 | lt_current = 2 19 | lt_revision = 10 20 | lt_age = 0 21 | lt_version = '@0@.@1@.@2@'.format(lt_current, lt_age, lt_revision) 22 | 23 | # get supported warning flags 24 | test_args = [ 25 | '-Waggregate-return', 26 | '-Wunused', 27 | '-Warray-bounds', 28 | '-Wcast-align', 29 | '-Wclobbered', 30 | '-Wdeclaration-after-statement', 31 | '-Wempty-body', 32 | '-Wextra', 33 | '-Wformat=2', 34 | '-Wformat-nonliteral', 35 | '-Wformat-security', 36 | '-Wformat-signedness', 37 | '-Wignored-qualifiers', 38 | '-Wimplicit-function-declaration', 39 | '-Winit-self', 40 | '-Wmissing-declarations', 41 | '-Wmissing-format-attribute', 42 | '-Wmissing-include-dirs', 43 | '-Wmissing-noreturn', 44 | '-Wmissing-parameter-type', 45 | '-Wmissing-prototypes', 46 | '-Wnested-externs', 47 | '-Wno-discarded-qualifiers', 48 | '-Wno-missing-field-initializers', 49 | '-Wno-strict-aliasing', 50 | '-Wno-suggest-attribute=format', 51 | '-Wno-unused-parameter', 52 | '-Wno-gnu-variable-sized-type-not-at-end', 53 | '-Wold-style-definition', 54 | '-Woverride-init', 55 | '-Wpointer-arith', 56 | '-Wreturn-type', 57 | '-Wshadow', 58 | '-Wsign-compare', 59 | '-Wstrict-aliasing', 60 | '-Wstrict-prototypes', 61 | '-Wswitch-default', 62 | '-Wswitch-enum', 63 | '-Wtype-limits', 64 | '-Wundef', 65 | '-Wuninitialized', 66 | '-Wunused-but-set-variable', 67 | '-Wwrite-strings' 68 | ] 69 | cc = meson.get_compiler('c') 70 | foreach arg: test_args 71 | if cc.has_argument(arg) 72 | add_project_arguments(arg, language : 'c') 73 | endif 74 | endforeach 75 | 76 | if not meson.is_cross_build() 77 | add_project_arguments('-fstack-protector-strong', language : 'c') 78 | endif 79 | 80 | # enable full RELRO where possible 81 | # FIXME: until https://github.com/mesonbuild/meson/issues/1140 is fixed 82 | global_link_args = [] 83 | test_link_args = [ 84 | '-Wl,-z,relro', 85 | '-Wl,-z,now', 86 | ] 87 | foreach arg: test_link_args 88 | if cc.has_link_argument(arg) 89 | global_link_args += arg 90 | endif 91 | endforeach 92 | add_project_link_arguments( 93 | global_link_args, 94 | language: 'c' 95 | ) 96 | 97 | if get_option('tests') 98 | umockdev = dependency('umockdev-1.0', version: '>= 0.17.7', disabler: true, required: get_option('umockdev')) 99 | else 100 | umockdev = disabler() 101 | endif 102 | libgio = dependency('gio-2.0', version : '>= 2.44.0') 103 | libusb = dependency('libusb-1.0', version : '>= 1.0.9') 104 | if cc.has_header_symbol('libusb.h', 'LIBUSB_CAP_HAS_HOTPLUG', dependencies: libusb) 105 | conf.set('HAVE_LIBUSB_CAP_HAS_HOTPLUG', '1') 106 | endif 107 | if cc.has_header_symbol('libusb.h', 'libusb_has_capability', dependencies: libusb) 108 | conf.set('HAVE_LIBUSB_HAS_CAPABILITY', '1') 109 | endif 110 | if cc.has_header_symbol('libusb.h', 'libusb_set_option', dependencies: libusb) 111 | conf.set('HAVE_LIBUSB_SET_OPTION', '1') 112 | endif 113 | if cc.has_header_symbol('libusb.h', 'libusb_get_parent', dependencies: libusb) 114 | conf.set('HAVE_LIBUSB_GET_PARENT', '1') 115 | endif 116 | if cc.has_header_symbol('libusb.h', 'libusb_get_port_number', dependencies: libusb) 117 | conf.set('HAVE_LIBUSB_GET_PORT_NUMBER', '1') 118 | endif 119 | libjsonglib = dependency('json-glib-1.0', version: '>= 1.1.1') 120 | 121 | gusb_deps = [ 122 | libgio, 123 | libusb, 124 | libjsonglib, 125 | ] 126 | 127 | if not get_option('introspection') and get_option('vapi') 128 | error('-Dvapi=true requires -Dintrospection=true') 129 | endif 130 | 131 | gnome = import('gnome') 132 | 133 | add_project_arguments('-DGUSB_COMPILATION', language: 'c') 134 | 135 | prefix = get_option('prefix') 136 | bindir = join_paths(prefix, get_option('bindir')) 137 | datadir = join_paths(prefix, get_option('datadir')) 138 | 139 | configure_file( 140 | output : 'config.h', 141 | configuration : conf 142 | ) 143 | 144 | root_incdir = include_directories('.') 145 | 146 | subdir('gusb') 147 | subdir('tools') 148 | if get_option('docs') 149 | subdir('docs') 150 | endif 151 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('tests', type : 'boolean', value : true, description : 'Build self tests') 2 | option('vapi', type : 'boolean', value : true, description : 'Build VAPI') 3 | option('usb_ids', type : 'string', value : '/usr/share/hwdata/usb.ids', description : 'Path to usb.ids file') 4 | option('docs', type : 'boolean', value : true, description : 'Generate documentation') 5 | option('introspection', type : 'boolean', value : true, description : 'Generate gobject introspection data') 6 | option('umockdev', type : 'feature', value : 'auto', description : 'Build and run umockdev based tests') 7 | -------------------------------------------------------------------------------- /tools/gusb-main.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2011 Richard Hughes 4 | * 5 | * SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #include "config.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static void 16 | gusb_log_ignore_cb(const gchar *log_domain, 17 | GLogLevelFlags log_level, 18 | const gchar *message, 19 | gpointer user_data) 20 | { 21 | } 22 | 23 | static void 24 | gusb_log_handler_cb(const gchar *log_domain, 25 | GLogLevelFlags log_level, 26 | const gchar *message, 27 | gpointer user_data) 28 | { 29 | gchar str_time[255]; 30 | time_t the_time; 31 | 32 | /* header always in green */ 33 | time(&the_time); 34 | strftime(str_time, 254, "%H:%M:%S", localtime(&the_time)); 35 | g_print("%c[%dmTI:%s\t", 0x1B, 32, str_time); 36 | 37 | /* critical is also in red */ 38 | if (log_level == G_LOG_LEVEL_CRITICAL || log_level == G_LOG_LEVEL_WARNING || 39 | log_level == G_LOG_LEVEL_ERROR) { 40 | g_print("%c[%dm%s\n%c[%dm", 0x1B, 31, message, 0x1B, 0); 41 | } else { 42 | /* debug in blue */ 43 | g_print("%c[%dm%s\n%c[%dm", 0x1B, 34, message, 0x1B, 0); 44 | } 45 | } 46 | 47 | typedef struct { 48 | GOptionContext *context; 49 | GUsbContext *usb_ctx; 50 | GPtrArray *cmd_array; 51 | } GUsbCmdPrivate; 52 | 53 | typedef gboolean (*GUsbCmdPrivateCb)(GUsbCmdPrivate *cmd, gchar **values, GError **error); 54 | 55 | typedef struct { 56 | gchar *name; 57 | gchar *description; 58 | GUsbCmdPrivateCb callback; 59 | } GUsbCmdItem; 60 | 61 | static void 62 | gusb_cmd_item_free(GUsbCmdItem *item) 63 | { 64 | g_free(item->name); 65 | g_free(item->description); 66 | g_slice_free(GUsbCmdItem, item); 67 | } 68 | 69 | /* 70 | * gusb_sort_command_name_cb: 71 | */ 72 | static gint 73 | gusb_sort_command_name_cb(GUsbCmdItem **item1, GUsbCmdItem **item2) 74 | { 75 | return g_strcmp0((*item1)->name, (*item2)->name); 76 | } 77 | 78 | static void 79 | gusb_cmd_add(GPtrArray *array, 80 | const gchar *name, 81 | const gchar *description, 82 | GUsbCmdPrivateCb callback) 83 | { 84 | g_auto(GStrv) names = g_strsplit(name, ",", -1); 85 | for (guint i = 0; names[i] != NULL; i++) { 86 | GUsbCmdItem *item = g_slice_new0(GUsbCmdItem); 87 | item->name = g_strdup(names[i]); 88 | if (i == 0) { 89 | item->description = g_strdup(description); 90 | } else { 91 | /* TRANSLATORS: this is a command alias */ 92 | item->description = g_strdup_printf("Alias to %s", names[0]); 93 | } 94 | item->callback = callback; 95 | g_ptr_array_add(array, item); 96 | } 97 | } 98 | 99 | static gchar * 100 | gusb_cmd_get_descriptions(GPtrArray *array) 101 | { 102 | guint len; 103 | guint max_len = 19; 104 | g_autoptr(GString) string = NULL; 105 | 106 | /* print each command */ 107 | string = g_string_new(""); 108 | for (guint i = 0; i < array->len; i++) { 109 | GUsbCmdItem *item = g_ptr_array_index(array, i); 110 | g_string_append(string, " "); 111 | g_string_append(string, item->name); 112 | g_string_append(string, " "); 113 | len = strlen(item->name); 114 | for (guint j = len; j < max_len + 2; j++) 115 | g_string_append_c(string, ' '); 116 | g_string_append(string, item->description); 117 | g_string_append_c(string, '\n'); 118 | } 119 | 120 | /* remove trailing newline */ 121 | if (string->len > 0) 122 | g_string_set_size(string, string->len - 1); 123 | 124 | return g_string_free(g_steal_pointer(&string), FALSE); 125 | } 126 | 127 | static void 128 | gusb_main_device_open(GUsbDevice *device) 129 | { 130 | guint8 idx; 131 | g_autoptr(GError) error = NULL; 132 | 133 | /* open */ 134 | if (!g_usb_device_open(device, &error)) { 135 | g_print("failed to open: %s\n", error->message); 136 | return; 137 | } 138 | 139 | /* print info we can only get whilst open */ 140 | idx = g_usb_device_get_product_index(device); 141 | if (idx != 0x00) { 142 | g_autofree gchar *product = g_usb_device_get_string_descriptor(device, idx, &error); 143 | if (product == NULL) { 144 | g_print("failed to get string desc: %s\n", error->message); 145 | return; 146 | } 147 | g_print("product: %s\n", product); 148 | } 149 | if (!g_usb_device_close(device, &error)) { 150 | g_print("failed to close: %s\n", error->message); 151 | return; 152 | } 153 | } 154 | 155 | static void 156 | gusb_device_list_added_cb(GUsbContext *context, GUsbDevice *device, gpointer user_data) 157 | { 158 | g_print("device %s added %x:%x\n", 159 | g_usb_device_get_platform_id(device), 160 | g_usb_device_get_bus(device), 161 | g_usb_device_get_address(device)); 162 | gusb_main_device_open(device); 163 | } 164 | 165 | static void 166 | gusb_device_list_removed_cb(GUsbContext *context, GUsbDevice *device, gpointer user_data) 167 | { 168 | g_print("device %s removed %x:%x\n", 169 | g_usb_device_get_platform_id(device), 170 | g_usb_device_get_bus(device), 171 | g_usb_device_get_address(device)); 172 | } 173 | 174 | static gint 175 | gusb_devices_sort_by_platform_id_cb(gconstpointer a, gconstpointer b) 176 | { 177 | GUsbDevice *device_a = *((GUsbDevice **)a); 178 | GUsbDevice *device_b = *((GUsbDevice **)b); 179 | return g_strcmp0(g_usb_device_get_platform_id(device_a), 180 | g_usb_device_get_platform_id(device_b)); 181 | } 182 | 183 | static gboolean 184 | gusb_cmd_show_cb(GNode *node, gpointer data) 185 | { 186 | GUsbDevice *device = G_USB_DEVICE(node->data); 187 | const gchar *tmp; 188 | g_autofree gchar *product = NULL; 189 | g_autofree gchar *vendor = NULL; 190 | g_autoptr(GString) str = NULL; 191 | 192 | if (device == NULL) { 193 | g_print("Root Device\n"); 194 | return FALSE; 195 | } 196 | 197 | /* indent */ 198 | str = g_string_new(""); 199 | for (GNode *n = node; n->data != NULL; n = n->parent) 200 | g_string_append(str, " "); 201 | 202 | /* add bus:address */ 203 | g_string_append_printf(str, 204 | "%02x:%02x [%04x:%04x]", 205 | g_usb_device_get_bus(device), 206 | g_usb_device_get_address(device), 207 | g_usb_device_get_vid(device), 208 | g_usb_device_get_pid(device)); 209 | 210 | /* pad */ 211 | for (guint i = str->len; i < 30; i++) 212 | g_string_append(str, " "); 213 | 214 | /* We don't error check these as not all devices have these 215 | (and the device_open may have failed). */ 216 | g_usb_device_open(device, NULL); 217 | vendor = g_usb_device_get_string_descriptor(device, 218 | g_usb_device_get_manufacturer_index(device), 219 | NULL); 220 | product = g_usb_device_get_string_descriptor(device, 221 | g_usb_device_get_product_index(device), 222 | NULL); 223 | 224 | /* lookup from usb.ids */ 225 | if (vendor == NULL) { 226 | tmp = g_usb_device_get_vid_as_str(device); 227 | if (tmp != NULL) 228 | vendor = g_strdup(tmp); 229 | } 230 | 231 | if (product == NULL) { 232 | tmp = g_usb_device_get_pid_as_str(device); 233 | if (tmp != NULL) 234 | product = g_strdup(tmp); 235 | } 236 | 237 | /* a hub */ 238 | if (g_usb_device_get_device_class(device) == 0x09 && product == NULL) { 239 | product = g_strdup("USB HUB"); 240 | } 241 | 242 | /* fall back to the VID/PID */ 243 | if (product == NULL) 244 | product = g_strdup("Unknown"); 245 | 246 | if (vendor == NULL) 247 | vendor = g_strdup("Unknown"); 248 | 249 | /* add bus:address */ 250 | g_string_append_printf(str, "%s - %s", vendor, product); 251 | 252 | g_print("%s\n", str->str); 253 | g_usb_device_close(device, NULL); 254 | return FALSE; 255 | } 256 | 257 | static gboolean 258 | gusb_cmd_show(GUsbCmdPrivate *priv, gchar **values, GError **error) 259 | { 260 | g_autoptr(GNode) node = NULL; 261 | g_autoptr(GPtrArray) devices = NULL; 262 | 263 | /* sort */ 264 | devices = g_usb_context_get_devices(priv->usb_ctx); 265 | g_ptr_array_sort(devices, gusb_devices_sort_by_platform_id_cb); 266 | 267 | /* make a tree of the devices */ 268 | node = g_node_new(NULL); 269 | for (guint i = 0; i < devices->len; i++) { 270 | GNode *n; 271 | GUsbDevice *device = g_ptr_array_index(devices, i); 272 | GUsbDevice *parent = g_usb_device_get_parent(device); 273 | if (parent == NULL) { 274 | g_node_append_data(node, device); 275 | continue; 276 | } 277 | n = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, parent); 278 | if (n == NULL) { 279 | g_set_error(error, 280 | 1, 281 | 0, 282 | "no parent node for %s", 283 | g_usb_device_get_platform_id(device)); 284 | return FALSE; 285 | } 286 | g_node_append_data(n, device); 287 | } 288 | 289 | g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, gusb_cmd_show_cb, priv); 290 | return TRUE; 291 | } 292 | 293 | static gboolean 294 | gusb_cmd_watch(GUsbCmdPrivate *priv, gchar **values, GError **error) 295 | { 296 | g_autoptr(GMainLoop) loop = NULL; 297 | g_autoptr(GPtrArray) devices = NULL; 298 | 299 | devices = g_usb_context_get_devices(priv->usb_ctx); 300 | for (guint i = 0; i < devices->len; i++) { 301 | GUsbDevice *device = g_ptr_array_index(devices, i); 302 | g_print("device %s already present %x:%x\n", 303 | g_usb_device_get_platform_id(device), 304 | g_usb_device_get_bus(device), 305 | g_usb_device_get_address(device)); 306 | gusb_main_device_open(device); 307 | } 308 | 309 | loop = g_main_loop_new(NULL, FALSE); 310 | g_signal_connect(priv->usb_ctx, 311 | "device-added", 312 | G_CALLBACK(gusb_device_list_added_cb), 313 | priv); 314 | g_signal_connect(priv->usb_ctx, 315 | "device-removed", 316 | G_CALLBACK(gusb_device_list_removed_cb), 317 | priv); 318 | g_main_loop_run(loop); 319 | return TRUE; 320 | } 321 | 322 | static gboolean 323 | gusb_cmd_replug(GUsbCmdPrivate *priv, gchar **values, GError **error) 324 | { 325 | guint16 vid, pid; 326 | g_autoptr(GUsbDevice) device = NULL; 327 | g_autoptr(GUsbDevice) device_new = NULL; 328 | 329 | /* check args */ 330 | if (g_strv_length(values) != 2) { 331 | g_set_error_literal(error, 1, 0, "no VID:PID specified"); 332 | return FALSE; 333 | } 334 | 335 | /* get vid:pid */ 336 | vid = g_ascii_strtoull(values[0], NULL, 16); 337 | pid = g_ascii_strtoull(values[1], NULL, 16); 338 | device = g_usb_context_find_by_vid_pid(priv->usb_ctx, vid, pid, error); 339 | if (device == NULL) 340 | return FALSE; 341 | 342 | /* watch for debugging */ 343 | g_signal_connect(priv->usb_ctx, 344 | "device-added", 345 | G_CALLBACK(gusb_device_list_added_cb), 346 | priv); 347 | g_signal_connect(priv->usb_ctx, 348 | "device-removed", 349 | G_CALLBACK(gusb_device_list_removed_cb), 350 | priv); 351 | 352 | /* wait for replug */ 353 | device_new = g_usb_context_wait_for_replug(priv->usb_ctx, device, 5000, error); 354 | return device_new != NULL; 355 | } 356 | 357 | static gboolean 358 | gusb_cmd_load(GUsbCmdPrivate *priv, gchar **values, GError **error) 359 | { 360 | /* check args */ 361 | if (g_strv_length(values) == 0) { 362 | g_set_error_literal(error, 363 | G_IO_ERROR, 364 | G_IO_ERROR_INVALID_ARGUMENT, 365 | "no filename specified"); 366 | return FALSE; 367 | } 368 | 369 | for (guint i = 0; values[i] != NULL; i++) { 370 | JsonObject *json_obj; 371 | JsonNode *json_node; 372 | g_autoptr(JsonParser) parser = json_parser_new(); 373 | 374 | /* parse */ 375 | if (!json_parser_load_from_file(parser, values[i], error)) 376 | return FALSE; 377 | 378 | /* sanity check */ 379 | json_node = json_parser_get_root(parser); 380 | if (!JSON_NODE_HOLDS_OBJECT(json_node)) { 381 | g_set_error_literal(error, 382 | G_IO_ERROR, 383 | G_IO_ERROR_INVALID_DATA, 384 | "not a JSON object"); 385 | return FALSE; 386 | } 387 | 388 | /* not supplied */ 389 | json_obj = json_node_get_object(json_node); 390 | if (!g_usb_context_load(priv->usb_ctx, json_obj, error)) 391 | return FALSE; 392 | } 393 | 394 | /* success */ 395 | return gusb_cmd_show(priv, NULL, error); 396 | } 397 | 398 | static gboolean 399 | gusb_cmd_save(GUsbCmdPrivate *priv, gchar **values, GError **error) 400 | { 401 | g_autofree gchar *data = NULL; 402 | g_autoptr(JsonBuilder) json_builder = json_builder_new(); 403 | g_autoptr(JsonGenerator) json_generator = NULL; 404 | g_autoptr(JsonNode) json_root = NULL; 405 | 406 | if (!g_usb_context_save(priv->usb_ctx, json_builder, error)) 407 | return FALSE; 408 | 409 | /* export as a string */ 410 | json_root = json_builder_get_root(json_builder); 411 | json_generator = json_generator_new(); 412 | json_generator_set_pretty(json_generator, TRUE); 413 | json_generator_set_root(json_generator, json_root); 414 | data = json_generator_to_data(json_generator, NULL); 415 | if (data == NULL) { 416 | g_set_error_literal(error, 417 | G_IO_ERROR, 418 | G_IO_ERROR_INVALID_DATA, 419 | "Failed to convert to JSON string"); 420 | return FALSE; 421 | } 422 | 423 | /* save to file */ 424 | if (g_strv_length(values) == 1) 425 | return g_file_set_contents(values[0], data, -1, error); 426 | 427 | /* just print */ 428 | g_print("%s\n", data); 429 | return TRUE; 430 | } 431 | 432 | static gboolean 433 | gusb_cmd_run(GUsbCmdPrivate *priv, const gchar *command, gchar **values, GError **error) 434 | { 435 | g_autoptr(GString) string = g_string_new(NULL); 436 | 437 | /* find command */ 438 | for (guint i = 0; i < priv->cmd_array->len; i++) { 439 | GUsbCmdItem *item = g_ptr_array_index(priv->cmd_array, i); 440 | if (g_strcmp0(item->name, command) == 0) { 441 | return item->callback(priv, values, error); 442 | } 443 | } 444 | 445 | /* TRANSLATORS: error message */ 446 | g_string_append_printf(string, "%s\n", "Command not found, valid commands are:"); 447 | for (guint i = 0; i < priv->cmd_array->len; i++) { 448 | GUsbCmdItem *item = g_ptr_array_index(priv->cmd_array, i); 449 | g_string_append_printf(string, " * %s\n", item->name); 450 | } 451 | g_set_error_literal(error, 1, 0, string->str); 452 | return FALSE; 453 | } 454 | 455 | static void 456 | gusb_cmd_private_free(GUsbCmdPrivate *priv) 457 | { 458 | if (priv->cmd_array != NULL) 459 | g_ptr_array_unref(priv->cmd_array); 460 | if (priv->usb_ctx != NULL) 461 | g_object_unref(priv->usb_ctx); 462 | g_option_context_free(priv->context); 463 | g_slice_free(GUsbCmdPrivate, priv); 464 | } 465 | 466 | G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUsbCmdPrivate, gusb_cmd_private_free) 467 | 468 | int 469 | main(int argc, char *argv[]) 470 | { 471 | GUsbContextFlags context_flags = G_USB_CONTEXT_FLAGS_AUTO_OPEN_DEVICES; 472 | gboolean verbose = FALSE; 473 | gboolean save_events = FALSE; 474 | g_autofree gchar *cmd_descriptions = NULL; 475 | g_autofree gchar *options_help = NULL; 476 | g_autoptr(GError) error = NULL; 477 | g_autoptr(GUsbCmdPrivate) priv = NULL; 478 | 479 | const GOptionEntry options[] = {{"verbose", 480 | 'v', 481 | 0, 482 | G_OPTION_ARG_NONE, 483 | &verbose, 484 | "Show extra debugging information", 485 | NULL}, 486 | {"events", 487 | '\0', 488 | 0, 489 | G_OPTION_ARG_NONE, 490 | &save_events, 491 | "Save USB events", 492 | NULL}, 493 | {NULL}}; 494 | 495 | setlocale(LC_ALL, ""); 496 | 497 | /* create helper object */ 498 | priv = g_slice_new0(GUsbCmdPrivate); 499 | priv->context = g_option_context_new("GUSB Console Program"); 500 | g_option_context_add_main_entries(priv->context, options, NULL); 501 | if (!g_option_context_parse(priv->context, &argc, &argv, &error)) { 502 | g_printerr("Failed to parse arguments: %s\n", error->message); 503 | return 2; 504 | } 505 | 506 | /* verbose? */ 507 | if (verbose) { 508 | g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); 509 | g_log_set_handler("GUsb", 510 | G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_DEBUG | 511 | G_LOG_LEVEL_WARNING, 512 | gusb_log_handler_cb, 513 | NULL); 514 | } else { 515 | /* hide all debugging */ 516 | g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); 517 | g_log_set_handler("GUsb", G_LOG_LEVEL_DEBUG, gusb_log_ignore_cb, NULL); 518 | } 519 | 520 | /* GUsbContext */ 521 | priv->usb_ctx = g_usb_context_new(NULL); 522 | if (save_events) 523 | context_flags |= G_USB_CONTEXT_FLAGS_SAVE_EVENTS; 524 | if (verbose) 525 | context_flags |= G_USB_CONTEXT_FLAGS_DEBUG; 526 | g_usb_context_set_flags(priv->usb_ctx, context_flags); 527 | 528 | /* add commands */ 529 | priv->cmd_array = g_ptr_array_new_with_free_func((GDestroyNotify)gusb_cmd_item_free); 530 | gusb_cmd_add(priv->cmd_array, "show", "Show currently connected devices", gusb_cmd_show); 531 | gusb_cmd_add(priv->cmd_array, "watch", "Watch devices as they come and go", gusb_cmd_watch); 532 | gusb_cmd_add(priv->cmd_array, "replug", "Watch a device as it reconnects", gusb_cmd_replug); 533 | gusb_cmd_add(priv->cmd_array, "load", "Load a set of devices from JSON", gusb_cmd_load); 534 | gusb_cmd_add(priv->cmd_array, "save", "Save a set of devices to JSON", gusb_cmd_save); 535 | 536 | /* sort by command name */ 537 | g_ptr_array_sort(priv->cmd_array, (GCompareFunc)gusb_sort_command_name_cb); 538 | 539 | /* get a list of the commands */ 540 | cmd_descriptions = gusb_cmd_get_descriptions(priv->cmd_array); 541 | g_option_context_set_summary(priv->context, cmd_descriptions); 542 | 543 | /* nothing specified */ 544 | if (argc < 2) { 545 | options_help = g_option_context_get_help(priv->context, TRUE, NULL); 546 | g_print("%s", options_help); 547 | return 1; 548 | } 549 | 550 | /* run the specified command */ 551 | if (!gusb_cmd_run(priv, argv[1], (gchar **)&argv[2], &error)) { 552 | g_print("%s\n", error->message); 553 | return 1; 554 | } 555 | 556 | /* success */ 557 | return 0; 558 | } 559 | -------------------------------------------------------------------------------- /tools/meson.build: -------------------------------------------------------------------------------- 1 | cargs = ['-DG_LOG_DOMAIN="GUsb"'] 2 | 3 | executable( 4 | 'gusbcmd', 5 | sources : [ 6 | 'gusb-main.c', 7 | ], 8 | include_directories : [ 9 | lib_incdir, 10 | root_incdir, 11 | ], 12 | dependencies : [ 13 | libgio, 14 | libusb, 15 | libjsonglib, 16 | ], 17 | link_with : gusb, 18 | c_args : [ 19 | cargs, 20 | ], 21 | install : true, 22 | install_dir : bindir 23 | ) 24 | --------------------------------------------------------------------------------