├── .drone.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── drm.c ├── drm.h ├── drm_master.c ├── drm_master.h ├── input.c ├── input.h ├── keymap.c ├── keymap.h ├── kmsvnc.c ├── kmsvnc.h ├── va.c └── va.h /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: docker 3 | name: default 4 | 5 | steps: 6 | - name: build 7 | image: archlinux:latest 8 | commands: 9 | - pacman -Syu --noconfirm --needed base-devel libvncserver libxkbcommon libdrm libva git cmake clang 10 | - export CFLAGS="-pipe -fno-plt -fexceptions -fstack-clash-protection -fcf-protection -Wp,-D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security" 11 | - CC=gcc cmake -B gcc-out 12 | - cmake --build gcc-out 13 | - CC=clang cmake -B clang-out 14 | - cmake --build clang-out 15 | 16 | trigger: 17 | branch: 18 | - dev 19 | event: 20 | exclude: 21 | - pull_request 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(kmsvnc LANGUAGES C) 3 | 4 | IF(NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "") 5 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) 6 | endif() 7 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 8 | 9 | find_package(PkgConfig REQUIRED) 10 | pkg_search_module(LIBDRM REQUIRED libdrm) 11 | pkg_search_module(LIBVNCSERVER REQUIRED libvncserver) 12 | pkg_search_module(XKBCOMMON REQUIRED xkbcommon) 13 | pkg_search_module(LIBVA REQUIRED libva) 14 | pkg_search_module(LIBVA_DRM REQUIRED libva-drm) 15 | 16 | add_executable(kmsvnc) 17 | set(kmsvnc_SOURCES kmsvnc.c drm.c input.c keymap.c va.c drm_master.c) 18 | 19 | include(CheckIncludeFiles) 20 | CHECK_INCLUDE_FILES("linux/uinput.h;linux/dma-buf.h" HAVE_LINUX_API_HEADERS) 21 | IF(NOT HAVE_LINUX_API_HEADERS) 22 | message(FATAL_ERROR "linux-api-headers not found") 23 | ENDIF() 24 | 25 | include(CheckSymbolExists) 26 | check_symbol_exists(SYS_pidfd_getfd "sys/syscall.h" HAVE_LIBC_SYS_pidfd_getfd) 27 | IF(NOT HAVE_LIBC_SYS_pidfd_getfd) 28 | message(WARNING "pidfd_getfd syscall not found, the --screen-blank options will be disabled") 29 | target_compile_options(kmsvnc PUBLIC -DDISABLE_KMSVNC_SCREEN_BLANK) 30 | list(REMOVE_ITEM kmsvnc_SOURCES drm_master.c) 31 | ENDIF() 32 | include(CMakePushCheckState) 33 | cmake_push_check_state() 34 | set(CMAKE_REQUIRED_INCLUDES ${LIBDRM_INCLUDE_DIRS}) 35 | set(CMAKE_REQUIRED_LIBRARIES ${LIBDRM_LIBRARIES}) 36 | check_symbol_exists(drmGetFormatName "xf86drm.h" HAVE_LIBDRM_drmGetFormatName) 37 | cmake_pop_check_state() 38 | IF(NOT HAVE_LIBDRM_drmGetFormatName) 39 | message(WARNING "drmGetFormatName not found, format name printing will be disabled") 40 | target_compile_options(kmsvnc PUBLIC -DDISABLE_KMSVNC_drmGetFormatName) 41 | ENDIF() 42 | 43 | 44 | target_sources(kmsvnc PUBLIC 45 | ${kmsvnc_SOURCES} 46 | ) 47 | target_include_directories(kmsvnc PUBLIC 48 | ${LIBDRM_INCLUDE_DIRS} 49 | ${LIBVNCSERVER_INCLUDE_DIRS} 50 | ${XKBCOMMON_INCLUDE_DIRS} 51 | ${LIBVA_INCLUDE_DIRS} 52 | ${LIBVA_DRM_INCLUDE_DIRS} 53 | ) 54 | target_link_libraries(kmsvnc PUBLIC 55 | m 56 | ${LIBDRM_LIBRARIES} 57 | ${LIBVNCSERVER_LIBRARIES} 58 | ${XKBCOMMON_LIBRARIES} 59 | ${LIBVA_LIBRARIES} 60 | ${LIBVA_DRM_LIBRARIES} 61 | ) 62 | install(TARGETS kmsvnc RUNTIME DESTINATION bin) 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kmsvnc 2 | 3 | [![Build Status](https://drone.jerryxiao.cc/api/badges/Jerry/kmsvnc/status.svg)](https://drone.jerryxiao.cc/Jerry/kmsvnc) 4 | 5 | ## Introduction 6 | A VNC server for DRM/KMS capable GNU/Linux devices. 7 | The goal is to simply have a universally working vncserver on X, wayland and even something like your linux VT. 8 | Currently in very early development stage. 9 | 10 | ## Notes 11 | Intel made a great thing called CCS (Color Control Surface), however that won't work with kmsvnc. Please set `INTEL_DEBUG=noccs` globally, ideally in /etc/systemd/system.conf.d. Manpage is at `man 5 systemd-system.conf`. For example: 12 | ``` 13 | # /etc/systemd/system.conf.d/intel-no-ccs.conf 14 | [Manager] 15 | DefaultEnvironment=INTEL_DEBUG=noccs 16 | ``` 17 | NixOS: 18 | ``` 19 | systemd.extraConfig = '' 20 | DefaultEnvironment=INTEL_DEBUG=noccs 21 | '' 22 | ``` 23 | 24 | If you plan to use the default vaapi driver for Intel and AMD GPUs, please make sure your vaapi configuration is working. 25 | Nvidia support is highly experimental (nvidia-legacy with drm enabled or nvidia-open). Only one X-TILED modifier is supported as of now. 26 | 27 | ## Dependencies 28 | * cmake 29 | * libvncserver 30 | * libxkbcommon 31 | * libdrm 32 | * libva 33 | 34 | ## Building 35 | ``` 36 | mkdir build 37 | cd build 38 | cmake .. 39 | make 40 | ``` 41 | 42 | ## Running 43 | Helps are available via `kmsvnc --help`. 44 | For example, `kmsvnc -p 5901 -b 0.0.0.0 -4 -d /dev/dri/card2` 45 | Note that no security is currently supported. 46 | -------------------------------------------------------------------------------- /drm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "drm.h" 12 | #include "va.h" 13 | 14 | #ifndef DISABLE_KMSVNC_SCREEN_BLANK 15 | #include "drm_master.h" 16 | #endif 17 | 18 | #ifndef fourcc_mod_is_vendor 19 | #define fourcc_mod_is_vendor(modifier, vendor) \ 20 | (fourcc_mod_get_vendor(modifier) == DRM_FORMAT_MOD_VENDOR_## vendor) 21 | #endif 22 | #ifdef DISABLE_KMSVNC_drmGetFormatName 23 | static char* drmGetFormatName(uint32_t data) { 24 | char *name = "missing drmGetFormatName"; 25 | char *out = malloc(strlen(name)+1); 26 | if (out) { 27 | memcpy(out, name, strlen(name)+1); 28 | } 29 | return out; 30 | } 31 | #endif 32 | 33 | extern struct kmsvnc_data *kmsvnc; 34 | 35 | static int check_pixfmt_non_vaapi() { 36 | if ( 37 | kmsvnc->drm->mfb->pixel_format != KMSVNC_FOURCC_TO_INT('X', 'R', '2', '4') && 38 | kmsvnc->drm->mfb->pixel_format != KMSVNC_FOURCC_TO_INT('A', 'R', '2', '4') 39 | ) 40 | { 41 | KMSVNC_FATAL("Unsupported pixfmt %s, please create an issue with your pixfmt.\n", kmsvnc->drm->pixfmt_name); 42 | } 43 | return 0; 44 | } 45 | 46 | static void convert_copy(const char *in, int width, int height, char *buff) 47 | { 48 | if (likely(in != buff)) { 49 | memcpy(buff, in, width * height * BYTES_PER_PIXEL); 50 | } 51 | } 52 | 53 | static void convert_bgra_to_rgba(const char *in, int width, int height, char *buff) 54 | { 55 | if (likely(in != buff)) { 56 | memcpy(buff, in, width * height * BYTES_PER_PIXEL); 57 | } 58 | for (int i = 0; i < width * height * BYTES_PER_PIXEL; i += BYTES_PER_PIXEL) { 59 | uint32_t pixdata = htonl(*((uint32_t*)(buff + i))); 60 | buff[i+0] = (pixdata & 0x0000ff00) >> 8; 61 | buff[i+2] = (pixdata & 0xff000000) >> 24; 62 | } 63 | } 64 | 65 | static inline char convert_buf_allocate(size_t len) { 66 | if (kmsvnc->drm->kms_convert_buf_len < len) 67 | { 68 | if (kmsvnc->drm->kms_convert_buf) 69 | free(kmsvnc->drm->kms_convert_buf); 70 | kmsvnc->drm->kms_convert_buf = malloc(len); 71 | if (!kmsvnc->drm->kms_convert_buf) return 1; 72 | kmsvnc->drm->kms_convert_buf_len = len; 73 | } 74 | return 0; 75 | } 76 | static inline void convert_x_tiled(const int tilex, const int tiley, const char *in, int width, int height, char *buff) 77 | { 78 | if (width % tilex) 79 | { 80 | return; 81 | } 82 | if (height % tiley) 83 | { 84 | int sno = (width / tilex) + (height / tiley) * (width / tilex); 85 | int ord = (width % tilex) + (height % tiley) * tilex; 86 | int max_offset = sno * tilex * tiley + ord; 87 | if (kmsvnc->drm->kms_cpy_tmp_buf_len < max_offset * 4 + 4) 88 | { 89 | if (kmsvnc->drm->kms_cpy_tmp_buf) 90 | free(kmsvnc->drm->kms_convert_buf); 91 | kmsvnc->drm->kms_cpy_tmp_buf = malloc(max_offset * 4 + 4); 92 | if (!kmsvnc->drm->kms_cpy_tmp_buf) return; 93 | kmsvnc->drm->kms_cpy_tmp_buf_len = max_offset * 4 + 4; 94 | } 95 | memcpy(kmsvnc->drm->kms_cpy_tmp_buf, in, max_offset * 4 + 4); 96 | in = (const char *)kmsvnc->drm->kms_cpy_tmp_buf; 97 | } 98 | if (convert_buf_allocate(width * height * 4)) return; 99 | for (int y = 0; y < height; y++) 100 | { 101 | for (int x = 0; x < width; x++) 102 | { 103 | int sno = (x / tilex) + (y / tiley) * (width / tilex); 104 | int ord = (x % tilex) + (y % tiley) * tilex; 105 | int offset = sno * tilex * tiley + ord; 106 | memcpy(kmsvnc->drm->kms_convert_buf + (x + y * width) * 4, in + offset * 4, 4); 107 | } 108 | } 109 | convert_bgra_to_rgba(kmsvnc->drm->kms_convert_buf, width, height, buff); 110 | } 111 | 112 | void convert_nvidia_x_tiled_kmsbuf(const char *in, int width, int height, char *buff) 113 | { 114 | convert_x_tiled(16, 128, in, width, height, buff); 115 | } 116 | void convert_intel_x_tiled_kmsbuf(const char *in, int width, int height, char *buff) 117 | { 118 | convert_x_tiled(128, 8, in, width, height, buff); 119 | } 120 | 121 | static void convert_vaapi(const char *in, int width, int height, char *buff) { 122 | va_hwframe_to_vaapi(buff); 123 | if ( 124 | (KMSVNC_FOURCC_TO_INT('R','G','B',0) & kmsvnc->va->selected_fmt->fourcc) == KMSVNC_FOURCC_TO_INT('R','G','B',0) 125 | ) {} 126 | else { 127 | // is 30 depth? 128 | if (kmsvnc->va->selected_fmt->depth == 30) { 129 | for (int i = 0; i < width * height * BYTES_PER_PIXEL; i += BYTES_PER_PIXEL) { 130 | // ensure little endianess 131 | uint32_t pixdata = __builtin_bswap32(htonl(*((uint32_t*)(buff + i)))); 132 | buff[i] = (pixdata & 0x3ff00000) >> 20 >> 2; 133 | buff[i+1] = (pixdata & 0xffc00) >> 10 >> 2; 134 | buff[i+2] = (pixdata & 0x3ff) >> 2; 135 | } 136 | } 137 | else { 138 | // actually, does anyone use this? 139 | if (!kmsvnc->va->selected_fmt->byte_order) { 140 | for (int i = 0; i < width * height * BYTES_PER_PIXEL; i += BYTES_PER_PIXEL) { 141 | uint32_t *pixdata = (uint32_t*)(buff + i); 142 | *pixdata = __builtin_bswap32(*pixdata); 143 | } 144 | } 145 | } 146 | // is xrgb? 147 | if ((kmsvnc->va->selected_fmt->blue_mask | kmsvnc->va->selected_fmt->red_mask) < 0x1000000) { 148 | for (int i = 0; i < width * height * BYTES_PER_PIXEL; i += BYTES_PER_PIXEL) { 149 | uint32_t *pixdata = (uint32_t*)(buff + i); 150 | *pixdata = ntohl(htonl(*pixdata) << 8); 151 | } 152 | } 153 | // is bgrx? 154 | if (kmsvnc->va->selected_fmt->blue_mask > kmsvnc->va->selected_fmt->red_mask) { 155 | for (int i = 0; i < width * height * BYTES_PER_PIXEL; i += BYTES_PER_PIXEL) { 156 | uint32_t pixdata = htonl(*((uint32_t*)(buff + i))); 157 | buff[i+0] = (pixdata & 0x0000ff00) >> 8; 158 | buff[i+2] = (pixdata & 0xff000000) >> 24; 159 | } 160 | } 161 | } 162 | } 163 | 164 | static inline void drm_sync(int drmfd, uint64_t flags) 165 | { 166 | struct dma_buf_sync sync = { 167 | .flags = flags, 168 | }; 169 | DRM_R_IOCTL_MAY(drmfd, DMA_BUF_IOCTL_SYNC, &sync); 170 | } 171 | 172 | void drm_sync_start(int drmfd) 173 | { 174 | drm_sync(drmfd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ); 175 | } 176 | void drm_sync_end(int drmfd) 177 | { 178 | drm_sync(drmfd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ); 179 | } 180 | void drm_sync_noop(int drmfd) 181 | { 182 | } 183 | 184 | void drm_cleanup() { 185 | if (kmsvnc->drm) { 186 | #ifndef DISABLE_KMSVNC_SCREEN_BLANK 187 | if (kmsvnc->drm->gamma && kmsvnc->drm->gamma->size && kmsvnc->drm->gamma->red && kmsvnc->drm->gamma->green && kmsvnc->drm->gamma->blue) { 188 | if (drmModeCrtcSetGamma(kmsvnc->drm->drm_master_fd ?: kmsvnc->drm->drm_fd, kmsvnc->drm->plane->crtc_id, kmsvnc->drm->gamma->size, kmsvnc->drm->gamma->red, kmsvnc->drm->gamma->green, kmsvnc->drm->gamma->blue)) perror("Failed to restore gamma"); 189 | } 190 | if (kmsvnc->drm->gamma && kmsvnc->drm->gamma->red) { 191 | free(kmsvnc->drm->gamma->red); 192 | kmsvnc->drm->gamma->red = kmsvnc->drm->gamma->green = kmsvnc->drm->gamma->blue = NULL; 193 | } 194 | if (kmsvnc->drm->gamma) { 195 | free(kmsvnc->drm->gamma); 196 | kmsvnc->drm->gamma = NULL; 197 | } 198 | #endif 199 | if (kmsvnc->drm->drm_ver) { 200 | drmFreeVersion(kmsvnc->drm->drm_ver); 201 | kmsvnc->drm->drm_ver = NULL; 202 | } 203 | if (kmsvnc->drm->pixfmt_name) { 204 | free(kmsvnc->drm->pixfmt_name); 205 | kmsvnc->drm->pixfmt_name = NULL; 206 | } 207 | if (kmsvnc->drm->mod_vendor) { 208 | free(kmsvnc->drm->mod_vendor); 209 | kmsvnc->drm->mod_vendor = NULL; 210 | } 211 | if (kmsvnc->drm->mod_name) { 212 | free(kmsvnc->drm->mod_name); 213 | kmsvnc->drm->mod_name = NULL; 214 | } 215 | if (kmsvnc->drm->plane) { 216 | drmModeFreePlane(kmsvnc->drm->plane); 217 | kmsvnc->drm->plane = NULL; 218 | } 219 | if (kmsvnc->drm->cursor_plane) { 220 | drmModeFreePlane(kmsvnc->drm->cursor_plane); 221 | kmsvnc->drm->cursor_plane = NULL; 222 | } 223 | if (kmsvnc->drm->mfb) { 224 | drmModeFreeFB2(kmsvnc->drm->mfb); 225 | kmsvnc->drm->mfb = NULL; 226 | } 227 | if (kmsvnc->drm->cursor_mfb) { 228 | drmModeFreeFB2(kmsvnc->drm->cursor_mfb); 229 | kmsvnc->drm->cursor_mfb = NULL; 230 | } 231 | if (kmsvnc->drm->mapped && kmsvnc->drm->mapped != MAP_FAILED) { 232 | munmap(kmsvnc->drm->mapped, kmsvnc->drm->mmap_size); 233 | kmsvnc->drm->mapped = NULL; 234 | } 235 | if (kmsvnc->drm->cursor_mapped && kmsvnc->drm->cursor_mapped != MAP_FAILED) { 236 | munmap(kmsvnc->drm->cursor_mapped, kmsvnc->drm->cursor_mmap_size); 237 | kmsvnc->drm->cursor_mapped = NULL; 238 | } 239 | if (kmsvnc->drm->prime_fd > 0) { 240 | close(kmsvnc->drm->prime_fd); 241 | kmsvnc->drm->prime_fd = 0; 242 | } 243 | if (kmsvnc->drm->drm_fd > 0) { 244 | close(kmsvnc->drm->drm_fd); 245 | kmsvnc->drm->drm_fd = 0; 246 | } 247 | if (kmsvnc->drm->drm_master_fd > 0) { 248 | close(kmsvnc->drm->drm_master_fd); 249 | kmsvnc->drm->drm_master_fd = 0; 250 | } 251 | if (kmsvnc->drm->plane_res) { 252 | drmModeFreePlaneResources(kmsvnc->drm->plane_res); 253 | kmsvnc->drm->plane_res = NULL; 254 | } 255 | if (kmsvnc->drm->kms_convert_buf) { 256 | free(kmsvnc->drm->kms_convert_buf); 257 | kmsvnc->drm->kms_convert_buf = NULL; 258 | } 259 | kmsvnc->drm->kms_convert_buf_len = 0; 260 | if (kmsvnc->drm->kms_cpy_tmp_buf) { 261 | free(kmsvnc->drm->kms_cpy_tmp_buf); 262 | kmsvnc->drm->kms_cpy_tmp_buf = NULL; 263 | } 264 | kmsvnc->drm->kms_cpy_tmp_buf_len = 0; 265 | if (kmsvnc->drm->kms_cursor_buf) { 266 | free(kmsvnc->drm->kms_cursor_buf); 267 | kmsvnc->drm->kms_cursor_buf = NULL; 268 | } 269 | kmsvnc->drm->kms_cursor_buf_len = 0; 270 | free(kmsvnc->drm); 271 | kmsvnc->drm = NULL; 272 | } 273 | } 274 | 275 | static const char* drm_get_plane_type_name(uint64_t plane_type) { 276 | switch (plane_type) { 277 | case DRM_PLANE_TYPE_OVERLAY: 278 | return "overlay"; 279 | case DRM_PLANE_TYPE_PRIMARY: 280 | return "primary"; 281 | case DRM_PLANE_TYPE_CURSOR: 282 | return "cursor"; 283 | default: 284 | return "unknown"; 285 | } 286 | }; 287 | 288 | static int drm_refresh_planes(char first_time) { 289 | struct kmsvnc_drm_data *drm = kmsvnc->drm; 290 | if (!drm->plane && kmsvnc->source_plane > 0) 291 | { 292 | drm->plane = drmModeGetPlane(drm->drm_fd, kmsvnc->source_plane); 293 | if (!drm->plane) 294 | KMSVNC_FATAL("Failed to get plane %d: %s\n", kmsvnc->source_plane, strerror(errno)); 295 | if (drm->plane->fb_id == 0) 296 | fprintf(stderr, "Place %d does not have an attached framebuffer\n", kmsvnc->source_plane); 297 | } 298 | if (!drm->plane || (kmsvnc->capture_cursor && !drm->cursor_plane)) { 299 | drmModePlane *current_plane = NULL; 300 | if (drm->plane_res) { 301 | drmModeFreePlaneResources(kmsvnc->drm->plane_res); 302 | drm->plane_res = NULL; 303 | } 304 | drm->plane_res = drmModeGetPlaneResources(drm->drm_fd); 305 | if (!drm->plane_res) 306 | KMSVNC_FATAL("Failed to get plane resources: %s\n", strerror(errno)); 307 | int i; 308 | for (i = 0; i < drm->plane_res->count_planes; i++) 309 | { 310 | current_plane = drmModeGetPlane(drm->drm_fd, drm->plane_res->planes[i]); 311 | if (!current_plane) 312 | { 313 | fprintf(stderr, "Failed to get plane %u: %s\n", drm->plane_res->planes[i], strerror(errno)); 314 | continue; 315 | } 316 | // get plane type 317 | uint64_t plane_type = 114514; 318 | drmModeObjectPropertiesPtr plane_props = drmModeObjectGetProperties(drm->drm_fd, current_plane->plane_id, DRM_MODE_OBJECT_PLANE); 319 | if (!plane_props) { 320 | fprintf(stderr, "Failed to get plane prop %u: %s\n", drm->plane_res->planes[i], strerror(errno)); 321 | } 322 | else { 323 | for (int i = 0; i < plane_props->count_props; i++) { 324 | drmModePropertyPtr plane_prop = drmModeGetProperty(drm->drm_fd, plane_props->props[i]); 325 | if (strcmp(plane_prop->name, "type") == 0) { 326 | plane_type = plane_props->prop_values[i]; 327 | } 328 | drmModeFreeProperty(plane_prop); 329 | } 330 | drmModeFreeObjectProperties(plane_props); 331 | } 332 | assert(drm->plane_res->planes[i] == current_plane->plane_id); 333 | if (first_time) { 334 | printf("Plane %u CRTC %u FB %u Type %s\n", current_plane->plane_id, current_plane->crtc_id, current_plane->fb_id, drm_get_plane_type_name(plane_type)); 335 | } 336 | // populate drm->plane and drm->cursor_plane 337 | char nofree = 0; 338 | if (current_plane->fb_id != 0) { 339 | if (!drm->plane) { 340 | if (kmsvnc->source_crtc == 0 || current_plane->crtc_id == kmsvnc->source_crtc) { 341 | nofree = 1; 342 | drm->plane = current_plane; 343 | } 344 | } 345 | // assume cursor plane is always after primary plane 346 | if (!drm->cursor_plane) { 347 | if (drm->plane && drm->plane->crtc_id == current_plane->crtc_id && plane_type == DRM_PLANE_TYPE_CURSOR) { 348 | nofree = 1; 349 | drm->cursor_plane = current_plane; 350 | } 351 | } 352 | } 353 | if ((!kmsvnc->capture_cursor || drm->cursor_plane) && drm->plane) { 354 | break; 355 | } 356 | if (!nofree) { 357 | drmModeFreePlane(current_plane); 358 | } 359 | current_plane = NULL; 360 | } 361 | if (!first_time) return 0; 362 | if (i == drm->plane_res->count_planes) 363 | { 364 | if (!drm->plane) { 365 | if (kmsvnc->source_crtc != 0) 366 | { 367 | KMSVNC_FATAL("No usable planes found on CRTC %d\n", kmsvnc->source_crtc); 368 | } 369 | else 370 | { 371 | KMSVNC_FATAL("No usable planes found\n"); 372 | } 373 | } 374 | else if (!drm->cursor_plane) { 375 | fprintf(stderr, "No usable cursor plane found, cursor capture currently unavailable\n"); 376 | } 377 | } 378 | printf("Using plane %u to locate framebuffers\n", drm->plane->plane_id); 379 | if (drm->cursor_plane) { 380 | printf("Using cursor plane %u\n", drm->cursor_plane->plane_id); 381 | } 382 | } 383 | return 0; 384 | } 385 | 386 | int drm_dump_cursor_plane(char **data, int *width, int *height) { 387 | struct kmsvnc_drm_data *drm = kmsvnc->drm; 388 | 389 | if (!drm->cursor_plane) { 390 | drm_refresh_planes(0); // ignore error 391 | if (drm->cursor_plane) { 392 | printf("Using cursor plane %u\n", drm->cursor_plane->plane_id); 393 | } 394 | } 395 | else { 396 | uint32_t plane_id = drm->cursor_plane->plane_id; 397 | drmModeFreePlane(drm->cursor_plane); 398 | drm->cursor_plane = NULL; 399 | drm->cursor_plane = drmModeGetPlane(drm->drm_fd, plane_id); 400 | } 401 | if (!drm->cursor_plane) { 402 | data = NULL; 403 | return 1; 404 | } 405 | if (drm->cursor_mfb) drmModeFreeFB2(drm->cursor_mfb); 406 | drm->cursor_mfb = drmModeGetFB2(drm->drm_fd, drm->cursor_plane->fb_id); 407 | if (!drm->cursor_mfb) { 408 | KMSVNC_DEBUG("Cursor framebuffer missing\n"); 409 | return 1; 410 | } 411 | 412 | if (drm->cursor_mfb->modifier != DRM_FORMAT_MOD_NONE && drm->cursor_mfb->modifier != DRM_FORMAT_MOD_LINEAR) { 413 | //kmsvnc->capture_cursor = 0; 414 | KMSVNC_DEBUG("Cursor plane modifier is not linear: %lu\n", drm->cursor_mfb->modifier); 415 | return 1; 416 | } 417 | 418 | if ( 419 | drm->cursor_mfb->pixel_format != KMSVNC_FOURCC_TO_INT('A', 'R', '2', '4') && 420 | drm->cursor_mfb->pixel_format != KMSVNC_FOURCC_TO_INT('A', 'R', '3', '0') 421 | ) 422 | { 423 | //kmsvnc->capture_cursor = 0; 424 | char *fmtname = drmGetFormatName(drm->cursor_mfb->pixel_format); 425 | KMSVNC_DEBUG("Cursor plane pixel format unsupported (%u, %s)\n", drm->cursor_mfb->pixel_format, fmtname); 426 | free(fmtname); 427 | return 1; 428 | } 429 | 430 | struct drm_gem_flink flink; 431 | flink.handle = drm->cursor_mfb->handles[0]; 432 | DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_GEM_FLINK, &flink); 433 | 434 | struct drm_gem_open open_arg; 435 | open_arg.name = flink.name; 436 | DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_GEM_OPEN, &open_arg); 437 | 438 | struct drm_mode_map_dumb mreq; 439 | memset(&mreq, 0, sizeof(mreq)); 440 | mreq.handle = open_arg.handle; 441 | DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); 442 | 443 | size_t mmap_size = open_arg.size; 444 | if (mmap_size != drm->cursor_mfb->width * drm->cursor_mfb->height * BYTES_PER_PIXEL) { 445 | KMSVNC_DEBUG("Cursor plane mmap_size != calculated size (%ld, %d)\n", mmap_size, drm->cursor_mfb->width * drm->cursor_mfb->height * BYTES_PER_PIXEL); 446 | return 1; 447 | } 448 | 449 | off_t mmap_offset = mreq.offset; 450 | if (drm->cursor_mapped && drm->cursor_mapped != MAP_FAILED) munmap(drm->cursor_mapped, drm->cursor_mmap_size); 451 | drm->cursor_mapped = mmap(NULL, mmap_size, PROT_READ, MAP_SHARED, drm->drm_fd, mmap_offset); 452 | if (drm->cursor_mapped == MAP_FAILED) 453 | { 454 | KMSVNC_DEBUG("Failed to mmap cursor: %s\n", strerror(errno)); 455 | return 1; 456 | } 457 | else 458 | { 459 | if (kmsvnc->drm->kms_cursor_buf_len < mmap_size) 460 | { 461 | if (kmsvnc->drm->kms_cursor_buf) 462 | free(kmsvnc->drm->kms_cursor_buf); 463 | kmsvnc->drm->kms_cursor_buf = malloc(mmap_size); 464 | if (!kmsvnc->drm->kms_cursor_buf) return 1; 465 | kmsvnc->drm->kms_cursor_buf_len = mmap_size; 466 | } 467 | memcpy(drm->kms_cursor_buf, drm->cursor_mapped, mmap_size); 468 | if (drm->cursor_mfb->pixel_format == KMSVNC_FOURCC_TO_INT('X', 'R', '3', '0') || 469 | drm->cursor_mfb->pixel_format == KMSVNC_FOURCC_TO_INT('A', 'R', '3', '0')) 470 | { 471 | for (int i = 0; i < drm->cursor_mfb->width * drm->cursor_mfb->height * BYTES_PER_PIXEL; i += BYTES_PER_PIXEL) { 472 | uint32_t pixdata = __builtin_bswap32(htonl(*((uint32_t*)(kmsvnc->drm->kms_cursor_buf + i)))); 473 | kmsvnc->drm->kms_cursor_buf[i] = (pixdata & 0x3ff00000) >> 20 >> 2; 474 | kmsvnc->drm->kms_cursor_buf[i+1] = (pixdata & 0xffc00) >> 10 >> 2; 475 | kmsvnc->drm->kms_cursor_buf[i+2] = (pixdata & 0x3ff) >> 2; 476 | kmsvnc->drm->kms_cursor_buf[i+3] = (pixdata & 0xc0000000) >> 30 << 6; 477 | } 478 | } 479 | if (drm->cursor_mfb->pixel_format == KMSVNC_FOURCC_TO_INT('X', 'R', '2', '4') || 480 | drm->cursor_mfb->pixel_format == KMSVNC_FOURCC_TO_INT('A', 'R', '2', '4')) 481 | { 482 | // bgra to rgba 483 | for (int i = 0; i < drm->cursor_mfb->width * drm->cursor_mfb->height * BYTES_PER_PIXEL; i += BYTES_PER_PIXEL) { 484 | uint32_t pixdata = htonl(*((uint32_t*)(kmsvnc->drm->kms_cursor_buf + i))); 485 | kmsvnc->drm->kms_cursor_buf[i+0] = (pixdata & 0x0000ff00) >> 8; 486 | kmsvnc->drm->kms_cursor_buf[i+2] = (pixdata & 0xff000000) >> 24; 487 | } 488 | } 489 | *width = drm->cursor_mfb->width; 490 | *height = drm->cursor_mfb->height; 491 | *data = drm->kms_cursor_buf; 492 | } 493 | return 0; 494 | } 495 | 496 | int drm_open() { 497 | struct kmsvnc_drm_data *drm = malloc(sizeof(struct kmsvnc_drm_data)); 498 | if (!drm) KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 499 | memset(drm, 0, sizeof(struct kmsvnc_drm_data)); 500 | kmsvnc->drm = drm; 501 | 502 | drm->drm_fd = open(kmsvnc->card, O_RDONLY); 503 | if (drm->drm_fd < 0) 504 | { 505 | KMSVNC_FATAL("card %s open failed: %s\n", kmsvnc->card, strerror(errno)); 506 | } 507 | if (!kmsvnc->screen_blank && drmIsMaster(drm->drm_fd)) { 508 | if (drmDropMaster(drm->drm_fd)) fprintf(stderr, "Failed to drop master"); 509 | } 510 | #ifndef DISABLE_KMSVNC_SCREEN_BLANK 511 | if (kmsvnc->screen_blank && !drmIsMaster(drm->drm_fd)) { 512 | drm->drm_master_fd = drm_get_master_fd(); 513 | drm->drm_master_fd = drm->drm_master_fd > 0 ? drm->drm_master_fd : 0; 514 | if (kmsvnc->debug_enabled) { 515 | fprintf(stderr, "not master client, master fd %d\n", drm->drm_master_fd); 516 | } 517 | } 518 | #endif 519 | 520 | drm->drm_ver = drmGetVersion(drm->drm_fd); 521 | printf("drm driver is %s\n", drm->drm_ver->name); 522 | 523 | int err = drmSetClientCap(drm->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); 524 | if (err < 0) 525 | { 526 | perror("Failed to set universal planes capability: primary planes will not be usable"); 527 | } 528 | 529 | if (drm_refresh_planes(1)) return 1; 530 | 531 | #ifndef DISABLE_KMSVNC_SCREEN_BLANK 532 | if (kmsvnc->screen_blank) { 533 | drm->gamma = malloc(sizeof(struct kmsvnc_drm_gamma_data)); 534 | if (!drm->gamma) KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 535 | memset(drm->gamma, 0, sizeof(struct kmsvnc_drm_gamma_data)); 536 | drmModeCrtc *target_crtc = drmModeGetCrtc(drm->drm_fd, drm->plane->crtc_id); 537 | if (target_crtc) { 538 | drm->gamma->size = (uint32_t)target_crtc->gamma_size; 539 | drm->gamma->red = malloc(drm->gamma->size*sizeof(uint16_t)*3); 540 | if (!drm->gamma->size) { 541 | fprintf(stderr, "drm->gamma->size = %u, not setting gamma.\n", drm->gamma->size); 542 | } 543 | else if (!drm->gamma->red) { 544 | fprintf(stderr, "memory allocation error at %s:%d\n", __FILE__, __LINE__); 545 | fprintf(stderr, "not setting gamma.\n"); 546 | } 547 | else { 548 | memset(drm->gamma->red, 0, drm->gamma->size*sizeof(uint16_t)*3); 549 | drm->gamma->green = drm->gamma->red + drm->gamma->size; 550 | drm->gamma->blue = drm->gamma->red + drm->gamma->size*2; 551 | if (kmsvnc->screen_blank_restore) { 552 | int step = 0x10000 / drm->gamma->size; 553 | for (int i = 0; i < drm->gamma->size; i++) { 554 | drm->gamma->red[i] = drm->gamma->green[i] = drm->gamma->blue[i] = step * i; 555 | } 556 | } 557 | else { 558 | // legacy api, but weston also uses this, so whatever 559 | drmModeCrtcGetGamma(drm->drm_fd, drm->plane->crtc_id, drm->gamma->size, drm->gamma->red, drm->gamma->green, drm->gamma->blue); 560 | } 561 | if (kmsvnc->debug_enabled) { 562 | for (int i = 0; i < drm->gamma->size; i++) { 563 | fprintf(stderr, "gamma: %05d %05hu %05hu %05hu\n", i, drm->gamma->red[i], drm->gamma->green[i], drm->gamma->blue[i]); 564 | } 565 | } 566 | uint16_t *new_gamma_red = malloc(drm->gamma->size*sizeof(uint16_t)*3); 567 | if (!new_gamma_red) { 568 | fprintf(stderr, "memory allocation error at %s:%d\n", __FILE__, __LINE__); 569 | fprintf(stderr, "not setting gamma.\n"); 570 | } 571 | else { 572 | memset(new_gamma_red, 0, drm->gamma->size*sizeof(uint16_t)*3); 573 | uint16_t *new_gamma_green = new_gamma_red + drm->gamma->size; 574 | uint16_t *new_gamma_blue = new_gamma_red + drm->gamma->size*2; 575 | if (drmModeCrtcSetGamma(drm->drm_master_fd ?: drm->drm_fd, drm->plane->crtc_id, drm->gamma->size, new_gamma_red, new_gamma_green, new_gamma_blue)) perror("Failed to set gamma"); 576 | } 577 | if (new_gamma_red) { 578 | free(new_gamma_red); 579 | new_gamma_red = NULL; 580 | } 581 | } 582 | } 583 | else { 584 | fprintf(stderr, "Did not get a crtc structure, not setting gamma.\n"); 585 | } 586 | if (target_crtc) { 587 | drmModeFreeCrtc(target_crtc); 588 | target_crtc = NULL; 589 | } 590 | } 591 | #endif 592 | 593 | drm->mfb = drmModeGetFB2(drm->drm_fd, drm->plane->fb_id); 594 | if (!drm->mfb) { 595 | KMSVNC_FATAL("Failed to get framebuffer %u: %s\n", drm->plane->fb_id, strerror(errno)); 596 | } 597 | drm->pixfmt_name = drmGetFormatName(drm->mfb->pixel_format); 598 | drm->mod_vendor = drmGetFormatModifierVendor(drm->mfb->modifier); 599 | drm->mod_name = drmGetFormatModifierName(drm->mfb->modifier); 600 | printf("Template framebuffer is %u: %ux%u fourcc:%u mod:%lu flags:%u\n", drm->mfb->fb_id, drm->mfb->width, drm->mfb->height, drm->mfb->pixel_format, drm->mfb->modifier, drm->mfb->flags); 601 | printf("handles %u %u %u %u\n", drm->mfb->handles[0], drm->mfb->handles[1], drm->mfb->handles[2], drm->mfb->handles[3]); 602 | printf("offsets %u %u %u %u\n", drm->mfb->offsets[0], drm->mfb->offsets[1], drm->mfb->offsets[2], drm->mfb->offsets[3]); 603 | printf("pitches %u %u %u %u\n", drm->mfb->pitches[0], drm->mfb->pitches[1], drm->mfb->pitches[2], drm->mfb->pitches[3]); 604 | printf("format %s, modifier %s:%s\n", drm->pixfmt_name, drm->mod_vendor, drm->mod_name); 605 | 606 | if (!drm->mfb->handles[0]) 607 | { 608 | KMSVNC_FATAL("No handle set on framebuffer: maybe you need some additional capabilities?\n"); 609 | } 610 | 611 | drm->mmap_fd = drm->drm_fd; 612 | drm->mmap_size = drm->mfb->width * drm->mfb->height * BYTES_PER_PIXEL; 613 | drm->funcs = malloc(sizeof(struct kmsvnc_drm_funcs)); 614 | if (!drm->funcs) KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 615 | drm->funcs->convert = convert_bgra_to_rgba; 616 | drm->funcs->sync_start = drm_sync_noop; 617 | drm->funcs->sync_end = drm_sync_noop; 618 | 619 | if (drm_vendors()) return 1; 620 | 621 | return 0; 622 | } 623 | 624 | 625 | static int drm_kmsbuf_prime() { 626 | struct kmsvnc_drm_data *drm = kmsvnc->drm; 627 | 628 | int err = drmPrimeHandleToFD(drm->drm_fd, drm->mfb->handles[0], O_RDWR, &drm->prime_fd); 629 | if (err < 0 || drm->prime_fd < 0) 630 | { 631 | KMSVNC_FATAL("Failed to get PRIME fd from framebuffer handle\n"); 632 | } 633 | drm->funcs->sync_start = &drm_sync_start; 634 | drm->funcs->sync_end = &drm_sync_end; 635 | drm->mmap_fd = drm->prime_fd; 636 | return 0; 637 | } 638 | 639 | static int drm_kmsbuf_prime_vaapi() { 640 | struct kmsvnc_drm_data *drm = kmsvnc->drm; 641 | 642 | int err = drmPrimeHandleToFD(drm->drm_fd, drm->mfb->handles[0], O_RDWR, &drm->prime_fd); 643 | if (err < 0 || drm->prime_fd < 0) 644 | { 645 | KMSVNC_FATAL("Failed to get PRIME fd from framebuffer handle\n"); 646 | } 647 | 648 | if (va_init()) return 1; 649 | 650 | drm->mmap_fd = drm->prime_fd; 651 | drm->skip_map = 1; 652 | return 0; 653 | } 654 | 655 | static int drm_kmsbuf_dumb() { 656 | struct kmsvnc_drm_data *drm = kmsvnc->drm; 657 | 658 | struct drm_gem_flink flink; 659 | flink.handle = drm->mfb->handles[0]; 660 | DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_GEM_FLINK, &flink); 661 | 662 | struct drm_gem_open open_arg; 663 | open_arg.name = flink.name; 664 | DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_GEM_OPEN, &open_arg); 665 | 666 | struct drm_mode_map_dumb mreq; 667 | memset(&mreq, 0, sizeof(mreq)); 668 | mreq.handle = open_arg.handle; 669 | DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); 670 | 671 | drm->mmap_size = open_arg.size; 672 | drm->mmap_offset = mreq.offset; 673 | return 0; 674 | } 675 | 676 | int drm_vendors() { 677 | struct kmsvnc_drm_data *drm = kmsvnc->drm; 678 | 679 | char *driver_name; 680 | if (kmsvnc->force_driver) { 681 | printf("using %s instead of %s\n", kmsvnc->force_driver, drm->drm_ver->name); 682 | driver_name = kmsvnc->force_driver; 683 | } 684 | else { 685 | driver_name = drm->drm_ver->name; 686 | } 687 | 688 | if (strcmp(driver_name, "i915") == 0 || strcmp(driver_name, "amdgpu") == 0) 689 | { 690 | if (fourcc_mod_is_vendor(drm->mfb->modifier, INTEL)) { 691 | if (strstr(drm->mod_name, "CCS")) { 692 | printf("warn: intel with CCS modifier detected, please set INTEL_DEBUG=noccs\n"); 693 | } 694 | }; 695 | drm->funcs->convert = &convert_vaapi; 696 | if (drm_kmsbuf_prime_vaapi()) return 1; 697 | } 698 | else if (strcmp(driver_name, "nvidia-drm") == 0) 699 | { 700 | if (check_pixfmt_non_vaapi()) return 1; 701 | printf("warn: nvidia card detected. Currently only x-tiled framebuffer is supported. Performance may suffer.\n"); 702 | if (drm->mfb->modifier != DRM_FORMAT_MOD_NONE && drm->mfb->modifier != DRM_FORMAT_MOD_LINEAR) { 703 | drm->funcs->convert = &convert_nvidia_x_tiled_kmsbuf; 704 | } 705 | if (drm_kmsbuf_dumb()) return 1; 706 | } 707 | else if (strcmp(driver_name, "vmwgfx") == 0 || 708 | strcmp(driver_name, "vboxvideo") == 0 || 709 | strcmp(driver_name, "virtio_gpu") == 0 710 | ) 711 | { 712 | if (check_pixfmt_non_vaapi()) return 1; 713 | if (drm->mfb->modifier != DRM_FORMAT_MOD_NONE && drm->mfb->modifier != DRM_FORMAT_MOD_LINEAR) { 714 | printf("warn: modifier is not LINEAR, please create an issue with your modifier.\n"); 715 | } 716 | // virgl does not work 717 | if (drm_kmsbuf_dumb()) return 1; 718 | } 719 | else if (strcmp(driver_name, "test-prime") == 0) 720 | { 721 | if (check_pixfmt_non_vaapi()) return 1; 722 | if (drm_kmsbuf_prime()) return 1; 723 | } 724 | else if (strcmp(driver_name, "test-map-dumb") == 0) 725 | { 726 | if (check_pixfmt_non_vaapi()) return 1; 727 | if (drm_kmsbuf_dumb()) return 1; 728 | } 729 | else if (strcmp(driver_name, "test-i915-gem") == 0) 730 | { 731 | if (check_pixfmt_non_vaapi()) return 1; 732 | struct drm_gem_flink flink; 733 | flink.handle = drm->mfb->handles[0]; 734 | DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_GEM_FLINK, &flink); 735 | 736 | struct drm_gem_open open_arg; 737 | open_arg.name = flink.name; 738 | DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_GEM_OPEN, &open_arg); 739 | 740 | struct drm_i915_gem_mmap_gtt mmap_arg; 741 | mmap_arg.handle = open_arg.handle; 742 | DRM_IOCTL_MUST(drm->drm_fd, DRM_IOCTL_I915_GEM_MMAP_GTT, &mmap_arg); 743 | drm->mmap_size = open_arg.size; 744 | drm->mmap_offset = mmap_arg.offset; 745 | } 746 | else if (strcmp(driver_name, "test-i915-prime-xtiled") == 0) 747 | { 748 | if (check_pixfmt_non_vaapi()) return 1; 749 | drm->funcs->convert = &convert_intel_x_tiled_kmsbuf; 750 | if (drm_kmsbuf_prime()) return 1; 751 | } 752 | else 753 | { 754 | if (check_pixfmt_non_vaapi()) return 1; 755 | fprintf(stderr, "Untested drm driver, use at your own risk!\n"); 756 | if (drm->mfb->modifier != DRM_FORMAT_MOD_NONE && drm->mfb->modifier != DRM_FORMAT_MOD_LINEAR) { 757 | printf("warn: modifier is not LINEAR, please create an issue with your driver and modifier.\n"); 758 | } 759 | if (drm_kmsbuf_dumb()) return 1; 760 | } 761 | 762 | if (!drm->skip_map && !drm->mapped) 763 | { 764 | printf("mapping with size = %lu, offset = %ld, fd = %d\n", drm->mmap_size, drm->mmap_offset, drm->mmap_fd); 765 | drm->mapped = mmap(NULL, drm->mmap_size, PROT_READ, MAP_SHARED, drm->mmap_fd, drm->mmap_offset); 766 | if (drm->mapped == MAP_FAILED) 767 | { 768 | KMSVNC_FATAL("Failed to mmap: %s\n", strerror(errno)); 769 | } 770 | } 771 | 772 | return 0; 773 | } 774 | -------------------------------------------------------------------------------- /drm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kmsvnc.h" 4 | 5 | #define DRM_IOCTL_MUST(...) do{ int e; if ((e = drmIoctl(__VA_ARGS__))) KMSVNC_FATAL("DRM ioctl error %d on line %d\n", e, __LINE__); } while(0) 6 | #define DRM_IOCTL_MAY(...) do{ int e; if ((e = drmIoctl(__VA_ARGS__))) fprintf(stderr, "DRM ioctl error %d on line %d\n", e, __LINE__); } while(0) 7 | #define DRM_R_IOCTL_MAY(...) do{ int e; if ((e = ioctl(__VA_ARGS__))) fprintf(stderr, "DRM ioctl error %d on line %d\n", e, __LINE__); } while(0) 8 | 9 | 10 | void drm_cleanup(); 11 | int drm_open(); 12 | int drm_vendors(); 13 | int drm_dump_cursor_plane(char **data, int *width, int *height); 14 | -------------------------------------------------------------------------------- /drm_master.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "drm_master.h" 12 | 13 | extern struct kmsvnc_data *kmsvnc; 14 | 15 | 16 | static inline int clone_fd(pid_t pid, int target_fd) { 17 | int pidfd = syscall(SYS_pidfd_open, pid, 0); 18 | if (pidfd <= 0) { 19 | perror("pidfd_open"); 20 | return -1; 21 | } 22 | int cloned = syscall(SYS_pidfd_getfd, pidfd, target_fd, 0); 23 | if (cloned <= 0) { 24 | perror("pidfd_getfd"); 25 | } 26 | close(pidfd); 27 | return cloned; 28 | } 29 | 30 | static inline int cmp_fds(pid_t pid, const char *drm_pth) { 31 | char path[PATH_MAX+1]; 32 | snprintf(path, PATH_MAX+1, "/proc/%d/fd", pid); 33 | 34 | struct dirent **fdlist; 35 | int count = scandir(path, &fdlist, NULL, versionsort); 36 | int ret = -1; 37 | if (count >= 0) { 38 | for (int n = 0; n < count; n++) { 39 | if (ret == -1 && fdlist[n]->d_type == DT_LNK) { 40 | char link_pth[PATH_MAX+1]; 41 | char real_pth[PATH_MAX+1]; 42 | #pragma GCC diagnostic push 43 | #pragma GCC diagnostic ignored "-Wpragmas" 44 | #pragma GCC diagnostic ignored "-Wunknown-warning-option" 45 | #pragma GCC diagnostic ignored "-Wformat-truncation" 46 | snprintf(link_pth, PATH_MAX+1, "%s/%s", path, fdlist[n]->d_name); 47 | #pragma GCC diagnostic pop 48 | memset(real_pth, 0, PATH_MAX+1); 49 | #pragma GCC diagnostic push 50 | #pragma GCC diagnostic ignored "-Wunused-result" 51 | realpath(link_pth, real_pth); 52 | #pragma GCC diagnostic pop 53 | if (!strncmp(real_pth, drm_pth, PATH_MAX)) { 54 | int fd = atoi(fdlist[n]->d_name); 55 | if (fd > 0) { 56 | int cloned = clone_fd(pid, fd); 57 | if (cloned > 0 && drmIsMaster(cloned)) { 58 | ret = cloned; 59 | if (kmsvnc->debug_enabled) { 60 | fprintf(stderr, "found drm master pid=%d, fd=%d, cloned=%d\n", pid, fd, cloned); 61 | } 62 | } 63 | else { 64 | if (cloned > 0) close(cloned); 65 | } 66 | } 67 | } 68 | } 69 | free(fdlist[n]); 70 | fdlist[n] = NULL; 71 | } 72 | free(fdlist); 73 | fdlist = NULL; 74 | } 75 | return ret; 76 | } 77 | 78 | int drm_get_master_fd() { 79 | char drm_pth[PATH_MAX+1]; 80 | memset(drm_pth, 0, PATH_MAX+1); 81 | #pragma GCC diagnostic push 82 | #pragma GCC diagnostic ignored "-Wunused-result" 83 | realpath(kmsvnc->card, drm_pth); 84 | #pragma GCC diagnostic pop 85 | 86 | struct dirent **proclist; 87 | int count = scandir("/proc", &proclist, NULL, versionsort); 88 | int ret = -1; 89 | if (count >= 0) { 90 | for (int n = 0; n < count; n++) { 91 | if (ret == -1 && proclist[n]->d_type == DT_DIR) { 92 | pid_t pid = (pid_t)atoi(proclist[n]->d_name); 93 | if (pid > 0) { 94 | int cloned = cmp_fds(pid, drm_pth); 95 | if (cloned > 0) { 96 | ret = cloned; 97 | } 98 | } 99 | } 100 | free(proclist[n]); 101 | proclist[n] = NULL; 102 | } 103 | free(proclist); 104 | proclist = NULL; 105 | } 106 | else { 107 | perror("open /proc"); 108 | } 109 | return ret; 110 | } 111 | -------------------------------------------------------------------------------- /drm_master.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kmsvnc.h" 4 | 5 | int drm_get_master_fd(); 6 | -------------------------------------------------------------------------------- /input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "input.h" 8 | #include "keymap.h" 9 | 10 | extern struct kmsvnc_data *kmsvnc; 11 | 12 | void uinput_cleanup() 13 | { 14 | if (kmsvnc->input) { 15 | if (kmsvnc->input->uinput_fd > 0){ 16 | INP_IOCTL_MAY(kmsvnc->input->uinput_fd, UI_DEV_DESTROY); 17 | close(kmsvnc->input->uinput_fd); 18 | kmsvnc->input->uinput_fd = 0; 19 | } 20 | if (kmsvnc->input->keystate){ 21 | free(kmsvnc->input->keystate); 22 | kmsvnc->input->keystate = NULL; 23 | } 24 | free(kmsvnc->input); 25 | kmsvnc->input = NULL; 26 | } 27 | } 28 | 29 | static void wake_system_up(); 30 | int uinput_init() 31 | { 32 | struct kmsvnc_input_data *inp = malloc(sizeof(struct kmsvnc_input_data)); 33 | if (!inp) KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 34 | memset(inp, 0, sizeof(struct kmsvnc_input_data)); 35 | kmsvnc->input = inp; 36 | 37 | inp->uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); 38 | if (inp->uinput_fd <= 0) 39 | { 40 | KMSVNC_FATAL("Failed to open uinput\n"); 41 | } 42 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_EVBIT, EV_KEY); 43 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_EVBIT, EV_SYN); 44 | for (int i = 0; i < UINPUT_MAX_KEY; i++) 45 | { 46 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_KEYBIT, i); 47 | } 48 | 49 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_EVBIT, EV_ABS); 50 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_ABSBIT, ABS_X); 51 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_ABSBIT, ABS_Y); 52 | 53 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_EVBIT, EV_REL); 54 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_RELBIT, REL_X); 55 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_RELBIT, REL_Y); 56 | 57 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_KEYBIT, BTN_LEFT); 58 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_KEYBIT, BTN_MIDDLE); 59 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_KEYBIT, BTN_RIGHT); 60 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_EVBIT, EV_REL); 61 | INP_IOCTL_MUST(inp->uinput_fd, UI_SET_RELBIT, REL_WHEEL); 62 | 63 | struct uinput_abs_setup abs; 64 | memset(&abs, 0, sizeof(abs)); 65 | abs.absinfo.maximum = UINPUT_ABS_MAX; 66 | abs.absinfo.minimum = 0; 67 | abs.code = ABS_X; 68 | INP_IOCTL_MUST(inp->uinput_fd, UI_ABS_SETUP, &abs); 69 | abs.code = ABS_Y; 70 | INP_IOCTL_MUST(inp->uinput_fd, UI_ABS_SETUP, &abs); 71 | 72 | struct uinput_setup usetup; 73 | memset(&usetup, 0, sizeof(usetup)); 74 | usetup.id.bustype = BUS_USB; 75 | usetup.id.vendor = 0x0011; 76 | usetup.id.product = 0x4514; 77 | strcpy(usetup.name, "kmsvnc"); 78 | 79 | INP_IOCTL_MUST(inp->uinput_fd, UI_DEV_SETUP, &usetup); 80 | INP_IOCTL_MUST(inp->uinput_fd, UI_DEV_CREATE); 81 | 82 | inp->keystate = malloc(UINPUT_MAX_KEY); 83 | if (!inp->keystate) KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 84 | memset(inp->keystate, 0, UINPUT_MAX_KEY); 85 | 86 | if (kmsvnc->input_wakeup) { 87 | printf("waiting for 1 second for userspace to detect the input devive...\n"); 88 | sleep(1); 89 | wake_system_up(); 90 | printf("waiting for 1 second for mouse input to be processed...\n"); 91 | sleep(1); 92 | } 93 | return 0; 94 | } 95 | 96 | void rfb_key_hook(rfbBool down, rfbKeySym keysym, rfbClientPtr cl) 97 | { 98 | struct key_iter_search search = { 99 | .keysym = keysym, 100 | .keycode = XKB_KEYCODE_INVALID, 101 | .level = 0, 102 | }; 103 | xkb_keymap_key_for_each(kmsvnc->keymap->map, key_iter, &search); 104 | if (search.keycode == XKB_KEYCODE_INVALID) 105 | { 106 | fprintf(stderr, "Keysym %04x not found in our keymap\n", keysym); 107 | return; 108 | } 109 | // printf("key %s, keysym %04x, keycode %u\n", down ? "down" : "up", keysym, search.keycode); 110 | if (search.keycode >= UINPUT_MAX_KEY) 111 | { 112 | fprintf(stderr, "Keycode %d >= %d\n", search.keycode, UINPUT_MAX_KEY); 113 | return; 114 | } 115 | if (down != kmsvnc->input->keystate[search.keycode]) 116 | { 117 | struct input_event ies[] = { 118 | { 119 | .type = EV_KEY, 120 | .code = search.keycode - 8, // magic 121 | .value = down, 122 | .time.tv_sec = 0, 123 | .time.tv_usec = 0, 124 | }, 125 | { 126 | .type = EV_SYN, 127 | .code = SYN_REPORT, 128 | .value = 0, 129 | }, 130 | }; 131 | for (int i = 0; i < KMSVNC_ARRAY_ELEMENTS(ies); i++) 132 | { 133 | KMSVNC_WRITE_MAY(kmsvnc->input->uinput_fd, &ies[i], sizeof(ies[0])); 134 | } 135 | 136 | kmsvnc->input->keystate[search.keycode] = down; 137 | } 138 | } 139 | 140 | void rfb_ptr_hook(int mask, int screen_x, int screen_y, rfbClientPtr cl) 141 | { 142 | // printf("pointer to %d, %d\n", screen_x, screen_y); 143 | float global_x = (float)(screen_x + kmsvnc->input_offx); 144 | float global_y = (float)(screen_y + kmsvnc->input_offy); 145 | int touch_x = round(global_x / (kmsvnc->input_width ?: kmsvnc->drm->mfb->width) * UINPUT_ABS_MAX); 146 | int touch_y = round(global_y / (kmsvnc->input_height ?: kmsvnc->drm->mfb->height) * UINPUT_ABS_MAX); 147 | struct input_event ies1[] = { 148 | { 149 | .type = EV_ABS, 150 | .code = ABS_X, 151 | .value = touch_x, 152 | }, 153 | { 154 | .type = EV_ABS, 155 | .code = ABS_Y, 156 | .value = touch_y, 157 | }, 158 | { 159 | .type = EV_KEY, 160 | .code = BTN_LEFT, 161 | .value = !!(mask & 0b1)}, 162 | { 163 | .type = EV_KEY, 164 | .code = BTN_MIDDLE, 165 | .value = !!(mask & 0b10)}, 166 | { 167 | .type = EV_KEY, 168 | .code = BTN_RIGHT, 169 | .value = !!(mask & 0b100)}, 170 | { 171 | .type = EV_SYN, 172 | .code = SYN_REPORT, 173 | .value = 0, 174 | }, 175 | }; 176 | for (int i = 0; i < KMSVNC_ARRAY_ELEMENTS(ies1); i++) 177 | { 178 | KMSVNC_WRITE_MAY(kmsvnc->input->uinput_fd, &ies1[i], sizeof(ies1[0])); 179 | } 180 | if (mask & 0b11000) 181 | { 182 | struct input_event ies2[] = { 183 | { 184 | .type = EV_REL, 185 | .code = REL_WHEEL, 186 | .value = mask & 0b1000 ? 1 : -1, 187 | }, 188 | { 189 | .type = EV_SYN, 190 | .code = SYN_REPORT, 191 | .value = 0, 192 | }, 193 | }; 194 | for (int i = 0; i < KMSVNC_ARRAY_ELEMENTS(ies2); i++) 195 | { 196 | KMSVNC_WRITE_MAY(kmsvnc->input->uinput_fd, &ies2[i], sizeof(ies2[0])); 197 | } 198 | } 199 | } 200 | 201 | static void wake_system_up() 202 | { 203 | struct input_event ies1[] = { 204 | { 205 | .type = EV_REL, 206 | .code = REL_X, 207 | .value = 1, 208 | }, 209 | { 210 | .type = EV_SYN, 211 | .code = SYN_REPORT, 212 | .value = 0, 213 | }, 214 | { 215 | .type = EV_REL, 216 | .code = REL_X, 217 | .value = -1, 218 | }, 219 | { 220 | .type = EV_SYN, 221 | .code = SYN_REPORT, 222 | .value = 0, 223 | }, 224 | }; 225 | for (int i = 0; i < KMSVNC_ARRAY_ELEMENTS(ies1); i++) 226 | { 227 | KMSVNC_WRITE_MAY(kmsvnc->input->uinput_fd, &ies1[i], sizeof(ies1[0])); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "kmsvnc.h" 6 | 7 | #define UINPUT_ABS_MAX INT16_MAX 8 | #define UINPUT_MAX_KEY 256 9 | 10 | #define INP_IOCTL_MUST(...) do{ int e; if ((e = ioctl(__VA_ARGS__))) KMSVNC_FATAL("uinput ioctl error %d on line %d\n", e, __LINE__); } while(0) 11 | #define INP_IOCTL_MAY(...) do{ int e; if ((e = ioctl(__VA_ARGS__))) fprintf(stderr, "uinput ioctl error %d on line %d\n", e, __LINE__); } while(0) 12 | 13 | void uinput_cleanup(); 14 | int uinput_init(); 15 | void rfb_key_hook(rfbBool down, rfbKeySym keysym, rfbClientPtr cl); 16 | void rfb_ptr_hook(int mask, int screen_x, int screen_y, rfbClientPtr cl); 17 | -------------------------------------------------------------------------------- /keymap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "keymap.h" 7 | 8 | extern struct kmsvnc_data *kmsvnc; 9 | 10 | void xkb_cleanup() { 11 | if (kmsvnc->keymap) { 12 | if (kmsvnc->keymap->map) { 13 | xkb_keymap_unref(kmsvnc->keymap->map); 14 | kmsvnc->keymap->map = NULL; 15 | } 16 | if (kmsvnc->keymap->ctx) { 17 | xkb_context_unref(kmsvnc->keymap->ctx); 18 | kmsvnc->keymap->ctx = NULL; 19 | } 20 | free(kmsvnc->keymap); 21 | kmsvnc->keymap = NULL; 22 | } 23 | } 24 | 25 | int xkb_init() 26 | { 27 | struct kmsvnc_keymap_data *xkb = malloc(sizeof(struct kmsvnc_keymap_data)); 28 | if (!xkb) KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 29 | memset(xkb, 0, sizeof(struct kmsvnc_keymap_data)); 30 | kmsvnc->keymap = xkb; 31 | 32 | xkb->ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 33 | if (xkb->ctx == NULL) 34 | { 35 | KMSVNC_FATAL("Failed to create XKB context\n"); 36 | } 37 | struct xkb_rule_names names = { 38 | .rules = NULL, 39 | .model = NULL, 40 | .layout = NULL, 41 | .variant = NULL, 42 | .options = NULL, 43 | }; 44 | xkb->map = xkb_keymap_new_from_names(xkb->ctx, &names, 0); 45 | if (xkb->map == NULL) 46 | { 47 | KMSVNC_FATAL("Failed to create XKB keymap\n"); 48 | } 49 | // printf("xkb: keymap string\n%s\n", xkb_keymap_get_as_string(xkb->map, XKB_KEYMAP_USE_ORIGINAL_FORMAT)); 50 | return 0; 51 | } 52 | 53 | 54 | void key_iter(struct xkb_keymap *xkb, xkb_keycode_t key, void *data) 55 | { 56 | struct key_iter_search *search = data; 57 | if (search->keycode != XKB_KEYCODE_INVALID) 58 | { 59 | return; // We are done 60 | } 61 | xkb_level_index_t num_levels = xkb_keymap_num_levels_for_key(xkb, key, 0); 62 | for (xkb_level_index_t i = 0; i < num_levels; i++) 63 | { 64 | const xkb_keysym_t *syms; 65 | int num_syms = xkb_keymap_key_get_syms_by_level(xkb, key, 0, i, &syms); 66 | for (int k = 0; k < num_syms; k++) 67 | { 68 | if (syms[k] == search->keysym) 69 | { 70 | search->keycode = key; 71 | search->level = i; 72 | goto end; 73 | } 74 | } 75 | } 76 | end: 77 | return; 78 | } 79 | -------------------------------------------------------------------------------- /keymap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kmsvnc.h" 4 | 5 | void xkb_cleanup(); 6 | int xkb_init(); 7 | void key_iter(struct xkb_keymap *xkb, xkb_keycode_t key, void *data); 8 | -------------------------------------------------------------------------------- /kmsvnc.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "kmsvnc.h" 14 | #include "keymap.h" 15 | #include "input.h" 16 | #include "drm.h" 17 | #include "va.h" 18 | 19 | struct kmsvnc_data *kmsvnc = NULL; 20 | 21 | #define NS_IN_S 1000000000 22 | 23 | static void between_frames() 24 | { 25 | static struct timespec now = {0, 0}, then = {0, 0}, tmp = {0, 0}; 26 | 27 | clock_gettime(CLOCK_MONOTONIC, &now); 28 | memcpy((char *)&then, (char *)&tmp, sizeof(struct timespec)); 29 | tmp.tv_nsec += kmsvnc->vnc_opt->sleep_ns; 30 | if (tmp.tv_nsec >= NS_IN_S) 31 | { 32 | tmp.tv_sec++; 33 | tmp.tv_nsec %= NS_IN_S; 34 | } 35 | if (now.tv_sec < tmp.tv_sec || (now.tv_sec == tmp.tv_sec && now.tv_nsec < tmp.tv_nsec)) 36 | { 37 | then.tv_sec = tmp.tv_sec - now.tv_sec; 38 | then.tv_nsec = tmp.tv_nsec - now.tv_nsec; 39 | if (then.tv_nsec < 0) 40 | { 41 | then.tv_sec--; 42 | then.tv_nsec += NS_IN_S; 43 | } 44 | nanosleep(&then, &then); 45 | } 46 | memcpy((char *)&now, (char *)&then, sizeof(struct timespec)); 47 | } 48 | 49 | static void update_screen_buf(char* to, char *from, int width, int height) { 50 | uint64_t *double_pix_from = (uint64_t *)from; 51 | uint64_t *double_pix_to = (uint64_t *)to; 52 | int min_x = INT32_MAX; 53 | int min_y = INT32_MAX; 54 | int max_x = -1; 55 | int max_y = -1; 56 | if (!kmsvnc->vnc_opt->disable_cmpfb && width % 2 == 0) { 57 | for (int y = 0; y < height; y++) { 58 | for (int x = 0; x < width; x+=2) { 59 | if (*double_pix_from != *double_pix_to) { 60 | if (x < min_x) { 61 | min_x = x; 62 | } 63 | if (x > max_x) { 64 | max_x = x; 65 | } 66 | if (y < min_y) { 67 | min_y = y; 68 | } 69 | if (y > max_y) { 70 | max_y = y; 71 | } 72 | } 73 | double_pix_from ++; 74 | double_pix_to ++; 75 | } 76 | } 77 | } 78 | else { 79 | memcpy(to, from, width * height * BYTES_PER_PIXEL); 80 | rfbMarkRectAsModified(kmsvnc->server, 0, 0, width, height); 81 | return; 82 | } 83 | max_x = max_x < 0 ? 0 : max_x; 84 | max_y = max_y < 0 ? 0 : max_y; 85 | min_x = min_x > width ? 0 : min_x; 86 | min_y = min_y > height ? 0 : min_y; 87 | 88 | //printf("dirty: %d, %d, %d, %d\n", min_x, min_y, max_x, max_y); 89 | if (max_x || max_y || min_x || min_y) { 90 | memcpy(to, from, width * height * BYTES_PER_PIXEL); 91 | rfbMarkRectAsModified(kmsvnc->server, min_x, min_y, max_x + 2, max_y + 1); 92 | } 93 | } 94 | 95 | static inline void update_vnc_cursor(char *data, int width, int height) { 96 | uint8_t r, g, b, a; 97 | #define CURSOR_MIN_A 160 // ~63% 98 | int min_x = width; 99 | int max_x = -1; 100 | int min_y = height; 101 | int max_y = -1; 102 | int x, y; 103 | 104 | for (int i = 0; i < width * height * BYTES_PER_PIXEL; i += BYTES_PER_PIXEL) { 105 | uint32_t pixdata = htonl(*((uint32_t*)(data + i))); 106 | //r = (pixdata & 0xff000000u) >> 24; 107 | //g = (pixdata & 0x00ff0000u) >> 16; 108 | //b = (pixdata & 0x0000ff00u) >> 8; 109 | a = pixdata & 0xff; 110 | if (a > CURSOR_MIN_A) { 111 | x = (i / BYTES_PER_PIXEL) % width; 112 | y = (i / BYTES_PER_PIXEL) / width; 113 | if (x < min_x) min_x = x; 114 | if (y < min_y) min_y = y; 115 | if (x > max_x) max_x = x; 116 | if (y > max_y) max_y = y; 117 | } 118 | } 119 | if (min_x > max_x || min_y > max_y) { 120 | // no cursor detected 121 | return; 122 | } 123 | int rwidth = max_x - min_x + 1; 124 | int rheight = max_y - min_y + 1; 125 | if (kmsvnc->cursor_bitmap_len < rwidth * rheight * BYTES_PER_PIXEL) 126 | { 127 | if (kmsvnc->cursor_bitmap) 128 | free(kmsvnc->cursor_bitmap); 129 | kmsvnc->cursor_bitmap = malloc(rwidth * rheight * BYTES_PER_PIXEL); 130 | if (!kmsvnc->cursor_bitmap) return; 131 | kmsvnc->cursor_bitmap_len = rwidth * rheight * BYTES_PER_PIXEL; 132 | } 133 | unsigned char *rich_source = malloc(rwidth * rheight * BYTES_PER_PIXEL); 134 | if (!rich_source) return; 135 | char *maskString = malloc(rwidth * rheight); 136 | if (!maskString) { 137 | free(rich_source); 138 | return; 139 | } 140 | memset(maskString, ' ', rwidth * rheight); 141 | for (int i = 0; i < rwidth; i++) { 142 | for (int j = 0; j < rheight; j++) { 143 | int t = (i + j * rwidth) * BYTES_PER_PIXEL; 144 | int s = ((i+min_x) + (j+min_y) * width) * BYTES_PER_PIXEL; 145 | *((uint32_t*)(rich_source + t)) = *((uint32_t*)(data + s)); 146 | if ((uint8_t)*(rich_source + t + 3) > CURSOR_MIN_A) { 147 | maskString[i + j * rwidth] = 'x'; 148 | } 149 | } 150 | } 151 | 152 | if ((kmsvnc->server->cursor->width != rwidth || kmsvnc->server->cursor->height != rheight) || memcmp(kmsvnc->cursor_bitmap, rich_source, rwidth * rheight * BYTES_PER_PIXEL)) { 153 | KMSVNC_DEBUG("cursor update %dx%d\n", rwidth, rheight); 154 | memcpy(kmsvnc->cursor_bitmap, rich_source, kmsvnc->cursor_bitmap_len); 155 | char *cursorString = malloc(rwidth * rheight); 156 | if (!cursorString) { 157 | free(rich_source); 158 | free(maskString); 159 | return; 160 | } 161 | 162 | memset(cursorString, 'x', rwidth * rheight); 163 | 164 | rfbCursorPtr cursor = rfbMakeXCursor(rwidth, rheight, cursorString, maskString); 165 | free(cursorString); 166 | cursor->richSource = rich_source; 167 | cursor->cleanupRichSource = TRUE; 168 | cursor->xhot = 0; 169 | cursor->yhot = 0; 170 | rfbSetCursor(kmsvnc->server, cursor); 171 | } 172 | else { 173 | free(rich_source); 174 | free(maskString); 175 | } 176 | } 177 | 178 | static void cleanup() { 179 | if (kmsvnc->keymap) { 180 | xkb_cleanup(); 181 | } 182 | if (kmsvnc->input) { 183 | uinput_cleanup(); 184 | } 185 | if (kmsvnc->drm) { 186 | drm_cleanup(); 187 | } 188 | if (kmsvnc->va) { 189 | va_cleanup(); 190 | } 191 | if (kmsvnc) { 192 | if (kmsvnc->vnc_opt) { 193 | free(kmsvnc->vnc_opt); 194 | kmsvnc->vnc_opt = NULL; 195 | } 196 | if (kmsvnc->buf1) { 197 | free(kmsvnc->buf1); 198 | kmsvnc->buf1 = NULL; 199 | } 200 | if (kmsvnc->buf) { 201 | free(kmsvnc->buf); 202 | kmsvnc->buf = NULL; 203 | } 204 | if (kmsvnc->cursor_bitmap) { 205 | free(kmsvnc->cursor_bitmap); 206 | kmsvnc->cursor_bitmap = NULL; 207 | } 208 | kmsvnc->cursor_bitmap_len = 0; 209 | free(kmsvnc); 210 | kmsvnc = NULL; 211 | } 212 | } 213 | 214 | void signal_handler_noop(int signum){} 215 | void signal_handler(int signum){ 216 | if (kmsvnc->shutdown) { 217 | return; 218 | } 219 | kmsvnc->shutdown = 1; 220 | if (kmsvnc->server) { 221 | rfbShutdownServer(kmsvnc->server,TRUE); 222 | } 223 | } 224 | 225 | static struct argp_option kmsvnc_main_options[] = { 226 | {"device", 'd', "/dev/dri/cardX", 0, "DRM device"}, 227 | {"source-plane", 0xfefc, "0", 0, "Use specific plane"}, 228 | {"source-crtc", 0xfefd, "0", 0, "Use specific crtc (to list all crtcs and planes, set this to -1)"}, 229 | {"force-driver", 0xfefe, "i915", 0, "force a certain driver (for debugging)"}, 230 | {"bind", 'b', "0.0.0.0", 0, "Listen on (ipv4 address)"}, 231 | {"bind6", 0xfeff, "::", 0, "Listen on (ipv6 address)"}, 232 | {"port", 'p', "5900", 0, "Listen port"}, 233 | {"disable-ipv6", '4', 0, OPTION_ARG_OPTIONAL, "Disable ipv6"}, 234 | {"fps", 0xff00, "30", 0, "Target frames per second"}, 235 | {"disable-always-shared", 0xff01, 0, OPTION_ARG_OPTIONAL, "Do not always treat incoming connections as shared"}, 236 | {"disable-compare-fb", 0xff02, 0, OPTION_ARG_OPTIONAL, "Do not compare pixels"}, 237 | {"capture-cursor", 'c', 0, OPTION_ARG_OPTIONAL, "Capture mouse cursor"}, 238 | {"capture-raw-fb", 0xff03, "/tmp/rawfb.bin", 0, "Capture RAW framebuffer instead of starting the vnc server (for debugging)"}, 239 | {"va-derive", 0xff04, "off", 0, "Enable derive with vaapi"}, 240 | {"debug", 0xff05, 0, OPTION_ARG_OPTIONAL, "Print debug message"}, 241 | {"input-width", 0xff06, "0", 0, "Explicitly set input width, normally this is inferred from screen width on a single display system"}, 242 | {"input-height", 0xff07, "0", 0, "Explicitly set input height"}, 243 | {"input-offx", 0xff08, "0", 0, "Set input offset of x axis on a multi display system"}, 244 | {"input-offy", 0xff09, "0", 0, "Set input offset of y axis on a multi display system"}, 245 | #ifndef DISABLE_KMSVNC_SCREEN_BLANK 246 | {"screen-blank", 0xff0a, 0, OPTION_ARG_OPTIONAL, "Blank screen with gamma set on crtc"}, 247 | {"screen-blank-restore-linear", 0xff0b, 0, OPTION_ARG_OPTIONAL, "Restore linear values on exit in case of messed up gamma"}, 248 | #endif 249 | {"va-byteorder-swap", 0xff0c, 0, OPTION_ARG_OPTIONAL, "Force swap vaapi image rgb byteorder"}, 250 | {"wakeup", 'w', 0, OPTION_ARG_OPTIONAL, "Move mouse to wake the system up before start"}, 251 | {"disable-input", 'i', 0, OPTION_ARG_OPTIONAL, "Disable uinput"}, 252 | {"desktop-name", 'n', "kmsvnc", 0, "Specify vnc desktop name"}, 253 | {"password-file", 0xff0d, "", 0, "File containing password (max 8 characters)"}, 254 | {0} 255 | }; 256 | 257 | static error_t parse_opt(int key, char *arg, struct argp_state *state) { 258 | int *arg_cout = state->input; 259 | 260 | switch (key) { 261 | case 'd': 262 | kmsvnc->card = arg; 263 | break; 264 | case 0xfefc: 265 | kmsvnc->source_plane = atoi(arg); 266 | break; 267 | case 0xfefd: 268 | kmsvnc->source_crtc = atoi(arg); 269 | break; 270 | case 0xfefe: 271 | kmsvnc->force_driver = arg; 272 | break; 273 | case 'b': 274 | if (!inet_aton(arg, kmsvnc->vnc_opt->bind)) { 275 | argp_error(state, "invalid ipv4 address %s", arg); 276 | } 277 | break; 278 | case 0xfeff: 279 | kmsvnc->vnc_opt->bind6 = arg; 280 | break; 281 | case 'p': 282 | { 283 | int port = atoi(arg); 284 | if (port > 0 && port < 65536) { 285 | kmsvnc->vnc_opt->port = port; 286 | } 287 | else { 288 | argp_error(state, "invalid port %s", arg); 289 | } 290 | } 291 | break; 292 | case '4': 293 | kmsvnc->vnc_opt->disable_ipv6 = 1; 294 | break; 295 | case 0xff00: 296 | { 297 | int fps = atoi(arg); 298 | if (fps > 0 && fps < 1000) { 299 | kmsvnc->vnc_opt->sleep_ns = NS_IN_S / fps; 300 | } 301 | else { 302 | argp_error(state, "invalid fps %s", arg); 303 | } 304 | } 305 | break; 306 | case 0xff01: 307 | kmsvnc->vnc_opt->always_shared = 0; 308 | break; 309 | case 0xff02: 310 | kmsvnc->vnc_opt->disable_cmpfb = 1; 311 | break; 312 | case 'c': 313 | kmsvnc->capture_cursor = 1; 314 | break; 315 | case 0xff03: 316 | kmsvnc->debug_capture_fb = arg; 317 | kmsvnc->disable_input = 1; 318 | break; 319 | case 0xff04: 320 | if (!strcmp("on", arg) || !strcmp("y", arg) || !strcmp("yes", arg) || !strcmp("1", arg)) { 321 | kmsvnc->va_derive_enabled = 1; 322 | } 323 | else { 324 | kmsvnc->va_derive_enabled = 0; 325 | } 326 | break; 327 | case 0xff05: 328 | kmsvnc->debug_enabled = 1; 329 | break; 330 | case 0xff06: 331 | { 332 | int width = atoi(arg); 333 | if (width > 0) { 334 | kmsvnc->input_width = width; 335 | } 336 | } 337 | break; 338 | case 0xff07: 339 | { 340 | int height = atoi(arg); 341 | if (height > 0) { 342 | kmsvnc->input_height = height; 343 | } 344 | } 345 | break; 346 | case 0xff08: 347 | { 348 | int offset_x = atoi(arg); 349 | if (offset_x > 0) { 350 | kmsvnc->input_offx = offset_x; 351 | } 352 | } 353 | break; 354 | case 0xff09: 355 | { 356 | int offset_y = atoi(arg); 357 | if (offset_y > 0) { 358 | kmsvnc->input_offy = offset_y; 359 | } 360 | } 361 | break; 362 | case 0xff0a: 363 | kmsvnc->screen_blank = 1; 364 | break; 365 | case 0xff0b: 366 | kmsvnc->screen_blank_restore = 1; 367 | break; 368 | case 0xff0c: 369 | kmsvnc->va_byteorder_swap = 1; 370 | break; 371 | case 0xff0d: 372 | kmsvnc->vnc_opt->password_file = arg; 373 | break; 374 | case 'w': 375 | kmsvnc->input_wakeup = 1; 376 | break; 377 | case 'i': 378 | kmsvnc->disable_input = 1; 379 | break; 380 | case 'n': 381 | kmsvnc->vnc_opt->desktop_name = arg; 382 | break; 383 | case ARGP_KEY_ARG: 384 | return ARGP_ERR_UNKNOWN; 385 | default: 386 | return ARGP_ERR_UNKNOWN; 387 | } 388 | return 0; 389 | } 390 | 391 | int main(int argc, char **argv) 392 | { 393 | kmsvnc = malloc(sizeof(struct kmsvnc_data)); 394 | if (!kmsvnc) KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 395 | memset(kmsvnc, 0, sizeof(struct kmsvnc_data)); 396 | 397 | struct vnc_opt *vncopt = malloc(sizeof(struct vnc_opt)); 398 | if (!vncopt) { 399 | free(kmsvnc); 400 | KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 401 | } 402 | memset(vncopt, 0, sizeof(struct vnc_opt)); 403 | 404 | kmsvnc->vnc_opt = vncopt; 405 | 406 | #define DEVICE_EXAMPLE_MAX_SIZE 15 407 | #define DEVICE_EXAMPLE_FALLBACK "/dev/dri/card0" 408 | static char device_example[DEVICE_EXAMPLE_MAX_SIZE] = DEVICE_EXAMPLE_FALLBACK; 409 | kmsvnc->card = device_example; 410 | kmsvnc->va_derive_enabled = -1; 411 | kmsvnc->vnc_opt->bind = &(struct in_addr){0}; 412 | kmsvnc->vnc_opt->always_shared = 1; 413 | kmsvnc->vnc_opt->port = 5900; 414 | kmsvnc->vnc_opt->sleep_ns = NS_IN_S / 30; 415 | kmsvnc->vnc_opt->desktop_name = "kmsvnc"; 416 | 417 | static char *args_doc = ""; 418 | static char *doc = "kmsvnc -- vncserver for DRM/KMS capable GNU/Linux devices"; 419 | 420 | struct argp argp = {kmsvnc_main_options, parse_opt, args_doc, doc}; 421 | argp_parse(&argp, argc, argv, 0, 0, NULL); 422 | 423 | if (kmsvnc->card == device_example) { 424 | for (int i = 0; i < 10; i++) { 425 | snprintf(kmsvnc->card, DEVICE_EXAMPLE_MAX_SIZE, "/dev/dri/card%d", i); 426 | if (!access(kmsvnc->card, F_OK)) { 427 | break; 428 | } 429 | else { 430 | snprintf(kmsvnc->card, DEVICE_EXAMPLE_MAX_SIZE, DEVICE_EXAMPLE_FALLBACK); 431 | } 432 | } 433 | } 434 | 435 | if (!kmsvnc->disable_input) { 436 | const char* XKB_DEFAULT_LAYOUT = getenv("XKB_DEFAULT_LAYOUT"); 437 | if (!XKB_DEFAULT_LAYOUT || strcmp(XKB_DEFAULT_LAYOUT, "") == 0) { 438 | printf("No keyboard layout set from environment variables, use US layout by default\n"); 439 | printf("See https://xkbcommon.org/doc/current/structxkb__rule__names.html\n"); 440 | setenv("XKB_DEFAULT_LAYOUT", "us", 1); 441 | } 442 | 443 | if (xkb_init()) { 444 | cleanup(); 445 | return 1; 446 | } 447 | if (uinput_init()) { 448 | cleanup(); 449 | return 1; 450 | } 451 | } 452 | if (drm_open()) { 453 | cleanup(); 454 | return 1; 455 | } 456 | 457 | if (kmsvnc->debug_capture_fb) { 458 | int wfd = open(kmsvnc->debug_capture_fb, O_WRONLY | O_CREAT, 00644); 459 | int max_size = 0; 460 | for (int i = 0; i < 4; i++) { 461 | int size = kmsvnc->drm->mfb->offsets[i] + kmsvnc->drm->mfb->height * kmsvnc->drm->mfb->pitches[i]; 462 | if (size > max_size) max_size = size; 463 | } 464 | printf("attempt to write %d bytes\n", max_size); 465 | if (wfd > 0) { 466 | if (kmsvnc->va) { 467 | if (!kmsvnc->drm->mapped) kmsvnc->drm->mapped = malloc(max_size); 468 | if (!kmsvnc->drm->mapped) { 469 | cleanup(); 470 | KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 471 | } 472 | va_hwframe_to_vaapi(kmsvnc->drm->mapped); 473 | } 474 | KMSVNC_WRITE_MAY(wfd, kmsvnc->drm->mapped, (ssize_t)max_size); 475 | fsync(wfd); 476 | printf("wrote raw frame buffer to %s\n", kmsvnc->debug_capture_fb); 477 | } 478 | else { 479 | fprintf(stderr, "open file %s failed, %s\n", kmsvnc->debug_capture_fb, strerror(errno)); 480 | } 481 | if (kmsvnc->screen_blank) { 482 | sigset_t signal_set; 483 | int sig; 484 | sigemptyset(&signal_set); 485 | signal(SIGHUP, &signal_handler_noop); 486 | signal(SIGINT, &signal_handler_noop); 487 | signal(SIGTERM, &signal_handler_noop); 488 | sigaddset(&signal_set, SIGHUP); 489 | sigaddset(&signal_set, SIGINT); 490 | sigaddset(&signal_set, SIGTERM); 491 | fprintf(stderr, "blanking screen...\n"); 492 | sigwait(&signal_set, &sig); 493 | fprintf(stderr, "got sig %d\n", sig); 494 | } 495 | cleanup(); 496 | return 0; 497 | } 498 | 499 | size_t buflen = kmsvnc->drm->mfb->width * kmsvnc->drm->mfb->height * BYTES_PER_PIXEL; 500 | kmsvnc->buf = malloc(buflen); 501 | if (!kmsvnc->buf) { 502 | cleanup(); 503 | KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 504 | } 505 | memset(kmsvnc->buf, 0, buflen); 506 | kmsvnc->buf1 = malloc(buflen); 507 | if (!kmsvnc->buf1) { 508 | cleanup(); 509 | KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 510 | } 511 | memset(kmsvnc->buf1, 0, buflen); 512 | 513 | signal(SIGHUP, &signal_handler); 514 | signal(SIGINT, &signal_handler); 515 | signal(SIGTERM, &signal_handler); 516 | 517 | kmsvnc->server = rfbGetScreen(0, NULL, kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height, 8, 3, 4); 518 | if (!kmsvnc->server) { 519 | cleanup(); 520 | return 1; 521 | } 522 | kmsvnc->server->desktopName = kmsvnc->vnc_opt->desktop_name; 523 | kmsvnc->server->frameBuffer = kmsvnc->buf; 524 | kmsvnc->server->port = kmsvnc->vnc_opt->port; 525 | kmsvnc->server->listenInterface = kmsvnc->vnc_opt->bind->s_addr; 526 | kmsvnc->server->ipv6port = kmsvnc->vnc_opt->disable_ipv6 ? 0 : kmsvnc->vnc_opt->port; 527 | kmsvnc->server->listen6Interface = kmsvnc->vnc_opt->bind6; 528 | kmsvnc->server->alwaysShared = kmsvnc->vnc_opt->always_shared; 529 | if (!kmsvnc->disable_input) { 530 | kmsvnc->server->kbdAddEvent = rfb_key_hook; 531 | kmsvnc->server->ptrAddEvent = rfb_ptr_hook; 532 | } 533 | if (kmsvnc->vnc_opt->password_file) { 534 | static char password[9] = ""; 535 | static const char* passwords[2] = { password, 0 }; 536 | FILE *password_file = fopen(kmsvnc->vnc_opt->password_file, "r"); 537 | 538 | if (password_file) { 539 | fgets(password, sizeof(password), password_file); 540 | fclose(password_file); 541 | } 542 | 543 | if (*password) password[strcspn(password, "\n")] = '\0'; 544 | 545 | if (*password) { 546 | kmsvnc->server->authPasswdData = passwords; 547 | kmsvnc->server->passwordCheck = rfbCheckPasswordByList; 548 | } 549 | } 550 | rfbInitServer(kmsvnc->server); 551 | rfbRunEventLoop(kmsvnc->server, -1, TRUE); 552 | int cursor_frame = 0; 553 | while (rfbIsActive(kmsvnc->server)) 554 | { 555 | between_frames(); 556 | if (kmsvnc->server->clientHead) 557 | { 558 | kmsvnc->drm->funcs->sync_start(kmsvnc->drm->prime_fd); 559 | kmsvnc->drm->funcs->convert(kmsvnc->drm->mapped, kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height, kmsvnc->buf1); 560 | kmsvnc->drm->funcs->sync_end(kmsvnc->drm->prime_fd); 561 | update_screen_buf(kmsvnc->buf, kmsvnc->buf1, kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height); 562 | if (kmsvnc->capture_cursor) { 563 | cursor_frame++; 564 | cursor_frame %= CURSOR_FRAMESKIP; 565 | if (!cursor_frame) { 566 | char *data = NULL; 567 | int width, height; 568 | int err = drm_dump_cursor_plane(&data, &width, &height); 569 | if (!err && data) { 570 | update_vnc_cursor(data, width, height); 571 | } 572 | } 573 | } 574 | } 575 | } 576 | cleanup(); 577 | return 0; 578 | } 579 | -------------------------------------------------------------------------------- /kmsvnc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | #define BYTES_PER_PIXEL 4 16 | #define CURSOR_FRAMESKIP 15 17 | 18 | struct vnc_opt 19 | { 20 | int port; 21 | struct in_addr *bind; 22 | char *bind6; 23 | char disable_ipv6; 24 | int sleep_ns; 25 | char always_shared; 26 | char disable_cmpfb; 27 | char *desktop_name; 28 | char *password_file; 29 | }; 30 | 31 | struct kmsvnc_data 32 | { 33 | char *debug_capture_fb; 34 | char *card; 35 | char *force_driver; 36 | struct vnc_opt *vnc_opt; 37 | char input_wakeup; 38 | char disable_input; 39 | int va_derive_enabled; 40 | char debug_enabled; 41 | int source_plane; 42 | int source_crtc; 43 | int input_width; 44 | int input_height; 45 | int input_offx; 46 | int input_offy; 47 | char screen_blank; 48 | char screen_blank_restore; 49 | char va_byteorder_swap; 50 | struct kmsvnc_drm_data *drm; 51 | struct kmsvnc_input_data *input; 52 | struct kmsvnc_keymap_data *keymap; 53 | struct kmsvnc_va_data *va; 54 | rfbScreenInfoPtr server; 55 | char shutdown; 56 | char capture_cursor; 57 | char *cursor_bitmap; 58 | int cursor_bitmap_len; 59 | char *buf; 60 | char *buf1; 61 | }; 62 | 63 | 64 | 65 | struct key_iter_search 66 | { 67 | xkb_keysym_t keysym; 68 | 69 | xkb_keycode_t keycode; 70 | xkb_level_index_t level; 71 | }; 72 | 73 | struct kmsvnc_keymap_data 74 | { 75 | struct xkb_context *ctx; 76 | struct xkb_keymap *map; 77 | }; 78 | 79 | 80 | struct kmsvnc_input_data { 81 | int uinput_fd; 82 | char *keystate; 83 | }; 84 | 85 | 86 | struct kmsvnc_drm_funcs 87 | { 88 | void (*sync_start)(int); 89 | void (*sync_end)(int); 90 | void (*convert)(const char *, int, int, char *); 91 | }; 92 | 93 | struct kmsvnc_drm_gamma_data 94 | { 95 | uint32_t size; 96 | uint16_t *red; 97 | uint16_t *green; 98 | uint16_t *blue; 99 | }; 100 | 101 | struct kmsvnc_drm_data 102 | { 103 | int drm_fd; 104 | int drm_master_fd; 105 | drmVersionPtr drm_ver; 106 | int prime_fd; 107 | drmModePlane *plane; 108 | drmModePlane *cursor_plane; 109 | drmModePlaneRes *plane_res; 110 | drmModeFB2 *mfb; 111 | drmModeFB2 *cursor_mfb; 112 | uint32_t plane_id; 113 | int mmap_fd; 114 | size_t mmap_size; 115 | off_t mmap_offset; 116 | char *mapped; 117 | char *cursor_mapped; 118 | size_t cursor_mmap_size; 119 | char skip_map; 120 | struct kmsvnc_drm_funcs *funcs; 121 | char *pixfmt_name; 122 | char *mod_vendor; 123 | char *mod_name; 124 | char *kms_convert_buf; 125 | size_t kms_convert_buf_len; 126 | char *kms_cpy_tmp_buf; 127 | size_t kms_cpy_tmp_buf_len; 128 | char *kms_cursor_buf; 129 | size_t kms_cursor_buf_len; 130 | struct kmsvnc_drm_gamma_data *gamma; 131 | }; 132 | 133 | struct kmsvnc_va_data 134 | { 135 | VADisplay dpy; 136 | int render_node_fd; 137 | VASurfaceID surface_id; 138 | VAImage *image; 139 | char *imgbuf; 140 | char derive_enabled; 141 | VAImageFormat* img_fmts; 142 | int img_fmt_count; 143 | VAImageFormat* selected_fmt; 144 | const char *vendor_string; 145 | }; 146 | 147 | #define KMSVNC_FATAL(...) do{ fprintf(stderr, __VA_ARGS__); return 1; } while(0) 148 | #define KMSVNC_ARRAY_ELEMENTS(x) (sizeof(x) / sizeof(x[0])) 149 | #define KMSVNC_FOURCC_TO_INT(a,b,c,d) (((a) << 0) + ((b) << 8) + ((c) << 16) + ((d) << 24)) 150 | #define KMSVNC_WRITE_MAY(fd,buf,count) do { ssize_t e = write((fd), (buf), (count)); if (e != (count)) fprintf(stderr, "should write %ld bytes, actually wrote %ld, on line %d\n", (count), e, __LINE__); } while (0) 151 | 152 | #define KMSVNC_DEBUG(...) do{ if (kmsvnc->debug_enabled) fprintf(stdout, __VA_ARGS__); } while(0) 153 | 154 | #define likely(x) __builtin_expect(!!(x), 1) 155 | #define unlikely(x) __builtin_expect(!!(x), 0) 156 | -------------------------------------------------------------------------------- /va.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "va.h" 12 | #include "kmsvnc.h" 13 | 14 | extern struct kmsvnc_data *kmsvnc; 15 | 16 | void va_cleanup() { 17 | VAStatus s; 18 | if (kmsvnc->va) { 19 | if (kmsvnc->va->img_fmts) { 20 | free(kmsvnc->va->img_fmts); 21 | kmsvnc->va->img_fmts = NULL; 22 | } 23 | if (kmsvnc->va->imgbuf) { 24 | VA_MAY(vaUnmapBuffer(kmsvnc->va->dpy, kmsvnc->va->image->buf)); 25 | kmsvnc->va->imgbuf = NULL; 26 | } 27 | if (kmsvnc->va->image) { 28 | if ((s = vaDestroyImage(kmsvnc->va->dpy, kmsvnc->va->image->image_id)) == VA_STATUS_SUCCESS) { 29 | free(kmsvnc->va->image); 30 | } 31 | VA_MAY(s); 32 | kmsvnc->va->image = NULL; 33 | } 34 | if (kmsvnc->va->surface_id > 0) { 35 | VA_MAY(vaDestroySurfaces(kmsvnc->va->dpy, &kmsvnc->va->surface_id, 1)); 36 | kmsvnc->va->surface_id = 0; 37 | } 38 | if (kmsvnc->va->dpy) { 39 | VA_MAY(vaTerminate(kmsvnc->va->dpy)); 40 | kmsvnc->va->dpy = NULL; 41 | } 42 | if (kmsvnc->va->vendor_string) { 43 | kmsvnc->va->vendor_string = NULL; 44 | } 45 | free(kmsvnc->va); 46 | kmsvnc->va = NULL; 47 | } 48 | } 49 | 50 | static void va_msg_callback(void *user_context, const char *message) { 51 | if (kmsvnc->debug_enabled) { 52 | printf("va msg: %s", message); 53 | } 54 | } 55 | 56 | static void va_error_callback(void *user_context, const char *message) { 57 | printf("va error: %s", message); 58 | } 59 | 60 | static char* fourcc_to_str(int fourcc) { 61 | static char ret[5]; 62 | ret[4] = 0; 63 | for (int i = 0; i < 4; i++) { 64 | ret[i] = fourcc >> 8*i & 0xff; 65 | } 66 | return ret; 67 | } 68 | 69 | static const struct { 70 | uint32_t drm_fourcc; 71 | uint32_t va_fourcc; 72 | uint32_t va_rt_format; 73 | char alpha; 74 | } va_format_map[] = { 75 | {KMSVNC_FOURCC_TO_INT('X', 'R', '2', '4'), KMSVNC_FOURCC_TO_INT('B', 'G', 'R', 'X'), VA_RT_FORMAT_RGB32, 0}, 76 | {KMSVNC_FOURCC_TO_INT('A', 'R', '2', '4'), KMSVNC_FOURCC_TO_INT('B', 'G', 'R', 'A'), VA_RT_FORMAT_RGB32, 1}, 77 | {KMSVNC_FOURCC_TO_INT('X', 'R', '3', '0'), KMSVNC_FOURCC_TO_INT('X', 'R', '3', '0'), VA_RT_FORMAT_RGB32_10, 0}, 78 | {KMSVNC_FOURCC_TO_INT('A', 'R', '3', '0'), KMSVNC_FOURCC_TO_INT('A', 'R', '3', '0'), VA_RT_FORMAT_RGB32_10, 1}, 79 | }; 80 | 81 | struct va_fmt_data { 82 | uint32_t va_fourcc; 83 | VAImageFormat *fmt; 84 | char is_alpha; 85 | uint32_t va_rt_format; 86 | uint32_t depth; 87 | }; 88 | 89 | static VAImageFormat* vaImgFmt_apply_quirks(struct va_fmt_data* data) { 90 | static VAImageFormat ret = {0}; 91 | memcpy(&ret, data->fmt, sizeof(VAImageFormat)); 92 | if ((kmsvnc->va_byteorder_swap ^ !strncmp(kmsvnc->va->vendor_string, "Mesa", 4)) && data->depth != 30) { 93 | printf("applying rgb mask byte order swap\n"); 94 | ret.blue_mask = __builtin_bswap32(data->fmt->blue_mask); 95 | ret.green_mask = __builtin_bswap32(data->fmt->green_mask); 96 | ret.red_mask = __builtin_bswap32(data->fmt->red_mask); 97 | } 98 | return &ret; 99 | } 100 | 101 | static void print_va_image_fmt(VAImageFormat *fmt) { 102 | printf("image fmt: fourcc %d, %s, byte_order %s, bpp %d, depth %d, blue_mask %#x, green_mask %#x, red_mask %#x, alpha_mask %#x, reserved %#x %#x %#x %#x\n", fmt->fourcc, 103 | fourcc_to_str(fmt->fourcc), 104 | fmt->byte_order == 1 ? "VA_LSB_FIRST" : "VA_MSB_FIRST", 105 | fmt->bits_per_pixel, 106 | fmt->depth, 107 | fmt->blue_mask, 108 | fmt->green_mask, 109 | fmt->red_mask, 110 | fmt->alpha_mask, 111 | fmt->va_reserved[0], 112 | fmt->va_reserved[1], 113 | fmt->va_reserved[2], 114 | fmt->va_reserved[3] 115 | ); 116 | } 117 | 118 | int va_init() { 119 | if (!kmsvnc->drm || !kmsvnc->drm->drm_fd || !kmsvnc->drm->prime_fd) { 120 | KMSVNC_FATAL("drm is not initialized\n"); 121 | } 122 | 123 | setenv("DISPLAY", "", 1); 124 | setenv("WAYLAND_DISPLAY", "", 1); 125 | 126 | struct kmsvnc_va_data *va = malloc(sizeof(struct kmsvnc_va_data)); 127 | if (!va) KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 128 | memset(va, 0, sizeof(struct kmsvnc_va_data)); 129 | kmsvnc->va = va; 130 | 131 | char* render_node; 132 | int effective_fd = 0; 133 | if ((render_node = drmGetRenderDeviceNameFromFd(kmsvnc->drm->drm_fd))) { 134 | va->render_node_fd = open(render_node, O_RDWR); 135 | free(render_node); 136 | } 137 | else { 138 | printf("Using non-render node because the device does not have an associated render node.\n"); 139 | } 140 | if (va->render_node_fd > 0) { 141 | effective_fd = va->render_node_fd; 142 | } 143 | else { 144 | printf("Using non-render node because render node fails to open.\n"); 145 | effective_fd = kmsvnc->drm->drm_fd; 146 | } 147 | 148 | va->dpy = vaGetDisplayDRM(effective_fd); 149 | if (!va->dpy) { 150 | KMSVNC_FATAL("vaGetDisplayDRM failed\n"); 151 | } 152 | 153 | vaSetErrorCallback(va->dpy, &va_error_callback, NULL); 154 | vaSetInfoCallback(va->dpy, &va_msg_callback, NULL); 155 | 156 | int major, minor; 157 | VAStatus status; 158 | VA_MUST(vaInitialize(va->dpy, &major, &minor)); 159 | 160 | va->vendor_string = vaQueryVendorString(va->dpy); 161 | printf("vaapi vendor %s\n", va->vendor_string); 162 | 163 | VADRMPRIMESurfaceDescriptor prime_desc; 164 | VASurfaceAttrib prime_attrs[2] = { 165 | { 166 | .type = VASurfaceAttribMemoryType, 167 | .flags = VA_SURFACE_ATTRIB_SETTABLE, 168 | .value.type = VAGenericValueTypeInteger, 169 | .value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, 170 | }, 171 | { 172 | .type = VASurfaceAttribExternalBufferDescriptor, 173 | .flags = VA_SURFACE_ATTRIB_SETTABLE, 174 | .value.type = VAGenericValueTypePointer, 175 | .value.value.p = &prime_desc, 176 | } 177 | }; 178 | 179 | uint32_t rt_format = 0; 180 | char is_alpha = 0; 181 | for (int i = 0; i < KMSVNC_ARRAY_ELEMENTS(va_format_map); i++) { 182 | if (kmsvnc->drm->mfb->pixel_format == va_format_map[i].drm_fourcc) { 183 | prime_desc.fourcc = va_format_map[i].va_fourcc; 184 | rt_format = va_format_map[i].va_rt_format; 185 | is_alpha = va_format_map[i].alpha; 186 | break; 187 | } 188 | } 189 | if (!rt_format) { 190 | KMSVNC_FATAL("Unsupported pixfmt %s for vaapi, please create an issue with your pixfmt.", kmsvnc->drm->pixfmt_name); 191 | } 192 | if (kmsvnc->debug_enabled) { 193 | printf("selected rt_format %u, alpha %d\n", rt_format, is_alpha); 194 | } 195 | prime_desc.width = kmsvnc->drm->mfb->width; 196 | prime_desc.height = kmsvnc->drm->mfb->height; 197 | 198 | int i; 199 | int max_size = 0; 200 | for (i = 0; i < 4; i++) { 201 | int size = kmsvnc->drm->mfb->offsets[i] + kmsvnc->drm->mfb->height * kmsvnc->drm->mfb->pitches[i]; 202 | if (size > max_size) max_size = size; 203 | } 204 | for (i = 0; i < 4; i++) { 205 | prime_desc.objects[i].fd = kmsvnc->drm->prime_fd; 206 | prime_desc.objects[i].size = max_size; 207 | prime_desc.objects[i].drm_format_modifier = kmsvnc->drm->mfb->modifier; 208 | } 209 | 210 | prime_desc.num_layers = 1; 211 | prime_desc.layers[0].drm_format = kmsvnc->drm->mfb->pixel_format; 212 | for (i = 0; i < 4; i++) { 213 | prime_desc.layers[0].object_index[i] = 0; 214 | prime_desc.layers[0].offset[i] = kmsvnc->drm->mfb->offsets[i]; 215 | prime_desc.layers[0].pitch[i] = kmsvnc->drm->mfb->pitches[i]; 216 | } 217 | for (i = 0; i < 4; i++) { 218 | if (!kmsvnc->drm->mfb->handles[i]) { 219 | break; 220 | } 221 | } 222 | prime_desc.layers[0].num_planes = i; 223 | prime_desc.num_objects = 1; 224 | 225 | VAStatus s; 226 | if ((s = vaCreateSurfaces(va->dpy, rt_format, 227 | kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height, &va->surface_id, 1, 228 | prime_attrs, KMSVNC_ARRAY_ELEMENTS(prime_attrs))) != VA_STATUS_SUCCESS) 229 | { 230 | printf("vaCreateSurfaces prime2 error %#x %s, trying prime\n", s, vaErrorStr(s)); 231 | 232 | VASurfaceAttribExternalBuffers buffer_desc; 233 | VASurfaceAttrib buffer_attrs[2] = { 234 | { 235 | .type = VASurfaceAttribMemoryType, 236 | .flags = VA_SURFACE_ATTRIB_SETTABLE, 237 | .value.type = VAGenericValueTypeInteger, 238 | .value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME, 239 | }, 240 | { 241 | .type = VASurfaceAttribExternalBufferDescriptor, 242 | .flags = VA_SURFACE_ATTRIB_SETTABLE, 243 | .value.type = VAGenericValueTypePointer, 244 | .value.value.p = &buffer_desc, 245 | } 246 | }; 247 | 248 | unsigned long fd = kmsvnc->drm->prime_fd; 249 | 250 | buffer_desc.pixel_format = prime_desc.fourcc; 251 | buffer_desc.width = kmsvnc->drm->mfb->width; 252 | buffer_desc.height = kmsvnc->drm->mfb->height; 253 | buffer_desc.data_size = max_size; 254 | buffer_desc.buffers = &fd; 255 | buffer_desc.num_buffers = 1; 256 | buffer_desc.flags = 0; 257 | 258 | for (i = 0; i < 4; i++) { 259 | buffer_desc.pitches[i] = kmsvnc->drm->mfb->pitches[i]; 260 | buffer_desc.offsets[i] = kmsvnc->drm->mfb->offsets[i]; 261 | } 262 | buffer_desc.num_planes = prime_desc.layers[0].num_planes; 263 | 264 | 265 | VA_MUST(vaCreateSurfaces(va->dpy, rt_format, 266 | kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height, &va->surface_id, 1, 267 | buffer_attrs, KMSVNC_ARRAY_ELEMENTS(buffer_attrs))); 268 | } 269 | 270 | 271 | va->img_fmt_count = vaMaxNumImageFormats(va->dpy); 272 | va->img_fmts = malloc(sizeof(VAImageFormat) * va->img_fmt_count); 273 | if (!va->img_fmts) KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 274 | { 275 | int got; 276 | vaQueryImageFormats(va->dpy, va->img_fmts, &got); 277 | if (got != va->img_fmt_count) { 278 | printf("got less VAImageFormats, %d instead of %d\n", got, va->img_fmt_count); 279 | va->img_fmt_count = got; 280 | } 281 | } 282 | 283 | if (kmsvnc->debug_enabled) { 284 | for (int i = 0; i < va->img_fmt_count; i++) { 285 | print_va_image_fmt(va->img_fmts + i); 286 | } 287 | } 288 | 289 | struct va_fmt_data format_to_try[] = { 290 | {KMSVNC_FOURCC_TO_INT('R','G','B','X'), NULL, 0, VA_RT_FORMAT_RGB32, 24}, 291 | {KMSVNC_FOURCC_TO_INT('R','G','B','A'), NULL, 1, VA_RT_FORMAT_RGB32, 32}, 292 | 293 | {KMSVNC_FOURCC_TO_INT('X','B','G','R'), NULL, 0, VA_RT_FORMAT_RGB32, 24}, 294 | {KMSVNC_FOURCC_TO_INT('A','B','G','R'), NULL, 1, VA_RT_FORMAT_RGB32, 32}, 295 | 296 | {KMSVNC_FOURCC_TO_INT('X','R','G','B'), NULL, 0, VA_RT_FORMAT_RGB32, 24}, 297 | {KMSVNC_FOURCC_TO_INT('A','R','G','B'), NULL, 1, VA_RT_FORMAT_RGB32, 32}, 298 | 299 | {KMSVNC_FOURCC_TO_INT('B','G','R','X'), NULL, 0, VA_RT_FORMAT_RGB32, 24}, 300 | {KMSVNC_FOURCC_TO_INT('B','G','R','A'), NULL, 1, VA_RT_FORMAT_RGB32, 32}, 301 | 302 | 303 | {KMSVNC_FOURCC_TO_INT('X','R','3','0'), NULL, 0, VA_RT_FORMAT_RGB32_10, 30}, 304 | {KMSVNC_FOURCC_TO_INT('A','R','3','0'), NULL, 1, VA_RT_FORMAT_RGB32_10, 30}, 305 | {KMSVNC_FOURCC_TO_INT('X','B','3','0'), NULL, 0, VA_RT_FORMAT_RGB32_10, 30}, 306 | {KMSVNC_FOURCC_TO_INT('A','B','3','0'), NULL, 1, VA_RT_FORMAT_RGB32_10, 30}, 307 | }; 308 | 309 | for (int i = 0; i < va->img_fmt_count; i++) { 310 | for (int j = 0; j < KMSVNC_ARRAY_ELEMENTS(format_to_try); j++) { 311 | if (va->img_fmts[i].fourcc == format_to_try[j].va_fourcc) { 312 | format_to_try[j].fmt = va->img_fmts + i; 313 | } 314 | } 315 | } 316 | 317 | va->image = malloc(sizeof(VAImage)); 318 | if (!va->image) KMSVNC_FATAL("memory allocation error at %s:%d\n", __FILE__, __LINE__); 319 | 320 | va->derive_enabled = 0; 321 | va->derive_enabled = kmsvnc->va_derive_enabled < 0 ? va->derive_enabled : kmsvnc->va_derive_enabled != 0; 322 | if (va->derive_enabled) { 323 | if ((s = vaDeriveImage(va->dpy, va->surface_id, va->image)) == VA_STATUS_SUCCESS) { 324 | for (int i = 0; i < KMSVNC_ARRAY_ELEMENTS(format_to_try); i++) { 325 | if (format_to_try[i].fmt == NULL) continue; 326 | if (va->image->format.fourcc == format_to_try[i].fmt->fourcc) { 327 | va->selected_fmt = vaImgFmt_apply_quirks(format_to_try + i); 328 | break; 329 | } 330 | } 331 | if (!va->selected_fmt) { 332 | va->derive_enabled = 0; 333 | printf("vaDeriveImage returned unknown fourcc %d %s\n", va->image->format.fourcc, fourcc_to_str(va->image->format.fourcc)); 334 | VA_MAY(vaDestroyImage(kmsvnc->va->dpy, kmsvnc->va->image->image_id)); 335 | } 336 | } 337 | VA_MAY(s); 338 | } 339 | if (va->derive_enabled) { 340 | if ((s = vaMapBuffer(va->dpy, va->image->buf, (void**)&va->imgbuf)) != VA_STATUS_SUCCESS) { 341 | VA_MAY(s); 342 | VA_MAY(vaDestroyImage(kmsvnc->va->dpy, kmsvnc->va->image->image_id)); 343 | va->derive_enabled = 0; 344 | } 345 | } 346 | if (!va->derive_enabled) { 347 | for (int i = 0; i < KMSVNC_ARRAY_ELEMENTS(format_to_try); i++) { 348 | if (format_to_try[i].fmt == NULL) continue; 349 | if (!kmsvnc->debug_enabled && rt_format != format_to_try[i].va_rt_format) continue; 350 | if (is_alpha != format_to_try[i].is_alpha) continue; 351 | 352 | VAImageFormat *fmt = format_to_try[i].fmt; 353 | if ((s = vaCreateImage(va->dpy, fmt, kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height, va->image)) != VA_STATUS_SUCCESS) { 354 | VA_MAY(s); 355 | continue; 356 | } 357 | if ((s = vaMapBuffer(va->dpy, va->image->buf, (void**)&va->imgbuf)) != VA_STATUS_SUCCESS) { 358 | VA_MAY(s); 359 | VA_MAY(vaDestroyImage(kmsvnc->va->dpy, kmsvnc->va->image->image_id)); 360 | continue; 361 | } 362 | if ((s = vaGetImage(kmsvnc->va->dpy, kmsvnc->va->surface_id, 0, 0, 363 | kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height, 364 | kmsvnc->va->image->image_id)) != VA_STATUS_SUCCESS) 365 | { 366 | VA_MAY(s); 367 | VA_MAY(vaUnmapBuffer(kmsvnc->va->dpy, kmsvnc->va->image->buf)); 368 | VA_MAY(vaDestroyImage(kmsvnc->va->dpy, kmsvnc->va->image->image_id)); 369 | continue; 370 | } 371 | else { 372 | va->selected_fmt = vaImgFmt_apply_quirks(format_to_try + i); 373 | break; 374 | } 375 | } 376 | if (!va->selected_fmt) { 377 | va->imgbuf = NULL; 378 | KMSVNC_FATAL("failed to get vaapi image\n"); 379 | } 380 | } 381 | printf("got vaapi %simage:\n", va->derive_enabled ? "derive " : ""); 382 | print_va_image_fmt(&va->image->format); 383 | if (kmsvnc->debug_enabled) { 384 | fprintf(stderr, "selected image format:\n"); 385 | print_va_image_fmt(va->selected_fmt); 386 | } 387 | return 0; 388 | } 389 | 390 | int va_hwframe_to_vaapi(char *out) { 391 | if (!kmsvnc->va->derive_enabled) { 392 | VA_MUST(vaGetImage(kmsvnc->va->dpy, kmsvnc->va->surface_id, 0, 0, 393 | kmsvnc->drm->mfb->width, kmsvnc->drm->mfb->height, kmsvnc->va->image->image_id)); 394 | } 395 | memcpy(out, kmsvnc->va->imgbuf, kmsvnc->drm->mfb->width * kmsvnc->drm->mfb->height * BYTES_PER_PIXEL); 396 | return 0; 397 | } 398 | -------------------------------------------------------------------------------- /va.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define VA_MUST(x) do{VAStatus _s; if ((_s = (x)) != VA_STATUS_SUCCESS) KMSVNC_FATAL("va operation error %#x %s on line %d\n", _s, vaErrorStr(_s), __LINE__); } while (0) 4 | #define VA_MAY(x) do{VAStatus _s; if ((_s = (x)) != VA_STATUS_SUCCESS) fprintf(stderr, "va operation error %#x %s on line %d\n", _s, vaErrorStr(_s), __LINE__); } while (0) 5 | 6 | void va_cleanup(); 7 | int va_init(); 8 | int va_hwframe_to_vaapi(char *out); 9 | --------------------------------------------------------------------------------