Compare commits
499 Commits
seat-manag
...
master
Author | SHA1 | Date |
---|---|---|
Jonas Letzbor | 6dca473059 | |
Jonas Letzbor | ffa0a56335 | |
Andri Yngvason | 50f095d6e8 | |
Attila Fidan | b7de0d9fa6 | |
Attila Fidan | f970c5ceb7 | |
Simon Ser | 3c596455e8 | |
Andri Yngvason | 15660cd4a7 | |
Andri Yngvason | fbd98edae9 | |
Andri Yngvason | 56c38af25f | |
Andri Yngvason | 333381326d | |
Andri Yngvason | 17841f9ece | |
Andri Yngvason | b292c086fe | |
Andri Yngvason | 54fb881aab | |
Andri Yngvason | bd9daa85c3 | |
Jan Beich | 5b01551673 | |
Andri Yngvason | d71bca5270 | |
Andri Yngvason | 5d55944dab | |
Andri Yngvason | d819ca477c | |
Andri Yngvason | 42494fbbe4 | |
Andri Yngvason | 5ef322701e | |
Andri Yngvason | fb4c5c55a1 | |
Andri Yngvason | a7ed782bb9 | |
Andri Yngvason | 830142701a | |
Andri Yngvason | fcfd4280b7 | |
Andri Yngvason | a34a2d1cb9 | |
Andri Yngvason | f2c9ab0fb5 | |
Andri Yngvason | 8dbce970c0 | |
Andri Yngvason | 11a477f92e | |
Consolatis | 6e1d8aaddc | |
Consolatis | ac907a07f3 | |
Andri Yngvason | 64497d9388 | |
Andri Yngvason | e0bdf98112 | |
Andri Yngvason | e71956769c | |
Andri Yngvason | 27a8008edf | |
Andri Yngvason | 3af4851e4d | |
Andri Yngvason | 9647d1089a | |
Andri Yngvason | 6470dfef5f | |
Lucas Servén Marín | b4c234b3fa | |
Andri Yngvason | a53ff1f769 | |
Andri Yngvason | 27cd6351be | |
Christopher Snowhill | 57fb350cfe | |
Andri Yngvason | 1c489ad8e9 | |
Andri Yngvason | 301b4c0b53 | |
Andri Yngvason | c1281dad60 | |
Andri Yngvason | b5128070ae | |
Andri Yngvason | 10a8dbe167 | |
Andri Yngvason | b827af6270 | |
Andri Yngvason | 9ff7ba61e2 | |
Andri Yngvason | cdeefd0851 | |
Andri Yngvason | 247dbb38bb | |
Andri Yngvason | afec0e131c | |
Andri Yngvason | 6d59ab8323 | |
Andri Yngvason | 16ff0fa11d | |
Andri Yngvason | 89e4e8508f | |
Andri Yngvason | f2c367f191 | |
Andri Yngvason | 8395f7b106 | |
Andri Yngvason | 3a01753a22 | |
MazTheMan | ec885cf23d | |
Andri Yngvason | 8ed08d6f33 | |
Andri Yngvason | 945f5909d5 | |
Andri Yngvason | c9a3702940 | |
Andri Yngvason | d50c616127 | |
Andri Yngvason | c01e5c6080 | |
Andri Yngvason | 87c034db43 | |
Andri Yngvason | d53a04b5b0 | |
Andri Yngvason | 51f868c6ff | |
Andri Yngvason | 1c9a8c0769 | |
Andri Yngvason | 4700546026 | |
Andri Yngvason | c29bb465ee | |
Andri Yngvason | 1635bb5f91 | |
Jim Ramsay | 30c5581ce4 | |
Jim Ramsay | 5f5e5d4b3d | |
Jim Ramsay | f56c487456 | |
Jim Ramsay | 00d7a7bde3 | |
Jim Ramsay | 88f0571dc3 | |
Jim Ramsay | e3ea6652f0 | |
Jim Ramsay | 80c25e7820 | |
Jim Ramsay | 2ffb5476e2 | |
Jim Ramsay | ba99e7180e | |
Andri Yngvason | 9384bf3819 | |
Andri Yngvason | a8984f40a6 | |
Andri Yngvason | ab1fbdf98a | |
Andri Yngvason | 4700716d00 | |
Benjamin Richter | 78e7e448c6 | |
Jim Ramsay | f811beefb3 | |
Michael Vetter | 5ac05ac574 | |
Jim Ramsay | 496439c64b | |
Jim Ramsay | ba91cbd71f | |
Michael Vetter | 2f6a1c4c23 | |
Jim Ramsay | fd652e7a23 | |
Jim Ramsay | 8d5df2f0d5 | |
Andri Yngvason | 4b5812a607 | |
Andri Yngvason | e710d47f2e | |
Andri Yngvason | b1c1ade90d | |
Andri Yngvason | 1e9b531ad9 | |
Andri Yngvason | 267a4e55e3 | |
Andri Yngvason | 5bf6f23eed | |
Andri Yngvason | b54b0a31f7 | |
Andri Yngvason | 6fccedcf41 | |
Andri Yngvason | f49aea40a4 | |
Andri Yngvason | 0b970ba355 | |
Andri Yngvason | 6e326af2ff | |
Andri Yngvason | aa96103ae8 | |
Andri Yngvason | 5d1421a063 | |
Andri Yngvason | d8bc5cd328 | |
Andri Yngvason | bbea931f8c | |
Andri Yngvason | 0fc43ef757 | |
Andri Yngvason | baea2e8f4c | |
Andri Yngvason | 889f5900cc | |
Andri Yngvason | 98db05434a | |
Andri Yngvason | cec46490a9 | |
Jim Ramsay | 749b64666a | |
Jim Ramsay | cc27b127d1 | |
Jim Ramsay | 177ea507e3 | |
Jim Ramsay | 104040291b | |
Jim Ramsay | dcb23ebfe1 | |
Jim Ramsay | a52b7a1985 | |
Jim Ramsay | 405e9a13df | |
Jim Ramsay | 23527a095a | |
Jim Ramsay | e5ab6a3134 | |
Jim Ramsay | a28ce15521 | |
Jim Ramsay | 8df085a65a | |
Jim Ramsay | dd19da6143 | |
Jim Ramsay | d1e1f62d1e | |
Jim Ramsay | aec9304885 | |
Jim Ramsay | fbd373143c | |
Jim Ramsay | d475e0e52f | |
Jim Ramsay | 522b1deb28 | |
Jim Ramsay | e0a4a26c42 | |
Jim Ramsay | 14615ae3b0 | |
Jim Ramsay | 7c8c52dfb2 | |
Jim Ramsay | 4b126da54c | |
Andri Yngvason | 65ed029e22 | |
Andri Yngvason | 474ce23d42 | |
Jim Ramsay | 86652c8a42 | |
Jim Ramsay | 92e79bb971 | |
Jim Ramsay | 72cc04098d | |
Jim Ramsay | 2f2f6f410b | |
Jim Ramsay | 5cf9ad6eab | |
Jim Ramsay | 5d443bfa60 | |
Jim Ramsay | 3e5d6ea8eb | |
Jim Ramsay | 2a9e3dac58 | |
Jim Ramsay | 43e3015af6 | |
Jim Ramsay | 00539935ba | |
Jim Ramsay | cb116cc980 | |
Jim Ramsay | d75ca4bf51 | |
Jim Ramsay | 4def8f3cb8 | |
Andri Yngvason | e3238cf71d | |
Andri Yngvason | 03d7f1dc6d | |
Jim Ramsay | 5bb8cbfa92 | |
Jim Ramsay | 6b44a6648e | |
Jim Ramsay | 3cba374172 | |
Jim Ramsay | 308308b63a | |
Jim Ramsay | 9639740bc8 | |
Andri Yngvason | 312ddd8960 | |
Andri Yngvason | 052160cbd4 | |
Andri Yngvason | c3c3e97794 | |
Jim Ramsay | 89bd6da3bb | |
Jim Ramsay | e958b06e44 | |
Jim Ramsay | 467cfa1889 | |
Jim Ramsay | 19862aace8 | |
Jim Ramsay | 5c8014d19b | |
Jim Ramsay | 3e9aa0e3ae | |
Jim Ramsay | 80fd6b074e | |
Andri Yngvason | ac5e207321 | |
Andri Yngvason | 8a70c54928 | |
Andri Yngvason | 800a460444 | |
Jim Ramsay | 80efc9d487 | |
Jim Ramsay | 19e3d78d78 | |
Jim Ramsay | 72238686c4 | |
Jim Ramsay | 349693ed87 | |
Jim Ramsay | 7491a319d5 | |
Andri Yngvason | ef2e68af70 | |
Andri Yngvason | 1604b12f78 | |
Andri Yngvason | b99120ddfc | |
Andri Yngvason | b79ab71dca | |
Jim Ramsay | fe10e46e29 | |
Jim Ramsay | 325b45ef49 | |
Jim Ramsay | 1d25535e7a | |
Jim Ramsay | 6e13974b27 | |
Jim Ramsay | 372b530d3a | |
Jim Ramsay | 66018dd0d6 | |
Jim Ramsay | c86e2a756e | |
Andri Yngvason | 13aa584075 | |
Andri Yngvason | 125121613e | |
Andri Yngvason | 33f98048d5 | |
Jim Ramsay | 3ee620b8b6 | |
Jim Ramsay | d8239109e5 | |
Jim Ramsay | debd8a67cb | |
Jim Ramsay | c75b64eae8 | |
Jim Ramsay | 8d32dfaead | |
Jim Ramsay | c859c50463 | |
Jim Ramsay | 231a08ce19 | |
Jim Ramsay | 01bd225247 | |
Jim Ramsay | 1275609aee | |
Jim Ramsay | 4b1fbf9508 | |
Jim Ramsay | 15735b3256 | |
Jim Ramsay | 73fd2e386f | |
Jim Ramsay | 01851dc339 | |
Jim Ramsay | be42c8b7bf | |
Jim Ramsay | 5043f8e149 | |
Jim Ramsay | 1a0e8aae97 | |
Jim Ramsay | 19e1e14eab | |
Simon Ser | efc73904fb | |
Ahmad Fatoum | 208e7ae601 | |
Jim Ramsay | cb95ce931e | |
Jim Ramsay | 7a159570ef | |
Jim Ramsay | 4018c698c2 | |
Jim Ramsay | a1aa69625c | |
Jim Ramsay | 49ecbe14fa | |
Andri Yngvason | 8bf505749c | |
Andri Yngvason | e9ee4e644c | |
Andri Yngvason | 6c8ad1db1e | |
Andri Yngvason | ec4c90b0b9 | |
Andri Yngvason | 29b5988834 | |
Jim Ramsay | d04da7edb7 | |
Jim Ramsay | a2d9afc90f | |
Jim Ramsay | e9a7d6ecf9 | |
Jim Ramsay | c74c0e67d9 | |
Jim Ramsay | 7c03f0d6e7 | |
Andri Yngvason | 9e85093a6d | |
Andri Yngvason | 535a142ebd | |
Consolatis | 34e95378e1 | |
Consolatis | f3247ad933 | |
Andri Yngvason | 50aec16a5e | |
Andri Yngvason | 069d2de873 | |
Andri Yngvason | fbf2dee187 | |
Andri Yngvason | f851f4fb89 | |
Andri Yngvason | 28963df226 | |
shironeko | 4209a4deb2 | |
Andri Yngvason | 5e3c53b8db | |
Andri Yngvason | 7c75c8919c | |
Andri Yngvason | 43164f799d | |
Andri Yngvason | 099bdb8e17 | |
Andri Yngvason | d1057f481f | |
shironeko | c07f4483b2 | |
Andri Yngvason | 57f31be52e | |
Andri Yngvason | dfc34bbe9d | |
Joel Jensen | 5ed57b90b4 | |
Gunnar Wolf | 50ea576309 | |
Andri Yngvason | ce183e82e5 | |
Consolatis | e9e7c1a33f | |
Andri Yngvason | 3b26a43b97 | |
Andri Yngvason | 93335e7ba5 | |
Andri Yngvason | fba97621e2 | |
Andri Yngvason | ead9cdf121 | |
Andri Yngvason | 7f372d334c | |
Andri Yngvason | f614e4aea7 | |
Andri Yngvason | 87584ef934 | |
Andri Yngvason | 85563d59f8 | |
Matthias Braun | 8a65a8557e | |
Andri Yngvason | 7581cf0bc4 | |
Andri Yngvason | 1a41154b8c | |
Andri Yngvason | 0d05b08fb0 | |
Andri Yngvason | 61ebb57696 | |
Andri Yngvason | 87c040c919 | |
Andri Yngvason | 6c3d9bbb9f | |
Andri Yngvason | 3b6a07a3b6 | |
Andri Yngvason | 7a32cae4ac | |
Ryan Farley | 81192ac74d | |
Andri Yngvason | 7a60ab7db8 | |
Andri Yngvason | 13323a742f | |
Andri Yngvason | 2dc16d2e07 | |
Andri Yngvason | da52a28a75 | |
Andri Yngvason | 0dd4840065 | |
Andri Yngvason | e3af211523 | |
Andri Yngvason | 07f42ecb36 | |
Andri Yngvason | 0178c13627 | |
Andri Yngvason | c1a5de76ea | |
Andri Yngvason | 572e3138e3 | |
Andri Yngvason | b9142a94ae | |
Andri Yngvason | d0aa51aa6e | |
Andri Yngvason | 482ebaf168 | |
Andri Yngvason | 2957f6f3a2 | |
Arnavion | a9b2d93568 | |
Andri Yngvason | 5ae312c1b4 | |
Andri Yngvason | d757e6db88 | |
Andri Yngvason | d978d94041 | |
Andri Yngvason | 22562183db | |
Andri Yngvason | 7f5fbbf613 | |
Andri Yngvason | 9d0cc287a8 | |
Andri Yngvason | 457ed9c0b5 | |
Andri Yngvason | a19cc2fa16 | |
Aisha Tammy | 4fc9493e2f | |
NickSica | 6a73f293fc | |
Andri Yngvason | fa4dc0f169 | |
Andri Yngvason | d923f212d0 | |
Flakebi | 98d703bfa8 | |
Antonin Décimo | 41f30bf7ca | |
Antonin Décimo | a9d9547930 | |
Andri Yngvason | 7624d3d22f | |
Andri Yngvason | 18ab7bc60e | |
Andri Yngvason | d235f9394b | |
Andri Yngvason | 15c6768f4f | |
Andri Yngvason | b93d55d068 | |
Andri Yngvason | c20474604e | |
Andri Yngvason | 4a098e27f9 | |
Andri Yngvason | 30295bb715 | |
Andri Yngvason | 5c30d7752c | |
Andri Yngvason | 9a2f761a02 | |
Andri Yngvason | 630e2e67bc | |
Scott Moreau | 3ee9aac35e | |
Andri Yngvason | 8038e65597 | |
Andri Yngvason | dcf3b5869c | |
Andri Yngvason | 7ef8d0b0ae | |
Jony | e67e4b5985 | |
Alexander Graul | a6d738e087 | |
Andri Yngvason | d8f94d2613 | |
Andri Yngvason | 69d36dd7ef | |
Jan Beich | 72dd8a159c | |
Jan Beich | 6916780389 | |
Andri Yngvason | 800b0d6cb7 | |
Andri Yngvason | 3742dc7144 | |
Andri Yngvason | ca069ea738 | |
Andri Yngvason | f8344fda16 | |
Andri Yngvason | ef6756d0f4 | |
Andri Yngvason | 8ff47ee559 | |
Andri Yngvason | c53ab3bbf5 | |
Andri Yngvason | 0be56b2100 | |
Andri Yngvason | c8ba15c455 | |
Andri Yngvason | 85fca04e27 | |
Andri Yngvason | 4a5838c180 | |
Andri Yngvason | c79eb98e68 | |
Andri Yngvason | f9b3d98f83 | |
Andri Yngvason | bed4b7261e | |
Andri Yngvason | 0c86f9cf53 | |
Andri Yngvason | ce86f51699 | |
Andri Yngvason | 14768ca6e3 | |
Andri Yngvason | fbd8020778 | |
Jan Beich | 1bc095bd75 | |
Andri Yngvason | 2cb9f663a0 | |
Andri Yngvason | 184ed0a7ef | |
Andri Yngvason | 6682324710 | |
Andri Yngvason | 73ade6b84e | |
Andri Yngvason | e5272618ba | |
Andri Yngvason | 0268b52c53 | |
Andri Yngvason | c0f1036f97 | |
Andri Yngvason | ef74911298 | |
Andri Yngvason | 9d2e22b9bd | |
Andri Yngvason | 9a9c7c8be6 | |
Andri Yngvason | 1406ce1cf8 | |
Andri Yngvason | 3be37d24bd | |
Andri Yngvason | cd7594320b | |
Andri Yngvason | 4c14c11de4 | |
Andri Yngvason | 441079d2c5 | |
Andri Yngvason | b8df02838e | |
Andri Yngvason | ee3b6d74cf | |
Andri Yngvason | 7b6372ef49 | |
Andri Yngvason | 843368c993 | |
Andri Yngvason | 46fdbfb109 | |
Andri Yngvason | 2b522ee596 | |
Andri Yngvason | b0ec79acf8 | |
Andri Yngvason | 0615cd44c6 | |
Andri Yngvason | 21405082a0 | |
Andri Yngvason | a6d4c380fc | |
Andri Yngvason | c89ddb33c9 | |
Andri Yngvason | 0edaded063 | |
Andri Yngvason | 84c57a7333 | |
Andri Yngvason | f68bb825e1 | |
Andri Yngvason | 0178dd8a39 | |
Andri Yngvason | 19eb9af7e5 | |
Andri Yngvason | 3fc07f571d | |
Andri Yngvason | 70216b5829 | |
Andri Yngvason | 9b4a3e950e | |
Andri Yngvason | 4a21939b43 | |
Andri Yngvason | 4fa019d31f | |
Andri Yngvason | 492b1ce8d8 | |
Andri Yngvason | 9b136c90de | |
Andri Yngvason | 480392e40c | |
Andri Yngvason | 59cc119d76 | |
Andri Yngvason | fe590f3940 | |
Andri Yngvason | e398dcc235 | |
Andri Yngvason | 439d3997d4 | |
Andri Yngvason | c515a29951 | |
Andri Yngvason | 946ace1760 | |
Andri Yngvason | 12555bea13 | |
Andri Yngvason | 1cafc25655 | |
Andri Yngvason | 6b1dc2e6c3 | |
Andri Yngvason | 507b76dfbd | |
Andri Yngvason | 29f1669d55 | |
Andri Yngvason | e65cae43c2 | |
Andri Yngvason | a6e1ba25ea | |
Andri Yngvason | 4fb472f611 | |
Andri Yngvason | 0a139a1ac4 | |
Andri Yngvason | 4f1bca55c3 | |
Andri Yngvason | 041ac60ef6 | |
Andri Yngvason | ff882b7774 | |
Andri Yngvason | ff1223e4f9 | |
Andri Yngvason | 9b48290d09 | |
Andri Yngvason | 151e916752 | |
Andri Yngvason | 0761a5b818 | |
Andri Yngvason | 8eb6658162 | |
Andri Yngvason | bacae1b515 | |
Andri Yngvason | 57588b537c | |
Bob Hepple | 84045921e4 | |
Andri Yngvason | aaa782c551 | |
Andri Yngvason | 4f5933c07f | |
Andri Yngvason | f5453ffe1e | |
Andri Yngvason | 405268fc58 | |
Andri Yngvason | 6e889211db | |
Andri Yngvason | 8cc8c198d2 | |
Andri Yngvason | 06a249897b | |
Andri Yngvason | 36f0480038 | |
Andri Yngvason | 1e53e5e45e | |
Andri Yngvason | 80a06f13ea | |
Andri Yngvason | 04569b01f2 | |
Andri Yngvason | 9d6310cb14 | |
Andri Yngvason | 52b2d2cad9 | |
Andri Yngvason | 6becbacbe5 | |
Andri Yngvason | 1fc664a014 | |
Andri Yngvason | a5082fac17 | |
Andri Yngvason | 2b6b863eb1 | |
Andri Yngvason | f9deca5c9c | |
Andri Yngvason | 4799f5f959 | |
Andri Yngvason | 83a226c439 | |
Greg V | 3f62295214 | |
Greg V | fa49aca45a | |
Greg V | ccc582cd58 | |
Andri Yngvason | 14d62d0029 | |
Andri Yngvason | 58a181ccbc | |
Andri Yngvason | b39655df15 | |
Greg V | d610076614 | |
Greg V | 720b127dee | |
Andri Yngvason | be401b5e4a | |
Andri Yngvason | 6e521a07a0 | |
Andri Yngvason | 238c196e6b | |
Andri Yngvason | ee4917f200 | |
Andri Yngvason | 06ea9db40e | |
Andri Yngvason | 6c91b38205 | |
Andri Yngvason | b215df32a8 | |
Andri Yngvason | 8ce312e9eb | |
Andri Yngvason | 075680994e | |
Andri Yngvason | e00c492a05 | |
Andri Yngvason | dc7adf8f3b | |
Andri Yngvason | e85f219aff | |
Andri Yngvason | b14a0b854b | |
Andri Yngvason | e381c89378 | |
Andri Yngvason | 1384ab99d2 | |
Andri Yngvason | 2bb17c8215 | |
Andri Yngvason | e7586e8753 | |
Andri Yngvason | 1ebd9a7647 | |
Andri Yngvason | b6ca7aff8c | |
Andri Yngvason | bfbc81bf04 | |
Andri Yngvason | 52c18ffe23 | |
Andri Yngvason | 7f5431d922 | |
Andri Yngvason | c91816f247 | |
Andri Yngvason | b0a6b6bd2a | |
Andri Yngvason | f0fa12425b | |
Andri Yngvason | 222d636bc7 | |
Andri Yngvason | ae90348ece | |
Andri Yngvason | b5f1ff0898 | |
Andri Yngvason | 7e709c23e9 | |
Andri Yngvason | 6536cbd56f | |
Andri Yngvason | 557f0f365b | |
Andri Yngvason | a8fd0d6765 | |
Andri Yngvason | 996729f9bb | |
Andri Yngvason | 13216fa507 | |
Andri Yngvason | 49c584f80d | |
Andri Yngvason | 4da96d0dc1 | |
Andri Yngvason | 9d058c85ce | |
Andri Yngvason | b39e7535ee | |
Andri Yngvason | a12ce12ba6 | |
Andri Yngvason | 32fa2a3d29 | |
Andri Yngvason | 26cef852b6 | |
Andri Yngvason | cdccafa2b5 | |
Andri Yngvason | b4fad7e5ac | |
Andri Yngvason | 9509544cf9 | |
Andri Yngvason | ed62d20d2a | |
Matthias Tafelmeier | d30cd08f74 | |
Andri Yngvason | 7ea17d04aa | |
Andri Yngvason | e8279e57f5 | |
Andri Yngvason | 8ff7128714 | |
Andri Yngvason | 433ee722ec | |
Andri Yngvason | c32993d87d | |
Andri Yngvason | 9642e086d8 | |
Andri Yngvason | e01f75bfbd | |
Andri Yngvason | 35ffc2dac7 | |
Andri Yngvason | a3d4189a0b | |
Andri Yngvason | a327a2f6dd | |
Andri Yngvason | 1bf618a098 | |
Andri Yngvason | 6b3b448405 | |
Andri Yngvason | 1e6c89d9a9 | |
Andri Yngvason | a70a2b1bc4 | |
Andri Yngvason | 10e5c08752 | |
Andri Yngvason | 345d1d054a | |
Andri Yngvason | 56f099e505 | |
Andri Yngvason | 7416afe593 | |
Andri Yngvason | 2df6ea3982 | |
danshick | 0a390830fb | |
danshick | a263839c60 | |
Andri Yngvason | d1212affb8 | |
Dan Shick | 4ad4c712b2 | |
Andri Yngvason | 4d6f477d71 | |
Dan Shick | e53b4dfc9d | |
Andri Yngvason | e5512114f8 | |
Andri Yngvason | c274c81fa3 | |
Andri Yngvason | a8d49f3022 | |
Andri Yngvason | 4b3be2c972 | |
Andri Yngvason | 3c4b81862b |
|
@ -0,0 +1,45 @@
|
|||
image: archlinux
|
||||
|
||||
packages:
|
||||
- base-devel
|
||||
- libglvnd
|
||||
- libxkbcommon
|
||||
- pixman
|
||||
- gnutls
|
||||
- jansson
|
||||
- wayland
|
||||
- wayland-protocols
|
||||
- meson
|
||||
# runtime deps for integration testing:
|
||||
- sway
|
||||
- jq
|
||||
- lsof
|
||||
- python-pycryptodomex # needed by vncdotool
|
||||
- vncdotool
|
||||
|
||||
sources:
|
||||
- http://github.com/any1/wayvnc
|
||||
- http://github.com/any1/neatvnc
|
||||
- http://github.com/any1/aml
|
||||
|
||||
tasks:
|
||||
- aml: |
|
||||
cd aml
|
||||
meson --prefix=/usr build
|
||||
ninja -C build
|
||||
sudo ninja -C build install
|
||||
- neatvnc: |
|
||||
cd neatvnc
|
||||
meson --prefix=/usr build
|
||||
ninja -C build
|
||||
sudo ninja -C build install
|
||||
- build: |
|
||||
cd wayvnc
|
||||
meson --prefix=/usr build
|
||||
ninja -C build
|
||||
- test: |
|
||||
cd wayvnc
|
||||
ninja -C build test
|
||||
- integration: |
|
||||
cd wayvnc
|
||||
./test/integration/integration.sh
|
|
@ -0,0 +1,50 @@
|
|||
image: freebsd/latest
|
||||
|
||||
packages:
|
||||
- devel/meson
|
||||
- devel/pkgconf
|
||||
- devel/jansson
|
||||
- devel/evdev-proto
|
||||
- graphics/wayland
|
||||
- graphics/libdrm
|
||||
- graphics/libjpeg-turbo
|
||||
- graphics/mesa-libs
|
||||
- x11/pixman
|
||||
- x11/libxkbcommon
|
||||
- multimedia/ffmpeg
|
||||
- security/gnutls
|
||||
# runtime deps for integration testing:
|
||||
- x11-wm/sway
|
||||
- textproc/jq
|
||||
- sysutils/lsof
|
||||
- shells/bash
|
||||
- devel/py-pip
|
||||
|
||||
sources:
|
||||
- http://github.com/any1/wayvnc
|
||||
- http://github.com/any1/neatvnc
|
||||
- http://github.com/any1/aml
|
||||
|
||||
tasks:
|
||||
- pip-vncdotool: |
|
||||
sudo pip install vncdotool
|
||||
- aml: |
|
||||
cd aml
|
||||
meson --prefix=/usr build
|
||||
ninja -C build
|
||||
sudo ninja -C build install
|
||||
- neatvnc: |
|
||||
cd neatvnc
|
||||
meson --prefix=/usr build
|
||||
ninja -C build
|
||||
sudo ninja -C build install
|
||||
- build: |
|
||||
cd wayvnc
|
||||
meson --prefix=/usr build
|
||||
ninja -C build
|
||||
- test: |
|
||||
cd wayvnc
|
||||
ninja -C build test
|
||||
- integration: |
|
||||
cd wayvnc
|
||||
./test/integration/integration.sh
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
name: Bugs
|
||||
about: Crashes and other bugs
|
||||
labels: 'bug'
|
||||
|
||||
---
|
||||
|
||||
### Useful information:
|
||||
Please, try to gather as much of useful information as possible and follow
|
||||
these instructions:
|
||||
|
||||
- **Version:**
|
||||
- Run this command: `wayvnc -V`
|
||||
|
||||
- Try to reproduce while capturing a **trace log:**
|
||||
- `wayvnc -Ltrace | tee wayvnc-crash.log`
|
||||
|
||||
- Get the **stack trace**:
|
||||
- If have `coredumpctl`, you can gather the stack trace after a crash using
|
||||
`coredumpctl gdb wayvnc` and then run `bt full` to obtain the stack trace.
|
||||
- Otherwise, you can either locate the core file and load it into gdb or run
|
||||
wayvnc in gdb like so:
|
||||
- `gdb --args wayvnc -Ltrace`
|
||||
- If the lines mentioning wayvnc, neatvnc or aml have `??`, please compile
|
||||
wayvnc and those other projects from source with debug symbols and try
|
||||
again.
|
||||
|
||||
- Describe how to **reproduce** the problem
|
|
@ -0,0 +1,5 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Questions
|
||||
url: "https://github.com/any1/wayvnc/discussions"
|
||||
about: "Please ask questions on IRC in #wayvnc on Libera Chat or in Discussions"
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
name: Enhancements
|
||||
about: New functionality
|
||||
labels: 'enhancement'
|
||||
|
||||
---
|
|
@ -0,0 +1 @@
|
|||
Please read CONTRIBUTING.md before making a pull request.
|
|
@ -0,0 +1,38 @@
|
|||
name: Build and Unit Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master", "ci-test" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: checkout aml
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: any1/aml
|
||||
path: subprojects/aml
|
||||
- name: checkout neatvnc
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: any1/neatvnc
|
||||
path: subprojects/neatvnc
|
||||
- name: prepare environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y meson libdrm-dev libxkbcommon-dev libwlroots-dev libjansson-dev libpam0g-dev libgnutls28-dev libavfilter-dev libavcodec-dev libavutil-dev libturbojpeg0-dev scdoc
|
||||
# runtime deps for integration testing:
|
||||
sudo apt-get install -y sway jq lsof
|
||||
pip install vncdotool
|
||||
- name: configure
|
||||
run: meson build -D tests=true
|
||||
- name: compile
|
||||
run: meson compile -C build
|
||||
- name: unit tests
|
||||
run: meson test --verbose -C build
|
||||
- name: integration tests
|
||||
run: ./test/integration/integration.sh
|
|
@ -4,3 +4,7 @@ subprojects
|
|||
.clang_complete
|
||||
.ycm_extra_conf.py
|
||||
perf.*
|
||||
*.pem
|
||||
.vimrc
|
||||
.cache
|
||||
.vscode
|
|
@ -0,0 +1,185 @@
|
|||
# Contributing to wayvnc
|
||||
|
||||
## Commit Messages
|
||||
|
||||
Please, try to write good commit messages. Do your best to follow these 7 rules,
|
||||
borrowed from [Chris Beams](https://chris.beams.io/posts/git-commit/), plus 1
|
||||
extra rule:
|
||||
|
||||
1. Separate subject from body with a blank line
|
||||
2. Limit the subject line to 50 characters
|
||||
3. Capitalize the subject line
|
||||
4. Do not end the subject line with a period
|
||||
5. Use the imperative mood in the subject line
|
||||
6. Wrap the body at 72 characters
|
||||
7. Use the body to explain what and why vs. how
|
||||
8. (Extra) Prefix the subject line with the component that's modified
|
||||
|
||||
If you wish to know why we follow these rules, please read Chris Beams' blog
|
||||
entry, linked above.
|
||||
|
||||
Rule number 8 allows us to quickly gauge if a given commit is relevant to what
|
||||
we're looking for when skimming the log. It adds consistency and simplifies the
|
||||
message. For example
|
||||
```
|
||||
ctl-client: Print trailing newline for events
|
||||
```
|
||||
is better than
|
||||
```
|
||||
Print trailing newline for events in ctl-client
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
ctl-client: Print trailing newline for events
|
||||
|
||||
If someone wants to parse this instead of using jq, a trailing
|
||||
newline delimits the end of the event.
|
||||
```
|
||||
|
||||
## Style
|
||||
|
||||
This project follows the the
|
||||
[Linux kernel's style guide](https://www.kernel.org/doc/html/latest/process/coding-style.html#codingstyle)
|
||||
as far as coding style is concerned, with the following exceptions:
|
||||
|
||||
* When declaring pointer variables, the asterisk (`*`) is placed on the left
|
||||
with the type rather than the variable name. Declaring multiple variables in
|
||||
the same line is not allowed.
|
||||
* Wrapped argument lists should not be aligned. Use two tabs instead. There is
|
||||
a lot of code that uses aligned argument lists in the project, but I have
|
||||
come to the conclusion that these alignments are not very nice to maintain.
|
||||
|
||||
### Summary & Examples:
|
||||
|
||||
In case you aren't familiar with Linux's coding style, here is a short summary
|
||||
and some examples of acceptable formatting:
|
||||
|
||||
* Use tabs for indenting.
|
||||
* We do not align code (mostly), but when we do, we use spaces rather than
|
||||
tabs. This rule is not according to the Linux style guide.
|
||||
* The preferred limit on the length of a single line is 80 columns.
|
||||
* User-visible string such as log messages must not be split up.
|
||||
* Use space after the following keywords: `if`, `switch`, `case`, `for`, `do`,
|
||||
`while`.
|
||||
* Do **not** use space after others such as: `sizeof`, `typeof`, `alignof`
|
||||
or `__attribute__`.
|
||||
* Do **not** use typedefs.
|
||||
* It is allowed to use typedefs for function pointers. This rule is not
|
||||
according to the Linux style guide.
|
||||
|
||||
#### Functions
|
||||
|
||||
```
|
||||
static int do_something(int number, const char* text)
|
||||
{
|
||||
body of function
|
||||
}
|
||||
```
|
||||
|
||||
#### `if`
|
||||
|
||||
```
|
||||
// Single statement
|
||||
if (condition)
|
||||
do_this();
|
||||
|
||||
// Multiple statements
|
||||
if (condition) {
|
||||
do_this(2, "41");
|
||||
do_that();
|
||||
}
|
||||
|
||||
// Single statement if/else
|
||||
if (condition)
|
||||
do_this();
|
||||
else
|
||||
do_that();
|
||||
|
||||
// Multi-statement if/else
|
||||
if (condition) {
|
||||
do_this();
|
||||
do_that();
|
||||
} else {
|
||||
otherwise();
|
||||
}
|
||||
```
|
||||
|
||||
#### `switch`
|
||||
|
||||
```
|
||||
switch (value) {
|
||||
case 3:
|
||||
printf("three!\n");
|
||||
break;
|
||||
case 5:
|
||||
printf("five!\n");
|
||||
break;
|
||||
case 42:
|
||||
printf("the answer to life, the universe and everything: ");
|
||||
// fallthrough
|
||||
default:
|
||||
printf("%d\n", value);
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
#### Arithmetic
|
||||
|
||||
```
|
||||
int a = b * c + 5;
|
||||
```
|
||||
|
||||
#### Pointers
|
||||
|
||||
```
|
||||
char* some_text = "some text";
|
||||
char* just_text = text + 5;
|
||||
char t = *just_text;
|
||||
char e = just_text[1];
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
wayvnc has a small but growing set of unit tests, which are run on every GitHub
|
||||
PR. To run them locally, do the following:
|
||||
```bash
|
||||
meson test -C build
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
There are also a handful of integration tests which also run on every PR. Read
|
||||
the [integration tests documentation](test/integration/README.md) for more
|
||||
details, but to run them locally:
|
||||
```
|
||||
./test/integration/integration.sh
|
||||
```
|
||||
|
||||
### Valgrind
|
||||
|
||||
There is a helper script in [util/valgrind.sh](util/valgrind.sh) to aid in
|
||||
memory profiling of wayvnc and wayvncctl. This can help find and eliminate
|
||||
memory leaks.
|
||||
|
||||
### Automated Tests
|
||||
|
||||
We run a set of tests on every PR, in three different environments.
|
||||
|
||||
Each run ensures that the proposed code change:
|
||||
1. Builds successfully
|
||||
2. Passes all unit tests
|
||||
3. Passes all integration tests
|
||||
|
||||
And does so in 3 different environments:
|
||||
- Ubuntu as a [github action](.github/workflows/build.yml)
|
||||
- Arch Linux as a [sourcehut build](.builds/archlinux.yml)
|
||||
- FreeBSD as a [sourcehut build](.builds/freebsd.yaml)
|
||||
|
||||
## No Brown M&Ms
|
||||
|
||||
All pull requests must contain the following sentence in the description:
|
||||
I have read and understood CONTRIBUTING.md.
|
|
@ -0,0 +1,36 @@
|
|||
# FAQ
|
||||
|
||||
**Q: How can I run wayvnc in headless mode/over an SSH session?**
|
||||
|
||||
A: Set the environment variables `WLR_BACKENDS=headless` and
|
||||
`WLR_LIBINPUT_NO_DEVICES=1` before starting sway, then set
|
||||
`WAYLAND_DISPLAY=wayland-1` and run wayvnc. For older versions of sway,
|
||||
`WAYLAND_DISPLAY` is `wayland-0`. Try that if `wayland-1` doesn't work.
|
||||
|
||||
**Q: How can I pass my mod-key from Sway to the remote desktop session?**
|
||||
|
||||
A: Create an almost empty mode in your sway config. Example:
|
||||
```
|
||||
mode passthrough {
|
||||
bindsym $mod+Pause mode default
|
||||
}
|
||||
bindsym $mod+Pause mode passthrough
|
||||
```
|
||||
This makes it so that when you press $mod+Pause, all keybindings, except the one
|
||||
to switch back, are disabled.
|
||||
|
||||
Disable `floating_modifier` during the mode if it's set up in your config file
|
||||
and you wish to be able to use the same functionality in the nested desktop:
|
||||
```
|
||||
mode passthrough {
|
||||
bindsym $mod+Pause mode default; floating_modifier $mod normal
|
||||
}
|
||||
bindsym $mod+Pause mode passthrough; floating_modifier none
|
||||
```
|
||||
Replace `$mod normal` with different arguments if applicable.
|
||||
|
||||
**Q: Not all symbols show up when I'm typing. What can I do to fix this?**
|
||||
|
||||
A: Try setting the keyboard layout in wayvnc to the one that most closely
|
||||
matches the keyboard layout that you're using on the client side. An exact
|
||||
layout isn't needed, just one that has all the symbols that you use.
|
|
@ -0,0 +1,2 @@
|
|||
github: any1
|
||||
patreon: andriyngvason
|
159
README.md
159
README.md
|
@ -1,21 +1,31 @@
|
|||
# wayvnc
|
||||
|
||||
## Introduction
|
||||
This is a VNC server for wlroots based Wayland compositors. It attaches to a
|
||||
running Wayland session, creates virtual input devices and exposes a single
|
||||
display via the RFB protocol. The Wayland session may be a headless one, so it
|
||||
is also possible to run wayvnc without a physical display attached.
|
||||
[![Build and Unit Test](https://github.com/any1/wayvnc/actions/workflows/build.yml/badge.svg)](https://github.com/any1/wayvnc/actions/workflows/build.yml)
|
||||
[![builds.sr.ht status](https://builds.sr.ht/~andri/wayvnc/pulls/1.svg)](https://builds.sr.ht/~andri/wayvnc/pulls/1?)
|
||||
[![Packaging status](https://repology.org/badge/tiny-repos/wayvnc.svg)](https://repology.org/project/wayvnc/versions)
|
||||
|
||||
For support, join the #wayvnc IRC channel on freenode.
|
||||
## Introduction
|
||||
This is a VNC server for wlroots-based Wayland compositors (:no_entry: Gnome,
|
||||
KDE and Weston are **not** supported). It attaches to a running Wayland session,
|
||||
creates virtual input devices, and exposes a single display via the RFB
|
||||
protocol. The Wayland session may be a headless one, so it is also possible
|
||||
to run wayvnc without a physical display attached.
|
||||
|
||||
Please check the [FAQ](FAQ.md) for answers to common questions. For further
|
||||
support, join the #wayvnc IRC channel on libera.chat, or ask your questions on the
|
||||
GitHub [discussion forum](https://github.com/any1/wayvnc/discussions) for the
|
||||
project.
|
||||
|
||||
## Building
|
||||
### Runtime Dependencies
|
||||
* EGL
|
||||
* libuv
|
||||
* aml
|
||||
* drm
|
||||
* gbm (optional)
|
||||
* libxkbcommon
|
||||
* neatvnc
|
||||
* OpenGL ES V2.0
|
||||
* pam (optional)
|
||||
* pixman
|
||||
* jansson
|
||||
|
||||
### Build Dependencies
|
||||
* GCC
|
||||
|
@ -23,20 +33,71 @@ For support, join the #wayvnc IRC channel on freenode.
|
|||
* ninja
|
||||
* pkg-config
|
||||
|
||||
The easiest way to satisfy the neatvnc dependency is to clone it into the
|
||||
subprojects directory:
|
||||
#### For Arch Linux
|
||||
```
|
||||
mkdir subprojects
|
||||
git clone https://github.com/any1/neatvnc.git subprojects/neatvnc
|
||||
pacman -S base-devel libglvnd libxkbcommon pixman gnutls jansson
|
||||
```
|
||||
|
||||
Setting the buildtype flag is not required but it is recommended as there are
|
||||
significant performance gains to be had from an optimised build.
|
||||
#### For Fedora 37
|
||||
```
|
||||
meson build --buildtype=release
|
||||
dnf install -y meson gcc ninja-build pkg-config egl-wayland egl-wayland-devel \
|
||||
mesa-libEGL-devel mesa-libEGL libwayland-egl libglvnd-devel \
|
||||
libglvnd-core-devel libglvnd mesa-libGLES-devel mesa-libGLES \
|
||||
libxkbcommon-devel libxkbcommon libwayland-client \
|
||||
pam-devel pixman-devel libgbm-devel libdrm-devel scdoc \
|
||||
libavcodec-free-devel libavfilter-free-devel libavutil-free-devel \
|
||||
turbojpeg-devel wayland-devel gnutls-devel jansson-devel
|
||||
```
|
||||
|
||||
#### For Debian (unstable / testing)
|
||||
```
|
||||
apt build-dep wayvnc
|
||||
```
|
||||
|
||||
#### For Ubuntu
|
||||
```
|
||||
apt install meson libdrm-dev libxkbcommon-dev libwlroots-dev libjansson-dev \
|
||||
libpam0g-dev libgnutls28-dev libavfilter-dev libavcodec-dev \
|
||||
libavutil-dev libturbojpeg0-dev scdoc
|
||||
```
|
||||
|
||||
#### Additional build-time dependencies
|
||||
|
||||
The easiest way to satisfy the neatvnc and aml dependencies is to link to them
|
||||
in the subprojects directory:
|
||||
```
|
||||
git clone https://github.com/any1/wayvnc.git
|
||||
git clone https://github.com/any1/neatvnc.git
|
||||
git clone https://github.com/any1/aml.git
|
||||
|
||||
mkdir wayvnc/subprojects
|
||||
cd wayvnc/subprojects
|
||||
ln -s ../../neatvnc .
|
||||
ln -s ../../aml .
|
||||
cd -
|
||||
|
||||
mkdir neatvnc/subprojects
|
||||
cd neatvnc/subprojects
|
||||
ln -s ../../aml .
|
||||
cd -
|
||||
```
|
||||
|
||||
### Configure and Build
|
||||
```
|
||||
meson build
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
To run the unit tests:
|
||||
```
|
||||
meson test -C build
|
||||
```
|
||||
|
||||
To run the [integration tests](test/integration/README.md):
|
||||
```
|
||||
./test/integration/integration.sh
|
||||
```
|
||||
|
||||
## Running
|
||||
Wayvnc can be run from the build directory like so:
|
||||
```
|
||||
|
@ -52,15 +113,20 @@ accept connections via any interface, set the address to `0.0.0.0` like this:
|
|||
:warning: Do not do this on a public network or the internet without
|
||||
user authentication enabled. The best way to protect your VNC connection is to
|
||||
use SSH tunneling while listening on localhost, but users can also be
|
||||
authenticated when connecting to Wayvnc.
|
||||
authenticated when connecting to wayvnc.
|
||||
|
||||
### Encryption & Authentication
|
||||
You'll need a private X509 key and a certificate. A self signed key with a
|
||||
certificate can be generated like so:
|
||||
|
||||
#### VeNCrypt (TLS)
|
||||
For TLS, you'll need a private X509 key and a certificate. A self-signed key
|
||||
with a certificate can be generated like so:
|
||||
```
|
||||
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
|
||||
-keyout key.pem -out cert.pem -subj /CN=localhost \
|
||||
cd ~/.config/wayvnc
|
||||
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -sha384 \
|
||||
-days 3650 -nodes -keyout tls_key.pem -out tls_cert.pem \
|
||||
-subj /CN=localhost \
|
||||
-addext subjectAltName=DNS:localhost,DNS:localhost,IP:127.0.0.1
|
||||
cd -
|
||||
```
|
||||
Replace `localhost` and `127.0.0.1` in the command above with your public facing
|
||||
host name and IP address, respectively, or just keep them as is if you're
|
||||
|
@ -70,13 +136,56 @@ Create a config with the authentication info and load it using the `--config`
|
|||
command line option or place it at the default location
|
||||
`$HOME/.config/wayvnc/config`.
|
||||
```
|
||||
use_relative_paths=true
|
||||
address=0.0.0.0
|
||||
enable_auth=true
|
||||
username=luser
|
||||
password=p455w0rd
|
||||
private_key_file=/path/to/key.pem
|
||||
certificate_file=/path/to/cert.pem
|
||||
private_key_file=tls_key.pem
|
||||
certificate_file=tls_cert.pem
|
||||
```
|
||||
|
||||
## Compatible Software
|
||||
See https://github.com/any1/neatvnc#client-compatibility
|
||||
#### RSA-AES
|
||||
The RSA-AES security type combines RSA with AES in EAX mode to provide secure
|
||||
authentication and encryption that's resilient to eavesdropping and MITM. Its
|
||||
main weakness is that the user has to verify the server's credentials on first
|
||||
use. Thereafter, the client software should warn the user if the server's
|
||||
credentials change. It's a Trust on First Use (TOFU) scheme as employed by SSH.
|
||||
|
||||
For the RSA-AES to be enabled, you need to generate an RSA key. This can be
|
||||
achieved like so:
|
||||
```
|
||||
ssh-keygen -m pem -f ~/.config/wayvnc/rsa_key.pem -t rsa -N ""
|
||||
```
|
||||
|
||||
You also need to tell wayvnc where this file is located, by setting setting the
|
||||
`rsa_private_key_file` configuration parameter:
|
||||
```
|
||||
use_relative_paths=true
|
||||
address=0.0.0.0
|
||||
enable_auth=true
|
||||
username=luser
|
||||
password=p455w0rd
|
||||
rsa_private_key_file=rsa_key.pem
|
||||
```
|
||||
|
||||
You may also add credentials for TLS in combination with RSA. The client will
|
||||
choose.
|
||||
|
||||
### wayvncctl control socket
|
||||
|
||||
To facilitate runtime interaction and control, wayvnc opens a unix domain socket
|
||||
at *$XDG_RUNTIME_DIR*/wayvncctl (or a fallback of /tmp/wayvncctl-*$UID*). A
|
||||
client can connect and exchange json-formatted IPC messages to query and control
|
||||
the running wayvnc instance.
|
||||
|
||||
Use the `wayvncctl` utility to interact with this control socket from the
|
||||
command line.
|
||||
|
||||
See the `wayvnc(1)` manpage for an in-depth description of the IPC protocol and
|
||||
the available commands, and `wayvncctl(1)` for more on the command line
|
||||
interface.
|
||||
|
||||
There is also a handy event-loop mode that can be used to run commands when
|
||||
various events occur in wayvnc. See
|
||||
[examples/event-watcher](examples/event-watcher) for more details.
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# Example Scripts
|
||||
|
||||
The scripts here are examples of how you can automate interesting things with the wayvncctl IPC events.
|
||||
|
||||
## event-watcher
|
||||
|
||||
This is a pretty simple example that just demonstrates how to tie the
|
||||
`wayvncctl event-receive` event loop into a bash script. It logs when clients
|
||||
connect and disconnect.
|
||||
|
||||
## single-output-sway
|
||||
|
||||
This is more purposeful, and implements an idea for multi-output wayland
|
||||
servers, collapsing all outputs down to one when the first client connects, and
|
||||
restoring the configuration when the last client exits.
|
||||
|
||||
The mechanism used to collapse the outputs depends on the version of sway installed:
|
||||
|
||||
- For sway-1.7 and earlier, the script just temporarily disables all outputs
|
||||
except the one being captured. This moves all workspaces to the single
|
||||
remaining output.
|
||||
|
||||
- For sway-1.8 and later, the script creates a temporary virtual output called
|
||||
`HEADLESS-[0-9]+' and then disables all physical outputs, which moves all
|
||||
workspaces to the virtual output. On disconnect, all original physical
|
||||
outputs are re-enabled, and the virtual output is destroyed.
|
|
@ -0,0 +1,88 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
import glob
|
||||
|
||||
class Program:
|
||||
command_seq = 0
|
||||
reader = None
|
||||
writer = None
|
||||
read_buffer = ""
|
||||
message_queue = asyncio.Queue()
|
||||
reply_queue = asyncio.Queue()
|
||||
decoder = json.JSONDecoder()
|
||||
tasks = []
|
||||
|
||||
async def read_message(self):
|
||||
while True:
|
||||
try:
|
||||
result, index = self.decoder.raw_decode(self.read_buffer)
|
||||
self.read_buffer = self.read_buffer[index:].lstrip()
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
data = await self.reader.read(4096)
|
||||
self.read_buffer += data.decode('utf-8')
|
||||
|
||||
async def send_command(self, method, params = None):
|
||||
cmd = {
|
||||
"method": method,
|
||||
"id": self.command_seq,
|
||||
}
|
||||
|
||||
if not params is None:
|
||||
cmd['params'] = params
|
||||
|
||||
self.command_seq += 1
|
||||
self.writer.write(json.dumps(cmd).encode())
|
||||
await self.writer.drain()
|
||||
|
||||
reply = await self.reply_queue.get()
|
||||
self.reply_queue.task_done()
|
||||
return reply['code'] == 0
|
||||
|
||||
async def attach(self, display):
|
||||
return await self.send_command('attach', {'display': display})
|
||||
|
||||
async def attach_any(self):
|
||||
for path in glob.iglob('/run/user/*/wayland-*'):
|
||||
if path.endswith('.lock'):
|
||||
continue
|
||||
|
||||
if await self.attach(path):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def handle_detached(self):
|
||||
while not await self.attach_any():
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
async def process_message(self, message):
|
||||
method = message['method']
|
||||
if (method == 'detached'):
|
||||
await self.handle_detached()
|
||||
|
||||
async def message_processor(self):
|
||||
while True:
|
||||
message = await self.read_message()
|
||||
if 'method' in message:
|
||||
await self.message_queue.put(message)
|
||||
elif 'code' in message:
|
||||
await self.reply_queue.put(message)
|
||||
|
||||
async def main(self):
|
||||
self.reader, self.writer = await asyncio.open_unix_connection("/tmp/wayvncctl-0")
|
||||
self.tasks.append(asyncio.create_task(self.message_processor()))
|
||||
|
||||
await self.attach_any()
|
||||
await self.send_command("event-receive")
|
||||
|
||||
while True:
|
||||
message = await self.message_queue.get()
|
||||
await self.process_message(message)
|
||||
|
||||
prog = Program()
|
||||
asyncio.run(prog.main())
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# This is free and unencumbered software released into the public domain.
|
||||
#
|
||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
# distribute this software, either in source code form or as a compiled
|
||||
# binary, for any purpose, commercial or non-commercial, and by any
|
||||
# means.
|
||||
#
|
||||
# In jurisdictions that recognize copyright laws, the author or authors
|
||||
# of this software dedicate any and all copyright interest in the
|
||||
# software to the public domain. We make this dedication for the benefit
|
||||
# of the public at large and to the detriment of our heirs and
|
||||
# successors. We intend this dedication to be an overt act of
|
||||
# relinquishment in perpetuity of all present and future rights to this
|
||||
# software under copyright law.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# For more information, please refer to <http://unlicense.org/>
|
||||
|
||||
WAYVNCCTL=${WAYVNCCTL:-wayvncctl}
|
||||
|
||||
connection_count_now() {
|
||||
echo "Total clients: $1"
|
||||
}
|
||||
|
||||
while IFS= read -r EVT; do
|
||||
case "$(jq -r '.method' <<<"$EVT")" in
|
||||
client-*onnected)
|
||||
count=$(jq -r '.params.connection_count' <<<"$EVT")
|
||||
connection_count_now "$count"
|
||||
;;
|
||||
wayvnc-shutdown)
|
||||
echo "wayvncctl is no longer running"
|
||||
connection_count_now 0
|
||||
;;
|
||||
wayvnc-startup)
|
||||
echo "Ready to receive wayvnc events"
|
||||
;;
|
||||
esac
|
||||
done < <("$WAYVNCCTL" --wait --reconnect --json event-receive)
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# This is free and unencumbered software released into the public domain.
|
||||
#
|
||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
# distribute this software, either in source code form or as a compiled
|
||||
# binary, for any purpose, commercial or non-commercial, and by any
|
||||
# means.
|
||||
#
|
||||
# In jurisdictions that recognize copyright laws, the author or authors
|
||||
# of this software dedicate any and all copyright interest in the
|
||||
# software to the public domain. We make this dedication for the benefit
|
||||
# of the public at large and to the detriment of our heirs and
|
||||
# successors. We intend this dedication to be an overt act of
|
||||
# relinquishment in perpetuity of all present and future rights to this
|
||||
# software under copyright law.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# For more information, please refer to <http://unlicense.org/>
|
||||
|
||||
WAYVNCCTL=${WAYVNCCTL:-wayvncctl}
|
||||
SWAYMSG=${SWAYMSG:-swaymsg}
|
||||
|
||||
SWAY_HAS_UNPLUG=false
|
||||
IFS=" .-" read -r _ _ SWAYMAJOR SWAYMINOR _ < <($SWAYMSG -v)
|
||||
if [[ $SWAYMAJOR -ge 1 && $SWAYMINOR -ge 8 ]]; then
|
||||
echo "Detected sway version 1.8 or later: Enabling virtual output device mode"
|
||||
SWAY_HAS_UNPLUG=true
|
||||
else
|
||||
echo "Detected sway version 1.7 or earlier: Not enabling virtual output device mode"
|
||||
fi
|
||||
|
||||
find_output_matching() {
|
||||
local pattern=$1
|
||||
$WAYVNCCTL -j output-list | jq -r ".[].name | match(\"$pattern\").string"
|
||||
}
|
||||
|
||||
wait_for_output_matching() {
|
||||
local pattern=$1
|
||||
local output
|
||||
output=$(find_output_matching "$pattern")
|
||||
while [[ -z $output ]]; do
|
||||
sleep 0.5
|
||||
output=$(find_output_matching "$pattern")
|
||||
done
|
||||
echo "$output"
|
||||
}
|
||||
|
||||
OUTPUTS_TO_RECONNECT=()
|
||||
HEADLESS=
|
||||
restore_outputs() {
|
||||
[[ ${#OUTPUTS_TO_RECONNECT[@]} -ge 1 ]] || return
|
||||
echo "Restoring original output state"
|
||||
for output in "${OUTPUTS_TO_RECONNECT[@]}"; do
|
||||
echo "Re-enabling output $output"
|
||||
$SWAYMSG output "$output" enable
|
||||
done
|
||||
if [[ $SWAY_HAS_UNPLUG == true && $HEADLESS ]]; then
|
||||
local firstOutput=${OUTPUTS_TO_RECONNECT[0]}
|
||||
echo "Switching wayvnc back to physical output $firstOutput"
|
||||
wait_for_output_matching "$firstOutput" >/dev/null
|
||||
$WAYVNCCTL output-set "$firstOutput"
|
||||
echo "Removing virtual output $HEADLESS"
|
||||
$SWAYMSG output "$HEADLESS" unplug
|
||||
fi
|
||||
OUTPUTS_TO_RECONNECT=()
|
||||
HEADLESS=
|
||||
}
|
||||
trap restore_outputs EXIT
|
||||
|
||||
collapse_outputs() {
|
||||
if [[ $SWAY_HAS_UNPLUG == true ]]; then
|
||||
local preexisting="$(find_output_matching 'HEADLESS-\\d+')"
|
||||
if [[ $preexisting ]]; then
|
||||
echo "Switching to preexisting virtual output $preexisting"
|
||||
$WAYVNCCTL output-set "$preexisting"
|
||||
else
|
||||
echo "Creating a virtual display"
|
||||
$SWAYMSG create_output
|
||||
echo "Waiting for virtusl output to be created..."
|
||||
HEADLESS=$(wait_for_output_matching 'HEADLESS-\\d+')
|
||||
echo "Switching to virtual output $HEADLESS"
|
||||
$WAYVNCCTL output-set "$HEADLESS"
|
||||
fi
|
||||
fi
|
||||
for output in $($WAYVNCCTL -j output-list | jq -r '.[] | select(.captured==false).name'); do
|
||||
echo "Disabling extra output $output"
|
||||
$SWAYMSG output "$output" disable
|
||||
OUTPUTS_TO_RECONNECT+=("$output")
|
||||
done
|
||||
}
|
||||
|
||||
connection_count_now() {
|
||||
local count=$1
|
||||
if [[ $count == 1 ]]; then
|
||||
collapse_outputs
|
||||
elif [[ $count == 0 ]]; then
|
||||
restore_outputs
|
||||
fi
|
||||
}
|
||||
|
||||
while IFS= read -r EVT; do
|
||||
case "$(jq -r '.method' <<<"$EVT")" in
|
||||
client-*onnected)
|
||||
count=$(jq -r '.params.connection_count' <<<"$EVT")
|
||||
connection_count_now "$count"
|
||||
;;
|
||||
wayvnc-shutdown)
|
||||
echo "wayvncctl is no longer running"
|
||||
connection_count_now 0
|
||||
;;
|
||||
wayvnc-startup)
|
||||
echo "Ready to receive wayvnc events"
|
||||
;;
|
||||
esac
|
||||
done < <("$WAYVNCCTL" --wait --reconnect --json event-receive)
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2020 - 2021 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sys/queue.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <pixman.h>
|
||||
|
||||
struct wl_buffer;
|
||||
struct gbm_bo;
|
||||
struct nvnc_fb;
|
||||
|
||||
enum wv_buffer_type {
|
||||
WV_BUFFER_UNSPEC = 0,
|
||||
WV_BUFFER_SHM,
|
||||
#ifdef ENABLE_SCREENCOPY_DMABUF
|
||||
WV_BUFFER_DMABUF,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct wv_buffer {
|
||||
enum wv_buffer_type type;
|
||||
TAILQ_ENTRY(wv_buffer) link;
|
||||
|
||||
struct nvnc_fb* nvnc_fb;
|
||||
struct wl_buffer* wl_buffer;
|
||||
|
||||
void* pixels;
|
||||
size_t size;
|
||||
int width, height, stride;
|
||||
uint32_t format;
|
||||
bool y_inverted;
|
||||
|
||||
struct pixman_region16 damage;
|
||||
|
||||
/* The following is only applicable to DMABUF */
|
||||
struct gbm_bo* bo;
|
||||
};
|
||||
|
||||
TAILQ_HEAD(wv_buffer_queue, wv_buffer);
|
||||
|
||||
struct wv_buffer_pool {
|
||||
struct wv_buffer_queue queue;
|
||||
enum wv_buffer_type type;
|
||||
int width, height, stride;
|
||||
uint32_t format;
|
||||
};
|
||||
|
||||
enum wv_buffer_type wv_buffer_get_available_types(void);
|
||||
|
||||
struct wv_buffer* wv_buffer_create(enum wv_buffer_type, int width, int height,
|
||||
int stride, uint32_t fourcc);
|
||||
void wv_buffer_destroy(struct wv_buffer* self);
|
||||
|
||||
void wv_buffer_damage_rect(struct wv_buffer* self, int x, int y, int width,
|
||||
int height);
|
||||
void wv_buffer_damage_whole(struct wv_buffer* self);
|
||||
void wv_buffer_damage_clear(struct wv_buffer* self);
|
||||
|
||||
struct wv_buffer_pool* wv_buffer_pool_create(enum wv_buffer_type, int width,
|
||||
int height, int stride, uint32_t format);
|
||||
void wv_buffer_pool_destroy(struct wv_buffer_pool* pool);
|
||||
void wv_buffer_pool_resize(struct wv_buffer_pool* pool, enum wv_buffer_type,
|
||||
int width, int height, int stride, uint32_t format);
|
||||
struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool);
|
||||
void wv_buffer_pool_release(struct wv_buffer_pool* pool,
|
||||
struct wv_buffer* buffer);
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Andri Yngvason
|
||||
* Copyright (c) 2020 - 2023 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -21,14 +21,24 @@
|
|||
|
||||
#define X_CFG_LIST \
|
||||
X(bool, enable_auth) \
|
||||
X(bool, relax_encryption) \
|
||||
X(string, private_key_file) \
|
||||
X(string, certificate_file) \
|
||||
X(string, rsa_private_key_file) \
|
||||
X(string, username) \
|
||||
X(string, password) \
|
||||
X(string, address) \
|
||||
X(uint, port) \
|
||||
X(bool, enable_pam) \
|
||||
X(string, xkb_rules) \
|
||||
X(string, xkb_model) \
|
||||
X(string, xkb_layout) \
|
||||
X(string, xkb_variant) \
|
||||
X(string, xkb_options) \
|
||||
X(bool, use_relative_paths) \
|
||||
|
||||
struct cfg {
|
||||
char* directory;
|
||||
#define string char*
|
||||
#define uint uint32_t
|
||||
#define X(type, name) type name;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Jim Ramsay
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct ctl_client;
|
||||
struct option_parser;
|
||||
|
||||
void ctl_client_debug_log(bool enable);
|
||||
|
||||
struct ctl_client* ctl_client_new(const char* socket_path, void* userdata);
|
||||
void ctl_client_destroy(struct ctl_client*);
|
||||
void* ctl_client_userdata(struct ctl_client*);
|
||||
|
||||
#define CTL_CLIENT_PRINT_JSON (1 << 0)
|
||||
#define CTL_CLIENT_SOCKET_WAIT (1 << 1)
|
||||
#define CTL_CLIENT_RECONNECT (1 << 2)
|
||||
|
||||
int ctl_client_run_command(struct ctl_client* self,
|
||||
struct option_parser* parent_options, unsigned flags);
|
||||
|
||||
void ctl_client_print_command_list(FILE* stream);
|
||||
void ctl_client_print_event_list(FILE* stream);
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023 Jim Ramsay
|
||||
* Copyright (c) 2023 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
enum cmd_type {
|
||||
CMD_ATTACH,
|
||||
CMD_DETACH,
|
||||
CMD_HELP,
|
||||
CMD_EVENT_RECEIVE,
|
||||
CMD_CLIENT_LIST,
|
||||
CMD_CLIENT_DISCONNECT,
|
||||
CMD_OUTPUT_LIST,
|
||||
CMD_OUTPUT_CYCLE,
|
||||
CMD_OUTPUT_SET,
|
||||
CMD_VERSION,
|
||||
CMD_WAYVNC_EXIT,
|
||||
CMD_UNKNOWN,
|
||||
};
|
||||
#define CMD_LIST_LEN CMD_UNKNOWN
|
||||
|
||||
enum event_type {
|
||||
EVT_CAPTURE_CHANGED,
|
||||
EVT_CLIENT_CONNECTED,
|
||||
EVT_CLIENT_DISCONNECTED,
|
||||
EVT_DETACHED,
|
||||
EVT_OUTPUT_ADDED,
|
||||
EVT_OUTPUT_REMOVED,
|
||||
EVT_UNKNOWN,
|
||||
};
|
||||
#define EVT_LIST_LEN EVT_UNKNOWN
|
||||
|
||||
struct cmd_param_info {
|
||||
char* name;
|
||||
char* description;
|
||||
char* schema;
|
||||
bool positional;
|
||||
};
|
||||
|
||||
struct cmd_info {
|
||||
char* name;
|
||||
char* description;
|
||||
struct cmd_param_info params[5];
|
||||
};
|
||||
|
||||
enum cmd_type ctl_command_parse_name(const char* name);
|
||||
struct cmd_info* ctl_command_by_type(enum cmd_type type);
|
||||
struct cmd_info* ctl_command_by_name(const char* name);
|
||||
|
||||
enum event_type ctl_event_parse_name(const char* name);
|
||||
struct cmd_info* ctl_event_by_type(enum event_type type);
|
||||
struct cmd_info* ctl_event_by_name(const char* name);
|
||||
|
||||
extern struct cmd_info ctl_command_list[];
|
||||
extern struct cmd_info ctl_event_list[];
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Jim Ramsay
|
||||
* Copyright (c) 2023 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "output.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
struct ctl;
|
||||
struct cmd_response;
|
||||
|
||||
struct ctl_server_client;
|
||||
|
||||
struct ctl_server_client_info {
|
||||
int id;
|
||||
union {
|
||||
struct sockaddr_storage address_storage;
|
||||
struct sockaddr address;
|
||||
};
|
||||
const char* username;
|
||||
const char* seat;
|
||||
};
|
||||
|
||||
struct ctl_server_output {
|
||||
char name[65];
|
||||
char description[128];
|
||||
unsigned height;
|
||||
unsigned width;
|
||||
bool captured;
|
||||
char power[8];
|
||||
};
|
||||
|
||||
struct ctl_server_actions {
|
||||
void* userdata;
|
||||
struct cmd_response* (*on_attach)(struct ctl*, const char* display);
|
||||
struct cmd_response* (*on_detach)(struct ctl*);
|
||||
struct cmd_response* (*on_output_cycle)(struct ctl*,
|
||||
enum output_cycle_direction direction);
|
||||
struct cmd_response* (*on_output_switch)(struct ctl*,
|
||||
const char* output_name);
|
||||
struct cmd_response* (*on_disconnect_client)(struct ctl*,
|
||||
const char* id);
|
||||
struct cmd_response* (*on_wayvnc_exit)(struct ctl*);
|
||||
|
||||
struct ctl_server_client *(*client_next)(struct ctl*,
|
||||
struct ctl_server_client* prev);
|
||||
void (*client_info)(const struct ctl_server_client*,
|
||||
struct ctl_server_client_info* info);
|
||||
|
||||
// Return number of elements created
|
||||
// Allocate 'outputs' array or set to NULL if none
|
||||
// Receiver will free(outputs) when done.
|
||||
int (*get_output_list)(struct ctl*,
|
||||
struct ctl_server_output** outputs);
|
||||
};
|
||||
|
||||
struct ctl* ctl_server_new(const char* socket_path,
|
||||
const struct ctl_server_actions* actions);
|
||||
void ctl_server_destroy(struct ctl*);
|
||||
void* ctl_server_userdata(struct ctl*);
|
||||
|
||||
struct cmd_response* cmd_ok(void);
|
||||
struct cmd_response* cmd_failed(const char* fmt, ...);
|
||||
|
||||
void ctl_server_event_connected(struct ctl*,
|
||||
const struct ctl_server_client_info *info,
|
||||
int new_connection_count);
|
||||
|
||||
void ctl_server_event_disconnected(struct ctl*,
|
||||
const struct ctl_server_client_info *info,
|
||||
int new_connection_count);
|
||||
|
||||
void ctl_server_event_capture_changed(struct ctl*,
|
||||
const char* captured_output);
|
||||
|
||||
void ctl_server_event_detached(struct ctl*);
|
||||
|
||||
void ctl_server_event_output_added(struct ctl*, const char* name);
|
||||
void ctl_server_event_output_removed(struct ctl*, const char* name);
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Scott Moreau
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <neatvnc.h>
|
||||
|
||||
#include "wlr-data-control-unstable-v1.h"
|
||||
|
||||
struct data_control {
|
||||
struct wl_display* wl_display;
|
||||
struct nvnc* server;
|
||||
struct zwlr_data_control_manager_v1* manager;
|
||||
struct zwlr_data_control_device_v1* device;
|
||||
struct zwlr_data_control_source_v1* selection;
|
||||
struct zwlr_data_control_source_v1* primary_selection;
|
||||
struct zwlr_data_control_offer_v1* offer;
|
||||
const char* mime_type;
|
||||
char* cb_data;
|
||||
size_t cb_len;
|
||||
};
|
||||
|
||||
void data_control_init(struct data_control* self, struct wl_display* wl_display, struct nvnc* server, struct wl_seat* seat);
|
||||
void data_control_destroy(struct data_control* self);
|
||||
void data_control_to_clipboard(struct data_control* self, const char* text, size_t len);
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct wl_output;
|
||||
|
||||
enum frame_capture_status {
|
||||
CAPTURE_STOPPED = 0,
|
||||
CAPTURE_IN_PROGRESS,
|
||||
CAPTURE_FAILED,
|
||||
CAPTURE_FATAL,
|
||||
CAPTURE_DONE,
|
||||
};
|
||||
|
||||
struct frame_capture {
|
||||
enum frame_capture_status status;
|
||||
|
||||
bool overlay_cursor;
|
||||
struct wl_output* wl_output;
|
||||
|
||||
void* userdata;
|
||||
void (*on_done)(struct frame_capture*);
|
||||
|
||||
struct {
|
||||
uint32_t fourcc_format;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t stride;
|
||||
} frame_info;
|
||||
|
||||
struct {
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
} damage_hint;
|
||||
|
||||
struct {
|
||||
int (*start)(struct frame_capture*);
|
||||
void (*stop)(struct frame_capture*);
|
||||
} backend;
|
||||
};
|
||||
|
||||
static inline int frame_capture_start(struct frame_capture* self)
|
||||
{
|
||||
return self->backend.start(self);
|
||||
}
|
||||
|
||||
static inline void frame_capture_stop(struct frame_capture* self)
|
||||
{
|
||||
self->backend.stop(self);
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Jim Ramsay
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
struct jsonipc_request {
|
||||
const char* method;
|
||||
json_t* params;
|
||||
json_t* id;
|
||||
|
||||
json_t* json;
|
||||
};
|
||||
|
||||
#define IPC_CODE_SUCCESS 0
|
||||
|
||||
struct jsonipc_error {
|
||||
int code;
|
||||
json_t* data;
|
||||
};
|
||||
|
||||
#define JSONIPC_ERR_INIT {0,NULL}
|
||||
|
||||
struct jsonipc_response {
|
||||
int code;
|
||||
json_t* data;
|
||||
json_t* id;
|
||||
|
||||
json_t* json;
|
||||
};
|
||||
|
||||
void jsonipc_error_set_new(struct jsonipc_error*, int code, json_t* data);
|
||||
void jsonipc_error_printf(struct jsonipc_error*, int code, const char* fmt, ...);
|
||||
void jsonipc_error_set_from_errno(struct jsonipc_error*, const char* context);
|
||||
void jsonipc_error_cleanup(struct jsonipc_error*);
|
||||
|
||||
struct jsonipc_request* jsonipc_request_parse_new(json_t* root,
|
||||
struct jsonipc_error* err);
|
||||
struct jsonipc_request* jsonipc_request_new(const char* method, json_t* params);
|
||||
struct jsonipc_request* jsonipc_event_new(const char* method, json_t* params);
|
||||
struct jsonipc_request* jsonipc_event_parse_new(json_t* root,
|
||||
struct jsonipc_error* err);
|
||||
json_t* jsonipc_request_pack(struct jsonipc_request*, json_error_t* err);
|
||||
void jsonipc_request_destroy(struct jsonipc_request*);
|
||||
|
||||
struct jsonipc_response* jsonipc_response_parse_new(json_t* root,
|
||||
struct jsonipc_error* err);
|
||||
struct jsonipc_response* jsonipc_response_new(int code, json_t* data,
|
||||
json_t* id);
|
||||
struct jsonipc_response* jsonipc_error_response_new(struct jsonipc_error* err,
|
||||
json_t* id);
|
||||
void jsonipc_response_destroy(struct jsonipc_response*);
|
||||
json_t* jsonipc_response_pack(struct jsonipc_response*, json_error_t* err);
|
||||
|
||||
json_t* jprintf(const char* fmt, ...);
|
||||
json_t* jvprintf(const char* fmt, va_list ap);
|
|
@ -19,11 +19,13 @@
|
|||
#include <stdlib.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <stdbool.h>
|
||||
#include <neatvnc.h>
|
||||
|
||||
#include "intset.h"
|
||||
|
||||
struct zwp_virtual_keyboard_v1;
|
||||
struct table_entry;
|
||||
struct nvnc;
|
||||
|
||||
struct keyboard {
|
||||
struct zwp_virtual_keyboard_v1* virtual_keyboard;
|
||||
|
@ -39,6 +41,9 @@ struct keyboard {
|
|||
struct intset key_state;
|
||||
};
|
||||
|
||||
int keyboard_init(struct keyboard* self, const char* layout);
|
||||
int keyboard_init(struct keyboard* self, const struct xkb_rule_names* rule_names);
|
||||
void keyboard_destroy(struct keyboard* self);
|
||||
void keyboard_feed(struct keyboard* self, xkb_keysym_t symbol, bool is_pressed);
|
||||
void keyboard_feed_code(struct keyboard* self, xkb_keycode_t code,
|
||||
bool is_pressed);
|
||||
enum nvnc_keyboard_led_state keyboard_get_led_state(const struct keyboard*);
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct wv_option {
|
||||
char short_opt;
|
||||
const char* long_opt;
|
||||
const char* schema;
|
||||
const char* help;
|
||||
const char* default_;
|
||||
const char* positional;
|
||||
bool is_subcommand;
|
||||
};
|
||||
|
||||
struct wv_option_value {
|
||||
const struct wv_option* option;
|
||||
char value[256];
|
||||
};
|
||||
|
||||
struct option_parser {
|
||||
const char* name;
|
||||
const struct wv_option* options;
|
||||
int n_opts;
|
||||
|
||||
struct wv_option_value values[128];
|
||||
int n_values;
|
||||
int position;
|
||||
|
||||
size_t remaining_argc;
|
||||
const char* const* remaining_argv;
|
||||
};
|
||||
|
||||
void option_parser_init(struct option_parser* self,
|
||||
const struct wv_option* options);
|
||||
|
||||
void option_parser_print_usage(struct option_parser* self, FILE* stream);
|
||||
|
||||
int option_parser_print_arguments(struct option_parser* self, FILE* stream);
|
||||
|
||||
void option_parser_print_options(struct option_parser* self, FILE* stream);
|
||||
|
||||
int option_parser_parse(struct option_parser* self, int argc,
|
||||
const char* const* argv);
|
||||
|
||||
const char* option_parser_get_value(const struct option_parser* self,
|
||||
const char* name);
|
||||
const char* option_parser_get_value_no_default(const struct option_parser* self,
|
||||
const char* name);
|
||||
|
||||
void option_parser_print_cmd_summary(const char* summary, FILE* stream);
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Andri Yngvason
|
||||
* Copyright (c) 2023 The wayvnc authors
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -18,39 +18,11 @@
|
|||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <uv.h>
|
||||
#include "frame-capture.h"
|
||||
|
||||
struct zwlr_export_dmabuf_manager_v1;
|
||||
struct zwlr_export_dmabuf_frame_v1;
|
||||
struct wl_output;
|
||||
struct output;
|
||||
struct zwlr_output_manager_v1;
|
||||
|
||||
struct dmabuf_plane {
|
||||
int fd;
|
||||
uint32_t offset;
|
||||
uint32_t size;
|
||||
uint32_t pitch;
|
||||
uint64_t modifier;
|
||||
};
|
||||
|
||||
struct dmabuf_frame {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t format;
|
||||
|
||||
uint32_t n_planes;
|
||||
struct dmabuf_plane plane[4];
|
||||
};
|
||||
|
||||
struct dmabuf_capture {
|
||||
struct frame_capture fc;
|
||||
|
||||
struct zwlr_export_dmabuf_manager_v1* manager;
|
||||
struct zwlr_export_dmabuf_frame_v1* zwlr_frame;
|
||||
struct dmabuf_frame frame;
|
||||
|
||||
uint64_t last_time;
|
||||
uv_timer_t timer;
|
||||
};
|
||||
|
||||
void dmabuf_capture_init(struct dmabuf_capture* self);
|
||||
void wlr_output_manager_setup(struct zwlr_output_manager_v1* output_manager);
|
||||
bool wlr_output_manager_resize_output(struct output* output,
|
||||
uint16_t width, uint16_t height);
|
||||
void wlr_output_manager_destroy(void);
|
|
@ -18,12 +18,25 @@
|
|||
|
||||
#include <wayland-client.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct zxdg_output_manager_v1;
|
||||
struct zxdg_output_v1;
|
||||
struct zwlr_output_power_manager_v1;
|
||||
struct zwlr_output_power_v1;
|
||||
|
||||
enum output_power_state {
|
||||
OUTPUT_POWER_UNKNOWN = 0,
|
||||
OUTPUT_POWER_OFF,
|
||||
OUTPUT_POWER_ON,
|
||||
};
|
||||
|
||||
const char* output_power_state_name(enum output_power_state state);
|
||||
|
||||
struct output {
|
||||
struct wl_output* wl_output;
|
||||
struct zxdg_output_v1* xdg_output;
|
||||
struct zwlr_output_power_v1* wlr_output_power;
|
||||
struct wl_list link;
|
||||
|
||||
uint32_t id;
|
||||
|
@ -34,17 +47,50 @@ struct output {
|
|||
uint32_t x;
|
||||
uint32_t y;
|
||||
|
||||
enum wl_output_transform transform;
|
||||
|
||||
char make[256];
|
||||
char model[256];
|
||||
char name[256];
|
||||
char description[256];
|
||||
enum output_power_state power;
|
||||
|
||||
bool is_dimension_changed;
|
||||
bool is_transform_changed;
|
||||
bool is_headless;
|
||||
|
||||
void (*on_dimension_change)(struct output*);
|
||||
void (*on_transform_change)(struct output*);
|
||||
void (*on_power_change)(struct output*);
|
||||
|
||||
void* userdata;
|
||||
};
|
||||
|
||||
struct output* output_new(struct wl_output* wl_output, uint32_t id);
|
||||
void output_destroy(struct output* output);
|
||||
void output_set_xdg_output(struct output* output,
|
||||
struct zxdg_output_v1* xdg_output);
|
||||
void output_setup_wl_managers(struct wl_list* list);
|
||||
int output_set_power_state(struct output* output, enum output_power_state state);
|
||||
void output_list_destroy(struct wl_list* list);
|
||||
struct output* output_find_by_id(struct wl_list* list, uint32_t id);
|
||||
struct output* output_find_by_name(struct wl_list* list, const char* name);
|
||||
struct output* output_first(struct wl_list* list);
|
||||
|
||||
enum output_cycle_direction {
|
||||
OUTPUT_CYCLE_FORWARD,
|
||||
OUTPUT_CYCLE_REVERSE,
|
||||
};
|
||||
struct output* output_cycle(const struct wl_list* list,
|
||||
const struct output* current,
|
||||
enum output_cycle_direction);
|
||||
|
||||
uint32_t output_get_transformed_width(const struct output* self);
|
||||
uint32_t output_get_transformed_height(const struct output* self);
|
||||
|
||||
void output_transform_coord(const struct output* self,
|
||||
uint32_t src_x, uint32_t src_y,
|
||||
uint32_t* dst_x, uint32_t* dst_y);
|
||||
void output_transform_box_coord(const struct output* self,
|
||||
uint32_t src_x0, uint32_t src_y0,
|
||||
uint32_t src_x1, uint32_t src_y1,
|
||||
uint32_t* dst_x0, uint32_t* dst_y0,
|
||||
uint32_t* dst_x1, uint32_t* dst_y1);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Andri Yngvason
|
||||
* Copyright (c) 2020 Nicholas Sica
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -16,12 +16,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define log_debug(...)
|
||||
#else
|
||||
#define log_debug(...) fprintf(stderr, "DEBUG: " __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#define log_error(...) fprintf(stderr, "ERROR: " __VA_ARGS__)
|
||||
bool pam_auth(const char* username, const char* password);
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum wl_shm_format fourcc_to_wl_shm(uint32_t in);
|
||||
uint32_t fourcc_from_wl_shm(enum wl_shm_format in);
|
||||
int pixel_size_from_fourcc(uint32_t fourcc);
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
#include <EGL/egl.h>
|
||||
|
||||
struct dmabuf_frame;
|
||||
|
||||
struct renderer {
|
||||
EGLDisplay display;
|
||||
EGLSurface surface;
|
||||
EGLContext context;
|
||||
GLuint dmabuf_shader_program;
|
||||
GLuint texture_shader_program;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
GLint read_format;
|
||||
GLint read_type;
|
||||
};
|
||||
|
||||
int renderer_init(struct renderer* self, uint32_t width, uint32_t height);
|
||||
void renderer_destroy(struct renderer* self);
|
||||
|
||||
int render_dmabuf_frame(struct renderer* self, struct dmabuf_frame* frame);
|
||||
int render_framebuffer(struct renderer* self, const void* addr, uint32_t format,
|
||||
uint32_t width, uint32_t height, uint32_t stride);
|
||||
|
||||
/* Copy a horizontal stripe from the GL frame into a pixel buffer */
|
||||
void render_copy_pixels(struct renderer* self, void* dst, uint32_t y,
|
||||
uint32_t height);
|
|
@ -1,44 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <uv.h>
|
||||
|
||||
#include "wlr-screencopy-unstable-v1.h"
|
||||
#include "frame-capture.h"
|
||||
#include "smooth.h"
|
||||
#include "buffer.h"
|
||||
|
||||
struct zwlr_screencopy_manager_v1;
|
||||
struct zwlr_screencopy_frame_v1;
|
||||
struct wl_output;
|
||||
struct wl_buffer;
|
||||
struct wl_shm;
|
||||
struct aml_timer;
|
||||
struct renderer;
|
||||
|
||||
enum screencopy_status {
|
||||
SCREENCOPY_STATUS_CAPTURING = 0,
|
||||
SCREENCOPY_STATUS_FATAL,
|
||||
SCREENCOPY_STATUS_FAILED,
|
||||
SCREENCOPY_STATUS_DONE,
|
||||
SCREENCOPY_STOPPED = 0,
|
||||
SCREENCOPY_IN_PROGRESS,
|
||||
SCREENCOPY_FAILED,
|
||||
SCREENCOPY_FATAL,
|
||||
SCREENCOPY_DONE,
|
||||
};
|
||||
|
||||
struct screencopy {
|
||||
struct frame_capture frame_capture;
|
||||
enum screencopy_status status;
|
||||
|
||||
struct wl_shm* wl_shm;
|
||||
struct wl_buffer* buffer;
|
||||
|
||||
void* pixels;
|
||||
size_t bufsize;
|
||||
struct wv_buffer_pool* pool;
|
||||
struct wv_buffer* front;
|
||||
struct wv_buffer* back;
|
||||
|
||||
struct zwlr_screencopy_manager_v1* manager;
|
||||
struct zwlr_screencopy_frame_v1* frame;
|
||||
|
||||
void* userdata;
|
||||
void (*on_done)(struct screencopy*);
|
||||
|
||||
uint64_t last_time;
|
||||
uint64_t start_time;
|
||||
uv_timer_t timer;
|
||||
struct aml_timer* timer;
|
||||
|
||||
struct smooth delay_smoother;
|
||||
double delay;
|
||||
bool is_immediate_copy;
|
||||
bool overlay_cursor;
|
||||
struct wl_output* wl_output;
|
||||
|
||||
uint32_t wl_shm_width, wl_shm_height, wl_shm_stride;
|
||||
enum wl_shm_format wl_shm_format;
|
||||
|
||||
bool have_linux_dmabuf;
|
||||
bool enable_linux_dmabuf;
|
||||
uint32_t dmabuf_width, dmabuf_height;
|
||||
uint32_t fourcc;
|
||||
|
||||
double rate_limit;
|
||||
};
|
||||
|
||||
void screencopy_init(struct screencopy* self);
|
||||
void screencopy_destroy(struct screencopy* self);
|
||||
|
||||
int screencopy_start(struct screencopy* self);
|
||||
int screencopy_start_immediate(struct screencopy* self);
|
||||
|
||||
void screencopy_stop(struct screencopy* self);
|
||||
|
|
|
@ -26,6 +26,8 @@ struct seat {
|
|||
uint32_t id;
|
||||
uint32_t capabilities;
|
||||
char name[256];
|
||||
|
||||
uint32_t occupancy;
|
||||
};
|
||||
|
||||
struct seat* seat_new(struct wl_seat* wl_seat, uint32_t id);
|
||||
|
@ -34,4 +36,5 @@ void seat_list_destroy(struct wl_list* list);
|
|||
|
||||
struct seat* seat_find_by_name(struct wl_list* list, const char* name);
|
||||
struct seat* seat_find_by_id(struct wl_list* list, uint32_t id);
|
||||
struct seat* seat_find_unoccupied(struct wl_list* list);
|
||||
struct seat* seat_first(struct wl_list* list);
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
int shm_alloc_fd(size_t size);
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Jim Ramsay
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
struct table_printer{
|
||||
FILE* stream;
|
||||
int max_width;
|
||||
int left_indent;
|
||||
int left_width;
|
||||
int column_offset;
|
||||
};
|
||||
|
||||
// Sets default values for every subsequent table_printer_new (Optional: defaults to 80/4/8)
|
||||
void table_printer_set_defaults(int max_width, int left_indent,
|
||||
int column_offset);
|
||||
|
||||
void table_printer_init(struct table_printer* self, FILE* stream,
|
||||
int left_width);
|
||||
|
||||
void table_printer_print_line(struct table_printer* self, const char* left_text,
|
||||
const char* right_text);
|
||||
|
||||
void table_printer_print_fmtline(struct table_printer* self,
|
||||
const char* right_text,
|
||||
const char* left_format, ...);
|
||||
|
||||
int table_printer_reflow_text(char* dst, int dst_size, const char* src,
|
||||
int width);
|
||||
|
||||
void table_printer_indent_and_reflow_text(FILE* stream, const char* src,
|
||||
int width, int first_line_indent, int subsequent_indent);
|
||||
|
|
@ -19,16 +19,28 @@
|
|||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static inline uint64_t timespec_to_us(const struct timespec* ts)
|
||||
{
|
||||
return (uint64_t)ts->tv_sec * UINT64_C(1000000) +
|
||||
(uint64_t)ts->tv_nsec / UINT64_C(1000);
|
||||
}
|
||||
|
||||
static inline uint64_t timespec_to_ms(const struct timespec* ts)
|
||||
{
|
||||
return (uint64_t)ts->tv_sec * UINT64_C(1000) +
|
||||
(uint64_t)ts->tv_nsec / UINT64_C(1000000);
|
||||
}
|
||||
|
||||
static inline uint64_t gettime_us(void)
|
||||
{
|
||||
struct timespec ts = { 0 };
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return ts.tv_sec * 1000000ULL + (double)ts.tv_nsec / 1000ULL;
|
||||
return timespec_to_us(&ts);
|
||||
}
|
||||
|
||||
static inline uint32_t gettime_ms(void)
|
||||
static inline uint64_t gettime_ms(void)
|
||||
{
|
||||
struct timespec ts = { 0 };
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return ts.tv_sec * 1000UL + ts.tv_nsec / 1000000UL;
|
||||
return timespec_to_ms(&ts);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <pixman.h>
|
||||
|
||||
void wv_region_transform(struct pixman_region16 *dst,
|
||||
struct pixman_region16 *src, enum wl_output_transform transform,
|
||||
int width, int height);
|
||||
|
||||
void wv_pixman_transform_from_wl_output_transform(pixman_transform_t* dst,
|
||||
enum wl_output_transform src, int width, int height);
|
||||
|
||||
enum wl_output_transform wv_output_transform_invert(enum wl_output_transform tr);
|
||||
enum wl_output_transform wv_output_transform_compose(
|
||||
enum wl_output_transform tr_a, enum wl_output_transform tr_b);
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#define XSTR(s) STR(s)
|
||||
#define STR(s) #s
|
||||
|
||||
#define ASSERT_TRUE(expr) do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be true\n"); \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_FALSE(expr) do { \
|
||||
if (expr) { \
|
||||
fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be false\n"); \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define TST_ASSERT_EQ_(value, expr, type, fmt) do { \
|
||||
type expr_ = (expr); \
|
||||
if (expr_ != (value)) { \
|
||||
fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be equal to " XSTR(value) "; was " fmt "\n", \
|
||||
expr_); \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_INT_EQ(value, expr) TST_ASSERT_EQ_(value, expr, int, "%d")
|
||||
#define ASSERT_UINT_EQ(value, expr) TST_ASSERT_EQ_(value, expr, unsigned int, "%u")
|
||||
#define ASSERT_INT32_EQ(value, expr) TST_ASSERT_EQ_(value, expr, int32_t, "%" PRIi32)
|
||||
#define ASSERT_UINT32_EQ(value, expr) TST_ASSERT_EQ_(value, expr, uint32_t, "%" PRIu32)
|
||||
#define ASSERT_DOUBLE_EQ(value, expr) TST_ASSERT_EQ_(value, expr, double, "%f")
|
||||
#define ASSERT_PTR_EQ(value, expr) TST_ASSERT_EQ_(value, expr, void*, "%p")
|
||||
|
||||
#define TST_ASSERT_GT_(value, expr, type, fmt) do { \
|
||||
type expr_ = (expr); \
|
||||
if (!(expr_ > (value))) { \
|
||||
fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be greater than " XSTR(value) "; was " fmt "\n", \
|
||||
expr_); \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_INT_GT(value, expr) TST_ASSERT_GT_(value, expr, int, "%d")
|
||||
#define ASSERT_UINT_GT(value, expr) TST_ASSERT_GT_(value, expr, unsigned int, "%u")
|
||||
#define ASSERT_INT32_GT(value, expr) TST_ASSERT_GT_(value, expr, int32_t, "%" PRIi32)
|
||||
#define ASSERT_UINT32_GT(value, expr) TST_ASSERT_GT_(value, expr, uint32_t, "%" PRIu32)
|
||||
#define ASSERT_DOUBLE_GT(value, expr) TST_ASSERT_GT_(value, expr, double, "%f")
|
||||
|
||||
#define TST_ASSERT_GE_(value, expr, type, fmt) do { \
|
||||
type expr_ = (expr); \
|
||||
if (!(expr_ >= (value))) { \
|
||||
fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be greater than or equal to " XSTR(value) "; was " fmt "\n", \
|
||||
expr_); \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_INT_GE(value, expr) TST_ASSERT_GE_(value, expr, int, "%d")
|
||||
#define ASSERT_UINT_GE(value, expr) TST_ASSERT_GE_(value, expr, unsigned int, "%u")
|
||||
#define ASSERT_INT32_GE(value, expr) TST_ASSERT_GE_(value, expr, int32_t, "%" PRIi32)
|
||||
#define ASSERT_UINT32_GE(value, expr) TST_ASSERT_GE_(value, expr, uint32_t, "%" PRIu32)
|
||||
#define ASSERT_DOUBLE_GE(value, expr) TST_ASSERT_GE_(value, expr, double, "%f")
|
||||
|
||||
#define TST_ASSERT_LT_(value, expr, type, fmt) do { \
|
||||
type expr_ = (expr); \
|
||||
if (!(expr_ < (value))) { \
|
||||
fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be less than " XSTR(value) "; was " fmt "\n", \
|
||||
expr_); \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_INT_LT(value, expr) TST_ASSERT_LT_(value, expr, int, "%d")
|
||||
#define ASSERT_UINT_LT(value, expr) TST_ASSERT_LT_(value, expr, unsigned int, "%u")
|
||||
#define ASSERT_INT32_LT(value, expr) TST_ASSERT_LT_(value, expr, int32_t, "%" PRIi32)
|
||||
#define ASSERT_UINT32_LT(value, expr) TST_ASSERT_LT_(value, expr, uint32_t, "%" PRIu32)
|
||||
#define ASSERT_DOUBLE_LT(value, expr) TST_ASSERT_LT_(value, expr, double, "%f")
|
||||
|
||||
#define TST_ASSERT_LE_(value, expr, type, fmt) do { \
|
||||
type expr_ = (expr); \
|
||||
if (!(expr_ <= (value))) { \
|
||||
fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be less than or equal to " XSTR(value) "; was " fmt "\n", \
|
||||
expr_); \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_INT_LE(value, expr) TST_ASSERT_LE_(value, expr, int, "%d")
|
||||
#define ASSERT_UINT_LE(value, expr) TST_ASSERT_LE_(value, expr, unsigned int, "%u")
|
||||
#define ASSERT_INT32_LE(value, expr) TST_ASSERT_LE_(value, expr, int32_t, "%" PRIi32)
|
||||
#define ASSERT_UINT32_LE(value, expr) TST_ASSERT_LE_(value, expr, uint32_t, "%" PRIu32)
|
||||
#define ASSERT_DOUBLE_LE(value, expr) TST_ASSERT_LE_(value, expr, double, "%f")
|
||||
|
||||
#define ASSERT_STR_EQ(value, expr) do { \
|
||||
const char* expr_ = (expr); \
|
||||
if (strcmp(expr_, (value)) != 0) { \
|
||||
fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to be " XSTR(value) "; was \"%s\"\n", \
|
||||
expr_); \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_NEQ(value, expr) do { \
|
||||
if ((expr) != (value)) { \
|
||||
fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to NOT be " XSTR(value) "\n"); \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_STR_NEQ(value, expr) do { \
|
||||
if (strcmp((expr), (value)) == 0) { \
|
||||
fprintf(stderr, "FAILED " XSTR(__LINE__) ": Expected " XSTR(expr) " to NOT be " XSTR(value) "\n"); \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define RUN_TEST(test) do { \
|
||||
if(!(test())) \
|
||||
fprintf(stderr, XSTR(test) " passed\n"); \
|
||||
else \
|
||||
r = 1; \
|
||||
} while(0);
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_USDT
|
||||
#include <sys/sdt.h>
|
||||
#else
|
||||
#define DTRACE_PROBE(...)
|
||||
#define DTRACE_PROBE1(...)
|
||||
#define DTRACE_PROBE2(...)
|
||||
#define DTRACE_PROBE3(...)
|
||||
#define DTRACE_PROBE4(...)
|
||||
#define DTRACE_PROBE5(...)
|
||||
#define DTRACE_PROBE6(...)
|
||||
#endif
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
|
||||
#define ALIGN_UP(a, b) ((b) * UDIV_UP((a), (b)))
|
||||
|
||||
extern const char* wayvnc_version;
|
||||
|
||||
const char* default_ctl_socket_path();
|
||||
|
||||
void advance_read_buffer(char (*buffer)[], size_t* current_len, size_t advance_by);
|
171
meson.build
171
meson.build
|
@ -1,20 +1,44 @@
|
|||
project(
|
||||
'wayvnc',
|
||||
'c',
|
||||
version: '0.0.0',
|
||||
version: '0.9-dev',
|
||||
license: 'ISC',
|
||||
default_options: [
|
||||
'c_std=gnu11',
|
||||
'warning_level=2',
|
||||
],
|
||||
)
|
||||
|
||||
buildtype = get_option('buildtype')
|
||||
host_system = host_machine.system()
|
||||
prefix = get_option('prefix')
|
||||
|
||||
c_args = [
|
||||
'-D_GNU_SOURCE',
|
||||
'-DAML_UNSTABLE_API=1',
|
||||
'-DWLR_USE_UNSTABLE=true',
|
||||
|
||||
'-Wno-unused-parameter',
|
||||
'-Wno-missing-field-initializers',
|
||||
]
|
||||
|
||||
if buildtype == 'release' or buildtype == 'plain'
|
||||
version = '"@0@"'.format(meson.project_version())
|
||||
git = find_program('git', native: true, required: false)
|
||||
if git.found()
|
||||
git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'], check: false)
|
||||
git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false)
|
||||
if git_commit.returncode() == 0 and git_branch.returncode() == 0
|
||||
version = '"v@0@-@1@ (@2@)"'.format(
|
||||
meson.project_version(),
|
||||
git_commit.stdout().strip(),
|
||||
git_branch.stdout().strip(),
|
||||
)
|
||||
endif
|
||||
endif
|
||||
|
||||
add_project_arguments('-DPROJECT_VERSION=@0@'.format(version), language: 'c')
|
||||
|
||||
if buildtype != 'debug' and buildtype != 'debugoptimized'
|
||||
c_args += '-DNDEBUG'
|
||||
endif
|
||||
|
||||
|
@ -23,57 +47,142 @@ add_project_arguments(c_args, language: 'c')
|
|||
cc = meson.get_compiler('c')
|
||||
|
||||
libm = cc.find_library('m', required: false)
|
||||
librt = cc.find_library('rt', required: false)
|
||||
libpam = cc.find_library('pam', required: get_option('pam'))
|
||||
|
||||
pixman = dependency('pixman-1')
|
||||
libuv = dependency('libuv')
|
||||
egl = dependency('egl')
|
||||
glesv2 = dependency('glesv2')
|
||||
xkbcommon = dependency('xkbcommon')
|
||||
gbm = dependency('gbm', required: get_option('screencopy-dmabuf'))
|
||||
drm = dependency('libdrm')
|
||||
xkbcommon = dependency('xkbcommon', version: '>=1.0.0')
|
||||
wayland_server = dependency('wayland-server')
|
||||
wayland_client = dependency('wayland-client')
|
||||
wayland_client_protocol = dependency('wayland-protocols')
|
||||
wayland_cursor = dependency('wayland-cursor')
|
||||
jansson = dependency('jansson')
|
||||
|
||||
# Cursor image
|
||||
x11_dep = dependency('x11')
|
||||
x11_fixes_dep = dependency('xfixes')
|
||||
|
||||
aml_version = ['>=0.3.0', '<0.4.0']
|
||||
neatvnc_version = ['>=0.9', '<0.10.0']
|
||||
|
||||
neatvnc_project = subproject(
|
||||
'neatvnc',
|
||||
required: false,
|
||||
version: neatvnc_version,
|
||||
)
|
||||
|
||||
aml_project = subproject('aml', required: false, version: aml_version)
|
||||
if aml_project.found()
|
||||
aml = aml_project.get_variable('aml_dep')
|
||||
else
|
||||
aml = dependency('aml', version: aml_version)
|
||||
endif
|
||||
|
||||
if neatvnc_project.found()
|
||||
neatvnc = neatvnc_project.get_variable('neatvnc_dep')
|
||||
else
|
||||
neatvnc = dependency('neatvnc')
|
||||
neatvnc = dependency('neatvnc', version: neatvnc_version)
|
||||
endif
|
||||
|
||||
inc = include_directories('include')
|
||||
inc = include_directories('include', '/usr/include/wlroots0.16')
|
||||
|
||||
subdir('protocols')
|
||||
|
||||
sources = [
|
||||
'src/main.c',
|
||||
'src/render.c',
|
||||
'src/dmabuf.c',
|
||||
'src/strlcpy.c',
|
||||
'src/shm.c',
|
||||
'src/screencopy.c',
|
||||
'src/data-control.c',
|
||||
'src/output.c',
|
||||
'src/output-management.c',
|
||||
'src/pointer.c',
|
||||
'src/keyboard.c',
|
||||
'src/seat.c',
|
||||
'src/smooth.c',
|
||||
'src/cfg.c',
|
||||
'src/intset.c',
|
||||
'src/buffer.c',
|
||||
'src/pixels.c',
|
||||
'src/transform-util.c',
|
||||
'src/util.c',
|
||||
'src/json-ipc.c',
|
||||
'src/ctl-server.c',
|
||||
'src/ctl-commands.c',
|
||||
'src/option-parser.c',
|
||||
'src/table-printer.c',
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
libm,
|
||||
librt,
|
||||
pixman,
|
||||
libuv,
|
||||
egl,
|
||||
glesv2,
|
||||
aml,
|
||||
gbm,
|
||||
drm,
|
||||
wayland_client,
|
||||
neatvnc,
|
||||
xkbcommon,
|
||||
client_protos,
|
||||
jansson,
|
||||
x11_dep,
|
||||
x11_fixes_dep,
|
||||
wayland_client_protocol,
|
||||
wayland_cursor,
|
||||
wayland_server
|
||||
]
|
||||
|
||||
ctlsources = [
|
||||
'src/wayvncctl.c',
|
||||
'src/util.c',
|
||||
'src/json-ipc.c',
|
||||
'src/ctl-client.c',
|
||||
'src/ctl-commands.c',
|
||||
'src/strlcpy.c',
|
||||
'src/option-parser.c',
|
||||
'src/table-printer.c',
|
||||
]
|
||||
|
||||
ctldependencies = [
|
||||
jansson,
|
||||
]
|
||||
|
||||
config = configuration_data()
|
||||
|
||||
config.set('PREFIX', '"' + prefix + '"')
|
||||
|
||||
if host_system == 'linux' and get_option('systemtap') and cc.has_header('sys/sdt.h')
|
||||
config.set('HAVE_USDT', true)
|
||||
endif
|
||||
|
||||
if cc.has_header('linux/dma-heap.h') and cc.has_header('linux/dma-buf.h')
|
||||
config.set('HAVE_LINUX_DMA_HEAP', true)
|
||||
endif
|
||||
|
||||
if cc.has_function('memfd_create')
|
||||
config.set('HAVE_MEMFD', true)
|
||||
config.set('HAVE_MEMFD_CREATE', true)
|
||||
elif cc.has_function('SYS_memfd_create', prefix : '#include <sys/syscall.h>')
|
||||
config.set('HAVE_MEMFD', true)
|
||||
endif
|
||||
|
||||
if gbm.found() and not get_option('screencopy-dmabuf').disabled()
|
||||
config.set('ENABLE_SCREENCOPY_DMABUF', true)
|
||||
endif
|
||||
|
||||
if libpam.found()
|
||||
dependencies += libpam
|
||||
sources += 'src/pam_auth.c'
|
||||
config.set('ENABLE_PAM', true)
|
||||
endif
|
||||
|
||||
configure_file(
|
||||
output: 'config.h',
|
||||
configuration: config,
|
||||
)
|
||||
|
||||
executable(
|
||||
'wayvnc',
|
||||
sources,
|
||||
|
@ -81,3 +190,39 @@ executable(
|
|||
include_directories: inc,
|
||||
install: true,
|
||||
)
|
||||
|
||||
executable(
|
||||
'wayvncctl',
|
||||
ctlsources,
|
||||
dependencies: ctldependencies,
|
||||
include_directories: inc,
|
||||
install: true,
|
||||
)
|
||||
|
||||
scdoc = dependency('scdoc', native: true, required: get_option('man-pages'))
|
||||
if scdoc.found()
|
||||
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
|
||||
sh = find_program('sh', native: true)
|
||||
mandir = get_option('mandir')
|
||||
manpages = {
|
||||
'wayvnc.scd': 'wayvnc.1',
|
||||
'wayvncctl.scd': 'wayvncctl.1',
|
||||
}
|
||||
|
||||
foreach input, output : manpages
|
||||
custom_target(
|
||||
output,
|
||||
input: input,
|
||||
output: output,
|
||||
command: [
|
||||
sh, '-c', '@0@ <@INPUT@ >@1@'.format(scdoc_prog.path(), output)
|
||||
],
|
||||
install: true,
|
||||
install_dir: '@0@/man1'.format(mandir)
|
||||
)
|
||||
endforeach
|
||||
endif
|
||||
|
||||
if get_option('tests')
|
||||
subdir('test')
|
||||
endif
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
option('screencopy-dmabuf', type: 'feature', value: 'auto',
|
||||
description: 'Enable GPU-side screencopy')
|
||||
option('pam', type: 'feature', value: 'auto',
|
||||
description: 'Enable PAM authentication')
|
||||
option('man-pages', type: 'feature', value: 'auto',
|
||||
description: 'Generate and install man pages')
|
||||
option('systemtap', type: 'boolean', value: false,
|
||||
description: 'Enable tracing using sdt')
|
||||
option('tests', type: 'boolean', value: true,
|
||||
description: 'Build unit tests')
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="ext_transient_seat_v1">
|
||||
<copyright>
|
||||
Copyright © 2020 - 2023 Andri Yngvason
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<description summary="protocol for creating temporary seats">
|
||||
The transient seat protocol can be used by privileged clients to create
|
||||
independent seats that will be removed from the compositor when the client
|
||||
destroys its transient seat.
|
||||
|
||||
This protocol is intended for use with virtual input protocols such as
|
||||
"virtual_keyboard_unstable_v1" or "wlr_virtual_pointer_unstable_v1", both
|
||||
of which allow the user to select a seat.
|
||||
|
||||
The "wl_seat" global created by this protocol does not generate input events
|
||||
on its own, or have any capabilities except those assigned to it by other
|
||||
protocol extensions, such as the ones mentioned above.
|
||||
|
||||
For example, a remote desktop server can create a seat with virtual inputs
|
||||
for each remote user by following these steps for each new connection:
|
||||
* Create a transient seat
|
||||
* Wait for the transient seat to be created
|
||||
* Locate a "wl_seat" global with a matching name
|
||||
* Create virtual inputs using the resulting "wl_seat" global
|
||||
</description>
|
||||
|
||||
<interface name="ext_transient_seat_manager_v1" version="1">
|
||||
<description summary="transient seat manager">
|
||||
The transient seat manager creates short-lived seats.
|
||||
</description>
|
||||
|
||||
<request name="create">
|
||||
<description summary="create a transient seat">
|
||||
Create a new seat that is removed when the client side transient seat
|
||||
object is destroyed.
|
||||
|
||||
The actual seat may be removed sooner, in which case the transient seat
|
||||
object shall become inert.
|
||||
</description>
|
||||
<arg name="seat" type="new_id" interface="ext_transient_seat_v1"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the manager">
|
||||
Destroy the manager.
|
||||
|
||||
All objects created by the manager will remain valid until they are
|
||||
destroyed themselves.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="ext_transient_seat_v1" version="1">
|
||||
<description summary="transient seat handle">
|
||||
When the transient seat handle is destroyed, the seat itself will also be
|
||||
destroyed.
|
||||
</description>
|
||||
|
||||
<event name="ready">
|
||||
<description summary="transient seat is ready">
|
||||
This event advertises the global name for the wl_seat to be used with
|
||||
wl_registry_bind.
|
||||
|
||||
It is sent exactly once, immediately after the transient seat is created
|
||||
and the new "wl_seat" global is advertised, if and only if the creation
|
||||
of the transient seat was allowed.
|
||||
</description>
|
||||
<arg name="global_name" type="uint"/>
|
||||
</event>
|
||||
|
||||
<event name="denied">
|
||||
<description summary="transient seat creation denied">
|
||||
The event informs the client that the compositor denied its request to
|
||||
create a transient seat.
|
||||
|
||||
It is sent exactly once, immediately after the transient seat object is
|
||||
created, if and only if the creation of the transient seat was denied.
|
||||
|
||||
After receiving this event, the client should destroy the object.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy transient seat">
|
||||
When the transient seat object is destroyed by the client, the
|
||||
associated seat created by the compositor is also destroyed.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
|
@ -0,0 +1,362 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="linux_dmabuf_unstable_v1">
|
||||
|
||||
<copyright>
|
||||
Copyright © 2014, 2015 Collabora, Ltd.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwp_linux_dmabuf_v1" version="3">
|
||||
<description summary="factory for creating dmabuf-based wl_buffers">
|
||||
Following the interfaces from:
|
||||
https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
|
||||
https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt
|
||||
and the Linux DRM sub-system's AddFb2 ioctl.
|
||||
|
||||
This interface offers ways to create generic dmabuf-based
|
||||
wl_buffers. Immediately after a client binds to this interface,
|
||||
the set of supported formats and format modifiers is sent with
|
||||
'format' and 'modifier' events.
|
||||
|
||||
The following are required from clients:
|
||||
|
||||
- Clients must ensure that either all data in the dma-buf is
|
||||
coherent for all subsequent read access or that coherency is
|
||||
correctly handled by the underlying kernel-side dma-buf
|
||||
implementation.
|
||||
|
||||
- Don't make any more attachments after sending the buffer to the
|
||||
compositor. Making more attachments later increases the risk of
|
||||
the compositor not being able to use (re-import) an existing
|
||||
dmabuf-based wl_buffer.
|
||||
|
||||
The underlying graphics stack must ensure the following:
|
||||
|
||||
- The dmabuf file descriptors relayed to the server will stay valid
|
||||
for the whole lifetime of the wl_buffer. This means the server may
|
||||
at any time use those fds to import the dmabuf into any kernel
|
||||
sub-system that might accept it.
|
||||
|
||||
To create a wl_buffer from one or more dmabufs, a client creates a
|
||||
zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params
|
||||
request. All planes required by the intended format are added with
|
||||
the 'add' request. Finally, a 'create' or 'create_immed' request is
|
||||
issued, which has the following outcome depending on the import success.
|
||||
|
||||
The 'create' request,
|
||||
- on success, triggers a 'created' event which provides the final
|
||||
wl_buffer to the client.
|
||||
- on failure, triggers a 'failed' event to convey that the server
|
||||
cannot use the dmabufs received from the client.
|
||||
|
||||
For the 'create_immed' request,
|
||||
- on success, the server immediately imports the added dmabufs to
|
||||
create a wl_buffer. No event is sent from the server in this case.
|
||||
- on failure, the server can choose to either:
|
||||
- terminate the client by raising a fatal error.
|
||||
- mark the wl_buffer as failed, and send a 'failed' event to the
|
||||
client. If the client uses a failed wl_buffer as an argument to any
|
||||
request, the behaviour is compositor implementation-defined.
|
||||
|
||||
Warning! The protocol described in this file is experimental and
|
||||
backward incompatible changes may be made. Backward compatible changes
|
||||
may be added together with the corresponding interface version bump.
|
||||
Backward incompatible changes are done by bumping the version number in
|
||||
the protocol and interface names and resetting the interface version.
|
||||
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
version number in the protocol and interface names are removed and the
|
||||
interface version number is reset.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="unbind the factory">
|
||||
Objects created through this interface, especially wl_buffers, will
|
||||
remain valid.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="create_params">
|
||||
<description summary="create a temporary object for buffer parameters">
|
||||
This temporary object is used to collect multiple dmabuf handles into
|
||||
a single batch to create a wl_buffer. It can only be used once and
|
||||
should be destroyed after a 'created' or 'failed' event has been
|
||||
received.
|
||||
</description>
|
||||
<arg name="params_id" type="new_id" interface="zwp_linux_buffer_params_v1"
|
||||
summary="the new temporary"/>
|
||||
</request>
|
||||
|
||||
<event name="format">
|
||||
<description summary="supported buffer format">
|
||||
This event advertises one buffer format that the server supports.
|
||||
All the supported formats are advertised once when the client
|
||||
binds to this interface. A roundtrip after binding guarantees
|
||||
that the client has received all supported formats.
|
||||
|
||||
For the definition of the format codes, see the
|
||||
zwp_linux_buffer_params_v1::create request.
|
||||
|
||||
Warning: the 'format' event is likely to be deprecated and replaced
|
||||
with the 'modifier' event introduced in zwp_linux_dmabuf_v1
|
||||
version 3, described below. Please refrain from using the information
|
||||
received from this event.
|
||||
</description>
|
||||
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
|
||||
</event>
|
||||
|
||||
<event name="modifier" since="3">
|
||||
<description summary="supported buffer format modifier">
|
||||
This event advertises the formats that the server supports, along with
|
||||
the modifiers supported for each format. All the supported modifiers
|
||||
for all the supported formats are advertised once when the client
|
||||
binds to this interface. A roundtrip after binding guarantees that
|
||||
the client has received all supported format-modifier pairs.
|
||||
|
||||
For legacy support, DRM_FORMAT_MOD_INVALID (that is, modifier_hi ==
|
||||
0x00ffffff and modifier_lo == 0xffffffff) is allowed in this event.
|
||||
It indicates that the server can support the format with an implicit
|
||||
modifier. When a plane has DRM_FORMAT_MOD_INVALID as its modifier, it
|
||||
is as if no explicit modifier is specified. The effective modifier
|
||||
will be derived from the dmabuf.
|
||||
|
||||
For the definition of the format and modifier codes, see the
|
||||
zwp_linux_buffer_params_v1::create and zwp_linux_buffer_params_v1::add
|
||||
requests.
|
||||
</description>
|
||||
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
|
||||
<arg name="modifier_hi" type="uint"
|
||||
summary="high 32 bits of layout modifier"/>
|
||||
<arg name="modifier_lo" type="uint"
|
||||
summary="low 32 bits of layout modifier"/>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zwp_linux_buffer_params_v1" version="3">
|
||||
<description summary="parameters for creating a dmabuf-based wl_buffer">
|
||||
This temporary object is a collection of dmabufs and other
|
||||
parameters that together form a single logical buffer. The temporary
|
||||
object may eventually create one wl_buffer unless cancelled by
|
||||
destroying it before requesting 'create'.
|
||||
|
||||
Single-planar formats only require one dmabuf, however
|
||||
multi-planar formats may require more than one dmabuf. For all
|
||||
formats, an 'add' request must be called once per plane (even if the
|
||||
underlying dmabuf fd is identical).
|
||||
|
||||
You must use consecutive plane indices ('plane_idx' argument for 'add')
|
||||
from zero to the number of planes used by the drm_fourcc format code.
|
||||
All planes required by the format must be given exactly once, but can
|
||||
be given in any order. Each plane index can be set only once.
|
||||
</description>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="already_used" value="0"
|
||||
summary="the dmabuf_batch object has already been used to create a wl_buffer"/>
|
||||
<entry name="plane_idx" value="1"
|
||||
summary="plane index out of bounds"/>
|
||||
<entry name="plane_set" value="2"
|
||||
summary="the plane index was already set"/>
|
||||
<entry name="incomplete" value="3"
|
||||
summary="missing or too many planes to create a buffer"/>
|
||||
<entry name="invalid_format" value="4"
|
||||
summary="format not supported"/>
|
||||
<entry name="invalid_dimensions" value="5"
|
||||
summary="invalid width or height"/>
|
||||
<entry name="out_of_bounds" value="6"
|
||||
summary="offset + stride * height goes out of dmabuf bounds"/>
|
||||
<entry name="invalid_wl_buffer" value="7"
|
||||
summary="invalid wl_buffer resulted from importing dmabufs via
|
||||
the create_immed request on given buffer_params"/>
|
||||
</enum>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="delete this object, used or not">
|
||||
Cleans up the temporary data sent to the server for dmabuf-based
|
||||
wl_buffer creation.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="add">
|
||||
<description summary="add a dmabuf to the temporary set">
|
||||
This request adds one dmabuf to the set in this
|
||||
zwp_linux_buffer_params_v1.
|
||||
|
||||
The 64-bit unsigned value combined from modifier_hi and modifier_lo
|
||||
is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the
|
||||
fb modifier, which is defined in drm_mode.h of Linux UAPI.
|
||||
This is an opaque token. Drivers use this token to express tiling,
|
||||
compression, etc. driver-specific modifications to the base format
|
||||
defined by the DRM fourcc code.
|
||||
|
||||
Warning: It should be an error if the format/modifier pair was not
|
||||
advertised with the modifier event. This is not enforced yet because
|
||||
some implementations always accept DRM_FORMAT_MOD_INVALID. Also
|
||||
version 2 of this protocol does not have the modifier event.
|
||||
|
||||
This request raises the PLANE_IDX error if plane_idx is too large.
|
||||
The error PLANE_SET is raised if attempting to set a plane that
|
||||
was already set.
|
||||
</description>
|
||||
<arg name="fd" type="fd" summary="dmabuf fd"/>
|
||||
<arg name="plane_idx" type="uint" summary="plane index"/>
|
||||
<arg name="offset" type="uint" summary="offset in bytes"/>
|
||||
<arg name="stride" type="uint" summary="stride in bytes"/>
|
||||
<arg name="modifier_hi" type="uint"
|
||||
summary="high 32 bits of layout modifier"/>
|
||||
<arg name="modifier_lo" type="uint"
|
||||
summary="low 32 bits of layout modifier"/>
|
||||
</request>
|
||||
|
||||
<enum name="flags">
|
||||
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
|
||||
<entry name="interlaced" value="2" summary="content is interlaced"/>
|
||||
<entry name="bottom_first" value="4" summary="bottom field first"/>
|
||||
</enum>
|
||||
|
||||
<request name="create">
|
||||
<description summary="create a wl_buffer from the given dmabufs">
|
||||
This asks for creation of a wl_buffer from the added dmabuf
|
||||
buffers. The wl_buffer is not created immediately but returned via
|
||||
the 'created' event if the dmabuf sharing succeeds. The sharing
|
||||
may fail at runtime for reasons a client cannot predict, in
|
||||
which case the 'failed' event is triggered.
|
||||
|
||||
The 'format' argument is a DRM_FORMAT code, as defined by the
|
||||
libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the
|
||||
authoritative source on how the format codes should work.
|
||||
|
||||
The 'flags' is a bitfield of the flags defined in enum "flags".
|
||||
'y_invert' means the that the image needs to be y-flipped.
|
||||
|
||||
Flag 'interlaced' means that the frame in the buffer is not
|
||||
progressive as usual, but interlaced. An interlaced buffer as
|
||||
supported here must always contain both top and bottom fields.
|
||||
The top field always begins on the first pixel row. The temporal
|
||||
ordering between the two fields is top field first, unless
|
||||
'bottom_first' is specified. It is undefined whether 'bottom_first'
|
||||
is ignored if 'interlaced' is not set.
|
||||
|
||||
This protocol does not convey any information about field rate,
|
||||
duration, or timing, other than the relative ordering between the
|
||||
two fields in one buffer. A compositor may have to estimate the
|
||||
intended field rate from the incoming buffer rate. It is undefined
|
||||
whether the time of receiving wl_surface.commit with a new buffer
|
||||
attached, applying the wl_surface state, wl_surface.frame callback
|
||||
trigger, presentation, or any other point in the compositor cycle
|
||||
is used to measure the frame or field times. There is no support
|
||||
for detecting missed or late frames/fields/buffers either, and
|
||||
there is no support whatsoever for cooperating with interlaced
|
||||
compositor output.
|
||||
|
||||
The composited image quality resulting from the use of interlaced
|
||||
buffers is explicitly undefined. A compositor may use elaborate
|
||||
hardware features or software to deinterlace and create progressive
|
||||
output frames from a sequence of interlaced input buffers, or it
|
||||
may produce substandard image quality. However, compositors that
|
||||
cannot guarantee reasonable image quality in all cases are recommended
|
||||
to just reject all interlaced buffers.
|
||||
|
||||
Any argument errors, including non-positive width or height,
|
||||
mismatch between the number of planes and the format, bad
|
||||
format, bad offset or stride, may be indicated by fatal protocol
|
||||
errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS,
|
||||
OUT_OF_BOUNDS.
|
||||
|
||||
Dmabuf import errors in the server that are not obvious client
|
||||
bugs are returned via the 'failed' event as non-fatal. This
|
||||
allows attempting dmabuf sharing and falling back in the client
|
||||
if it fails.
|
||||
|
||||
This request can be sent only once in the object's lifetime, after
|
||||
which the only legal request is destroy. This object should be
|
||||
destroyed after issuing a 'create' request. Attempting to use this
|
||||
object after issuing 'create' raises ALREADY_USED protocol error.
|
||||
|
||||
It is not mandatory to issue 'create'. If a client wants to
|
||||
cancel the buffer creation, it can just destroy this object.
|
||||
</description>
|
||||
<arg name="width" type="int" summary="base plane width in pixels"/>
|
||||
<arg name="height" type="int" summary="base plane height in pixels"/>
|
||||
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
|
||||
<arg name="flags" type="uint" summary="see enum flags"/>
|
||||
</request>
|
||||
|
||||
<event name="created">
|
||||
<description summary="buffer creation succeeded">
|
||||
This event indicates that the attempted buffer creation was
|
||||
successful. It provides the new wl_buffer referencing the dmabuf(s).
|
||||
|
||||
Upon receiving this event, the client should destroy the
|
||||
zlinux_dmabuf_params object.
|
||||
</description>
|
||||
<arg name="buffer" type="new_id" interface="wl_buffer"
|
||||
summary="the newly created wl_buffer"/>
|
||||
</event>
|
||||
|
||||
<event name="failed">
|
||||
<description summary="buffer creation failed">
|
||||
This event indicates that the attempted buffer creation has
|
||||
failed. It usually means that one of the dmabuf constraints
|
||||
has not been fulfilled.
|
||||
|
||||
Upon receiving this event, the client should destroy the
|
||||
zlinux_buffer_params object.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="create_immed" since="2">
|
||||
<description summary="immediately create a wl_buffer from the given
|
||||
dmabufs">
|
||||
This asks for immediate creation of a wl_buffer by importing the
|
||||
added dmabufs.
|
||||
|
||||
In case of import success, no event is sent from the server, and the
|
||||
wl_buffer is ready to be used by the client.
|
||||
|
||||
Upon import failure, either of the following may happen, as seen fit
|
||||
by the implementation:
|
||||
- the client is terminated with one of the following fatal protocol
|
||||
errors:
|
||||
- INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS,
|
||||
in case of argument errors such as mismatch between the number
|
||||
of planes and the format, bad format, non-positive width or
|
||||
height, or bad offset or stride.
|
||||
- INVALID_WL_BUFFER, in case the cause for failure is unknown or
|
||||
plaform specific.
|
||||
- the server creates an invalid wl_buffer, marks it as failed and
|
||||
sends a 'failed' event to the client. The result of using this
|
||||
invalid wl_buffer as an argument in any request by the client is
|
||||
defined by the compositor implementation.
|
||||
|
||||
This takes the same arguments as a 'create' request, and obeys the
|
||||
same restrictions.
|
||||
</description>
|
||||
<arg name="buffer_id" type="new_id" interface="wl_buffer"
|
||||
summary="id for the newly created wl_buffer"/>
|
||||
<arg name="width" type="int" summary="base plane width in pixels"/>
|
||||
<arg name="height" type="int" summary="base plane height in pixels"/>
|
||||
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
|
||||
<arg name="flags" type="uint" summary="see enum flags"/>
|
||||
</request>
|
||||
|
||||
</interface>
|
||||
|
||||
</protocol>
|
|
@ -19,6 +19,11 @@ client_protocols = [
|
|||
'wlr-virtual-pointer-unstable-v1.xml',
|
||||
'virtual-keyboard-unstable-v1.xml',
|
||||
'xdg-output-unstable-v1.xml',
|
||||
'linux-dmabuf-unstable-v1.xml',
|
||||
'wlr-data-control-unstable-v1.xml',
|
||||
'wlr-output-management-unstable-v1.xml',
|
||||
'wlr-output-power-management-unstable-v1.xml',
|
||||
'ext-transient-seat-v1.xml',
|
||||
]
|
||||
|
||||
client_protos_src = []
|
||||
|
|
|
@ -0,0 +1,278 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_data_control_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2018 Simon Ser
|
||||
Copyright © 2019 Ivan Molodetskikh
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<description summary="control data devices">
|
||||
This protocol allows a privileged client to control data devices. In
|
||||
particular, the client will be able to manage the current selection and take
|
||||
the role of a clipboard manager.
|
||||
|
||||
Warning! The protocol described in this file is experimental and
|
||||
backward incompatible changes may be made. Backward compatible changes
|
||||
may be added together with the corresponding interface version bump.
|
||||
Backward incompatible changes are done by bumping the version number in
|
||||
the protocol and interface names and resetting the interface version.
|
||||
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
version number in the protocol and interface names are removed and the
|
||||
interface version number is reset.
|
||||
</description>
|
||||
|
||||
<interface name="zwlr_data_control_manager_v1" version="2">
|
||||
<description summary="manager to control data devices">
|
||||
This interface is a manager that allows creating per-seat data device
|
||||
controls.
|
||||
</description>
|
||||
|
||||
<request name="create_data_source">
|
||||
<description summary="create a new data source">
|
||||
Create a new data source.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_data_control_source_v1"
|
||||
summary="data source to create"/>
|
||||
</request>
|
||||
|
||||
<request name="get_data_device">
|
||||
<description summary="get a data device for a seat">
|
||||
Create a data device that can be used to manage a seat's selection.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_data_control_device_v1"/>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the manager">
|
||||
All objects created by the manager will still remain valid, until their
|
||||
appropriate destroy request has been called.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_data_control_device_v1" version="2">
|
||||
<description summary="manage a data device for a seat">
|
||||
This interface allows a client to manage a seat's selection.
|
||||
|
||||
When the seat is destroyed, this object becomes inert.
|
||||
</description>
|
||||
|
||||
<request name="set_selection">
|
||||
<description summary="copy data to the selection">
|
||||
This request asks the compositor to set the selection to the data from
|
||||
the source on behalf of the client.
|
||||
|
||||
The given source may not be used in any further set_selection or
|
||||
set_primary_selection requests. Attempting to use a previously used
|
||||
source is a protocol error.
|
||||
|
||||
To unset the selection, set the source to NULL.
|
||||
</description>
|
||||
<arg name="source" type="object" interface="zwlr_data_control_source_v1"
|
||||
allow-null="true"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy this data device">
|
||||
Destroys the data device object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="data_offer">
|
||||
<description summary="introduce a new wlr_data_control_offer">
|
||||
The data_offer event introduces a new wlr_data_control_offer object,
|
||||
which will subsequently be used in either the
|
||||
wlr_data_control_device.selection event (for the regular clipboard
|
||||
selections) or the wlr_data_control_device.primary_selection event (for
|
||||
the primary clipboard selections). Immediately following the
|
||||
wlr_data_control_device.data_offer event, the new data_offer object
|
||||
will send out wlr_data_control_offer.offer events to describe the MIME
|
||||
types it offers.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_data_control_offer_v1"/>
|
||||
</event>
|
||||
|
||||
<event name="selection">
|
||||
<description summary="advertise new selection">
|
||||
The selection event is sent out to notify the client of a new
|
||||
wlr_data_control_offer for the selection for this device. The
|
||||
wlr_data_control_device.data_offer and the wlr_data_control_offer.offer
|
||||
events are sent out immediately before this event to introduce the data
|
||||
offer object. The selection event is sent to a client when a new
|
||||
selection is set. The wlr_data_control_offer is valid until a new
|
||||
wlr_data_control_offer or NULL is received. The client must destroy the
|
||||
previous selection wlr_data_control_offer, if any, upon receiving this
|
||||
event.
|
||||
|
||||
The first selection event is sent upon binding the
|
||||
wlr_data_control_device object.
|
||||
</description>
|
||||
<arg name="id" type="object" interface="zwlr_data_control_offer_v1"
|
||||
allow-null="true"/>
|
||||
</event>
|
||||
|
||||
<event name="finished">
|
||||
<description summary="this data control is no longer valid">
|
||||
This data control object is no longer valid and should be destroyed by
|
||||
the client.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<event name="primary_selection" since="2">
|
||||
<description summary="advertise new primary selection">
|
||||
The primary_selection event is sent out to notify the client of a new
|
||||
wlr_data_control_offer for the primary selection for this device. The
|
||||
wlr_data_control_device.data_offer and the wlr_data_control_offer.offer
|
||||
events are sent out immediately before this event to introduce the data
|
||||
offer object. The primary_selection event is sent to a client when a
|
||||
new primary selection is set. The wlr_data_control_offer is valid until
|
||||
a new wlr_data_control_offer or NULL is received. The client must
|
||||
destroy the previous primary selection wlr_data_control_offer, if any,
|
||||
upon receiving this event.
|
||||
|
||||
If the compositor supports primary selection, the first
|
||||
primary_selection event is sent upon binding the
|
||||
wlr_data_control_device object.
|
||||
</description>
|
||||
<arg name="id" type="object" interface="zwlr_data_control_offer_v1"
|
||||
allow-null="true"/>
|
||||
</event>
|
||||
|
||||
<request name="set_primary_selection" since="2">
|
||||
<description summary="copy data to the primary selection">
|
||||
This request asks the compositor to set the primary selection to the
|
||||
data from the source on behalf of the client.
|
||||
|
||||
The given source may not be used in any further set_selection or
|
||||
set_primary_selection requests. Attempting to use a previously used
|
||||
source is a protocol error.
|
||||
|
||||
To unset the primary selection, set the source to NULL.
|
||||
|
||||
The compositor will ignore this request if it does not support primary
|
||||
selection.
|
||||
</description>
|
||||
<arg name="source" type="object" interface="zwlr_data_control_source_v1"
|
||||
allow-null="true"/>
|
||||
</request>
|
||||
|
||||
<enum name="error" since="2">
|
||||
<entry name="used_source" value="1"
|
||||
summary="source given to set_selection or set_primary_selection was already used before"/>
|
||||
</enum>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_data_control_source_v1" version="1">
|
||||
<description summary="offer to transfer data">
|
||||
The wlr_data_control_source object is the source side of a
|
||||
wlr_data_control_offer. It is created by the source client in a data
|
||||
transfer and provides a way to describe the offered data and a way to
|
||||
respond to requests to transfer the data.
|
||||
</description>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_offer" value="1"
|
||||
summary="offer sent after wlr_data_control_device.set_selection"/>
|
||||
</enum>
|
||||
|
||||
<request name="offer">
|
||||
<description summary="add an offered MIME type">
|
||||
This request adds a MIME type to the set of MIME types advertised to
|
||||
targets. Can be called several times to offer multiple types.
|
||||
|
||||
Calling this after wlr_data_control_device.set_selection is a protocol
|
||||
error.
|
||||
</description>
|
||||
<arg name="mime_type" type="string"
|
||||
summary="MIME type offered by the data source"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy this source">
|
||||
Destroys the data source object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="send">
|
||||
<description summary="send the data">
|
||||
Request for data from the client. Send the data as the specified MIME
|
||||
type over the passed file descriptor, then close it.
|
||||
</description>
|
||||
<arg name="mime_type" type="string" summary="MIME type for the data"/>
|
||||
<arg name="fd" type="fd" summary="file descriptor for the data"/>
|
||||
</event>
|
||||
|
||||
<event name="cancelled">
|
||||
<description summary="selection was cancelled">
|
||||
This data source is no longer valid. The data source has been replaced
|
||||
by another data source.
|
||||
|
||||
The client should clean up and destroy this data source.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_data_control_offer_v1" version="1">
|
||||
<description summary="offer to transfer data">
|
||||
A wlr_data_control_offer represents a piece of data offered for transfer
|
||||
by another client (the source client). The offer describes the different
|
||||
MIME types that the data can be converted to and provides the mechanism
|
||||
for transferring the data directly from the source client.
|
||||
</description>
|
||||
|
||||
<request name="receive">
|
||||
<description summary="request that the data is transferred">
|
||||
To transfer the offered data, the client issues this request and
|
||||
indicates the MIME type it wants to receive. The transfer happens
|
||||
through the passed file descriptor (typically created with the pipe
|
||||
system call). The source client writes the data in the MIME type
|
||||
representation requested and then closes the file descriptor.
|
||||
|
||||
The receiving client reads from the read end of the pipe until EOF and
|
||||
then closes its end, at which point the transfer is complete.
|
||||
|
||||
This request may happen multiple times for different MIME types.
|
||||
</description>
|
||||
<arg name="mime_type" type="string"
|
||||
summary="MIME type desired by receiver"/>
|
||||
<arg name="fd" type="fd" summary="file descriptor for data transfer"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy this offer">
|
||||
Destroys the data offer object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="offer">
|
||||
<description summary="advertise offered MIME type">
|
||||
Sent immediately after creating the wlr_data_control_offer object.
|
||||
One event per offered MIME type.
|
||||
</description>
|
||||
<arg name="mime_type" type="string" summary="offered MIME type"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
|
@ -0,0 +1,601 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_output_management_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2019 Purism SPC
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<description summary="protocol to configure output devices">
|
||||
This protocol exposes interfaces to obtain and modify output device
|
||||
configuration.
|
||||
|
||||
Warning! The protocol described in this file is experimental and
|
||||
backward incompatible changes may be made. Backward compatible changes
|
||||
may be added together with the corresponding interface version bump.
|
||||
Backward incompatible changes are done by bumping the version number in
|
||||
the protocol and interface names and resetting the interface version.
|
||||
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
version number in the protocol and interface names are removed and the
|
||||
interface version number is reset.
|
||||
</description>
|
||||
|
||||
<interface name="zwlr_output_manager_v1" version="4">
|
||||
<description summary="output device configuration manager">
|
||||
This interface is a manager that allows reading and writing the current
|
||||
output device configuration.
|
||||
|
||||
Output devices that display pixels (e.g. a physical monitor or a virtual
|
||||
output in a window) are represented as heads. Heads cannot be created nor
|
||||
destroyed by the client, but they can be enabled or disabled and their
|
||||
properties can be changed. Each head may have one or more available modes.
|
||||
|
||||
Whenever a head appears (e.g. a monitor is plugged in), it will be
|
||||
advertised via the head event. Immediately after the output manager is
|
||||
bound, all current heads are advertised.
|
||||
|
||||
Whenever a head's properties change, the relevant wlr_output_head events
|
||||
will be sent. Not all head properties will be sent: only properties that
|
||||
have changed need to.
|
||||
|
||||
Whenever a head disappears (e.g. a monitor is unplugged), a
|
||||
wlr_output_head.finished event will be sent.
|
||||
|
||||
After one or more heads appear, change or disappear, the done event will
|
||||
be sent. It carries a serial which can be used in a create_configuration
|
||||
request to update heads properties.
|
||||
|
||||
The information obtained from this protocol should only be used for output
|
||||
configuration purposes. This protocol is not designed to be a generic
|
||||
output property advertisement protocol for regular clients. Instead,
|
||||
protocols such as xdg-output should be used.
|
||||
</description>
|
||||
|
||||
<event name="head">
|
||||
<description summary="introduce a new head">
|
||||
This event introduces a new head. This happens whenever a new head
|
||||
appears (e.g. a monitor is plugged in) or after the output manager is
|
||||
bound.
|
||||
</description>
|
||||
<arg name="head" type="new_id" interface="zwlr_output_head_v1"/>
|
||||
</event>
|
||||
|
||||
<event name="done">
|
||||
<description summary="sent all information about current configuration">
|
||||
This event is sent after all information has been sent after binding to
|
||||
the output manager object and after any subsequent changes. This applies
|
||||
to child head and mode objects as well. In other words, this event is
|
||||
sent whenever a head or mode is created or destroyed and whenever one of
|
||||
their properties has been changed. Not all state is re-sent each time
|
||||
the current configuration changes: only the actual changes are sent.
|
||||
|
||||
This allows changes to the output configuration to be seen as atomic,
|
||||
even if they happen via multiple events.
|
||||
|
||||
A serial is sent to be used in a future create_configuration request.
|
||||
</description>
|
||||
<arg name="serial" type="uint" summary="current configuration serial"/>
|
||||
</event>
|
||||
|
||||
<request name="create_configuration">
|
||||
<description summary="create a new output configuration object">
|
||||
Create a new output configuration object. This allows to update head
|
||||
properties.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_output_configuration_v1"/>
|
||||
<arg name="serial" type="uint"/>
|
||||
</request>
|
||||
|
||||
<request name="stop">
|
||||
<description summary="stop sending events">
|
||||
Indicates the client no longer wishes to receive events for output
|
||||
configuration changes. However the compositor may emit further events,
|
||||
until the finished event is emitted.
|
||||
|
||||
The client must not send any more requests after this one.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="finished" type="destructor">
|
||||
<description summary="the compositor has finished with the manager">
|
||||
This event indicates that the compositor is done sending manager events.
|
||||
The compositor will destroy the object immediately after sending this
|
||||
event, so it will become invalid and the client should release any
|
||||
resources associated with it.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_output_head_v1" version="4">
|
||||
<description summary="output device">
|
||||
A head is an output device. The difference between a wl_output object and
|
||||
a head is that heads are advertised even if they are turned off. A head
|
||||
object only advertises properties and cannot be used directly to change
|
||||
them.
|
||||
|
||||
A head has some read-only properties: modes, name, description and
|
||||
physical_size. These cannot be changed by clients.
|
||||
|
||||
Other properties can be updated via a wlr_output_configuration object.
|
||||
|
||||
Properties sent via this interface are applied atomically via the
|
||||
wlr_output_manager.done event. No guarantees are made regarding the order
|
||||
in which properties are sent.
|
||||
</description>
|
||||
|
||||
<event name="name">
|
||||
<description summary="head name">
|
||||
This event describes the head name.
|
||||
|
||||
The naming convention is compositor defined, but limited to alphanumeric
|
||||
characters and dashes (-). Each name is unique among all wlr_output_head
|
||||
objects, but if a wlr_output_head object is destroyed the same name may
|
||||
be reused later. The names will also remain consistent across sessions
|
||||
with the same hardware and software configuration.
|
||||
|
||||
Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
|
||||
not assume that the name is a reflection of an underlying DRM
|
||||
connector, X11 connection, etc.
|
||||
|
||||
If the compositor implements the xdg-output protocol and this head is
|
||||
enabled, the xdg_output.name event must report the same name.
|
||||
|
||||
The name event is sent after a wlr_output_head object is created. This
|
||||
event is only sent once per object, and the name does not change over
|
||||
the lifetime of the wlr_output_head object.
|
||||
</description>
|
||||
<arg name="name" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="description">
|
||||
<description summary="head description">
|
||||
This event describes a human-readable description of the head.
|
||||
|
||||
The description is a UTF-8 string with no convention defined for its
|
||||
contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11
|
||||
output via :1'. However, do not assume that the name is a reflection of
|
||||
the make, model, serial of the underlying DRM connector or the display
|
||||
name of the underlying X11 connection, etc.
|
||||
|
||||
If the compositor implements xdg-output and this head is enabled,
|
||||
the xdg_output.description must report the same description.
|
||||
|
||||
The description event is sent after a wlr_output_head object is created.
|
||||
This event is only sent once per object, and the description does not
|
||||
change over the lifetime of the wlr_output_head object.
|
||||
</description>
|
||||
<arg name="description" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="physical_size">
|
||||
<description summary="head physical size">
|
||||
This event describes the physical size of the head. This event is only
|
||||
sent if the head has a physical size (e.g. is not a projector or a
|
||||
virtual device).
|
||||
</description>
|
||||
<arg name="width" type="int" summary="width in millimeters of the output"/>
|
||||
<arg name="height" type="int" summary="height in millimeters of the output"/>
|
||||
</event>
|
||||
|
||||
<event name="mode">
|
||||
<description summary="introduce a mode">
|
||||
This event introduces a mode for this head. It is sent once per
|
||||
supported mode.
|
||||
</description>
|
||||
<arg name="mode" type="new_id" interface="zwlr_output_mode_v1"/>
|
||||
</event>
|
||||
|
||||
<event name="enabled">
|
||||
<description summary="head is enabled or disabled">
|
||||
This event describes whether the head is enabled. A disabled head is not
|
||||
mapped to a region of the global compositor space.
|
||||
|
||||
When a head is disabled, some properties (current_mode, position,
|
||||
transform and scale) are irrelevant.
|
||||
</description>
|
||||
<arg name="enabled" type="int" summary="zero if disabled, non-zero if enabled"/>
|
||||
</event>
|
||||
|
||||
<event name="current_mode">
|
||||
<description summary="current mode">
|
||||
This event describes the mode currently in use for this head. It is only
|
||||
sent if the output is enabled.
|
||||
</description>
|
||||
<arg name="mode" type="object" interface="zwlr_output_mode_v1"/>
|
||||
</event>
|
||||
|
||||
<event name="position">
|
||||
<description summary="current position">
|
||||
This events describes the position of the head in the global compositor
|
||||
space. It is only sent if the output is enabled.
|
||||
</description>
|
||||
<arg name="x" type="int"
|
||||
summary="x position within the global compositor space"/>
|
||||
<arg name="y" type="int"
|
||||
summary="y position within the global compositor space"/>
|
||||
</event>
|
||||
|
||||
<event name="transform">
|
||||
<description summary="current transformation">
|
||||
This event describes the transformation currently applied to the head.
|
||||
It is only sent if the output is enabled.
|
||||
</description>
|
||||
<arg name="transform" type="int" enum="wl_output.transform"/>
|
||||
</event>
|
||||
|
||||
<event name="scale">
|
||||
<description summary="current scale">
|
||||
This events describes the scale of the head in the global compositor
|
||||
space. It is only sent if the output is enabled.
|
||||
</description>
|
||||
<arg name="scale" type="fixed"/>
|
||||
</event>
|
||||
|
||||
<event name="finished">
|
||||
<description summary="the head has disappeared">
|
||||
This event indicates that the head is no longer available. The head
|
||||
object becomes inert. Clients should send a destroy request and release
|
||||
any resources associated with it.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<event name="make" since="2">
|
||||
<description summary="head manufacturer">
|
||||
This event describes the manufacturer of the head.
|
||||
|
||||
This must report the same make as the wl_output interface does in its
|
||||
geometry event.
|
||||
|
||||
Together with the model and serial_number events the purpose is to
|
||||
allow clients to recognize heads from previous sessions and for example
|
||||
load head-specific configurations back.
|
||||
|
||||
It is not guaranteed this event will be ever sent. A reason for that
|
||||
can be that the compositor does not have information about the make of
|
||||
the head or the definition of a make is not sensible in the current
|
||||
setup, for example in a virtual session. Clients can still try to
|
||||
identify the head by available information from other events but should
|
||||
be aware that there is an increased risk of false positives.
|
||||
|
||||
It is not recommended to display the make string in UI to users. For
|
||||
that the string provided by the description event should be preferred.
|
||||
</description>
|
||||
<arg name="make" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="model" since="2">
|
||||
<description summary="head model">
|
||||
This event describes the model of the head.
|
||||
|
||||
This must report the same model as the wl_output interface does in its
|
||||
geometry event.
|
||||
|
||||
Together with the make and serial_number events the purpose is to
|
||||
allow clients to recognize heads from previous sessions and for example
|
||||
load head-specific configurations back.
|
||||
|
||||
It is not guaranteed this event will be ever sent. A reason for that
|
||||
can be that the compositor does not have information about the model of
|
||||
the head or the definition of a model is not sensible in the current
|
||||
setup, for example in a virtual session. Clients can still try to
|
||||
identify the head by available information from other events but should
|
||||
be aware that there is an increased risk of false positives.
|
||||
|
||||
It is not recommended to display the model string in UI to users. For
|
||||
that the string provided by the description event should be preferred.
|
||||
</description>
|
||||
<arg name="model" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="serial_number" since="2">
|
||||
<description summary="head serial number">
|
||||
This event describes the serial number of the head.
|
||||
|
||||
Together with the make and model events the purpose is to allow clients
|
||||
to recognize heads from previous sessions and for example load head-
|
||||
specific configurations back.
|
||||
|
||||
It is not guaranteed this event will be ever sent. A reason for that
|
||||
can be that the compositor does not have information about the serial
|
||||
number of the head or the definition of a serial number is not sensible
|
||||
in the current setup. Clients can still try to identify the head by
|
||||
available information from other events but should be aware that there
|
||||
is an increased risk of false positives.
|
||||
|
||||
It is not recommended to display the serial_number string in UI to
|
||||
users. For that the string provided by the description event should be
|
||||
preferred.
|
||||
</description>
|
||||
<arg name="serial_number" type="string"/>
|
||||
</event>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
|
||||
<request name="release" type="destructor" since="3">
|
||||
<description summary="destroy the head object">
|
||||
This request indicates that the client will no longer use this head
|
||||
object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<!-- Version 4 additions -->
|
||||
|
||||
<enum name="adaptive_sync_state" since="4">
|
||||
<entry name="disabled" value="0" summary="adaptive sync is disabled"/>
|
||||
<entry name="enabled" value="1" summary="adaptive sync is enabled"/>
|
||||
</enum>
|
||||
|
||||
<event name="adaptive_sync" since="4">
|
||||
<description summary="current adaptive sync state">
|
||||
This event describes whether adaptive sync is currently enabled for
|
||||
the head or not. Adaptive sync is also known as Variable Refresh
|
||||
Rate or VRR.
|
||||
</description>
|
||||
<arg name="state" type="uint" enum="adaptive_sync_state"/>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_output_mode_v1" version="3">
|
||||
<description summary="output mode">
|
||||
This object describes an output mode.
|
||||
|
||||
Some heads don't support output modes, in which case modes won't be
|
||||
advertised.
|
||||
|
||||
Properties sent via this interface are applied atomically via the
|
||||
wlr_output_manager.done event. No guarantees are made regarding the order
|
||||
in which properties are sent.
|
||||
</description>
|
||||
|
||||
<event name="size">
|
||||
<description summary="mode size">
|
||||
This event describes the mode size. The size is given in physical
|
||||
hardware units of the output device. This is not necessarily the same as
|
||||
the output size in the global compositor space. For instance, the output
|
||||
may be scaled or transformed.
|
||||
</description>
|
||||
<arg name="width" type="int" summary="width of the mode in hardware units"/>
|
||||
<arg name="height" type="int" summary="height of the mode in hardware units"/>
|
||||
</event>
|
||||
|
||||
<event name="refresh">
|
||||
<description summary="mode refresh rate">
|
||||
This event describes the mode's fixed vertical refresh rate. It is only
|
||||
sent if the mode has a fixed refresh rate.
|
||||
</description>
|
||||
<arg name="refresh" type="int" summary="vertical refresh rate in mHz"/>
|
||||
</event>
|
||||
|
||||
<event name="preferred">
|
||||
<description summary="mode is preferred">
|
||||
This event advertises this mode as preferred.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<event name="finished">
|
||||
<description summary="the mode has disappeared">
|
||||
This event indicates that the mode is no longer available. The mode
|
||||
object becomes inert. Clients should send a destroy request and release
|
||||
any resources associated with it.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
|
||||
<request name="release" type="destructor" since="3">
|
||||
<description summary="destroy the mode object">
|
||||
This request indicates that the client will no longer use this mode
|
||||
object.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_output_configuration_v1" version="4">
|
||||
<description summary="output configuration">
|
||||
This object is used by the client to describe a full output configuration.
|
||||
|
||||
First, the client needs to setup the output configuration. Each head can
|
||||
be either enabled (and configured) or disabled. It is a protocol error to
|
||||
send two enable_head or disable_head requests with the same head. It is a
|
||||
protocol error to omit a head in a configuration.
|
||||
|
||||
Then, the client can apply or test the configuration. The compositor will
|
||||
then reply with a succeeded, failed or cancelled event. Finally the client
|
||||
should destroy the configuration object.
|
||||
</description>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="already_configured_head" value="1"
|
||||
summary="head has been configured twice"/>
|
||||
<entry name="unconfigured_head" value="2"
|
||||
summary="head has not been configured"/>
|
||||
<entry name="already_used" value="3"
|
||||
summary="request sent after configuration has been applied or tested"/>
|
||||
</enum>
|
||||
|
||||
<request name="enable_head">
|
||||
<description summary="enable and configure a head">
|
||||
Enable a head. This request creates a head configuration object that can
|
||||
be used to change the head's properties.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_output_configuration_head_v1"
|
||||
summary="a new object to configure the head"/>
|
||||
<arg name="head" type="object" interface="zwlr_output_head_v1"
|
||||
summary="the head to be enabled"/>
|
||||
</request>
|
||||
|
||||
<request name="disable_head">
|
||||
<description summary="disable a head">
|
||||
Disable a head.
|
||||
</description>
|
||||
<arg name="head" type="object" interface="zwlr_output_head_v1"
|
||||
summary="the head to be disabled"/>
|
||||
</request>
|
||||
|
||||
<request name="apply">
|
||||
<description summary="apply the configuration">
|
||||
Apply the new output configuration.
|
||||
|
||||
In case the configuration is successfully applied, there is no guarantee
|
||||
that the new output state matches completely the requested
|
||||
configuration. For instance, a compositor might round the scale if it
|
||||
doesn't support fractional scaling.
|
||||
|
||||
After this request has been sent, the compositor must respond with an
|
||||
succeeded, failed or cancelled event. Sending a request that isn't the
|
||||
destructor is a protocol error.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="test">
|
||||
<description summary="test the configuration">
|
||||
Test the new output configuration. The configuration won't be applied,
|
||||
but will only be validated.
|
||||
|
||||
Even if the compositor succeeds to test a configuration, applying it may
|
||||
fail.
|
||||
|
||||
After this request has been sent, the compositor must respond with an
|
||||
succeeded, failed or cancelled event. Sending a request that isn't the
|
||||
destructor is a protocol error.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="succeeded">
|
||||
<description summary="configuration changes succeeded">
|
||||
Sent after the compositor has successfully applied the changes or
|
||||
tested them.
|
||||
|
||||
Upon receiving this event, the client should destroy this object.
|
||||
|
||||
If the current configuration has changed, events to describe the changes
|
||||
will be sent followed by a wlr_output_manager.done event.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<event name="failed">
|
||||
<description summary="configuration changes failed">
|
||||
Sent if the compositor rejects the changes or failed to apply them. The
|
||||
compositor should revert any changes made by the apply request that
|
||||
triggered this event.
|
||||
|
||||
Upon receiving this event, the client should destroy this object.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<event name="cancelled">
|
||||
<description summary="configuration has been cancelled">
|
||||
Sent if the compositor cancels the configuration because the state of an
|
||||
output changed and the client has outdated information (e.g. after an
|
||||
output has been hotplugged).
|
||||
|
||||
The client can create a new configuration with a newer serial and try
|
||||
again.
|
||||
|
||||
Upon receiving this event, the client should destroy this object.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the output configuration">
|
||||
Using this request a client can tell the compositor that it is not going
|
||||
to use the configuration object anymore. Any changes to the outputs
|
||||
that have not been applied will be discarded.
|
||||
|
||||
This request also destroys wlr_output_configuration_head objects created
|
||||
via this object.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_output_configuration_head_v1" version="4">
|
||||
<description summary="head configuration">
|
||||
This object is used by the client to update a single head's configuration.
|
||||
|
||||
It is a protocol error to set the same property twice.
|
||||
</description>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="already_set" value="1" summary="property has already been set"/>
|
||||
<entry name="invalid_mode" value="2" summary="mode doesn't belong to head"/>
|
||||
<entry name="invalid_custom_mode" value="3" summary="mode is invalid"/>
|
||||
<entry name="invalid_transform" value="4" summary="transform value outside enum"/>
|
||||
<entry name="invalid_scale" value="5" summary="scale negative or zero"/>
|
||||
<entry name="invalid_adaptive_sync_state" value="6" since="4"
|
||||
summary="invalid enum value used in the set_adaptive_sync request"/>
|
||||
</enum>
|
||||
|
||||
<request name="set_mode">
|
||||
<description summary="set the mode">
|
||||
This request sets the head's mode.
|
||||
</description>
|
||||
<arg name="mode" type="object" interface="zwlr_output_mode_v1"/>
|
||||
</request>
|
||||
|
||||
<request name="set_custom_mode">
|
||||
<description summary="set a custom mode">
|
||||
This request assigns a custom mode to the head. The size is given in
|
||||
physical hardware units of the output device. If set to zero, the
|
||||
refresh rate is unspecified.
|
||||
|
||||
It is a protocol error to set both a mode and a custom mode.
|
||||
</description>
|
||||
<arg name="width" type="int" summary="width of the mode in hardware units"/>
|
||||
<arg name="height" type="int" summary="height of the mode in hardware units"/>
|
||||
<arg name="refresh" type="int" summary="vertical refresh rate in mHz or zero"/>
|
||||
</request>
|
||||
|
||||
<request name="set_position">
|
||||
<description summary="set the position">
|
||||
This request sets the head's position in the global compositor space.
|
||||
</description>
|
||||
<arg name="x" type="int" summary="x position in the global compositor space"/>
|
||||
<arg name="y" type="int" summary="y position in the global compositor space"/>
|
||||
</request>
|
||||
|
||||
<request name="set_transform">
|
||||
<description summary="set the transform">
|
||||
This request sets the head's transform.
|
||||
</description>
|
||||
<arg name="transform" type="int" enum="wl_output.transform"/>
|
||||
</request>
|
||||
|
||||
<request name="set_scale">
|
||||
<description summary="set the scale">
|
||||
This request sets the head's scale.
|
||||
</description>
|
||||
<arg name="scale" type="fixed"/>
|
||||
</request>
|
||||
|
||||
<!-- Version 4 additions -->
|
||||
|
||||
<request name="set_adaptive_sync" since="4">
|
||||
<description summary="enable/disable adaptive sync">
|
||||
This request enables/disables adaptive sync. Adaptive sync is also
|
||||
known as Variable Refresh Rate or VRR.
|
||||
</description>
|
||||
<arg name="state" type="uint" enum="zwlr_output_head_v1.adaptive_sync_state"/>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
|
@ -0,0 +1,128 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_output_power_management_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2019 Purism SPC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<description summary="Control power management modes of outputs">
|
||||
This protocol allows clients to control power management modes
|
||||
of outputs that are currently part of the compositor space. The
|
||||
intent is to allow special clients like desktop shells to power
|
||||
down outputs when the system is idle.
|
||||
|
||||
To modify outputs not currently part of the compositor space see
|
||||
wlr-output-management.
|
||||
|
||||
Warning! The protocol described in this file is experimental and
|
||||
backward incompatible changes may be made. Backward compatible changes
|
||||
may be added together with the corresponding interface version bump.
|
||||
Backward incompatible changes are done by bumping the version number in
|
||||
the protocol and interface names and resetting the interface version.
|
||||
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
version number in the protocol and interface names are removed and the
|
||||
interface version number is reset.
|
||||
</description>
|
||||
|
||||
<interface name="zwlr_output_power_manager_v1" version="1">
|
||||
<description summary="manager to create per-output power management">
|
||||
This interface is a manager that allows creating per-output power
|
||||
management mode controls.
|
||||
</description>
|
||||
|
||||
<request name="get_output_power">
|
||||
<description summary="get a power management for an output">
|
||||
Create a output power management mode control that can be used to
|
||||
adjust the power management mode for a given output.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_output_power_v1"/>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the manager">
|
||||
All objects created by the manager will still remain valid, until their
|
||||
appropriate destroy request has been called.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_output_power_v1" version="1">
|
||||
<description summary="adjust power management mode for an output">
|
||||
This object offers requests to set the power management mode of
|
||||
an output.
|
||||
</description>
|
||||
|
||||
<enum name="mode">
|
||||
<entry name="off" value="0"
|
||||
summary="Output is turned off."/>
|
||||
<entry name="on" value="1"
|
||||
summary="Output is turned on, no power saving"/>
|
||||
</enum>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_mode" value="1" summary="inexistent power save mode"/>
|
||||
</enum>
|
||||
|
||||
<request name="set_mode">
|
||||
<description summary="Set an outputs power save mode">
|
||||
Set an output's power save mode to the given mode. The mode change
|
||||
is effective immediately. If the output does not support the given
|
||||
mode a failed event is sent.
|
||||
</description>
|
||||
<arg name="mode" type="uint" enum="mode" summary="the power save mode to set"/>
|
||||
</request>
|
||||
|
||||
<event name="mode">
|
||||
<description summary="Report a power management mode change">
|
||||
Report the power management mode change of an output.
|
||||
|
||||
The mode event is sent after an output changed its power
|
||||
management mode. The reason can be a client using set_mode or the
|
||||
compositor deciding to change an output's mode.
|
||||
This event is also sent immediately when the object is created
|
||||
so the client is informed about the current power management mode.
|
||||
</description>
|
||||
<arg name="mode" type="uint" enum="mode"
|
||||
summary="the output's new power management mode"/>
|
||||
</event>
|
||||
|
||||
<event name="failed">
|
||||
<description summary="object no longer valid">
|
||||
This event indicates that the output power management mode control
|
||||
is no longer valid. This can happen for a number of reasons,
|
||||
including:
|
||||
- The output doesn't support power management
|
||||
- Another client already has exclusive power management mode control
|
||||
for this output
|
||||
- The output disappeared
|
||||
|
||||
Upon receiving this event, the client should destroy this object.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy this power management">
|
||||
Destroys the output power management mode control object.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
|
@ -38,7 +38,7 @@
|
|||
interface version number is reset.
|
||||
</description>
|
||||
|
||||
<interface name="zwlr_screencopy_manager_v1" version="2">
|
||||
<interface name="zwlr_screencopy_manager_v1" version="3">
|
||||
<description summary="manager to inform clients and begin capturing">
|
||||
This object is a manager which offers requests to start capturing from a
|
||||
source.
|
||||
|
@ -80,13 +80,18 @@
|
|||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_screencopy_frame_v1" version="2">
|
||||
<interface name="zwlr_screencopy_frame_v1" version="3">
|
||||
<description summary="a frame ready for copy">
|
||||
This object represents a single frame.
|
||||
|
||||
When created, a "buffer" event will be sent. The client will then be able
|
||||
to send a "copy" request. If the capture is successful, the compositor
|
||||
will send a "flags" followed by a "ready" event.
|
||||
When created, a series of buffer events will be sent, each representing a
|
||||
supported buffer type. The "buffer_done" event is sent afterwards to
|
||||
indicate that all supported buffer types have been enumerated. The client
|
||||
will then be able to send a "copy" request. If the capture is successful,
|
||||
the compositor will send a "flags" followed by a "ready" event.
|
||||
|
||||
For objects version 2 or lower, wl_shm buffers are always supported, ie.
|
||||
the "buffer" event is guaranteed to be sent.
|
||||
|
||||
If the capture failed, the "failed" event is sent. This can happen anytime
|
||||
before the "ready" event.
|
||||
|
@ -96,14 +101,12 @@
|
|||
</description>
|
||||
|
||||
<event name="buffer">
|
||||
<description summary="buffer information">
|
||||
Provides information about the frame's buffer. This event is sent once
|
||||
as soon as the frame is created.
|
||||
|
||||
The client should then create a buffer with the provided attributes, and
|
||||
send a "copy" request.
|
||||
<description summary="wl_shm buffer information">
|
||||
Provides information about wl_shm buffer parameters that need to be
|
||||
used for this frame. This event is sent once after the frame is created
|
||||
if wl_shm buffers are supported.
|
||||
</description>
|
||||
<arg name="format" type="uint" summary="buffer format"/>
|
||||
<arg name="format" type="uint" enum="wl_shm.format" summary="buffer format"/>
|
||||
<arg name="width" type="uint" summary="buffer width"/>
|
||||
<arg name="height" type="uint" summary="buffer height"/>
|
||||
<arg name="stride" type="uint" summary="buffer stride"/>
|
||||
|
@ -112,8 +115,9 @@
|
|||
<request name="copy">
|
||||
<description summary="copy the frame">
|
||||
Copy the frame to the supplied buffer. The buffer must have a the
|
||||
correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to
|
||||
have a supported format.
|
||||
correct size, see zwlr_screencopy_frame_v1.buffer and
|
||||
zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a
|
||||
supported format.
|
||||
|
||||
If the frame is successfully copied, a "flags" and a "ready" events are
|
||||
sent. Otherwise, a "failed" event is sent.
|
||||
|
@ -203,5 +207,26 @@
|
|||
<arg name="width" type="uint" summary="current width"/>
|
||||
<arg name="height" type="uint" summary="current height"/>
|
||||
</event>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
<event name="linux_dmabuf" since="3">
|
||||
<description summary="linux-dmabuf buffer information">
|
||||
Provides information about linux-dmabuf buffer parameters that need to
|
||||
be used for this frame. This event is sent once after the frame is
|
||||
created if linux-dmabuf buffers are supported.
|
||||
</description>
|
||||
<arg name="format" type="uint" summary="fourcc pixel format"/>
|
||||
<arg name="width" type="uint" summary="buffer width"/>
|
||||
<arg name="height" type="uint" summary="buffer height"/>
|
||||
</event>
|
||||
|
||||
<event name="buffer_done" since="3">
|
||||
<description summary="all buffer types reported">
|
||||
This event is sent once after all buffer events have been sent.
|
||||
|
||||
The client should proceed to create a buffer of one of the supported
|
||||
types, and send a "copy" request.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_virtual_pointer_v1" version="1">
|
||||
<interface name="zwlr_virtual_pointer_v1" version="2">
|
||||
<description summary="virtual pointer">
|
||||
This protocol allows clients to emulate a physical pointer device. The
|
||||
requests are mostly mirror opposites of those specified in wl_pointer.
|
||||
|
@ -118,7 +118,7 @@
|
|||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_virtual_pointer_manager_v1" version="1">
|
||||
<interface name="zwlr_virtual_pointer_manager_v1" version="2">
|
||||
<description summary="virtual pointer manager">
|
||||
This object allows clients to create individual virtual pointer objects.
|
||||
</description>
|
||||
|
@ -135,5 +135,18 @@
|
|||
<request name="destroy" type="destructor" since="1">
|
||||
<description summary="destroy the virtual pointer manager"/>
|
||||
</request>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
<request name="create_virtual_pointer_with_output" since="2">
|
||||
<description summary="Create a new virtual pointer">
|
||||
Creates a new virtual pointer. The seat and the output arguments are
|
||||
optional. If the seat argument is set, the compositor should assign the
|
||||
input device to the requested seat. If the output argument is set, the
|
||||
compositor should map the input device to the requested output.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
<arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
||||
|
|
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* Copyright (c) 2020 - 2024 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <sys/mman.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <wayland-client.h>
|
||||
#include <pixman.h>
|
||||
#include <neatvnc.h>
|
||||
|
||||
#include "linux-dmabuf-unstable-v1.h"
|
||||
#include "shm.h"
|
||||
#include "sys/queue.h"
|
||||
#include "buffer.h"
|
||||
#include "pixels.h"
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
|
||||
#ifdef ENABLE_SCREENCOPY_DMABUF
|
||||
#include <gbm.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
// #ifdef HAVE_LINUX_DMA_HEAP
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/dma-heap.h>
|
||||
|
||||
#define LINUX_CMA_PATH "/dev/dma_heap/linux,cma"
|
||||
//#endif // HAVE_LINUX_DMA_HEAP
|
||||
#endif // ENABLE_SCREENCOPY_DMABUF
|
||||
|
||||
extern struct wl_shm* wl_shm;
|
||||
extern struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf;
|
||||
extern struct gbm_device* gbm_device;
|
||||
|
||||
enum wv_buffer_type wv_buffer_get_available_types(void)
|
||||
{
|
||||
enum wv_buffer_type type = 0;
|
||||
|
||||
if (wl_shm)
|
||||
type |= WV_BUFFER_SHM;
|
||||
|
||||
#ifdef ENABLE_SCREENCOPY_DMABUF
|
||||
if (zwp_linux_dmabuf && gbm_device)
|
||||
type |= WV_BUFFER_DMABUF;
|
||||
#endif
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
struct wv_buffer* wv_buffer_create_shm(int width,
|
||||
int height, int stride, uint32_t fourcc)
|
||||
{
|
||||
assert(wl_shm);
|
||||
enum wl_shm_format wl_fmt = fourcc_to_wl_shm(fourcc);
|
||||
|
||||
struct wv_buffer* self = calloc(1, sizeof(*self));
|
||||
if (!self)
|
||||
return NULL;
|
||||
|
||||
self->type = WV_BUFFER_SHM;
|
||||
self->width = width;
|
||||
self->height = height;
|
||||
self->stride = stride;
|
||||
self->format = fourcc;
|
||||
|
||||
self->size = height * stride;
|
||||
int fd = shm_alloc_fd(self->size);
|
||||
if (fd < 0)
|
||||
goto failure;
|
||||
|
||||
self->pixels = mmap(NULL, self->size, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
if (!self->pixels)
|
||||
goto mmap_failure;
|
||||
|
||||
struct wl_shm_pool* pool = wl_shm_create_pool(wl_shm, fd, self->size);
|
||||
if (!pool)
|
||||
goto pool_failure;
|
||||
|
||||
self->wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
|
||||
stride, wl_fmt);
|
||||
wl_shm_pool_destroy(pool);
|
||||
if (!self->wl_buffer)
|
||||
goto shm_failure;
|
||||
|
||||
int bpp = pixel_size_from_fourcc(fourcc);
|
||||
assert(bpp > 0);
|
||||
self->nvnc_fb = nvnc_fb_from_buffer(self->pixels, width, height, fourcc,
|
||||
stride / bpp);
|
||||
if (!self->nvnc_fb) {
|
||||
goto nvnc_fb_failure;
|
||||
}
|
||||
|
||||
nvnc_set_userdata(self->nvnc_fb, self, NULL);
|
||||
|
||||
pixman_region_init(&self->damage);
|
||||
|
||||
close(fd);
|
||||
return self;
|
||||
|
||||
nvnc_fb_failure:
|
||||
wl_buffer_destroy(self->wl_buffer);
|
||||
shm_failure:
|
||||
pool_failure:
|
||||
mmap_failure:
|
||||
close(fd);
|
||||
failure:
|
||||
free(self);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCREENCOPY_DMABUF
|
||||
#ifdef HAVE_LINUX_DMA_HEAP
|
||||
static bool have_linux_cma(void)
|
||||
{
|
||||
return access(LINUX_CMA_PATH, R_OK | W_OK) == 0;
|
||||
}
|
||||
|
||||
static int linux_cma_alloc(size_t size)
|
||||
{
|
||||
int fd = open(LINUX_CMA_PATH, O_RDWR | O_CLOEXEC, 0);
|
||||
if (fd < 0) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "Failed to open CMA device: %m");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct dma_heap_allocation_data data = {
|
||||
.len = size,
|
||||
.fd_flags = O_CLOEXEC | O_RDWR,
|
||||
};
|
||||
|
||||
int r = ioctl(fd, DMA_HEAP_IOCTL_ALLOC, &data);
|
||||
if (r < 0) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "Failed to allocate CMA buffer: %m");
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
return data.fd;
|
||||
}
|
||||
|
||||
// Some devices (mostly ARM SBCs) need CMA for hardware encoders.
|
||||
static struct gbm_bo* create_cma_gbm_bo(int width, int height, uint32_t fourcc)
|
||||
{
|
||||
assert(gbm_device);
|
||||
|
||||
int bpp = pixel_size_from_fourcc(fourcc);
|
||||
if (!bpp) {
|
||||
nvnc_log(NVNC_LOG_PANIC, "Unsupported pixel format: %" PRIu32,
|
||||
fourcc);
|
||||
}
|
||||
|
||||
/* TODO: Get alignment through feedback mechanism.
|
||||
* Buffer sizes are aligned on both axes by 16 and we'll do the same
|
||||
* in the encoder, but this requirement should come from the encoder.
|
||||
*/
|
||||
int stride = bpp * ALIGN_UP(width, 16);
|
||||
|
||||
int fd = linux_cma_alloc(stride * ALIGN_UP(height, 16));
|
||||
if (fd < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct gbm_import_fd_modifier_data d = {
|
||||
.format = fourcc,
|
||||
.width = width,
|
||||
.height = height,
|
||||
// v4l2m2m doesn't support modifiers, so we use linear
|
||||
.modifier = DRM_FORMAT_MOD_LINEAR,
|
||||
.num_fds = 1,
|
||||
.fds[0] = fd,
|
||||
.offsets[0] = 0,
|
||||
.strides[0] = stride,
|
||||
};
|
||||
|
||||
struct gbm_bo* bo = gbm_bo_import(gbm_device, GBM_BO_IMPORT_FD_MODIFIER,
|
||||
&d, 0);
|
||||
if (!bo) {
|
||||
nvnc_log(NVNC_LOG_DEBUG, "Failed to import dmabuf: %m");
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return bo;
|
||||
}
|
||||
#endif // HAVE_LINUX_DMA_HEAP
|
||||
|
||||
static struct wv_buffer* wv_buffer_create_dmabuf(int width, int height,
|
||||
uint32_t fourcc)
|
||||
{
|
||||
assert(zwp_linux_dmabuf);
|
||||
assert(gbm_device);
|
||||
|
||||
struct wv_buffer* self = calloc(1, sizeof(*self));
|
||||
if (!self)
|
||||
return NULL;
|
||||
|
||||
self->type = WV_BUFFER_DMABUF;
|
||||
self->width = width;
|
||||
self->height = height;
|
||||
self->format = fourcc;
|
||||
|
||||
// Checks not needed anymore. Fixed with SCANOUT and within neatvnc for most GPUs.
|
||||
// But this could still fail!
|
||||
//#ifdef HAVE_LINUX_DMA_HEAP
|
||||
self->bo = have_linux_cma() ?
|
||||
create_cma_gbm_bo(width, height, fourcc) :
|
||||
gbm_bo_create(gbm_device, width, height, fourcc,
|
||||
GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT);
|
||||
//#endif
|
||||
// self->bo = gbm_bo_create(gbm_device, width, height, fourcc,
|
||||
// GBM_BO_USE_RENDERING);
|
||||
|
||||
if (!self->bo)
|
||||
goto bo_failure;
|
||||
|
||||
struct zwp_linux_buffer_params_v1* params;
|
||||
params = zwp_linux_dmabuf_v1_create_params(zwp_linux_dmabuf);
|
||||
if (!params)
|
||||
goto params_failure;
|
||||
|
||||
uint32_t offset = gbm_bo_get_offset(self->bo, 0);
|
||||
uint32_t stride = gbm_bo_get_stride(self->bo);
|
||||
uint64_t mod = gbm_bo_get_modifier(self->bo);
|
||||
int fd = gbm_bo_get_fd(self->bo);
|
||||
if (fd < 0)
|
||||
goto fd_failure;
|
||||
|
||||
zwp_linux_buffer_params_v1_add(params, fd, 0, offset, stride,
|
||||
mod >> 32, mod & 0xffffffff);
|
||||
self->wl_buffer = zwp_linux_buffer_params_v1_create_immed(params, width,
|
||||
height, fourcc, /* flags */ 0);
|
||||
zwp_linux_buffer_params_v1_destroy(params);
|
||||
close(fd);
|
||||
|
||||
if (!self->wl_buffer)
|
||||
goto buffer_failure;
|
||||
|
||||
self->nvnc_fb = nvnc_fb_from_gbm_bo(self->bo);
|
||||
if (!self->nvnc_fb) {
|
||||
goto nvnc_fb_failure;
|
||||
}
|
||||
|
||||
nvnc_set_userdata(self->nvnc_fb, self, NULL);
|
||||
|
||||
return self;
|
||||
|
||||
nvnc_fb_failure:
|
||||
wl_buffer_destroy(self->wl_buffer);
|
||||
buffer_failure:
|
||||
fd_failure:
|
||||
zwp_linux_buffer_params_v1_destroy(params);
|
||||
params_failure:
|
||||
gbm_bo_destroy(self->bo);
|
||||
bo_failure:
|
||||
free(self);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct wv_buffer* wv_buffer_create(enum wv_buffer_type type, int width,
|
||||
int height, int stride, uint32_t fourcc)
|
||||
{
|
||||
switch (type) {
|
||||
case WV_BUFFER_SHM:
|
||||
return wv_buffer_create_shm(width, height, stride, fourcc);
|
||||
#ifdef ENABLE_SCREENCOPY_DMABUF
|
||||
case WV_BUFFER_DMABUF:
|
||||
return wv_buffer_create_dmabuf(width, height, fourcc);
|
||||
#endif
|
||||
case WV_BUFFER_UNSPEC:;
|
||||
}
|
||||
|
||||
abort();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void wv_buffer_destroy_shm(struct wv_buffer* self)
|
||||
{
|
||||
nvnc_fb_unref(self->nvnc_fb);
|
||||
wl_buffer_destroy(self->wl_buffer);
|
||||
munmap(self->pixels, self->size);
|
||||
free(self);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCREENCOPY_DMABUF
|
||||
static void wv_buffer_destroy_dmabuf(struct wv_buffer* self)
|
||||
{
|
||||
nvnc_fb_unref(self->nvnc_fb);
|
||||
wl_buffer_destroy(self->wl_buffer);
|
||||
gbm_bo_destroy(self->bo);
|
||||
free(self);
|
||||
}
|
||||
#endif
|
||||
|
||||
void wv_buffer_destroy(struct wv_buffer* self)
|
||||
{
|
||||
pixman_region_fini(&self->damage);
|
||||
|
||||
switch (self->type) {
|
||||
case WV_BUFFER_SHM:
|
||||
wv_buffer_destroy_shm(self);
|
||||
return;
|
||||
#ifdef ENABLE_SCREENCOPY_DMABUF
|
||||
case WV_BUFFER_DMABUF:
|
||||
wv_buffer_destroy_dmabuf(self);
|
||||
return;
|
||||
#endif
|
||||
case WV_BUFFER_UNSPEC:;
|
||||
}
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
void wv_buffer_damage_rect(struct wv_buffer* self, int x, int y, int width,
|
||||
int height)
|
||||
{
|
||||
pixman_region_union_rect(&self->damage, &self->damage, x, y, width,
|
||||
height);
|
||||
}
|
||||
|
||||
void wv_buffer_damage_whole(struct wv_buffer* self)
|
||||
{
|
||||
wv_buffer_damage_rect(self, 0, 0, self->width, self->height);
|
||||
}
|
||||
|
||||
void wv_buffer_damage_clear(struct wv_buffer* self)
|
||||
{
|
||||
pixman_region_clear(&self->damage);
|
||||
}
|
||||
|
||||
struct wv_buffer_pool* wv_buffer_pool_create(enum wv_buffer_type type,
|
||||
int width, int height, int stride, uint32_t format)
|
||||
{
|
||||
struct wv_buffer_pool* self = calloc(1, sizeof(*self));
|
||||
if (!self)
|
||||
return NULL;
|
||||
|
||||
TAILQ_INIT(&self->queue);
|
||||
self->type = type;
|
||||
self->width = width;
|
||||
self->height = height;
|
||||
self->stride = stride;
|
||||
self->format = format;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static void wv_buffer_pool_clear(struct wv_buffer_pool* pool)
|
||||
{
|
||||
while (!TAILQ_EMPTY(&pool->queue)) {
|
||||
struct wv_buffer* buffer = TAILQ_FIRST(&pool->queue);
|
||||
TAILQ_REMOVE(&pool->queue, buffer, link);
|
||||
wv_buffer_destroy(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void wv_buffer_pool_destroy(struct wv_buffer_pool* pool)
|
||||
{
|
||||
wv_buffer_pool_clear(pool);
|
||||
free(pool);
|
||||
}
|
||||
|
||||
void wv_buffer_pool_resize(struct wv_buffer_pool* pool,
|
||||
enum wv_buffer_type type, int width, int height, int stride,
|
||||
uint32_t format)
|
||||
{
|
||||
if (pool->type != type || pool->width != width || pool->height != height
|
||||
|| pool->stride != stride || pool->format != format) {
|
||||
wv_buffer_pool_clear(pool);
|
||||
}
|
||||
|
||||
pool->type = type;
|
||||
pool->width = width;
|
||||
pool->height = height;
|
||||
pool->stride = stride;
|
||||
pool->format = format;
|
||||
}
|
||||
|
||||
static bool wv_buffer_pool_match_buffer(struct wv_buffer_pool* pool,
|
||||
struct wv_buffer* buffer)
|
||||
{
|
||||
if (pool->type != buffer->type)
|
||||
return false;
|
||||
|
||||
switch (pool->type) {
|
||||
case WV_BUFFER_SHM:
|
||||
if (pool->stride != buffer->stride) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCREENCOPY_DMABUF
|
||||
/* fall-through */
|
||||
case WV_BUFFER_DMABUF:
|
||||
#endif
|
||||
if (pool->width != buffer->width
|
||||
|| pool->height != buffer->height
|
||||
|| pool->format != buffer->format)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
case WV_BUFFER_UNSPEC:
|
||||
abort();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void wv_buffer_pool__on_release(struct nvnc_fb* fb, void* context)
|
||||
{
|
||||
struct wv_buffer* buffer = nvnc_get_userdata(fb);
|
||||
struct wv_buffer_pool* pool = context;
|
||||
|
||||
wv_buffer_pool_release(pool, buffer);
|
||||
}
|
||||
|
||||
struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool)
|
||||
{
|
||||
struct wv_buffer* buffer = TAILQ_FIRST(&pool->queue);
|
||||
if (buffer) {
|
||||
assert(wv_buffer_pool_match_buffer(pool, buffer));
|
||||
TAILQ_REMOVE(&pool->queue, buffer, link);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
buffer = wv_buffer_create(pool->type, pool->width, pool->height,
|
||||
pool->stride, pool->format);
|
||||
if (buffer)
|
||||
nvnc_fb_set_release_fn(buffer->nvnc_fb,
|
||||
wv_buffer_pool__on_release, pool);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void wv_buffer_pool_release(struct wv_buffer_pool* pool,
|
||||
struct wv_buffer* buffer)
|
||||
{
|
||||
wv_buffer_damage_clear(buffer);
|
||||
|
||||
if (wv_buffer_pool_match_buffer(pool, buffer)) {
|
||||
TAILQ_INSERT_TAIL(&pool->queue, buffer, link);
|
||||
} else {
|
||||
wv_buffer_destroy(buffer);
|
||||
}
|
||||
}
|
15
src/cfg.c
15
src/cfg.c
|
@ -18,6 +18,8 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "cfg.h"
|
||||
|
||||
|
@ -106,11 +108,16 @@ static int cfg__load_line(struct cfg* self, char* line)
|
|||
return cfg__load_key_value(self, key, value);
|
||||
}
|
||||
|
||||
static char* cfg__dirname(const char* path)
|
||||
{
|
||||
char buffer[PATH_MAX];
|
||||
return strdup(dirname(realpath(path, buffer)));
|
||||
}
|
||||
|
||||
int cfg_load(struct cfg* self, const char* requested_path)
|
||||
{
|
||||
const char* path = requested_path ? requested_path
|
||||
: cfg__get_default_path();
|
||||
|
||||
if (!path)
|
||||
return -1;
|
||||
|
||||
|
@ -118,6 +125,8 @@ int cfg_load(struct cfg* self, const char* requested_path)
|
|||
if (!stream)
|
||||
return -1;
|
||||
|
||||
self->directory = cfg__dirname(path);
|
||||
|
||||
char* line = NULL;
|
||||
size_t len = 0;
|
||||
int lineno = 0;
|
||||
|
@ -129,11 +138,14 @@ int cfg_load(struct cfg* self, const char* requested_path)
|
|||
goto failure;
|
||||
}
|
||||
|
||||
free(line);
|
||||
fclose(stream);
|
||||
return 0;
|
||||
|
||||
failure:
|
||||
cfg_destroy(self);
|
||||
free(line);
|
||||
free(self->directory);
|
||||
fclose(stream);
|
||||
return lineno;
|
||||
}
|
||||
|
@ -151,4 +163,5 @@ void cfg_destroy(struct cfg* self)
|
|||
#undef DESTROY_string
|
||||
#undef DESTROY_uint
|
||||
#undef DESTROY_bool
|
||||
free(self->directory);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,928 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023 Jim Ramsay
|
||||
* Copyright (c) 2023 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/stat.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <assert.h>
|
||||
#include <jansson.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#include "json-ipc.h"
|
||||
#include "ctl-client.h"
|
||||
#include "ctl-commands.h"
|
||||
#include "strlcpy.h"
|
||||
#include "util.h"
|
||||
#include "option-parser.h"
|
||||
#include "table-printer.h"
|
||||
|
||||
#define LOG(level, fmt, ...) \
|
||||
fprintf(stderr, level ": %s: %d: " fmt "\n", __FILE__, __LINE__, \
|
||||
##__VA_ARGS__)
|
||||
|
||||
#define ERROR(fmt, ...) \
|
||||
LOG("ERROR", fmt, ##__VA_ARGS__)
|
||||
|
||||
static bool do_debug = false;
|
||||
|
||||
#define DEBUG(fmt, ...) \
|
||||
if (do_debug) \
|
||||
LOG("DEBUG", fmt, ##__VA_ARGS__)
|
||||
|
||||
static struct cmd_info internal_events[] = {
|
||||
{ .name = "wayvnc-startup",
|
||||
.description = "Sent by wayvncctl when a successful wayvnc control connection is established and event registration has succeeded, both upon initial startup and on subsequent registrations with --reconnect.",
|
||||
.params = {{}},
|
||||
},
|
||||
{ .name = "wayvnc-shutdown",
|
||||
.description = "Sent by wayvncctl when the wayvnc control connection is dropped, usually due to wayvnc exiting.",
|
||||
.params = {{}},
|
||||
},
|
||||
};
|
||||
#define EVT_LOCAL_STARTUP internal_events[0].name
|
||||
#define EVT_LOCAL_SHUTDOWN internal_events[1].name
|
||||
#define INTERNAL_EVT_LEN 2
|
||||
|
||||
struct ctl_client {
|
||||
void* userdata;
|
||||
struct sockaddr_un addr;
|
||||
unsigned flags;
|
||||
|
||||
char read_buffer[1024];
|
||||
size_t read_len;
|
||||
|
||||
bool wait_for_events;
|
||||
|
||||
int fd;
|
||||
};
|
||||
|
||||
void ctl_client_debug_log(bool enable)
|
||||
{
|
||||
do_debug = enable;
|
||||
}
|
||||
|
||||
struct ctl_client* ctl_client_new(const char* socket_path, void* userdata)
|
||||
{
|
||||
if (!socket_path)
|
||||
socket_path = default_ctl_socket_path();
|
||||
|
||||
struct ctl_client* new = calloc(1, sizeof(*new));
|
||||
new->userdata = userdata;
|
||||
new->fd = -1;
|
||||
|
||||
if (strlen(socket_path) >= sizeof(new->addr.sun_path)) {
|
||||
errno = ENAMETOOLONG;
|
||||
ERROR("Failed to create unix socket: %m");
|
||||
goto socket_failure;
|
||||
}
|
||||
strcpy(new->addr.sun_path, socket_path);
|
||||
new->addr.sun_family = AF_UNIX;
|
||||
|
||||
return new;
|
||||
|
||||
socket_failure:
|
||||
free(new);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int wait_for_socket(const char* socket_path, int timeout)
|
||||
{
|
||||
bool needs_log = true;
|
||||
struct stat sb;
|
||||
|
||||
while (stat(socket_path, &sb) != 0) {
|
||||
if (timeout == 0) {
|
||||
ERROR("Failed to find socket path \"%s\": %m",
|
||||
socket_path);
|
||||
return 1;
|
||||
}
|
||||
if (needs_log) {
|
||||
needs_log = false;
|
||||
DEBUG("Waiting for socket path \"%s\" to appear",
|
||||
socket_path);
|
||||
}
|
||||
if (usleep(50000) == -1) {
|
||||
ERROR("Failed to wait for socket path: %m");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (S_ISSOCK(sb.st_mode)) {
|
||||
DEBUG("Found socket \"%s\"", socket_path);
|
||||
} else {
|
||||
ERROR("Path \"%s\" exists but is not a socket (0x%x)",
|
||||
socket_path, sb.st_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int try_connect(struct ctl_client* self, int timeout)
|
||||
{
|
||||
if (self->fd != -1)
|
||||
close(self->fd);
|
||||
|
||||
self->fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (self->fd < 0) {
|
||||
ERROR("Failed to create unix socket: %m");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (connect(self->fd, (struct sockaddr*)&self->addr,
|
||||
sizeof(self->addr)) != 0) {
|
||||
if (timeout == 0 || errno != ENOENT) {
|
||||
ERROR("Failed to connect to unix socket \"%s\": %m",
|
||||
self->addr.sun_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (usleep(50000) == -1) {
|
||||
ERROR("Failed to wait for connect to succeed: %m");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ctl_client_connect(struct ctl_client* self, int timeout)
|
||||
{
|
||||
// TODO: Support arbitrary timeouts?
|
||||
assert(timeout == 0 || timeout == -1);
|
||||
|
||||
if (wait_for_socket(self->addr.sun_path, timeout) != 0)
|
||||
return 1;
|
||||
|
||||
if (try_connect(self, timeout) != 0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ctl_client_destroy(struct ctl_client* self)
|
||||
{
|
||||
close(self->fd);
|
||||
free(self);
|
||||
}
|
||||
|
||||
void* ctl_client_userdata(struct ctl_client* self)
|
||||
{
|
||||
return self->userdata;
|
||||
}
|
||||
|
||||
static struct jsonipc_request* ctl_client_parse_args(struct ctl_client* self,
|
||||
enum cmd_type* cmd, struct option_parser* options)
|
||||
{
|
||||
struct jsonipc_request* request = NULL;
|
||||
json_t* params = json_object();
|
||||
struct cmd_info* info = ctl_command_by_type(*cmd);
|
||||
|
||||
if (option_parser_get_value(options, "help")) {
|
||||
json_object_set_new(params, "command", json_string(info->name));
|
||||
*cmd = CMD_HELP;
|
||||
info = ctl_command_by_type(*cmd);
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (int i = 0; info->params[i].name != NULL; ++i) {
|
||||
const char* key = info->params[i].name;
|
||||
const char* value = option_parser_get_value(options, key);
|
||||
if (!value)
|
||||
continue;
|
||||
json_object_set_new(params, key, json_string(value));
|
||||
}
|
||||
|
||||
out:
|
||||
request = jsonipc_request_new(info->name, params);
|
||||
json_decref(params);
|
||||
return request;
|
||||
}
|
||||
|
||||
static json_t* json_from_buffer(struct ctl_client* self)
|
||||
{
|
||||
if (self->read_len == 0) {
|
||||
DEBUG("Read buffer is empty");
|
||||
errno = EAGAIN;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
json_error_t err;
|
||||
json_t* root = json_loadb(self->read_buffer, self->read_len, JSON_DISABLE_EOF_CHECK, &err);
|
||||
if (root) {
|
||||
advance_read_buffer(&self->read_buffer, &self->read_len,
|
||||
err.position);
|
||||
} else if (json_error_code(&err) == json_error_premature_end_of_input) {
|
||||
if (self->read_len == sizeof(self->read_buffer)) {
|
||||
ERROR("Response message is too long");
|
||||
errno = EMSGSIZE;
|
||||
} else {
|
||||
DEBUG("Awaiting more data");
|
||||
errno = EAGAIN;
|
||||
}
|
||||
} else {
|
||||
ERROR("Json parsing failed: %s", err.text);
|
||||
errno = EINVAL;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
static json_t* read_one_object(struct ctl_client* self, int timeout_ms)
|
||||
{
|
||||
json_t* root = json_from_buffer(self);
|
||||
if (root)
|
||||
return root;
|
||||
if (errno != EAGAIN)
|
||||
return NULL;
|
||||
|
||||
struct pollfd pfd = {
|
||||
.fd = self->fd,
|
||||
.events = POLLIN,
|
||||
.revents = 0
|
||||
};
|
||||
|
||||
while (!root) {
|
||||
int n = poll(&pfd, 1, timeout_ms);
|
||||
if (n == -1) {
|
||||
if (errno == EINTR && self->wait_for_events)
|
||||
continue;
|
||||
ERROR("Error waiting for a response: %m");
|
||||
break;
|
||||
} else if (n == 0) {
|
||||
ERROR("Timeout waiting for a response");
|
||||
break;
|
||||
}
|
||||
|
||||
char* readptr = self->read_buffer + self->read_len;
|
||||
size_t remainder = sizeof(self->read_buffer) - self->read_len;
|
||||
|
||||
n = recv(self->fd, readptr, remainder, 0);
|
||||
if (n == -1) {
|
||||
ERROR("Read failed: %m");
|
||||
break;
|
||||
} else if (n == 0) {
|
||||
ERROR("Disconnected");
|
||||
errno = ECONNRESET;
|
||||
break;
|
||||
}
|
||||
|
||||
DEBUG("Read %d bytes", n);
|
||||
DEBUG("<< %.*s", n, readptr);
|
||||
|
||||
self->read_len += n;
|
||||
|
||||
root = json_from_buffer(self);
|
||||
if (!root && errno != EAGAIN)
|
||||
break;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
static struct jsonipc_response* ctl_client_wait_for_response(struct ctl_client* self)
|
||||
{
|
||||
DEBUG("Waiting for a response");
|
||||
json_t* root = read_one_object(self, 1000);
|
||||
if (!root)
|
||||
return NULL;
|
||||
|
||||
struct jsonipc_error jipc_err = JSONIPC_ERR_INIT;
|
||||
|
||||
struct jsonipc_response* response = jsonipc_response_parse_new(root,
|
||||
&jipc_err);
|
||||
if (!response) {
|
||||
char* msg = json_dumps(jipc_err.data, JSON_EMBED);
|
||||
ERROR("Could not parse json: %s", msg);
|
||||
free(msg);
|
||||
}
|
||||
|
||||
json_decref(root);
|
||||
jsonipc_error_cleanup(&jipc_err);
|
||||
return response;
|
||||
}
|
||||
|
||||
static void print_error(struct jsonipc_response* response, const char* method)
|
||||
{
|
||||
printf("ERROR: Failed to execute command: %s", method);
|
||||
if (!response->data)
|
||||
goto out;
|
||||
|
||||
json_t* data = response->data;
|
||||
if (json_is_string(data))
|
||||
printf(": %s", json_string_value(data));
|
||||
else if (json_is_object(data) &&
|
||||
json_is_string(json_object_get(data, "error")))
|
||||
printf(": %s", json_string_value(json_object_get(data, "error")));
|
||||
else
|
||||
json_dumpf(response->data, stdout, JSON_INDENT(2));
|
||||
|
||||
out:
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void pretty_version(json_t* data)
|
||||
{
|
||||
printf("wayvnc is running:\n");
|
||||
const char* key;
|
||||
json_t* value;
|
||||
json_object_foreach(data, key, value)
|
||||
printf(" %s: %s\n", key, json_string_value(value));
|
||||
}
|
||||
|
||||
static void pretty_client_list(json_t* data)
|
||||
{
|
||||
size_t i;
|
||||
json_t* value;
|
||||
json_array_foreach(data, i, value) {
|
||||
char* id = NULL;
|
||||
char* address = NULL;
|
||||
char* username = NULL;
|
||||
|
||||
json_unpack(value, "{s:s, s?s, s?s}", "id", &id, "address",
|
||||
&address, "username", &username);
|
||||
printf(" %s: ", id);
|
||||
|
||||
if (username)
|
||||
printf("%s@", username);
|
||||
|
||||
printf("%s\n", address ? address : "<unknown>");
|
||||
}
|
||||
}
|
||||
|
||||
static void pretty_output_list(json_t* data)
|
||||
{
|
||||
size_t i;
|
||||
json_t* value;
|
||||
json_array_foreach(data, i, value) {
|
||||
char* name = NULL;
|
||||
char* description = NULL;
|
||||
int height = -1;
|
||||
int width = -1;
|
||||
int captured = false;
|
||||
|
||||
json_unpack(value, "{s:s, s:s, s:i, s:i, s:b}", "name", &name,
|
||||
"description", &description,
|
||||
"height", &height,
|
||||
"width", &width,
|
||||
"captured", &captured);
|
||||
printf("%s %s: \"%s\" (%dx%d)\n",
|
||||
captured ? "*" : " ", name, description, width,
|
||||
height);
|
||||
}
|
||||
}
|
||||
|
||||
static void pretty_print(json_t* data,
|
||||
struct jsonipc_request* request)
|
||||
{
|
||||
enum cmd_type cmd = ctl_command_parse_name(request->method);
|
||||
switch (cmd) {
|
||||
case CMD_VERSION:
|
||||
pretty_version(data);
|
||||
break;
|
||||
case CMD_CLIENT_LIST:
|
||||
pretty_client_list(data);
|
||||
break;
|
||||
case CMD_OUTPUT_LIST:
|
||||
pretty_output_list(data);
|
||||
break;
|
||||
case CMD_ATTACH:
|
||||
case CMD_DETACH:
|
||||
case CMD_CLIENT_DISCONNECT:
|
||||
case CMD_OUTPUT_SET:
|
||||
case CMD_OUTPUT_CYCLE:
|
||||
case CMD_WAYVNC_EXIT:
|
||||
printf("Ok\n");
|
||||
break;
|
||||
case CMD_EVENT_RECEIVE:
|
||||
case CMD_HELP:
|
||||
abort(); // Handled directly by ctl_client_run_command
|
||||
case CMD_UNKNOWN:
|
||||
json_dumpf(data, stdout, JSON_INDENT(2));
|
||||
}
|
||||
}
|
||||
|
||||
static void print_compact_json(json_t* data)
|
||||
{
|
||||
json_dumpf(data, stdout, JSON_COMPACT);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static int ctl_client_print_response(struct ctl_client* self,
|
||||
struct jsonipc_request* request,
|
||||
struct jsonipc_response* response)
|
||||
{
|
||||
DEBUG("Response code: %d", response->code);
|
||||
|
||||
if (response->data) {
|
||||
if (self->flags & CTL_CLIENT_PRINT_JSON)
|
||||
print_compact_json(response->data);
|
||||
else if (response->code == 0)
|
||||
pretty_print(response->data, request);
|
||||
else
|
||||
print_error(response, request->method);
|
||||
}
|
||||
|
||||
return response->code;
|
||||
}
|
||||
|
||||
static struct ctl_client* sig_target = NULL;
|
||||
|
||||
static void stop_loop(int signal)
|
||||
{
|
||||
sig_target->wait_for_events = false;
|
||||
}
|
||||
|
||||
static void setup_signals(struct ctl_client* self)
|
||||
{
|
||||
sig_target = self;
|
||||
struct sigaction sa = { 0 };
|
||||
sa.sa_handler = stop_loop;
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
}
|
||||
|
||||
static void print_indent(int level)
|
||||
{
|
||||
for (int i = 0; i < level; ++i)
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
static bool json_has_content(json_t* root)
|
||||
{
|
||||
if (!root)
|
||||
return false;
|
||||
|
||||
size_t i;
|
||||
const char* key;
|
||||
json_t* value;
|
||||
|
||||
switch (json_typeof(root)) {
|
||||
case JSON_NULL:
|
||||
return false;
|
||||
case JSON_INTEGER:
|
||||
case JSON_REAL:
|
||||
case JSON_TRUE:
|
||||
case JSON_FALSE:
|
||||
return true;
|
||||
case JSON_STRING:
|
||||
return json_string_value(root)[0] != '\0';
|
||||
case JSON_OBJECT:
|
||||
json_object_foreach(root, key, value)
|
||||
if (json_has_content(value))
|
||||
return true;
|
||||
return false;
|
||||
case JSON_ARRAY:
|
||||
json_array_foreach(root, i, value)
|
||||
if (json_has_content(value))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void print_for_human(json_t* data, int level)
|
||||
{
|
||||
size_t i;
|
||||
const char* key;
|
||||
json_t* value;
|
||||
|
||||
switch(json_typeof(data)) {
|
||||
case JSON_NULL:
|
||||
printf("<null>\n");
|
||||
break;
|
||||
case JSON_OBJECT:
|
||||
json_object_foreach(data, key, value) {
|
||||
if (!json_has_content(value))
|
||||
continue;
|
||||
|
||||
print_indent(level);
|
||||
printf("%s: ", key);
|
||||
print_for_human(value, level + 1);
|
||||
}
|
||||
break;
|
||||
case JSON_ARRAY:
|
||||
json_array_foreach(data, i, value) {
|
||||
if (!json_has_content(value))
|
||||
continue;
|
||||
|
||||
print_indent(level);
|
||||
printf("- ");
|
||||
print_for_human(value, level + 1);
|
||||
}
|
||||
break;
|
||||
case JSON_STRING:
|
||||
printf("%s\n", json_string_value(data));
|
||||
break;
|
||||
case JSON_INTEGER:
|
||||
printf("%" JSON_INTEGER_FORMAT "\n", json_integer_value(data));
|
||||
break;
|
||||
case JSON_REAL:
|
||||
printf("%f\n", json_real_value(data));
|
||||
break;
|
||||
case JSON_TRUE:
|
||||
printf("true\n");
|
||||
break;
|
||||
case JSON_FALSE:
|
||||
printf("false\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_event(struct jsonipc_request* event, unsigned flags)
|
||||
{
|
||||
if (flags & CTL_CLIENT_PRINT_JSON) {
|
||||
print_compact_json(event->json);
|
||||
} else {
|
||||
printf("%s:\n", event->method);
|
||||
if (event->params)
|
||||
print_for_human(event->params, 1);
|
||||
printf("\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void send_local_event(struct ctl_client* self, const char* name)
|
||||
{
|
||||
struct jsonipc_request* event = jsonipc_event_new(name, NULL);
|
||||
event->json = jsonipc_request_pack(event, NULL);
|
||||
print_event(event, self->flags);
|
||||
jsonipc_request_destroy(event);
|
||||
}
|
||||
|
||||
static void send_startup_event(struct ctl_client* self)
|
||||
{
|
||||
send_local_event(self, EVT_LOCAL_STARTUP);
|
||||
}
|
||||
|
||||
static void send_shutdown_event(struct ctl_client* self)
|
||||
{
|
||||
send_local_event(self, EVT_LOCAL_SHUTDOWN);
|
||||
}
|
||||
|
||||
static ssize_t ctl_client_send_request(struct ctl_client* self,
|
||||
struct jsonipc_request* request)
|
||||
{
|
||||
json_error_t err;
|
||||
json_t* packed = jsonipc_request_pack(request, &err);
|
||||
if (!packed) {
|
||||
ERROR("Could not encode json: %s", err.text);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char buffer[512];
|
||||
int len = json_dumpb(packed, buffer, sizeof(buffer), JSON_COMPACT);
|
||||
json_decref(packed);
|
||||
DEBUG(">> %.*s", len, buffer);
|
||||
|
||||
return send(self->fd, buffer, len, MSG_NOSIGNAL);
|
||||
}
|
||||
|
||||
static struct jsonipc_response* ctl_client_run_single_command(struct ctl_client* self,
|
||||
struct jsonipc_request* request)
|
||||
{
|
||||
if (ctl_client_send_request(self, request) < 0)
|
||||
return NULL;
|
||||
|
||||
return ctl_client_wait_for_response(self);
|
||||
}
|
||||
|
||||
static int ctl_client_register_for_events(struct ctl_client* self,
|
||||
struct jsonipc_request* request)
|
||||
{
|
||||
struct jsonipc_response* response = ctl_client_run_single_command(self, request);
|
||||
if (!response)
|
||||
return -1;
|
||||
|
||||
int result = response->code;
|
||||
jsonipc_response_destroy(response);
|
||||
if (result == 0)
|
||||
send_startup_event(self);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int ctl_client_reconnect_event_loop(struct ctl_client* self,
|
||||
struct jsonipc_request* request)
|
||||
{
|
||||
if (ctl_client_connect(self, -1) != 0)
|
||||
return -1;
|
||||
|
||||
return ctl_client_register_for_events(self, request);
|
||||
}
|
||||
|
||||
static int block_until_reconnect(struct ctl_client* self,
|
||||
struct jsonipc_request* request)
|
||||
{
|
||||
while (ctl_client_reconnect_event_loop(self, request) != 0)
|
||||
if (usleep(50000) == -1) {
|
||||
DEBUG("Interrupted waiting for the IPC socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ctl_client_event_loop(struct ctl_client* self,
|
||||
struct jsonipc_request* request)
|
||||
{
|
||||
int result = ctl_client_register_for_events(self, request);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
self->wait_for_events = true;
|
||||
setup_signals(self);
|
||||
while (self->wait_for_events) {
|
||||
DEBUG("Waiting for an event");
|
||||
json_t* root = read_one_object(self, -1);
|
||||
if (!root) {
|
||||
if (errno == ECONNRESET) {
|
||||
send_shutdown_event(self);
|
||||
if (self->flags & CTL_CLIENT_RECONNECT &&
|
||||
block_until_reconnect(
|
||||
self, request) == 0)
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
struct jsonipc_error err = JSONIPC_ERR_INIT;
|
||||
struct jsonipc_request* event = jsonipc_event_parse_new(root, &err);
|
||||
json_decref(root);
|
||||
print_event(event, self->flags);
|
||||
jsonipc_request_destroy(event);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ctl_client_print_single_command(struct ctl_client* self,
|
||||
enum cmd_type cmd, struct jsonipc_request* request)
|
||||
{
|
||||
struct jsonipc_response* response = ctl_client_run_single_command(self,
|
||||
request);
|
||||
if (!response) {
|
||||
if (errno == ECONNRESET && cmd == CMD_WAYVNC_EXIT)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = ctl_client_print_response(self, request, response);
|
||||
jsonipc_response_destroy(response);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ctl_client_print_command_list(FILE* stream)
|
||||
{
|
||||
fprintf(stream, "Commands:\n");
|
||||
size_t max_namelen = 0;
|
||||
for (size_t i = 0; i < CMD_LIST_LEN; ++i) {
|
||||
if (i == CMD_HELP) // hidden
|
||||
continue;
|
||||
max_namelen = MAX(max_namelen, strlen(ctl_command_list[i].name));
|
||||
}
|
||||
|
||||
struct table_printer printer;
|
||||
table_printer_init(&printer, stdout, max_namelen);
|
||||
for (size_t i = 0; i < CMD_LIST_LEN; ++i) {
|
||||
if (i == CMD_HELP) // hidden
|
||||
continue;
|
||||
table_printer_print_line(&printer, ctl_command_list[i].name,
|
||||
ctl_command_list[i].description);
|
||||
}
|
||||
|
||||
fprintf(stream, "\nRun 'wayvncctl command-name --help' for command-specific details.\n");
|
||||
}
|
||||
|
||||
static size_t param_render_length(const struct cmd_param_info* param)
|
||||
{
|
||||
return strlen(param->name) + strlen(param->schema) + 1;
|
||||
}
|
||||
|
||||
static void print_event_info(const struct cmd_info* info)
|
||||
{
|
||||
printf("%s\n", info->name);
|
||||
option_parser_print_cmd_summary(info->description, stdout);
|
||||
if (info->params[0].name != NULL) {
|
||||
printf("Data fields:\n");
|
||||
size_t max_namelen = 0;
|
||||
for (int i = 0; info->params[i].name != NULL; ++i)
|
||||
max_namelen = MAX(max_namelen, param_render_length(&info->params[i]));
|
||||
|
||||
struct table_printer printer;
|
||||
table_printer_init(&printer, stdout, max_namelen);
|
||||
for (int i = 0; info->params[i].name != NULL; ++i)
|
||||
table_printer_print_fmtline(&printer,
|
||||
info->params[i].description,
|
||||
"%s: %s", info->params[i].name,
|
||||
info->params[i].schema);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int print_event_details(const char* evt_name)
|
||||
{
|
||||
struct cmd_info* info = ctl_event_by_name(evt_name);
|
||||
if (info) {
|
||||
print_event_info(info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < INTERNAL_EVT_LEN; ++i) {
|
||||
if (strcmp(evt_name, internal_events[i].name) == 0) {
|
||||
print_event_info(&internal_events[i]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ERROR("No such event \"%s\"\n", evt_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ctl_client_print_event_list(FILE* stream)
|
||||
{
|
||||
printf("Events:\n");
|
||||
size_t max_namelen = 0;
|
||||
for (size_t i = 0; i < EVT_LIST_LEN; ++i)
|
||||
max_namelen = MAX(max_namelen, strlen(ctl_event_list[i].name));
|
||||
|
||||
for (size_t i = 0; i < INTERNAL_EVT_LEN; ++i)
|
||||
max_namelen = MAX(max_namelen, strlen(internal_events[i].name));
|
||||
|
||||
struct table_printer printer;
|
||||
table_printer_init(&printer, stdout, max_namelen);
|
||||
for (size_t i = 0; i < EVT_LIST_LEN; ++i)
|
||||
table_printer_print_line(&printer, ctl_event_list[i].name,
|
||||
ctl_event_list[i].description);
|
||||
|
||||
for (size_t i = 0; i < INTERNAL_EVT_LEN; ++i)
|
||||
table_printer_print_line(&printer, internal_events[i].name,
|
||||
internal_events[i].description);
|
||||
}
|
||||
|
||||
static int print_command_usage(struct ctl_client* self,
|
||||
enum cmd_type cmd,
|
||||
struct option_parser* cmd_options,
|
||||
struct option_parser* parent_options)
|
||||
{
|
||||
if (self->flags & CTL_CLIENT_PRINT_JSON) {
|
||||
ERROR("JSON output is not supported for \"help\" output");
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct cmd_info* info = ctl_command_by_type(cmd);
|
||||
if (!info) {
|
||||
ERROR("No such command");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Usage: wayvncctl [options] %s", info->name);
|
||||
option_parser_print_usage(cmd_options, stdout);
|
||||
printf("\n");
|
||||
option_parser_print_cmd_summary(info->description, stdout);
|
||||
if (option_parser_print_arguments(cmd_options, stdout))
|
||||
printf("\n");
|
||||
|
||||
option_parser_print_options(cmd_options, stdout);
|
||||
printf("\n");
|
||||
option_parser_print_options(parent_options, stdout);
|
||||
printf("\n");
|
||||
if (cmd == CMD_EVENT_RECEIVE) {
|
||||
ctl_client_print_event_list(stdout);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ctl_client_init_cmd_parser(struct option_parser* parser, enum cmd_type cmd)
|
||||
{
|
||||
struct cmd_info* info = ctl_command_by_type(cmd);
|
||||
if (!info) {
|
||||
printf("Invalid command");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t param_count = 0;
|
||||
while (info->params[param_count].name != NULL)
|
||||
param_count++;
|
||||
|
||||
// Add 2: one for --help and one to null-terminate the list
|
||||
size_t alloc_count = param_count + 2;
|
||||
if (cmd == CMD_EVENT_RECEIVE)
|
||||
alloc_count++;
|
||||
|
||||
struct wv_option* options = calloc(alloc_count, sizeof(*options));
|
||||
|
||||
size_t i = 0;
|
||||
for (; i < param_count; ++i) {
|
||||
struct wv_option* option = &options[i];
|
||||
struct cmd_param_info* param = &info->params[i];
|
||||
option->help = param->description;
|
||||
if (param->positional) {
|
||||
option->positional = param->name;
|
||||
option->help = param->description;
|
||||
} else {
|
||||
option->long_opt = param->name;
|
||||
option->schema = param->schema;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd == CMD_EVENT_RECEIVE) {
|
||||
options[i].long_opt = "show";
|
||||
options[i].schema = "<event-name>";
|
||||
options[i].help = "Display details about the given event";
|
||||
i++;
|
||||
}
|
||||
|
||||
options[i].long_opt = "help";
|
||||
options[i].short_opt = 'h';
|
||||
options[i].help = "Display this help text";
|
||||
option_parser_init(parser, options);
|
||||
parser->name = "Parameters";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ctl_client_destroy_cmd_parser(struct option_parser* parser)
|
||||
{
|
||||
// const in the struct, but we allocated it above
|
||||
free((void*)parser->options);
|
||||
}
|
||||
|
||||
int ctl_client_run_command(struct ctl_client* self,
|
||||
struct option_parser* parent_options, unsigned flags)
|
||||
{
|
||||
self->flags = flags;
|
||||
int result = 1;
|
||||
|
||||
const char* method = option_parser_get_value(parent_options, "command");
|
||||
enum cmd_type cmd = ctl_command_parse_name(method);
|
||||
if (cmd == CMD_UNKNOWN || cmd == CMD_HELP) {
|
||||
ERROR("No such command \"%s\"\n", method);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct option_parser cmd_options = { };
|
||||
if (ctl_client_init_cmd_parser(&cmd_options, cmd) != 0)
|
||||
return 1;
|
||||
|
||||
if (option_parser_parse(&cmd_options, parent_options->remaining_argc,
|
||||
parent_options->remaining_argv) != 0)
|
||||
goto parse_failure;
|
||||
|
||||
if (option_parser_get_value(&cmd_options, "help")) {
|
||||
result = print_command_usage(self, cmd,
|
||||
&cmd_options, parent_options);
|
||||
goto help_printed;
|
||||
}
|
||||
|
||||
if (cmd == CMD_EVENT_RECEIVE && option_parser_get_value(&cmd_options, "show")) {
|
||||
result = print_event_details(option_parser_get_value(&cmd_options, "show"));
|
||||
goto help_printed;
|
||||
}
|
||||
|
||||
struct jsonipc_request* request = ctl_client_parse_args(self, &cmd,
|
||||
&cmd_options);
|
||||
if (!request)
|
||||
goto parse_failure;
|
||||
|
||||
int timeout = (flags & CTL_CLIENT_SOCKET_WAIT) ? -1 : 0;
|
||||
result = ctl_client_connect(self, timeout);
|
||||
if (result != 0)
|
||||
goto connect_failure;
|
||||
|
||||
switch (cmd) {
|
||||
case CMD_EVENT_RECEIVE:
|
||||
result = ctl_client_event_loop(self, request);
|
||||
break;
|
||||
default:
|
||||
result = ctl_client_print_single_command(self, cmd, request);
|
||||
break;
|
||||
}
|
||||
|
||||
connect_failure:
|
||||
jsonipc_request_destroy(request);
|
||||
help_printed:
|
||||
parse_failure:
|
||||
ctl_client_destroy_cmd_parser(&cmd_options);
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023 Jim Ramsay
|
||||
* Copyright (c) 2023 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ctl-commands.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct cmd_info ctl_command_list[] = {
|
||||
[CMD_ATTACH] = { "attach",
|
||||
"Attach to a running wayland compositor",
|
||||
{
|
||||
{ "display", "Display name", "<name>",
|
||||
.positional = true },
|
||||
{},
|
||||
}
|
||||
|
||||
},
|
||||
[CMD_DETACH] = { "detach",
|
||||
"Detach from the wayland compositor",
|
||||
{{}},
|
||||
},
|
||||
[CMD_HELP] = { "help",
|
||||
"List all commands and events, or show usage of a specific command or event",
|
||||
{
|
||||
{ "command",
|
||||
"The command to show (optional)",
|
||||
"<name>" },
|
||||
{ "event",
|
||||
"The event to show (optional)",
|
||||
"<name>" },
|
||||
{},
|
||||
}
|
||||
},
|
||||
[CMD_VERSION] = { "version",
|
||||
"Query the version of the wayvnc process",
|
||||
{{}}
|
||||
},
|
||||
[CMD_EVENT_RECEIVE] = { "event-receive",
|
||||
"Register to begin receiving asynchronous events from wayvnc",
|
||||
// TODO: Event type filtering?
|
||||
{{}}
|
||||
},
|
||||
[CMD_CLIENT_LIST] = { "client-list",
|
||||
"Return a list of all currently connected VNC sessions",
|
||||
{{}}
|
||||
},
|
||||
[CMD_CLIENT_DISCONNECT] = { "client-disconnect",
|
||||
"Disconnect a VNC session",
|
||||
{
|
||||
{ "id",
|
||||
"The ID of the client to disconnect",
|
||||
"<integer>", true },
|
||||
{},
|
||||
}
|
||||
},
|
||||
[CMD_OUTPUT_LIST] = { "output-list",
|
||||
"Return a list of all currently detected Wayland outputs",
|
||||
{{}}
|
||||
},
|
||||
[CMD_OUTPUT_CYCLE] = { "output-cycle",
|
||||
"Cycle the actively captured output to the next available output, wrapping through all outputs.",
|
||||
{{}}
|
||||
},
|
||||
[CMD_OUTPUT_SET] = { "output-set",
|
||||
"Switch the actively captured output",
|
||||
{
|
||||
{ "output-name",
|
||||
"The specific output name to capture",
|
||||
"<string>", true },
|
||||
{},
|
||||
}
|
||||
},
|
||||
[CMD_WAYVNC_EXIT] = { "wayvnc-exit",
|
||||
"Disconnect all clients and shut down wayvnc",
|
||||
{{}},
|
||||
},
|
||||
};
|
||||
|
||||
#define CLIENT_EVENT_PARAMS(including) \
|
||||
{ "id", \
|
||||
"A unique identifier for this client", \
|
||||
"<integer>" }, \
|
||||
{ "connection_count", \
|
||||
"The total number of connected VNC clients " including " this one.", \
|
||||
"<integer>" }, \
|
||||
{ "address", \
|
||||
"The IP address of this client (may be null)", \
|
||||
"<name|ip>" }, \
|
||||
{ "username", \
|
||||
"The username used to authentice this client (may be null).", \
|
||||
"<string>" }, \
|
||||
{},
|
||||
|
||||
struct cmd_info ctl_event_list[] = {
|
||||
[EVT_CAPTURE_CHANGED] = {"capture-changed",
|
||||
"Sent by wayvnc when the captured output is changed",
|
||||
{
|
||||
{ "output-name",
|
||||
"The name of the output now being captured",
|
||||
"<string>" },
|
||||
{},
|
||||
},
|
||||
},
|
||||
[EVT_CLIENT_CONNECTED] = {"client-connected",
|
||||
"Sent by wayvnc when a new VNC client connects",
|
||||
{ CLIENT_EVENT_PARAMS("including") }
|
||||
},
|
||||
[EVT_CLIENT_DISCONNECTED] = {"client-disconnected",
|
||||
"Sent by waynvc when a VNC client disconnects",
|
||||
{ CLIENT_EVENT_PARAMS("not including") }
|
||||
},
|
||||
[EVT_DETACHED] = {"detached",
|
||||
"Sent after detaching from compositor",
|
||||
{}
|
||||
},
|
||||
[EVT_OUTPUT_ADDED] = {"output-added",
|
||||
"Sent when an output is added by the compositor",
|
||||
{
|
||||
{ "name", "Output name", "<string>" },
|
||||
{}
|
||||
}
|
||||
},
|
||||
[EVT_OUTPUT_REMOVED] = {"output-removed",
|
||||
"Sent when an output is removed by the compositor",
|
||||
{
|
||||
{ "name", "Output name", "<string>" },
|
||||
{}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
enum cmd_type ctl_command_parse_name(const char* name)
|
||||
{
|
||||
if (!name || name[0] == '\0')
|
||||
return CMD_UNKNOWN;
|
||||
for (size_t i = 0; i < CMD_LIST_LEN; ++i) {
|
||||
if (strcmp(name, ctl_command_list[i].name) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return CMD_UNKNOWN;
|
||||
}
|
||||
|
||||
enum event_type ctl_event_parse_name(const char* name)
|
||||
{
|
||||
if (!name || name[0] == '\0')
|
||||
return EVT_UNKNOWN;
|
||||
for (size_t i = 0; i < EVT_LIST_LEN; ++i) {
|
||||
if (strcmp(name, ctl_event_list[i].name) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return EVT_UNKNOWN;
|
||||
}
|
||||
|
||||
struct cmd_info* ctl_command_by_type(enum cmd_type cmd)
|
||||
{
|
||||
if (cmd == CMD_UNKNOWN)
|
||||
return NULL;
|
||||
return &ctl_command_list[cmd];
|
||||
}
|
||||
|
||||
struct cmd_info* ctl_command_by_name(const char* name)
|
||||
{
|
||||
return ctl_command_by_type(ctl_command_parse_name(name));
|
||||
}
|
||||
|
||||
struct cmd_info* ctl_event_by_type(enum event_type evt)
|
||||
{
|
||||
if (evt == EVT_UNKNOWN)
|
||||
return NULL;
|
||||
return &ctl_event_list[evt];
|
||||
}
|
||||
|
||||
struct cmd_info* ctl_event_by_name(const char* name)
|
||||
{
|
||||
return ctl_event_by_type(ctl_event_parse_name(name));
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Scott Moreau
|
||||
* Copyright (c) 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <aml.h>
|
||||
#include <neatvnc.h>
|
||||
|
||||
#include "data-control.h"
|
||||
|
||||
struct receive_context {
|
||||
struct data_control* data_control;
|
||||
struct zwlr_data_control_offer_v1* offer;
|
||||
int fd;
|
||||
FILE* mem_fp;
|
||||
size_t mem_size;
|
||||
char* mem_data;
|
||||
};
|
||||
|
||||
static void destroy_receive_context(void* raw_ctx)
|
||||
{
|
||||
struct receive_context* ctx = raw_ctx;
|
||||
int fd = ctx->fd;
|
||||
|
||||
if (ctx->mem_fp)
|
||||
fclose(ctx->mem_fp);
|
||||
free(ctx->mem_data);
|
||||
zwlr_data_control_offer_v1_destroy(ctx->offer);
|
||||
close(fd);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void on_receive(void* handler)
|
||||
{
|
||||
struct receive_context* ctx = aml_get_userdata(handler);
|
||||
int fd = aml_get_fd(handler);
|
||||
assert(ctx->fd == fd);
|
||||
|
||||
char buf[4096];
|
||||
|
||||
ssize_t ret = read(fd, &buf, sizeof(buf));
|
||||
if (ret > 0) {
|
||||
fwrite(&buf, 1, ret, ctx->mem_fp);
|
||||
return;
|
||||
}
|
||||
|
||||
fclose(ctx->mem_fp);
|
||||
ctx->mem_fp = NULL;
|
||||
|
||||
if (ctx->mem_size)
|
||||
nvnc_send_cut_text(ctx->data_control->server, ctx->mem_data,
|
||||
ctx->mem_size);
|
||||
|
||||
aml_stop(aml_get_default(), handler);
|
||||
}
|
||||
|
||||
static void receive_data(void* data,
|
||||
struct zwlr_data_control_offer_v1* offer)
|
||||
{
|
||||
struct data_control* self = data;
|
||||
int pipe_fd[2];
|
||||
|
||||
if (pipe(pipe_fd) == -1) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "pipe() failed: %m");
|
||||
return;
|
||||
}
|
||||
|
||||
struct receive_context* ctx = calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "OOM");
|
||||
close(pipe_fd[0]);
|
||||
close(pipe_fd[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
zwlr_data_control_offer_v1_receive(offer, self->mime_type, pipe_fd[1]);
|
||||
wl_display_flush(self->wl_display);
|
||||
close(pipe_fd[1]);
|
||||
|
||||
ctx->fd = pipe_fd[0];
|
||||
ctx->data_control = self;
|
||||
ctx->offer = offer;
|
||||
ctx->mem_fp = open_memstream(&ctx->mem_data, &ctx->mem_size);
|
||||
if (!ctx->mem_fp) {
|
||||
close(ctx->fd);
|
||||
free(ctx);
|
||||
nvnc_log(NVNC_LOG_ERROR, "open_memstream() failed: %m");
|
||||
return;
|
||||
}
|
||||
|
||||
struct aml_handler* handler = aml_handler_new(ctx->fd, on_receive,
|
||||
ctx, destroy_receive_context);
|
||||
if (!handler) {
|
||||
close(ctx->fd);
|
||||
free(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
aml_start(aml_get_default(), handler);
|
||||
aml_unref(handler);
|
||||
}
|
||||
|
||||
static void data_control_offer(void* data,
|
||||
struct zwlr_data_control_offer_v1* zwlr_data_control_offer_v1,
|
||||
const char* mime_type)
|
||||
{
|
||||
struct data_control* self = data;
|
||||
|
||||
if (self->offer)
|
||||
return;
|
||||
if (strcmp(mime_type, self->mime_type) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->offer = zwlr_data_control_offer_v1;
|
||||
}
|
||||
|
||||
struct zwlr_data_control_offer_v1_listener data_control_offer_listener = {
|
||||
data_control_offer
|
||||
};
|
||||
|
||||
static void data_control_device_offer(void* data,
|
||||
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1,
|
||||
struct zwlr_data_control_offer_v1* id)
|
||||
{
|
||||
if (!id)
|
||||
return;
|
||||
|
||||
zwlr_data_control_offer_v1_add_listener(id, &data_control_offer_listener, data);
|
||||
}
|
||||
|
||||
static void data_control_device_selection(void* data,
|
||||
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1,
|
||||
struct zwlr_data_control_offer_v1* id)
|
||||
{
|
||||
struct data_control* self = data;
|
||||
if (id && self->offer == id) {
|
||||
receive_data(data, id);
|
||||
self->offer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void data_control_device_finished(void* data,
|
||||
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1)
|
||||
{
|
||||
zwlr_data_control_device_v1_destroy(zwlr_data_control_device_v1);
|
||||
}
|
||||
|
||||
static void data_control_device_primary_selection(void* data,
|
||||
struct zwlr_data_control_device_v1* zwlr_data_control_device_v1,
|
||||
struct zwlr_data_control_offer_v1* id)
|
||||
{
|
||||
struct data_control* self = data;
|
||||
if (id && self->offer == id) {
|
||||
receive_data(data, id);
|
||||
self->offer = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static struct zwlr_data_control_device_v1_listener data_control_device_listener = {
|
||||
.data_offer = data_control_device_offer,
|
||||
.selection = data_control_device_selection,
|
||||
.finished = data_control_device_finished,
|
||||
.primary_selection = data_control_device_primary_selection
|
||||
};
|
||||
|
||||
static void
|
||||
data_control_source_send(void* data,
|
||||
struct zwlr_data_control_source_v1* zwlr_data_control_source_v1,
|
||||
const char* mime_type,
|
||||
int32_t fd)
|
||||
{
|
||||
struct data_control* self = data;
|
||||
char* d = self->cb_data;
|
||||
size_t len = self->cb_len;
|
||||
int ret;
|
||||
|
||||
assert(d);
|
||||
|
||||
ret = write(fd, d, len);
|
||||
|
||||
if (ret < (int)len)
|
||||
nvnc_log(NVNC_LOG_ERROR, "write from clipboard incomplete");
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void data_control_source_cancelled(void* data,
|
||||
struct zwlr_data_control_source_v1* zwlr_data_control_source_v1)
|
||||
{
|
||||
struct data_control* self = data;
|
||||
|
||||
if (self->selection == zwlr_data_control_source_v1) {
|
||||
self->selection = NULL;
|
||||
}
|
||||
if (self->primary_selection == zwlr_data_control_source_v1) {
|
||||
self->primary_selection = NULL;
|
||||
}
|
||||
zwlr_data_control_source_v1_destroy(zwlr_data_control_source_v1);
|
||||
}
|
||||
|
||||
struct zwlr_data_control_source_v1_listener data_control_source_listener = {
|
||||
.send = data_control_source_send,
|
||||
.cancelled = data_control_source_cancelled
|
||||
};
|
||||
|
||||
static struct zwlr_data_control_source_v1* set_selection(struct data_control* self, bool primary) {
|
||||
struct zwlr_data_control_source_v1* selection;
|
||||
selection = zwlr_data_control_manager_v1_create_data_source(self->manager);
|
||||
if (selection == NULL) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "zwlr_data_control_manager_v1_create_data_source() failed");
|
||||
free(self->cb_data);
|
||||
self->cb_data = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
zwlr_data_control_source_v1_add_listener(selection, &data_control_source_listener, self);
|
||||
zwlr_data_control_source_v1_offer(selection, self->mime_type);
|
||||
|
||||
if (primary)
|
||||
zwlr_data_control_device_v1_set_primary_selection(self->device, selection);
|
||||
else
|
||||
zwlr_data_control_device_v1_set_selection(self->device, selection);
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
||||
void data_control_init(struct data_control* self, struct wl_display* wl_display, struct nvnc* server, struct wl_seat* seat)
|
||||
{
|
||||
self->wl_display = wl_display;
|
||||
self->server = server;
|
||||
self->device = zwlr_data_control_manager_v1_get_data_device(self->manager, seat);
|
||||
zwlr_data_control_device_v1_add_listener(self->device, &data_control_device_listener, self);
|
||||
self->selection = NULL;
|
||||
self->primary_selection = NULL;
|
||||
self->cb_data = NULL;
|
||||
self->cb_len = 0;
|
||||
self->mime_type = "text/plain;charset=utf-8";
|
||||
}
|
||||
|
||||
void data_control_destroy(struct data_control* self)
|
||||
{
|
||||
if (self->selection) {
|
||||
zwlr_data_control_source_v1_destroy(self->selection);
|
||||
self->selection = NULL;
|
||||
}
|
||||
if (self->primary_selection) {
|
||||
zwlr_data_control_source_v1_destroy(self->primary_selection);
|
||||
self->primary_selection = NULL;
|
||||
}
|
||||
zwlr_data_control_device_v1_destroy(self->device);
|
||||
free(self->cb_data);
|
||||
}
|
||||
|
||||
void data_control_to_clipboard(struct data_control* self, const char* text, size_t len)
|
||||
{
|
||||
if (!len) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "%s called with 0 length", __func__);
|
||||
return;
|
||||
}
|
||||
free(self->cb_data);
|
||||
|
||||
self->cb_data = malloc(len);
|
||||
if (!self->cb_data) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "OOM: %m");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(self->cb_data, text, len);
|
||||
self->cb_len = len;
|
||||
// Set copy/paste buffer
|
||||
self->selection = set_selection(self, false);
|
||||
// Set highlight/middle_click buffer
|
||||
self->primary_selection = set_selection(self, true);
|
||||
}
|
195
src/dmabuf.c
195
src/dmabuf.c
|
@ -1,195 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <wayland-client.h>
|
||||
#include <uv.h>
|
||||
#include <wayland-util.h>
|
||||
|
||||
#include "frame-capture.h"
|
||||
#include "logging.h"
|
||||
#include "dmabuf.h"
|
||||
#include "wlr-export-dmabuf-unstable-v1.h"
|
||||
#include "time-util.h"
|
||||
|
||||
#define RATE_LIMIT 20.0 // Hz
|
||||
|
||||
static void dmabuf_close_fds(struct dmabuf_capture* self)
|
||||
{
|
||||
for (size_t i = 0; i < self->frame.n_planes; ++i)
|
||||
close(self->frame.plane[i].fd);
|
||||
|
||||
self->frame.n_planes = 0;
|
||||
}
|
||||
|
||||
static void dmabuf_capture_stop(struct frame_capture* fc)
|
||||
{
|
||||
struct dmabuf_capture* self = (void*)fc;
|
||||
|
||||
if (uv_timer_stop(&self->timer))
|
||||
dmabuf_close_fds(self);
|
||||
|
||||
fc->status = CAPTURE_STOPPED;
|
||||
|
||||
if (self->zwlr_frame) {
|
||||
zwlr_export_dmabuf_frame_v1_destroy(self->zwlr_frame);
|
||||
self->zwlr_frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void dmabuf_frame_start(void* data,
|
||||
struct zwlr_export_dmabuf_frame_v1* frame,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t offset_x, uint32_t offset_y,
|
||||
uint32_t buffer_flags, uint32_t flags,
|
||||
uint32_t format,
|
||||
uint32_t mod_high, uint32_t mod_low,
|
||||
uint32_t num_objects)
|
||||
{
|
||||
struct dmabuf_capture* self = data;
|
||||
struct frame_capture* fc = data;
|
||||
|
||||
uv_timer_stop(&self->timer);
|
||||
dmabuf_close_fds(self);
|
||||
|
||||
uint64_t mod = ((uint64_t)mod_high << 32) | (uint64_t)mod_low;
|
||||
|
||||
self->frame.width = width;
|
||||
self->frame.height = height;
|
||||
self->frame.n_planes = num_objects;
|
||||
self->frame.format = format;
|
||||
|
||||
self->frame.plane[0].modifier = mod;
|
||||
self->frame.plane[1].modifier = mod;
|
||||
self->frame.plane[2].modifier = mod;
|
||||
self->frame.plane[3].modifier = mod;
|
||||
|
||||
fc->damage_hint.x = 0;
|
||||
fc->damage_hint.y = 0;
|
||||
fc->damage_hint.width = width;
|
||||
fc->damage_hint.height = height;
|
||||
}
|
||||
|
||||
static void dmabuf_frame_object(void* data,
|
||||
struct zwlr_export_dmabuf_frame_v1* frame,
|
||||
uint32_t index, int32_t fd, uint32_t size,
|
||||
uint32_t offset, uint32_t stride,
|
||||
uint32_t plane_index)
|
||||
{
|
||||
struct dmabuf_capture* self = data;
|
||||
|
||||
self->frame.plane[plane_index].fd = fd;
|
||||
self->frame.plane[plane_index].size = size;
|
||||
self->frame.plane[plane_index].offset = offset;
|
||||
self->frame.plane[plane_index].pitch = stride;
|
||||
}
|
||||
|
||||
static void dmabuf_timer_ready(uv_timer_t* timer)
|
||||
{
|
||||
struct dmabuf_capture* self = wl_container_of(timer, self, timer);
|
||||
struct frame_capture* fc = (struct frame_capture*)self;
|
||||
|
||||
if (fc->status != CAPTURE_IN_PROGRESS)
|
||||
return;
|
||||
|
||||
self->last_time = gettime_us();
|
||||
|
||||
fc->status = CAPTURE_DONE;
|
||||
fc->on_done(fc);
|
||||
|
||||
dmabuf_close_fds(self);
|
||||
}
|
||||
|
||||
static void dmabuf_frame_ready(void* data,
|
||||
struct zwlr_export_dmabuf_frame_v1* frame,
|
||||
uint32_t tv_sec_hi, uint32_t tv_sec_lo,
|
||||
uint32_t tv_nsec)
|
||||
{
|
||||
struct dmabuf_capture* self = data;
|
||||
struct frame_capture* fc = data;
|
||||
|
||||
dmabuf_capture_stop(fc);
|
||||
|
||||
uint64_t now = gettime_us();
|
||||
double dt = (now - self->last_time) * 1.0e-6;
|
||||
double time_left = (1.0 / RATE_LIMIT - dt) * 1.0e3;
|
||||
|
||||
if (time_left >= 0.0) {
|
||||
uv_timer_start(&self->timer, dmabuf_timer_ready, time_left, 0);
|
||||
frame_capture_start(fc);
|
||||
return;
|
||||
}
|
||||
|
||||
self->last_time = now;
|
||||
|
||||
fc->status = CAPTURE_DONE;
|
||||
fc->on_done(fc);
|
||||
|
||||
dmabuf_close_fds(self);
|
||||
}
|
||||
|
||||
static void dmabuf_frame_cancel(void* data,
|
||||
struct zwlr_export_dmabuf_frame_v1* frame,
|
||||
uint32_t reason)
|
||||
{
|
||||
struct dmabuf_capture* self = data;
|
||||
struct frame_capture* fc = data;
|
||||
|
||||
dmabuf_capture_stop(fc);
|
||||
fc->status = reason == ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT
|
||||
? CAPTURE_FATAL : CAPTURE_FAILED;
|
||||
|
||||
fc->on_done(fc);
|
||||
|
||||
dmabuf_close_fds(self);
|
||||
}
|
||||
|
||||
static int dmabuf_capture_start(struct frame_capture* fc)
|
||||
{
|
||||
struct dmabuf_capture* self = (void*)fc;
|
||||
|
||||
static const struct zwlr_export_dmabuf_frame_v1_listener
|
||||
dmabuf_frame_listener = {
|
||||
.frame = dmabuf_frame_start,
|
||||
.object = dmabuf_frame_object,
|
||||
.ready = dmabuf_frame_ready,
|
||||
.cancel = dmabuf_frame_cancel,
|
||||
};
|
||||
|
||||
self->zwlr_frame =
|
||||
zwlr_export_dmabuf_manager_v1_capture_output(self->manager,
|
||||
fc->overlay_cursor,
|
||||
fc->wl_output);
|
||||
if (!self->zwlr_frame)
|
||||
return -1;
|
||||
|
||||
fc->status = CAPTURE_IN_PROGRESS;
|
||||
|
||||
zwlr_export_dmabuf_frame_v1_add_listener(self->zwlr_frame,
|
||||
&dmabuf_frame_listener, self);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dmabuf_capture_init(struct dmabuf_capture* self)
|
||||
{
|
||||
uv_timer_init(uv_default_loop(), &self->timer);
|
||||
|
||||
self->fc.backend.start = dmabuf_capture_start;
|
||||
self->fc.backend.stop = dmabuf_capture_stop;
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Jim Ramsay
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include "json-ipc.h"
|
||||
|
||||
static const char* jsonipc_id_key = "id";
|
||||
static const char* jsonipc_method_key = "method";
|
||||
static const char* jsonipc_params_key = "params";
|
||||
static const char* jsonipc_code_key = "code";
|
||||
static const char* jsonipc_data_key = "data";
|
||||
|
||||
void jsonipc_error_set_new(struct jsonipc_error* err, int code, json_t* data)
|
||||
{
|
||||
if (!err)
|
||||
return;
|
||||
err->code = code;
|
||||
err->data = data;
|
||||
}
|
||||
|
||||
void jsonipc_error_printf(struct jsonipc_error* err, int code, const char* fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
jsonipc_error_set_new(err, code, json_pack("{s:o}", "error",
|
||||
jvprintf(fmt, ap)));
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonipc_error_set_from_errno(struct jsonipc_error* err,
|
||||
const char* context)
|
||||
{
|
||||
jsonipc_error_printf(err, errno, "%s: %m", context);
|
||||
}
|
||||
|
||||
void jsonipc_error_cleanup(struct jsonipc_error* err)
|
||||
{
|
||||
if (!err)
|
||||
return;
|
||||
json_decref(err->data);
|
||||
}
|
||||
|
||||
inline static bool is_valid_id(json_t* id)
|
||||
{
|
||||
return id == NULL ||
|
||||
json_is_string(id) || json_is_number(id);
|
||||
}
|
||||
|
||||
struct jsonipc_request* jsonipc_request_parse_new(json_t* root,
|
||||
struct jsonipc_error* err)
|
||||
{
|
||||
struct jsonipc_request* ipc = calloc(1, sizeof(*ipc));
|
||||
ipc->json = root;
|
||||
json_incref(ipc->json);
|
||||
json_error_t unpack_error;
|
||||
if (json_unpack_ex(root, &unpack_error, 0, "{s:s, s?O, s?O}",
|
||||
jsonipc_method_key, &ipc->method,
|
||||
jsonipc_params_key, &ipc->params,
|
||||
jsonipc_id_key, &ipc->id) == -1) {
|
||||
jsonipc_error_printf(err, EINVAL, unpack_error.text);
|
||||
goto failure;
|
||||
}
|
||||
if (!is_valid_id(ipc->id)) {
|
||||
char* id = json_dumps(ipc->id, JSON_EMBED | JSON_ENCODE_ANY);
|
||||
jsonipc_error_printf(err, EINVAL,
|
||||
"Invalid ID \"%s\"", id);
|
||||
free(id);
|
||||
goto failure;
|
||||
}
|
||||
return ipc;
|
||||
|
||||
failure:
|
||||
jsonipc_request_destroy(ipc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct jsonipc_request* jsonipc_request__new(const char* method, json_t* params,
|
||||
json_t* id)
|
||||
{
|
||||
struct jsonipc_request* ipc = calloc(1, sizeof(*ipc));
|
||||
ipc->method = method;
|
||||
ipc->params = params;
|
||||
json_incref(ipc->params);
|
||||
ipc->id = id;
|
||||
return ipc;
|
||||
}
|
||||
|
||||
static int request_id = 1;
|
||||
struct jsonipc_request* jsonipc_request_new(const char* method, json_t* params)
|
||||
{
|
||||
return jsonipc_request__new(method, params, json_integer(request_id++));
|
||||
}
|
||||
|
||||
struct jsonipc_request* jsonipc_event_new(const char* method, json_t* params)
|
||||
{
|
||||
return jsonipc_request__new(method, params, NULL);
|
||||
}
|
||||
|
||||
struct jsonipc_request* jsonipc_event_parse_new(json_t* root,
|
||||
struct jsonipc_error* err)
|
||||
{
|
||||
return jsonipc_request_parse_new(root, err);
|
||||
}
|
||||
|
||||
json_t* jsonipc_request_pack(struct jsonipc_request* self, json_error_t* err)
|
||||
{
|
||||
return json_pack_ex(err, 0, "{s:s, s:O*, s:O*}",
|
||||
jsonipc_method_key, self->method,
|
||||
jsonipc_params_key, self->params,
|
||||
jsonipc_id_key, self->id);
|
||||
}
|
||||
|
||||
void jsonipc_request_destroy(struct jsonipc_request* self)
|
||||
{
|
||||
json_decref(self->params);
|
||||
json_decref(self->id);
|
||||
json_decref(self->json);
|
||||
free(self);
|
||||
}
|
||||
|
||||
struct jsonipc_response* jsonipc_response_parse_new(json_t* root,
|
||||
struct jsonipc_error* err)
|
||||
{
|
||||
struct jsonipc_response* ipc = calloc(1, sizeof(*ipc));
|
||||
ipc->json = root;
|
||||
json_incref(ipc->json);
|
||||
json_error_t unpack_error;
|
||||
if (json_unpack_ex(root, &unpack_error, 0, "{s:i, s?O, s?O}",
|
||||
jsonipc_code_key, &ipc->code,
|
||||
jsonipc_data_key, &ipc->data,
|
||||
jsonipc_id_key, &ipc->id) == -1) {
|
||||
jsonipc_error_printf(err, EINVAL, unpack_error.text);
|
||||
goto failure;
|
||||
}
|
||||
if (!is_valid_id(ipc->id)) {
|
||||
char* id = json_dumps(ipc->id, JSON_EMBED | JSON_ENCODE_ANY);
|
||||
jsonipc_error_printf(err, EINVAL,
|
||||
"Invalid ID \"%s\"", id);
|
||||
free(id);
|
||||
goto failure;
|
||||
}
|
||||
return ipc;
|
||||
|
||||
failure:
|
||||
jsonipc_response_destroy(ipc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct jsonipc_response* jsonipc_response_new(int code,
|
||||
json_t* data, json_t* id)
|
||||
{
|
||||
struct jsonipc_response* rsp = calloc(1, sizeof(*rsp));
|
||||
rsp->code = code;
|
||||
json_incref(id);
|
||||
rsp->id = id;
|
||||
json_incref(data);
|
||||
rsp->data = data;
|
||||
return rsp;
|
||||
}
|
||||
|
||||
struct jsonipc_response* jsonipc_error_response_new(
|
||||
struct jsonipc_error* err,
|
||||
json_t* id)
|
||||
{
|
||||
return jsonipc_response_new(err->code, err->data, id);
|
||||
}
|
||||
|
||||
void jsonipc_response_destroy(struct jsonipc_response* self)
|
||||
{
|
||||
json_decref(self->data);
|
||||
json_decref(self->json);
|
||||
json_decref(self->id);
|
||||
free(self);
|
||||
}
|
||||
|
||||
json_t* jsonipc_response_pack(struct jsonipc_response* self, json_error_t* err)
|
||||
{
|
||||
return json_pack_ex(err, 0, "{s:i, s:O*, s:O*}",
|
||||
jsonipc_code_key, self->code,
|
||||
jsonipc_id_key, self->id,
|
||||
jsonipc_data_key, self->data);
|
||||
}
|
||||
|
||||
json_t* jprintf(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
json_t* result = jvprintf(fmt, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
json_t* jvprintf(const char* fmt, va_list ap)
|
||||
{
|
||||
char buffer[128];
|
||||
int len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
|
||||
return json_stringn(buffer, len);
|
||||
}
|
246
src/keyboard.c
246
src/keyboard.c
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Andri Yngvason
|
||||
* Copyright (c) 2019 - 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -18,6 +18,7 @@
|
|||
* interface.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
|
@ -26,19 +27,25 @@
|
|||
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <wayland-client.h>
|
||||
#include <neatvnc.h>
|
||||
|
||||
#include "virtual-keyboard-unstable-v1.h"
|
||||
#include "keyboard.h"
|
||||
#include "shm.h"
|
||||
#include "logging.h"
|
||||
#include "intset.h"
|
||||
|
||||
#define MAYBE_UNUSED __attribute__((unused))
|
||||
|
||||
struct table_entry {
|
||||
xkb_keysym_t symbol;
|
||||
xkb_keycode_t code;
|
||||
int level;
|
||||
};
|
||||
|
||||
struct kb_mods {
|
||||
xkb_mod_mask_t depressed, latched, locked;
|
||||
};
|
||||
|
||||
static void append_entry(struct keyboard* self, xkb_keysym_t symbol,
|
||||
xkb_keycode_t code, int level)
|
||||
{
|
||||
|
@ -84,7 +91,7 @@ static int compare_symbols(const void* a, const void* b)
|
|||
const struct table_entry* y = b;
|
||||
|
||||
if (x->symbol == y->symbol)
|
||||
return x->level < y->level ? -1 : x->level > y->level;
|
||||
return x->code < y->code ? -1 : x->code > y->code;
|
||||
|
||||
return x->symbol < y->symbol ? -1 : x->symbol > y->symbol;
|
||||
}
|
||||
|
@ -115,18 +122,28 @@ static int create_lookup_table(struct keyboard* self)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static char* get_symbol_name(xkb_keysym_t sym, char* dst, size_t size)
|
||||
{
|
||||
if (xkb_keysym_get_name(sym, dst, size) >= 0)
|
||||
return dst;
|
||||
|
||||
snprintf(dst, size, "UNKNOWN (%x)", sym);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static void keyboard__dump_entry(const struct keyboard* self,
|
||||
const struct table_entry* entry)
|
||||
{
|
||||
char sym_name[256];
|
||||
xkb_keysym_get_name(entry->symbol, sym_name, sizeof(sym_name));
|
||||
get_symbol_name(entry->symbol, sym_name, sizeof(sym_name));
|
||||
|
||||
const char* code_name =
|
||||
const char* code_name MAYBE_UNUSED =
|
||||
xkb_keymap_key_get_name(self->keymap, entry->code);
|
||||
|
||||
bool is_pressed = intset_is_set(&self->key_state, entry->code);
|
||||
bool is_pressed MAYBE_UNUSED =
|
||||
intset_is_set(&self->key_state, entry->code);
|
||||
|
||||
log_debug("symbol=%s level=%d code=%s %s\n", sym_name, entry->level,
|
||||
nvnc_log(NVNC_LOG_DEBUG, "symbol=%s level=%d code=%s %s", sym_name, entry->level,
|
||||
code_name, is_pressed ? "pressed" : "released");
|
||||
}
|
||||
|
||||
|
@ -136,7 +153,7 @@ void keyboard_dump_lookup_table(const struct keyboard* self)
|
|||
keyboard__dump_entry(self, &self->lookup_table[i]);
|
||||
}
|
||||
|
||||
int keyboard_init(struct keyboard* self, const char* layout)
|
||||
int keyboard_init(struct keyboard* self, const struct xkb_rule_names* rule_names)
|
||||
{
|
||||
self->context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||
if (!self->context)
|
||||
|
@ -145,15 +162,13 @@ int keyboard_init(struct keyboard* self, const char* layout)
|
|||
if (intset_init(&self->key_state, 0) < 0)
|
||||
goto key_state_failure;
|
||||
|
||||
struct xkb_rule_names rule_names = {
|
||||
.layout = layout,
|
||||
.model = "pc105",
|
||||
};
|
||||
|
||||
self->keymap = xkb_keymap_new_from_names(self->context, &rule_names, 0);
|
||||
self->keymap = xkb_keymap_new_from_names(self->context, rule_names, 0);
|
||||
if (!self->keymap)
|
||||
goto keymap_failure;
|
||||
|
||||
if (xkb_keymap_num_layouts(self->keymap) > 1)
|
||||
nvnc_log(NVNC_LOG_WARNING, "Multiple keyboard layouts have been specified, but only one is supported.");
|
||||
|
||||
self->state = xkb_state_new(self->keymap);
|
||||
if (!self->state)
|
||||
goto state_failure;
|
||||
|
@ -169,25 +184,34 @@ int keyboard_init(struct keyboard* self, const char* layout)
|
|||
if (!keymap_string)
|
||||
goto keymap_string_failure;
|
||||
|
||||
size_t keymap_len = strlen(keymap_string);
|
||||
size_t keymap_size = strlen(keymap_string) + 1;
|
||||
|
||||
int keymap_fd = shm_alloc_fd(0);
|
||||
int keymap_fd = shm_alloc_fd(keymap_size);
|
||||
if (keymap_fd < 0)
|
||||
goto fd_failure;
|
||||
|
||||
// TODO: Check that write finished writing everything
|
||||
write(keymap_fd, keymap_string, keymap_len);
|
||||
size_t written = 0;
|
||||
while (written < keymap_size) {
|
||||
ssize_t ret = write(keymap_fd, keymap_string + written, keymap_size - written);
|
||||
if (ret == -1 && errno == EINTR)
|
||||
continue;
|
||||
if (ret == -1)
|
||||
goto write_failure;
|
||||
written += ret;
|
||||
}
|
||||
|
||||
free(keymap_string);
|
||||
|
||||
zwp_virtual_keyboard_v1_keymap(self->virtual_keyboard,
|
||||
WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
|
||||
keymap_fd, keymap_len);
|
||||
keymap_fd, keymap_size);
|
||||
|
||||
close(keymap_fd);
|
||||
|
||||
return 0;
|
||||
|
||||
write_failure:
|
||||
close(keymap_fd);
|
||||
fd_failure:
|
||||
free(keymap_string);
|
||||
keymap_string_failure:
|
||||
|
@ -230,11 +254,23 @@ struct table_entry* keyboard_find_symbol(const struct keyboard* self,
|
|||
return entry;
|
||||
}
|
||||
|
||||
static void keyboard_send_mods(struct keyboard* self)
|
||||
{
|
||||
xkb_mod_mask_t depressed, latched, locked, group;
|
||||
|
||||
depressed = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_DEPRESSED);
|
||||
latched = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LATCHED);
|
||||
locked = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LOCKED);
|
||||
group = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_EFFECTIVE);
|
||||
|
||||
zwp_virtual_keyboard_v1_modifiers(self->virtual_keyboard, depressed,
|
||||
latched, locked, group);
|
||||
}
|
||||
|
||||
static void keyboard_apply_mods(struct keyboard* self, xkb_keycode_t code,
|
||||
bool is_pressed)
|
||||
{
|
||||
enum xkb_state_component comp, compmask;
|
||||
xkb_mod_mask_t depressed, latched, locked, group;
|
||||
|
||||
comp = xkb_state_update_key(self->state, code,
|
||||
is_pressed ? XKB_KEY_DOWN : XKB_KEY_UP);
|
||||
|
@ -247,17 +283,31 @@ static void keyboard_apply_mods(struct keyboard* self, xkb_keycode_t code,
|
|||
if (!(comp & compmask))
|
||||
return;
|
||||
|
||||
depressed = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_DEPRESSED);
|
||||
latched = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LATCHED);
|
||||
locked = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LOCKED);
|
||||
group = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_EFFECTIVE);
|
||||
|
||||
// TODO: Handle errors
|
||||
zwp_virtual_keyboard_v1_modifiers(self->virtual_keyboard, depressed,
|
||||
latched, locked, group);
|
||||
keyboard_send_mods(self);
|
||||
}
|
||||
|
||||
bool keyboard_symbol_is_mod(xkb_keysym_t symbol)
|
||||
static struct table_entry* match_level(struct keyboard* self,
|
||||
struct table_entry* entry)
|
||||
{
|
||||
xkb_keysym_t symbol = entry->symbol;
|
||||
|
||||
while (true) {
|
||||
int level;
|
||||
|
||||
level = xkb_state_key_get_level(self->state, entry->code, 0);
|
||||
|
||||
if (entry->level == level)
|
||||
return entry;
|
||||
|
||||
if (++entry >= &self->lookup_table[self->lookup_table_length] ||
|
||||
entry->symbol != symbol)
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool keyboard_symbol_is_mod(xkb_keysym_t symbol)
|
||||
{
|
||||
switch (symbol) {
|
||||
case XKB_KEY_Shift_L:
|
||||
|
@ -273,53 +323,131 @@ bool keyboard_symbol_is_mod(xkb_keysym_t symbol)
|
|||
case XKB_KEY_Super_R:
|
||||
case XKB_KEY_Hyper_L:
|
||||
case XKB_KEY_Hyper_R:
|
||||
case XKB_KEY_ISO_Level5_Shift:
|
||||
case XKB_KEY_ISO_Level5_Lock:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void send_key(struct keyboard* self, xkb_keycode_t code, bool is_pressed)
|
||||
{
|
||||
zwp_virtual_keyboard_v1_key(self->virtual_keyboard, 0, code - 8,
|
||||
is_pressed ? WL_KEYBOARD_KEY_STATE_PRESSED
|
||||
: WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||
}
|
||||
|
||||
static void save_mods(struct keyboard* self, struct kb_mods* mods)
|
||||
{
|
||||
mods->depressed = xkb_state_serialize_mods(self->state,
|
||||
XKB_STATE_MODS_DEPRESSED);
|
||||
mods->latched = xkb_state_serialize_mods(self->state,
|
||||
XKB_STATE_MODS_LATCHED);
|
||||
mods->locked = xkb_state_serialize_mods(self->state,
|
||||
XKB_STATE_MODS_LOCKED);
|
||||
}
|
||||
|
||||
static void restore_mods(struct keyboard* self, struct kb_mods* mods)
|
||||
{
|
||||
xkb_state_update_mask(self->state, mods->depressed, mods->latched,
|
||||
mods->locked, XKB_STATE_MODS_DEPRESSED,
|
||||
XKB_STATE_MODS_LATCHED, XKB_STATE_MODS_LOCKED);
|
||||
}
|
||||
|
||||
static void send_key_with_level(struct keyboard* self, xkb_keycode_t code,
|
||||
bool is_pressed, int level)
|
||||
{
|
||||
struct kb_mods save;
|
||||
save_mods(self, &save);
|
||||
|
||||
xkb_mod_mask_t mods = 0;
|
||||
xkb_keymap_key_get_mods_for_level(self->keymap, code, 0, level,
|
||||
&mods, 1);
|
||||
xkb_state_update_mask(self->state, mods, 0, 0, XKB_STATE_MODS_DEPRESSED,
|
||||
XKB_STATE_MODS_LATCHED, XKB_STATE_MODS_LOCKED);
|
||||
keyboard_send_mods(self);
|
||||
|
||||
nvnc_log(NVNC_LOG_DEBUG, "send key with level: old mods: %x, new mods: %x",
|
||||
save.latched | save.locked | save.depressed, mods);
|
||||
|
||||
send_key(self, code, is_pressed);
|
||||
|
||||
restore_mods(self, &save);
|
||||
keyboard_send_mods(self);
|
||||
}
|
||||
|
||||
static bool update_key_state(struct keyboard* self, xkb_keycode_t code,
|
||||
bool is_pressed)
|
||||
{
|
||||
bool was_pressed = intset_is_set(&self->key_state, code);
|
||||
if (was_pressed == is_pressed)
|
||||
return false;
|
||||
|
||||
if (is_pressed)
|
||||
intset_set(&self->key_state, code);
|
||||
else
|
||||
intset_clear(&self->key_state, code);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void keyboard_feed(struct keyboard* self, xkb_keysym_t symbol, bool is_pressed)
|
||||
{
|
||||
struct table_entry* entry = keyboard_find_symbol(self, symbol);
|
||||
if (!entry)
|
||||
return; // TODO: Notify the user about this
|
||||
|
||||
while (!keyboard_symbol_is_mod(symbol)) {
|
||||
int layout, level;
|
||||
|
||||
layout = xkb_state_key_get_layout(self->state, entry->code);
|
||||
level = xkb_state_key_get_level(self->state, entry->code, layout);
|
||||
|
||||
if (entry->level == level)
|
||||
break;
|
||||
|
||||
if (++entry >= &self->lookup_table[self->lookup_table_length])
|
||||
return; // TODO: Notify the user about this
|
||||
|
||||
if (entry->symbol != symbol)
|
||||
return; // TODO: Notify the user about this
|
||||
if (!entry) {
|
||||
char name[256];
|
||||
nvnc_log(NVNC_LOG_ERROR, "Failed to look up keyboard symbol: %s",
|
||||
get_symbol_name(symbol, name, sizeof(name)));
|
||||
return;
|
||||
}
|
||||
|
||||
bool was_pressed = intset_is_set(&self->key_state, entry->code);
|
||||
if (was_pressed == is_pressed)
|
||||
return;
|
||||
bool level_is_match = true;
|
||||
|
||||
if (is_pressed)
|
||||
intset_set(&self->key_state, entry->code);
|
||||
else
|
||||
intset_clear(&self->key_state, entry->code);
|
||||
if (!keyboard_symbol_is_mod(symbol)) {
|
||||
struct table_entry* level_entry = match_level(self, entry);
|
||||
if (level_entry)
|
||||
entry = level_entry;
|
||||
else
|
||||
level_is_match = false;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
keyboard__dump_entry(self, entry);
|
||||
#endif
|
||||
|
||||
// TODO: This could cause some synchronisation problems with other
|
||||
// keyboards in the seat.
|
||||
if (!update_key_state(self, entry->code, is_pressed))
|
||||
return;
|
||||
|
||||
keyboard_apply_mods(self, entry->code, is_pressed);
|
||||
|
||||
// TODO: Handle errors
|
||||
zwp_virtual_keyboard_v1_key(self->virtual_keyboard, 0, entry->code - 8,
|
||||
is_pressed ? WL_KEYBOARD_KEY_STATE_PRESSED
|
||||
: WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||
if (level_is_match)
|
||||
send_key(self, entry->code, is_pressed);
|
||||
else
|
||||
send_key_with_level(self, entry->code, is_pressed,
|
||||
entry->level);
|
||||
}
|
||||
|
||||
void keyboard_feed_code(struct keyboard* self, xkb_keycode_t code,
|
||||
bool is_pressed)
|
||||
{
|
||||
if (update_key_state(self, code, is_pressed)) {
|
||||
keyboard_apply_mods(self, code, is_pressed);
|
||||
send_key(self, code, is_pressed);
|
||||
}
|
||||
}
|
||||
|
||||
enum nvnc_keyboard_led_state keyboard_get_led_state(
|
||||
const struct keyboard* self)
|
||||
{
|
||||
enum nvnc_keyboard_led_state led_state = 0;
|
||||
|
||||
if (xkb_state_led_name_is_active(self->state, XKB_LED_NAME_SCROLL))
|
||||
led_state |= NVNC_KEYBOARD_LED_SCROLL_LOCK;
|
||||
if (xkb_state_led_name_is_active(self->state, XKB_LED_NAME_NUM))
|
||||
led_state |= NVNC_KEYBOARD_LED_NUM_LOCK;
|
||||
if (xkb_state_led_name_is_active(self->state, XKB_LED_NAME_CAPS))
|
||||
led_state |= NVNC_KEYBOARD_LED_CAPS_LOCK;
|
||||
|
||||
return led_state;
|
||||
}
|
||||
|
|
2201
src/main.c
2201
src/main.c
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,435 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "option-parser.h"
|
||||
#include "strlcpy.h"
|
||||
#include "table-printer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <sys/param.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||
|
||||
static int count_options(const struct wv_option* opts)
|
||||
{
|
||||
int n = 0;
|
||||
while (opts[n].short_opt || opts[n].long_opt || opts[n].positional)
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
void option_parser_init(struct option_parser* self,
|
||||
const struct wv_option* options)
|
||||
{
|
||||
memset(self, 0, sizeof(*self));
|
||||
|
||||
self->options = options;
|
||||
self->n_opts = count_options(options);
|
||||
self->name = "Options";
|
||||
}
|
||||
|
||||
static int get_left_col_width(const struct wv_option* opts, int n)
|
||||
{
|
||||
int max_width = 0;
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int width = 0;
|
||||
|
||||
if (opts[i].short_opt)
|
||||
width += 2;
|
||||
|
||||
if (opts[i].long_opt)
|
||||
width += 2 + strlen(opts[i].long_opt);
|
||||
|
||||
if (opts[i].short_opt && opts[i].long_opt)
|
||||
width += 1; // for ','
|
||||
|
||||
if (opts[i].schema) {
|
||||
width += strlen(opts[i].schema);
|
||||
|
||||
if (opts[i].long_opt)
|
||||
width += 1; // for '='
|
||||
}
|
||||
|
||||
if (width > max_width)
|
||||
max_width = width;
|
||||
}
|
||||
|
||||
return max_width;
|
||||
}
|
||||
|
||||
static const char* format_help(const struct wv_option* opt)
|
||||
{
|
||||
if (!opt->default_)
|
||||
return opt->help;
|
||||
|
||||
static char help_buf[256];
|
||||
snprintf(help_buf, sizeof(help_buf), "%s\nDefault: %s", opt->help, opt->default_);
|
||||
return help_buf;
|
||||
}
|
||||
|
||||
static void format_option(struct table_printer* printer, const struct wv_option* opt)
|
||||
{
|
||||
if (!opt->help || opt->positional)
|
||||
return;
|
||||
|
||||
int n_chars = 0;
|
||||
char buf[64];
|
||||
if (opt->short_opt)
|
||||
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
|
||||
"-%c", opt->short_opt);
|
||||
if (opt->long_opt)
|
||||
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
|
||||
"%s--%s", opt->short_opt ? "," : "",
|
||||
opt->long_opt);
|
||||
if (opt->schema)
|
||||
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
|
||||
"%s%s", opt->long_opt ? "=" : "", opt->schema);
|
||||
|
||||
table_printer_print_line(printer, buf, format_help(opt));
|
||||
}
|
||||
|
||||
void option_parser_print_options(struct option_parser* self, FILE* stream)
|
||||
{
|
||||
fprintf(stream, "%s:\n", self->name);
|
||||
int left_col_width = get_left_col_width(self->options, self->n_opts);
|
||||
struct table_printer printer;
|
||||
table_printer_init(&printer, stream, left_col_width);
|
||||
for (int i = 0; i < self->n_opts; ++i)
|
||||
format_option(&printer, &self->options[i]);
|
||||
}
|
||||
|
||||
static void print_string_tolower(FILE* stream, const char *src)
|
||||
{
|
||||
for (const char* c = src; *c != '\0'; c++)
|
||||
fprintf(stream, "%c", tolower(*c));
|
||||
}
|
||||
|
||||
void option_parser_print_usage(struct option_parser* self, FILE* stream)
|
||||
{
|
||||
fprintf(stream, " [");
|
||||
print_string_tolower(stream, self->name);
|
||||
fprintf(stream, "]");
|
||||
int optional_paren_count = 0;
|
||||
for (int i = 0; i < self->n_opts; ++i) {
|
||||
const struct wv_option* opt = &self->options[i];
|
||||
if (!opt->positional)
|
||||
continue;
|
||||
const char* open = "<";
|
||||
const char* close = ">";
|
||||
if (opt->default_) {
|
||||
open = "[";
|
||||
close = ""; // Closed via optional_paren_count loop below
|
||||
optional_paren_count++;
|
||||
} else {
|
||||
// Enforce there must be NO non-optional args after
|
||||
// we've processed at least one optional arg
|
||||
assert(optional_paren_count == 0);
|
||||
}
|
||||
fprintf(stream, " %s%s%s", open, opt->positional, close);
|
||||
}
|
||||
for (int i = 0; i < optional_paren_count; ++i)
|
||||
fprintf(stream, "]");
|
||||
}
|
||||
|
||||
int option_parser_print_arguments(struct option_parser* self, FILE* stream)
|
||||
{
|
||||
size_t max_arg = 0;
|
||||
for (int i = 0; i < self->n_opts; ++i) {
|
||||
const struct wv_option* opt = &self->options[i];
|
||||
if (!opt->positional || !opt->help || opt->is_subcommand)
|
||||
continue;
|
||||
max_arg = MAX(max_arg, strlen(opt->positional));
|
||||
}
|
||||
if (!max_arg)
|
||||
return 0;
|
||||
|
||||
fprintf(stream, "Arguments:\n");
|
||||
struct table_printer printer;
|
||||
table_printer_init(&printer, stream, max_arg);
|
||||
int i;
|
||||
for (i = 0; i < self->n_opts; ++i) {
|
||||
const struct wv_option* opt = &self->options[i];
|
||||
if (!opt->positional || !opt->help || opt->is_subcommand)
|
||||
continue;
|
||||
table_printer_print_line(&printer, opt->positional, format_help(opt));
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static const struct wv_option* find_long_option(
|
||||
const struct option_parser* self, const char* name)
|
||||
{
|
||||
for (int i = 0; i < self->n_opts; ++i) {
|
||||
if (!self->options[i].long_opt)
|
||||
continue;
|
||||
|
||||
if (strcmp(self->options[i].long_opt, name) == 0)
|
||||
return &self->options[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct wv_option* find_short_option(
|
||||
const struct option_parser* self, char name)
|
||||
{
|
||||
for (int i = 0; i < self->n_opts; ++i)
|
||||
if (self->options[i].short_opt == name)
|
||||
return &self->options[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct wv_option* find_positional_option(
|
||||
struct option_parser* self, int position)
|
||||
{
|
||||
int current_pos = 0;
|
||||
for (int i = 0; i < self->n_opts; ++i) {
|
||||
if (!self->options[i].positional)
|
||||
continue;
|
||||
|
||||
if (current_pos == position)
|
||||
return &self->options[i];
|
||||
|
||||
current_pos += 1;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct wv_option* find_positional_option_by_name(
|
||||
const struct option_parser* self, const char*name)
|
||||
{
|
||||
for (int i = 0; i < self->n_opts; ++i) {
|
||||
const struct wv_option* opt = &self->options[i];
|
||||
if (!opt->positional)
|
||||
continue;
|
||||
if (strcmp(opt->positional, name) == 0)
|
||||
return opt;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int append_value(struct option_parser* self,
|
||||
const struct wv_option* option, const char* value)
|
||||
{
|
||||
if ((size_t)self->n_values >= ARRAY_SIZE(self->values)) {
|
||||
fprintf(stderr, "ERROR: Too many arguments!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct wv_option_value* dst = &self->values[self->n_values++];
|
||||
dst->option = option;
|
||||
strlcpy(dst->value, value, sizeof(dst->value));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_long_arg(struct option_parser* self, int argc,
|
||||
const char* const* argv, int index)
|
||||
{
|
||||
int count = 1;
|
||||
char name[256];
|
||||
strlcpy(name, argv[index] + 2, sizeof(name));
|
||||
char* eq = strchr(name, '=');
|
||||
if (eq)
|
||||
*eq = '\0';
|
||||
|
||||
const struct wv_option* opt = find_long_option(self, name);
|
||||
if (!opt) {
|
||||
fprintf(stderr, "ERROR: Unknown option: \"%s\"\n", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* value = "1";
|
||||
if (opt->schema) {
|
||||
if (eq) {
|
||||
value = eq + 1;
|
||||
} else {
|
||||
if (index + 1 >= argc) {
|
||||
fprintf(stderr, "ERROR: An argument is required for the \"%s\" option\n",
|
||||
opt->long_opt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
value = argv[index + 1];
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (append_value(self, opt, value) < 0)
|
||||
return -1;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int parse_short_args(struct option_parser* self, char argc,
|
||||
const char* const* argv, int index)
|
||||
{
|
||||
int count = 1;
|
||||
int len = strlen(argv[index]);
|
||||
|
||||
for (int i = 1; i < len; ++i) {
|
||||
char name = argv[index][i];
|
||||
const struct wv_option* opt = find_short_option(self, name);
|
||||
if (!opt) {
|
||||
fprintf(stderr, "ERROR: Unknown option: \"%c\"\n", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* value = "1";
|
||||
if (opt->schema) {
|
||||
const char* tail = argv[index] + i + 1;
|
||||
if (tail[0] == '=') {
|
||||
value = tail + 1;
|
||||
} else if (tail[0]) {
|
||||
value = tail;
|
||||
} else {
|
||||
if (index + 1 >= argc) {
|
||||
fprintf(stderr, "ERROR: An argument is required for the \"%c\" option\n",
|
||||
opt->short_opt);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
value = argv[index + 1];
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (append_value(self, opt, value) < 0)
|
||||
return -1;
|
||||
|
||||
if (opt->schema)
|
||||
break;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int parse_positional_arg(struct option_parser* self, char argc,
|
||||
const char* const* argv, int i)
|
||||
{
|
||||
const struct wv_option* opt = find_positional_option(self, self->position);
|
||||
if (!opt)
|
||||
return 1;
|
||||
|
||||
if (append_value(self, opt, argv[i]) < 0)
|
||||
return -1;
|
||||
|
||||
self->position += 1;
|
||||
|
||||
return opt->is_subcommand ? 0 : 1;
|
||||
}
|
||||
|
||||
int option_parser_parse(struct option_parser* self, int argc,
|
||||
const char* const* argv)
|
||||
{
|
||||
int i = 1;
|
||||
while (i < argc) {
|
||||
if (argv[i][0] == '-') {
|
||||
if (argv[i][1] == '-') {
|
||||
if (argv[i][2] == '\0') {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
|
||||
int rc = parse_long_arg(self, argc, argv, i);
|
||||
if (rc < 0)
|
||||
return -1;
|
||||
i += rc;
|
||||
} else {
|
||||
int rc = parse_short_args(self, argc, argv, i);
|
||||
if (rc < 0)
|
||||
return -1;
|
||||
i += rc;
|
||||
}
|
||||
} else {
|
||||
int rc = parse_positional_arg(self, argc, argv, i);
|
||||
if (rc < 0)
|
||||
return -1;
|
||||
if (rc == 0)
|
||||
break;
|
||||
i += rc;
|
||||
}
|
||||
}
|
||||
self->remaining_argc = argc - i;
|
||||
if (self->remaining_argc)
|
||||
self->remaining_argv = argv + i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* option_parser_get_value_no_default(const struct option_parser* self,
|
||||
const char* name)
|
||||
{
|
||||
const struct wv_option* opt;
|
||||
|
||||
bool is_short = name[0] && !name[1];
|
||||
|
||||
for (int i = 0; i < self->n_values; ++i) {
|
||||
const struct wv_option_value* value = &self->values[i];
|
||||
opt = value->option;
|
||||
|
||||
if (is_short) {
|
||||
if (opt->short_opt && opt->short_opt == *name)
|
||||
return value->value;
|
||||
} else {
|
||||
if (opt->long_opt && strcmp(opt->long_opt, name) == 0)
|
||||
return value->value;
|
||||
}
|
||||
|
||||
if (opt->positional && strcmp(opt->positional, name) == 0)
|
||||
return value->value;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* option_parser_get_value(const struct option_parser* self,
|
||||
const char* name)
|
||||
{
|
||||
const char* value = option_parser_get_value_no_default(self, name);
|
||||
if (value)
|
||||
return value;
|
||||
|
||||
bool is_short = name[0] && !name[1];
|
||||
const struct wv_option* opt;
|
||||
if (is_short) {
|
||||
opt = find_short_option(self, name[0]);
|
||||
if (opt)
|
||||
return opt->default_;
|
||||
} else {
|
||||
opt = find_long_option(self, name);
|
||||
if (opt)
|
||||
return opt->default_;
|
||||
opt = find_positional_option_by_name(self, name);
|
||||
if (opt)
|
||||
return opt->default_;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void option_parser_print_cmd_summary(const char* summary, FILE* stream)
|
||||
{
|
||||
struct table_printer printer;
|
||||
table_printer_init(&printer, stream, 0);
|
||||
fprintf(stream, "\n");
|
||||
table_printer_indent_and_reflow_text(stream, summary, printer.max_width, 0, 0);
|
||||
fprintf(stream, "\n");
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* Copyright (c) 2023 The wayvnc authors
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <neatvnc.h>
|
||||
#include <string.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "output.h"
|
||||
#include "output-management.h"
|
||||
|
||||
#include "wlr-output-management-unstable-v1.h"
|
||||
|
||||
struct output_manager_head {
|
||||
struct zwlr_output_head_v1* head;
|
||||
struct wl_list link;
|
||||
char* name;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
static struct wl_list heads;
|
||||
static uint32_t last_config_serial;
|
||||
static struct zwlr_output_manager_v1* wlr_output_manager;
|
||||
|
||||
/* single head properties */
|
||||
static void output_head_name(void* data,
|
||||
struct zwlr_output_head_v1* output_head, const char* name)
|
||||
{
|
||||
struct output_manager_head* head = data;
|
||||
nvnc_trace("Got head name: %s", name);
|
||||
free(head->name);
|
||||
head->name = strdup(name);
|
||||
}
|
||||
|
||||
static void output_head_description(void* data,
|
||||
struct zwlr_output_head_v1* output_head,
|
||||
const char* description)
|
||||
{
|
||||
nvnc_trace("Got head description: %s", description);
|
||||
}
|
||||
|
||||
static void output_head_physical_size(void* data,
|
||||
struct zwlr_output_head_v1* output_head,
|
||||
int32_t width, int32_t height)
|
||||
{
|
||||
nvnc_trace("Got head size: %dx%d", width, height);
|
||||
}
|
||||
|
||||
static void output_head_mode(void* data,
|
||||
struct zwlr_output_head_v1* output_head,
|
||||
struct zwlr_output_mode_v1* mode)
|
||||
{
|
||||
nvnc_trace("Got head mode");
|
||||
}
|
||||
|
||||
static void output_head_enabled(void* data,
|
||||
struct zwlr_output_head_v1* output_head, int32_t enabled)
|
||||
{
|
||||
nvnc_trace("Got head enabled: %s", enabled ? "yes" : "no");
|
||||
struct output_manager_head* head = data;
|
||||
head->enabled = !!enabled;
|
||||
}
|
||||
|
||||
static void output_head_current_mode(void* data,
|
||||
struct zwlr_output_head_v1* output_head,
|
||||
struct zwlr_output_mode_v1* mode)
|
||||
{
|
||||
nvnc_trace("Got head current mode");
|
||||
}
|
||||
|
||||
static void output_head_position(void* data,
|
||||
struct zwlr_output_head_v1* output_head, int32_t x, int32_t y)
|
||||
{
|
||||
nvnc_trace("Got head position: %d,%d", x, y);
|
||||
}
|
||||
|
||||
static void output_head_transform(void* data,
|
||||
struct zwlr_output_head_v1* output_head, int32_t transform)
|
||||
{
|
||||
nvnc_trace("Got head transform: %d", transform);
|
||||
}
|
||||
|
||||
static void output_head_scale(void* data,
|
||||
struct zwlr_output_head_v1* output_head, wl_fixed_t scale_f)
|
||||
{
|
||||
double scale = wl_fixed_to_double(scale_f);
|
||||
nvnc_trace("Got head scale: %.2f", scale);
|
||||
}
|
||||
|
||||
static void output_head_finished(void* data,
|
||||
struct zwlr_output_head_v1* output_head)
|
||||
{
|
||||
nvnc_trace("head gone, removing");
|
||||
struct output_manager_head* head = data;
|
||||
zwlr_output_head_v1_destroy(output_head);
|
||||
wl_list_remove(&head->link);
|
||||
free(head->name);
|
||||
head->name = NULL;
|
||||
head->head = NULL;
|
||||
free(head);
|
||||
}
|
||||
|
||||
struct zwlr_output_head_v1_listener wlr_output_head_listener = {
|
||||
.name = output_head_name,
|
||||
.description = output_head_description,
|
||||
.physical_size = output_head_physical_size,
|
||||
.mode = output_head_mode,
|
||||
.enabled = output_head_enabled,
|
||||
.current_mode = output_head_current_mode,
|
||||
.position = output_head_position,
|
||||
.transform = output_head_transform,
|
||||
.scale = output_head_scale,
|
||||
.finished = output_head_finished,
|
||||
};
|
||||
|
||||
/* config object */
|
||||
static void output_manager_config_succeeded(void* data,
|
||||
struct zwlr_output_configuration_v1* config)
|
||||
{
|
||||
nvnc_trace("config request succeeded");
|
||||
zwlr_output_configuration_v1_destroy(config);
|
||||
}
|
||||
|
||||
static void output_manager_config_failed(void* data,
|
||||
struct zwlr_output_configuration_v1* config)
|
||||
{
|
||||
nvnc_trace("config request failed");
|
||||
zwlr_output_configuration_v1_destroy(config);
|
||||
}
|
||||
|
||||
static void output_manager_config_cancelled(void* data,
|
||||
struct zwlr_output_configuration_v1* config)
|
||||
{
|
||||
nvnc_trace("config request cancelled");
|
||||
zwlr_output_configuration_v1_destroy(config);
|
||||
}
|
||||
|
||||
struct zwlr_output_configuration_v1_listener wlr_output_config_listener = {
|
||||
.succeeded = output_manager_config_succeeded,
|
||||
.failed = output_manager_config_failed,
|
||||
.cancelled = output_manager_config_cancelled,
|
||||
};
|
||||
|
||||
/* manager itself */
|
||||
static void output_manager_done(void* data,
|
||||
struct zwlr_output_manager_v1* zwlr_output_manager_v1,
|
||||
uint32_t serial)
|
||||
{
|
||||
last_config_serial = serial;
|
||||
nvnc_trace("Got new serial: %u", serial);
|
||||
}
|
||||
|
||||
static void output_manager_finished(void* data,
|
||||
struct zwlr_output_manager_v1* zwlr_output_manager_v1)
|
||||
{
|
||||
nvnc_trace("output-manager destroyed");
|
||||
wlr_output_manager = NULL;
|
||||
}
|
||||
|
||||
static void output_manager_head(void* data,
|
||||
struct zwlr_output_manager_v1* zwlr_output_manager_v1,
|
||||
struct zwlr_output_head_v1* output_head)
|
||||
{
|
||||
struct output_manager_head* head = calloc(1, sizeof(*head));
|
||||
if (!head) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "OOM");
|
||||
return;
|
||||
}
|
||||
|
||||
head->head = output_head;
|
||||
wl_list_insert(heads.prev, &head->link);
|
||||
nvnc_trace("New head, now at %lu", wl_list_length(&heads));
|
||||
|
||||
zwlr_output_head_v1_add_listener(head->head,
|
||||
&wlr_output_head_listener, head);
|
||||
}
|
||||
|
||||
static const struct zwlr_output_manager_v1_listener
|
||||
wlr_output_manager_listener = {
|
||||
.head = output_manager_head,
|
||||
.done = output_manager_done,
|
||||
.finished = output_manager_finished,
|
||||
};
|
||||
|
||||
/* Public API */
|
||||
void wlr_output_manager_setup(struct zwlr_output_manager_v1* output_manager)
|
||||
{
|
||||
if (wlr_output_manager)
|
||||
return;
|
||||
|
||||
wl_list_init(&heads);
|
||||
wlr_output_manager = output_manager;
|
||||
zwlr_output_manager_v1_add_listener(wlr_output_manager,
|
||||
&wlr_output_manager_listener, NULL);
|
||||
}
|
||||
|
||||
void wlr_output_manager_destroy(void)
|
||||
{
|
||||
if (!wlr_output_manager)
|
||||
return;
|
||||
|
||||
struct output_manager_head* head;
|
||||
struct output_manager_head* tmp;
|
||||
wl_list_for_each_safe(head, tmp, &heads, link) {
|
||||
wl_list_remove(&head->link);
|
||||
free(head->name);
|
||||
free(head);
|
||||
}
|
||||
|
||||
zwlr_output_manager_v1_destroy(wlr_output_manager);
|
||||
wlr_output_manager = NULL;
|
||||
|
||||
last_config_serial = 0;
|
||||
}
|
||||
|
||||
bool wlr_output_manager_resize_output(struct output* output,
|
||||
uint16_t width, uint16_t height)
|
||||
{
|
||||
if (!wlr_output_manager) {
|
||||
nvnc_log(NVNC_LOG_INFO,
|
||||
"output-management protocol not available, not resizing output");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!output->is_headless) {
|
||||
nvnc_log(NVNC_LOG_INFO,
|
||||
"not resizing output %s: not a headless one",
|
||||
output->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: This could be synced to --max-fps
|
||||
int refresh_rate = 0;
|
||||
|
||||
struct zwlr_output_configuration_v1* config;
|
||||
struct zwlr_output_configuration_head_v1* config_head;
|
||||
|
||||
config = zwlr_output_manager_v1_create_configuration(
|
||||
wlr_output_manager, last_config_serial);
|
||||
zwlr_output_configuration_v1_add_listener(config,
|
||||
&wlr_output_config_listener, NULL);
|
||||
|
||||
struct output_manager_head* head;
|
||||
wl_list_for_each(head, &heads, link) {
|
||||
if (!head->enabled) {
|
||||
nvnc_trace("disabling output %s", head->name);
|
||||
zwlr_output_configuration_v1_disable_head(
|
||||
config, head->head);
|
||||
continue;
|
||||
}
|
||||
|
||||
config_head = zwlr_output_configuration_v1_enable_head(
|
||||
config, head->head);
|
||||
if (head->name && strcmp(head->name, output->name) == 0) {
|
||||
nvnc_trace("reconfiguring output %s", head->name);
|
||||
zwlr_output_configuration_head_v1_set_custom_mode(
|
||||
config_head, width, height, refresh_rate);
|
||||
|
||||
/* It doesn't make any sense to have rotation on a
|
||||
* headless display, so we set the transform here to be
|
||||
* sure.
|
||||
*/
|
||||
zwlr_output_configuration_head_v1_set_transform(
|
||||
config_head, WL_OUTPUT_TRANSFORM_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
nvnc_trace("applying new output config");
|
||||
zwlr_output_configuration_v1_apply(config);
|
||||
return true;
|
||||
}
|
300
src/output.c
300
src/output.c
|
@ -18,13 +18,108 @@
|
|||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <wayland-client.h>
|
||||
#include <neatvnc.h>
|
||||
|
||||
#include "output.h"
|
||||
#include "strlcpy.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include "xdg-output-unstable-v1.h"
|
||||
#include "wlr-output-power-management-unstable-v1.h"
|
||||
|
||||
extern struct zxdg_output_manager_v1* xdg_output_manager;
|
||||
extern struct zwlr_output_power_manager_v1* wlr_output_power_manager;
|
||||
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
void output_transform_coord(const struct output* self,
|
||||
uint32_t src_x, uint32_t src_y,
|
||||
uint32_t* dst_x, uint32_t* dst_y)
|
||||
{
|
||||
switch (self->transform) {
|
||||
case WL_OUTPUT_TRANSFORM_NORMAL:
|
||||
*dst_x = src_x;
|
||||
*dst_y = src_y;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_90:
|
||||
*dst_x = src_y;
|
||||
*dst_y = self->height - src_x;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_180:
|
||||
*dst_x = self->width - src_x;
|
||||
*dst_y = self->height - src_y;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_270:
|
||||
*dst_x = self->width - src_y;
|
||||
*dst_y = src_x;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED:
|
||||
*dst_x = self->width - src_x;
|
||||
*dst_y = src_y;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
|
||||
*dst_x = src_y;
|
||||
*dst_y = src_x;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
|
||||
*dst_x = src_x;
|
||||
*dst_y = self->height - src_y;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
|
||||
*dst_x = self->width - src_y;
|
||||
*dst_y = self->height - src_x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void output_transform_box_coord(const struct output* self,
|
||||
uint32_t src_x0, uint32_t src_y0,
|
||||
uint32_t src_x1, uint32_t src_y1,
|
||||
uint32_t* dst_x0, uint32_t* dst_y0,
|
||||
uint32_t* dst_x1, uint32_t* dst_y1)
|
||||
{
|
||||
uint32_t x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
||||
|
||||
output_transform_coord(self, src_x0, src_y0, &x0, &y0);
|
||||
output_transform_coord(self, src_x1, src_y1, &x1, &y1);
|
||||
|
||||
*dst_x0 = MIN(x0, x1);
|
||||
*dst_x1 = MAX(x0, x1);
|
||||
*dst_y0 = MIN(y0, y1);
|
||||
*dst_y1 = MAX(y0, y1);
|
||||
}
|
||||
|
||||
static bool is_transform_90_degrees(enum wl_output_transform transform)
|
||||
{
|
||||
switch (transform) {
|
||||
case WL_OUTPUT_TRANSFORM_90:
|
||||
case WL_OUTPUT_TRANSFORM_270:
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t output_get_transformed_width(const struct output* self)
|
||||
{
|
||||
return is_transform_90_degrees(self->transform)
|
||||
? self->height : self->width;
|
||||
}
|
||||
|
||||
uint32_t output_get_transformed_height(const struct output* self)
|
||||
{
|
||||
return is_transform_90_degrees(self->transform)
|
||||
? self->width : self->height;
|
||||
}
|
||||
|
||||
static void output_handle_geometry(void* data, struct wl_output* wl_output,
|
||||
int32_t x, int32_t y, int32_t phys_width,
|
||||
|
@ -34,6 +129,13 @@ static void output_handle_geometry(void* data, struct wl_output* wl_output,
|
|||
{
|
||||
struct output* output = data;
|
||||
|
||||
if (transform != (int32_t)output->transform)
|
||||
output->is_transform_changed = true;
|
||||
|
||||
output->x = x;
|
||||
output->y = y;
|
||||
output->transform = transform;
|
||||
|
||||
strlcpy(output->make, make, sizeof(output->make));
|
||||
strlcpy(output->model, model, sizeof(output->model));
|
||||
}
|
||||
|
@ -42,10 +144,30 @@ static void output_handle_mode(void* data, struct wl_output* wl_output,
|
|||
uint32_t flags, int32_t width, int32_t height,
|
||||
int32_t refresh)
|
||||
{
|
||||
struct output* output = data;
|
||||
|
||||
if (!(flags & WL_OUTPUT_MODE_CURRENT))
|
||||
return;
|
||||
|
||||
if (width != (int32_t)output->width || height != (int32_t)output->height)
|
||||
output->is_dimension_changed = true;
|
||||
|
||||
output->width = width;
|
||||
output->height = height;
|
||||
}
|
||||
|
||||
static void output_handle_done(void* data, struct wl_output* wl_output)
|
||||
{
|
||||
struct output* output = data;
|
||||
|
||||
if (output->is_dimension_changed && output->on_dimension_change)
|
||||
output->on_dimension_change(output);
|
||||
|
||||
if (output->is_transform_changed && output->on_transform_change)
|
||||
output->on_transform_change(output);
|
||||
|
||||
output->is_dimension_changed = false;
|
||||
output->is_transform_changed = false;
|
||||
}
|
||||
|
||||
static void output_handle_scale(void* data, struct wl_output* wl_output,
|
||||
|
@ -62,7 +184,10 @@ static const struct wl_output_listener output_listener = {
|
|||
|
||||
void output_destroy(struct output* output)
|
||||
{
|
||||
zxdg_output_v1_destroy(output->xdg_output);
|
||||
if (output->xdg_output)
|
||||
zxdg_output_v1_destroy(output->xdg_output);
|
||||
if (output->wlr_output_power)
|
||||
zwlr_output_power_v1_destroy(output->wlr_output_power);
|
||||
wl_output_destroy(output->wl_output);
|
||||
free(output);
|
||||
}
|
||||
|
@ -78,39 +203,14 @@ void output_list_destroy(struct wl_list* list)
|
|||
}
|
||||
}
|
||||
|
||||
struct output* output_new(struct wl_output* wl_output, uint32_t id)
|
||||
{
|
||||
struct output* output = calloc(1, sizeof(*output));
|
||||
if (!output) {
|
||||
log_error("OOM\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
output->wl_output = wl_output;
|
||||
output->id = id;
|
||||
|
||||
wl_output_add_listener(output->wl_output, &output_listener,
|
||||
output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void output_logical_position(void* data, struct zxdg_output_v1* xdg_output,
|
||||
int32_t x, int32_t y)
|
||||
{
|
||||
struct output* self = data;
|
||||
|
||||
self->x = x;
|
||||
self->y = y;
|
||||
}
|
||||
|
||||
void output_logical_size(void* data, struct zxdg_output_v1* xdg_output,
|
||||
int32_t width, int32_t height)
|
||||
{
|
||||
struct output* self = data;
|
||||
|
||||
self->width = width;
|
||||
self->height = height;
|
||||
}
|
||||
|
||||
void output_name(void* data, struct zxdg_output_v1* xdg_output,
|
||||
|
@ -119,6 +219,12 @@ void output_name(void* data, struct zxdg_output_v1* xdg_output,
|
|||
struct output* self = data;
|
||||
|
||||
strlcpy(self->name, name, sizeof(self->name));
|
||||
self->is_headless =
|
||||
(strncmp(name, "HEADLESS-", strlen("HEADLESS-")) == 0) ||
|
||||
(strncmp(name, "NOOP-", strlen("NOOP-")) == 0);
|
||||
|
||||
nvnc_trace("Output %u name: %s, headless: %s", self->id, self->name,
|
||||
self->is_headless ? "yes" : "no");
|
||||
}
|
||||
|
||||
void output_description(void* data, struct zxdg_output_v1* xdg_output,
|
||||
|
@ -127,6 +233,7 @@ void output_description(void* data, struct zxdg_output_v1* xdg_output,
|
|||
struct output* self = data;
|
||||
|
||||
strlcpy(self->description, description, sizeof(self->description));
|
||||
nvnc_trace("Output %u description: %s", self->id, self->description);
|
||||
}
|
||||
|
||||
static const struct zxdg_output_v1_listener xdg_output_listener = {
|
||||
|
@ -137,13 +244,97 @@ static const struct zxdg_output_v1_listener xdg_output_listener = {
|
|||
.description = output_description,
|
||||
};
|
||||
|
||||
void output_set_xdg_output(struct output* self,
|
||||
struct zxdg_output_v1* xdg_output)
|
||||
static void output_setup_xdg_output_manager(struct output* self)
|
||||
{
|
||||
self->xdg_output = xdg_output;
|
||||
if (!xdg_output_manager || self->xdg_output)
|
||||
return;
|
||||
|
||||
struct zxdg_output_v1* xdg_output =
|
||||
zxdg_output_manager_v1_get_xdg_output(
|
||||
xdg_output_manager, self->wl_output);
|
||||
self->xdg_output = xdg_output;
|
||||
zxdg_output_v1_add_listener(self->xdg_output, &xdg_output_listener,
|
||||
self);
|
||||
self);
|
||||
}
|
||||
|
||||
const char* output_power_state_name(enum output_power_state state)
|
||||
{
|
||||
switch(state) {
|
||||
case OUTPUT_POWER_ON:
|
||||
return "ON";
|
||||
case OUTPUT_POWER_OFF:
|
||||
return "OFF";
|
||||
case OUTPUT_POWER_UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
abort();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void output_power_mode(void *data,
|
||||
struct zwlr_output_power_v1 *zwlr_output_power_v1,
|
||||
uint32_t mode)
|
||||
{
|
||||
struct output* self = data;
|
||||
nvnc_trace("Output %s power state changed to %s", self->name,
|
||||
(mode == ZWLR_OUTPUT_POWER_V1_MODE_ON) ? "ON" : "OFF");
|
||||
|
||||
enum output_power_state old = self->power;
|
||||
switch (mode) {
|
||||
case ZWLR_OUTPUT_POWER_V1_MODE_OFF:
|
||||
self->power = OUTPUT_POWER_OFF;
|
||||
break;
|
||||
case ZWLR_OUTPUT_POWER_V1_MODE_ON:
|
||||
self->power = OUTPUT_POWER_ON;
|
||||
break;
|
||||
}
|
||||
if (old != self->power && self->on_power_change)
|
||||
self->on_power_change(self);
|
||||
}
|
||||
|
||||
static void output_power_failed(void *data,
|
||||
struct zwlr_output_power_v1 *zwlr_output_power_v1)
|
||||
{
|
||||
struct output* self = data;
|
||||
nvnc_log(NVNC_LOG_WARNING, "Output %s power state failure", self->name);
|
||||
self->power = OUTPUT_POWER_UNKNOWN;
|
||||
zwlr_output_power_v1_destroy(self->wlr_output_power);
|
||||
self->wlr_output_power = NULL;
|
||||
}
|
||||
|
||||
static const struct zwlr_output_power_v1_listener wlr_output_power_listener = {
|
||||
.mode = output_power_mode,
|
||||
.failed = output_power_failed,
|
||||
};
|
||||
|
||||
static void output_setup_wlr_output_power_manager(struct output* self)
|
||||
{
|
||||
if (!wlr_output_power_manager || self->wlr_output_power)
|
||||
return;
|
||||
|
||||
struct zwlr_output_power_v1* wlr_output_power =
|
||||
zwlr_output_power_manager_v1_get_output_power(
|
||||
wlr_output_power_manager,
|
||||
self->wl_output);
|
||||
self->wlr_output_power = wlr_output_power;
|
||||
|
||||
zwlr_output_power_v1_add_listener(self->wlr_output_power,
|
||||
&wlr_output_power_listener, self);
|
||||
}
|
||||
|
||||
int output_set_power_state(struct output* output, enum output_power_state state)
|
||||
{
|
||||
assert(state != OUTPUT_POWER_UNKNOWN);
|
||||
if (!output->wlr_output_power) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
nvnc_trace("Output %s requesting power %s", output->name,
|
||||
output_power_state_name(state));
|
||||
int mode = (state == OUTPUT_POWER_ON) ? ZWLR_OUTPUT_POWER_V1_MODE_ON :
|
||||
ZWLR_OUTPUT_POWER_V1_MODE_OFF;
|
||||
zwlr_output_power_v1_set_mode(output->wlr_output_power, mode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct output* output_find_by_id(struct wl_list* list, uint32_t id)
|
||||
|
@ -177,3 +368,50 @@ struct output* output_first(struct wl_list* list)
|
|||
|
||||
return output;
|
||||
}
|
||||
|
||||
struct output* output_cycle(const struct wl_list* list,
|
||||
const struct output* current,
|
||||
enum output_cycle_direction direction)
|
||||
{
|
||||
const struct wl_list* iter = current ? ¤t->link : list;
|
||||
iter = (direction == OUTPUT_CYCLE_FORWARD) ?
|
||||
iter->next : iter->prev;
|
||||
if (iter == list) {
|
||||
if (wl_list_empty(list))
|
||||
return NULL;
|
||||
iter = (direction == OUTPUT_CYCLE_FORWARD) ?
|
||||
iter->next : iter->prev;
|
||||
}
|
||||
struct output* output;
|
||||
return wl_container_of(iter, output, link);
|
||||
}
|
||||
|
||||
void output_setup_wl_managers(struct wl_list* list)
|
||||
{
|
||||
struct output* output;
|
||||
wl_list_for_each(output, list, link) {
|
||||
output_setup_xdg_output_manager(output);
|
||||
output_setup_wlr_output_power_manager(output);
|
||||
}
|
||||
}
|
||||
|
||||
struct output* output_new(struct wl_output* wl_output, uint32_t id)
|
||||
{
|
||||
struct output* output = calloc(1, sizeof(*output));
|
||||
if (!output) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "OOM");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
output->wl_output = wl_output;
|
||||
output->id = id;
|
||||
output->power = OUTPUT_POWER_UNKNOWN;
|
||||
|
||||
wl_output_add_listener(output->wl_output, &output_listener,
|
||||
output);
|
||||
|
||||
output_setup_xdg_output_manager(output);
|
||||
output_setup_wlr_output_power_manager(output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nicholas Sica
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "pam_auth.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <security/pam_appl.h>
|
||||
#include <neatvnc.h>
|
||||
|
||||
struct credentials {
|
||||
const char* user;
|
||||
const char* password;
|
||||
};
|
||||
|
||||
static int pam_return_pwd(int num_msg, const struct pam_message** msgm,
|
||||
struct pam_response** response, void* appdata_ptr)
|
||||
{
|
||||
struct credentials* cred = appdata_ptr;
|
||||
struct pam_response* resp = calloc(num_msg, sizeof(*resp));
|
||||
for (int i = 0; i < num_msg; i++) {
|
||||
resp[i].resp_retcode = PAM_SUCCESS;
|
||||
switch(msgm[i]->msg_style) {
|
||||
case PAM_PROMPT_ECHO_OFF:
|
||||
resp[i].resp = strdup(cred->password);
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
*response = resp;
|
||||
return PAM_SUCCESS;
|
||||
|
||||
error:
|
||||
for (int i = 0; i < num_msg; i++) {
|
||||
free(resp[i].resp);
|
||||
}
|
||||
free(resp);
|
||||
return PAM_CONV_ERR;
|
||||
}
|
||||
|
||||
bool pam_auth(const char* username, const char* password)
|
||||
{
|
||||
struct credentials cred = { username, password };
|
||||
struct pam_conv conv = { &pam_return_pwd, &cred };
|
||||
const char* service = "wayvnc";
|
||||
pam_handle_t* pamh;
|
||||
int result = pam_start(service, username, &conv, &pamh);
|
||||
if (result != PAM_SUCCESS) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "ERROR: PAM start failed: %s", pam_strerror(pamh, result));
|
||||
return false;
|
||||
}
|
||||
|
||||
result = pam_authenticate(pamh, PAM_SILENT|PAM_DISALLOW_NULL_AUTHTOK);
|
||||
if (result != PAM_SUCCESS) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "PAM authenticate failed: %s", pam_strerror(pamh, result));
|
||||
goto error;
|
||||
}
|
||||
|
||||
result = pam_acct_mgmt(pamh, 0);
|
||||
if (result != PAM_SUCCESS) {
|
||||
nvnc_log(NVNC_LOG_ERROR, "PAM account management failed: %s", pam_strerror(pamh, result));
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
pam_end(pamh, result);
|
||||
return result == PAM_SUCCESS;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "pixels.h"
|
||||
|
||||
#include <pixman.h>
|
||||
#include <wayland-client.h>
|
||||
#include <assert.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum wl_shm_format fourcc_to_wl_shm(uint32_t in)
|
||||
{
|
||||
assert(!(in & DRM_FORMAT_BIG_ENDIAN));
|
||||
|
||||
switch (in) {
|
||||
case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888;
|
||||
case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888;
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
uint32_t fourcc_from_wl_shm(enum wl_shm_format in)
|
||||
{
|
||||
switch (in) {
|
||||
case WL_SHM_FORMAT_ARGB8888: return DRM_FORMAT_ARGB8888;
|
||||
case WL_SHM_FORMAT_XRGB8888: return DRM_FORMAT_XRGB8888;
|
||||
default:;
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
int pixel_size_from_fourcc(uint32_t fourcc)
|
||||
{
|
||||
switch (fourcc & ~DRM_FORMAT_BIG_ENDIAN) {
|
||||
case DRM_FORMAT_RGBA1010102:
|
||||
case DRM_FORMAT_RGBX1010102:
|
||||
case DRM_FORMAT_BGRA1010102:
|
||||
case DRM_FORMAT_BGRX1010102:
|
||||
case DRM_FORMAT_ARGB2101010:
|
||||
case DRM_FORMAT_XRGB2101010:
|
||||
case DRM_FORMAT_ABGR2101010:
|
||||
case DRM_FORMAT_XBGR2101010:
|
||||
case DRM_FORMAT_RGBA8888:
|
||||
case DRM_FORMAT_RGBX8888:
|
||||
case DRM_FORMAT_BGRA8888:
|
||||
case DRM_FORMAT_BGRX8888:
|
||||
case DRM_FORMAT_ARGB8888:
|
||||
case DRM_FORMAT_XRGB8888:
|
||||
case DRM_FORMAT_ABGR8888:
|
||||
case DRM_FORMAT_XBGR8888:
|
||||
return 4;
|
||||
case DRM_FORMAT_BGR888:
|
||||
case DRM_FORMAT_RGB888:
|
||||
return 3;
|
||||
case DRM_FORMAT_RGBA4444:
|
||||
case DRM_FORMAT_RGBX4444:
|
||||
case DRM_FORMAT_BGRA4444:
|
||||
case DRM_FORMAT_BGRX4444:
|
||||
case DRM_FORMAT_ARGB4444:
|
||||
case DRM_FORMAT_XRGB4444:
|
||||
case DRM_FORMAT_ABGR4444:
|
||||
case DRM_FORMAT_XBGR4444:
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -50,21 +50,28 @@ static void pointer_set_button_mask(struct pointer* self, uint32_t t,
|
|||
zwlr_virtual_pointer_v1_button(self->pointer, t, BTN_RIGHT,
|
||||
!!(mask & NVNC_BUTTON_RIGHT));
|
||||
|
||||
int axis = WL_POINTER_AXIS_VERTICAL_SCROLL;
|
||||
int vaxis = WL_POINTER_AXIS_VERTICAL_SCROLL;
|
||||
int haxis = WL_POINTER_AXIS_HORIZONTAL_SCROLL;
|
||||
|
||||
/* I arrived at the magical value of 15 by connecting a mouse with a
|
||||
* scroll wheel and viewing the output of wev.
|
||||
*/
|
||||
|
||||
if ((diff & NVNC_SCROLL_UP) && !(mask & NVNC_SCROLL_UP))
|
||||
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, axis,
|
||||
wl_fixed_from_int(-15),
|
||||
-1);
|
||||
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, vaxis,
|
||||
wl_fixed_from_int(-15), -1);
|
||||
|
||||
if ((diff & NVNC_SCROLL_DOWN) && !(mask & NVNC_SCROLL_DOWN))
|
||||
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, axis,
|
||||
wl_fixed_from_int(15),
|
||||
1);
|
||||
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, vaxis,
|
||||
wl_fixed_from_int(15), 1);
|
||||
|
||||
if ((diff & NVNC_SCROLL_LEFT) && !(mask & NVNC_SCROLL_LEFT))
|
||||
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, haxis,
|
||||
wl_fixed_from_int(-15), -1);
|
||||
|
||||
if ((diff & NVNC_SCROLL_RIGHT) && !(mask & NVNC_SCROLL_RIGHT))
|
||||
zwlr_virtual_pointer_v1_axis_discrete(self->pointer, t, haxis,
|
||||
wl_fixed_from_int(15), 1);
|
||||
|
||||
self->current_mask = mask;
|
||||
}
|
||||
|
@ -76,8 +83,7 @@ void pointer_set(struct pointer* self, uint32_t x, uint32_t y,
|
|||
|
||||
if (x != self->current_x || y != self->current_y)
|
||||
zwlr_virtual_pointer_v1_motion_absolute(self->pointer, t,
|
||||
self->output->x + x,
|
||||
self->output->y + y,
|
||||
x, y,
|
||||
self->output->width,
|
||||
self->output->height);
|
||||
|
||||
|
|
481
src/render.c
481
src/render.c
|
@ -1,481 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <wayland-client.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
#include <GLES2/gl2ext.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "render.h"
|
||||
#include "dmabuf.h"
|
||||
|
||||
#define MAYBE_UNUSED __attribute__((unused))
|
||||
|
||||
#define XSTR(s) STR(s)
|
||||
#define STR(s) #s
|
||||
|
||||
#define X_GL_EARLY_EXTENSIONS \
|
||||
X(PFNEGLGETPLATFORMDISPLAYEXTPROC, eglGetPlatformDisplayEXT) \
|
||||
X(PFNEGLDEBUGMESSAGECONTROLKHRPROC, eglDebugMessageControlKHR) \
|
||||
X(PFNGLDEBUGMESSAGECALLBACKKHRPROC, glDebugMessageCallbackKHR) \
|
||||
|
||||
#define X_GL_LATE_EXTENSIONS \
|
||||
X(PFNEGLCREATEIMAGEKHRPROC, eglCreateImageKHR) \
|
||||
X(PFNEGLDESTROYIMAGEKHRPROC, eglDestroyImageKHR) \
|
||||
X(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC, glEGLImageTargetTexture2DOES) \
|
||||
|
||||
#define X_GL_EXTENSIONS \
|
||||
X_GL_EARLY_EXTENSIONS \
|
||||
X_GL_LATE_EXTENSIONS \
|
||||
|
||||
#define X(type, name) type name;
|
||||
X_GL_EXTENSIONS
|
||||
#undef X
|
||||
|
||||
int gl_format_from_fourcc(GLenum* result, uint32_t format)
|
||||
{
|
||||
switch (format) {
|
||||
case DRM_FORMAT_XRGB8888:
|
||||
case DRM_FORMAT_ARGB8888:
|
||||
*result = GL_BGRA_EXT;
|
||||
return 0;
|
||||
case DRM_FORMAT_XBGR8888:
|
||||
case DRM_FORMAT_ABGR8888:
|
||||
*result = GL_RGBA;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline void* gl_load_single_extension(const char* name)
|
||||
{
|
||||
void* ext = eglGetProcAddress(name);
|
||||
if (!ext)
|
||||
log_debug("GL: Failed to load procedure: %s\n", name);
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
||||
static int gl_load_early_extensions(void)
|
||||
{
|
||||
#define X(type, name) \
|
||||
name = gl_load_single_extension(XSTR(name)); \
|
||||
if (!name) \
|
||||
return -1;
|
||||
|
||||
X_GL_EARLY_EXTENSIONS
|
||||
#undef X
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gl_load_late_extensions(void)
|
||||
{
|
||||
#define X(type, name) \
|
||||
name = gl_load_single_extension(XSTR(name)); \
|
||||
if (!name) \
|
||||
return -1;
|
||||
|
||||
X_GL_LATE_EXTENSIONS
|
||||
#undef X
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MAYBE_UNUSED
|
||||
static void egl_log(EGLenum error, const char* command, EGLint msg_type,
|
||||
EGLLabelKHR thread, EGLLabelKHR obj, const char *msg)
|
||||
{
|
||||
(void)error;
|
||||
(void)msg_type;
|
||||
(void)thread;
|
||||
(void)obj;
|
||||
|
||||
log_debug("EGL: %s: %s\n", command, msg);
|
||||
}
|
||||
|
||||
MAYBE_UNUSED
|
||||
static void gles2_log(GLenum src, GLenum type, GLuint id, GLenum severity,
|
||||
GLsizei len, const GLchar *msg, const void *user)
|
||||
{
|
||||
(void)src;
|
||||
(void)type;
|
||||
(void)id;
|
||||
(void)severity;
|
||||
(void)len;
|
||||
(void)user;
|
||||
|
||||
log_debug("GLES2: %s\n", msg);
|
||||
}
|
||||
|
||||
static void gl_debug_init()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
static const EGLAttrib debug_attribs[] = {
|
||||
EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE,
|
||||
EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE,
|
||||
EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE,
|
||||
EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE,
|
||||
EGL_NONE,
|
||||
};
|
||||
eglDebugMessageControlKHR(egl_log, debug_attribs);
|
||||
|
||||
glEnable(GL_DEBUG_OUTPUT_KHR);
|
||||
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
|
||||
glDebugMessageCallbackKHR(gles2_log, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int gl_load_shader(GLuint* dst, const char* source, GLenum type)
|
||||
{
|
||||
GLuint shader = glCreateShader(type);
|
||||
|
||||
glShaderSource(shader, 1, &source, NULL);
|
||||
glCompileShader(shader);
|
||||
|
||||
if (glGetError() != GL_NO_ERROR) {
|
||||
glDeleteShader(shader);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*dst = shader;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char dmabuf_vertex_src[] =
|
||||
"attribute vec2 pos;\n"
|
||||
"attribute vec2 texture;\n"
|
||||
"varying vec2 v_texture;\n"
|
||||
"void main() {\n"
|
||||
" v_texture = vec2(texture.s, 1.0 - texture.t);\n"
|
||||
" gl_Position = vec4(pos, 0, 1);\n"
|
||||
"}\n";
|
||||
|
||||
static const char dmabuf_fragment_src[] =
|
||||
"#extension GL_OES_EGL_image_external: require\n\n"
|
||||
"precision mediump float;\n"
|
||||
"uniform samplerExternalOES u_tex;\n"
|
||||
"varying vec2 v_texture;\n"
|
||||
"void main() {\n"
|
||||
" gl_FragColor = texture2D(u_tex, v_texture);\n"
|
||||
"}\n";
|
||||
|
||||
static const char texture_vertex_src[] =
|
||||
"attribute vec2 pos;\n"
|
||||
"attribute vec2 texture;\n"
|
||||
"varying vec2 v_texture;\n"
|
||||
"void main() {\n"
|
||||
" v_texture = texture;\n"
|
||||
" gl_Position = vec4(pos, 0, 1);\n"
|
||||
"}\n";
|
||||
|
||||
static const char texture_fragment_src[] =
|
||||
"precision mediump float;\n"
|
||||
"uniform sampler2D u_tex;\n"
|
||||
"varying vec2 v_texture;\n"
|
||||
"void main() {\n"
|
||||
" gl_FragColor = texture2D(u_tex, v_texture);\n"
|
||||
"}\n";
|
||||
|
||||
static int gl_compile_shader_program(GLuint* dst, const char* vertex_src,
|
||||
const char* fragment_src)
|
||||
{
|
||||
|
||||
int rc = -1;
|
||||
GLuint vertex, fragment;
|
||||
|
||||
if (gl_load_shader(&vertex, vertex_src, GL_VERTEX_SHADER) < 0)
|
||||
return -1;
|
||||
|
||||
if (gl_load_shader(&fragment, fragment_src, GL_FRAGMENT_SHADER) < 0)
|
||||
goto fragment_failure;
|
||||
|
||||
GLuint program = glCreateProgram();
|
||||
|
||||
glAttachShader(program, vertex);
|
||||
glAttachShader(program, fragment);
|
||||
|
||||
glBindAttribLocation(program, 0, "pos");
|
||||
glBindAttribLocation(program, 1, "texture");
|
||||
|
||||
glLinkProgram(program);
|
||||
|
||||
glDeleteShader(vertex);
|
||||
glDeleteShader(fragment);
|
||||
|
||||
if (glGetError() != GL_NO_ERROR) {
|
||||
glDeleteProgram(program);
|
||||
goto program_failure;
|
||||
}
|
||||
|
||||
*dst = program;
|
||||
rc = 0;
|
||||
program_failure:
|
||||
glDeleteShader(fragment);
|
||||
fragment_failure:
|
||||
glDeleteShader(vertex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void gl_clear(void)
|
||||
{
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void gl_render(void)
|
||||
{
|
||||
static const GLfloat s_vertices[4][2] = {
|
||||
{ -1.0, 1.0 },
|
||||
{ 1.0, 1.0 },
|
||||
{ -1.0, -1.0 },
|
||||
{ 1.0, -1.0 },
|
||||
};
|
||||
|
||||
static const GLfloat s_positions[4][2] = {
|
||||
{ 0, 0 },
|
||||
{ 1, 0 },
|
||||
{ 0, 1 },
|
||||
{ 1, 1 },
|
||||
};
|
||||
|
||||
gl_clear();
|
||||
|
||||
glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, 0, s_vertices);
|
||||
glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, 0, s_positions);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
}
|
||||
|
||||
void renderer_destroy(struct renderer* self)
|
||||
{
|
||||
glDeleteProgram(self->dmabuf_shader_program);
|
||||
glDeleteProgram(self->texture_shader_program);
|
||||
eglMakeCurrent(self->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||
EGL_NO_CONTEXT);
|
||||
eglDestroySurface(self->display, self->surface);
|
||||
eglDestroyContext(self->display, self->context);
|
||||
eglTerminate(self->display);
|
||||
}
|
||||
|
||||
int renderer_init(struct renderer* self, uint32_t width, uint32_t height)
|
||||
{
|
||||
if (!eglBindAPI(EGL_OPENGL_ES_API))
|
||||
return -1;
|
||||
|
||||
if (gl_load_early_extensions() < 0)
|
||||
return -1;
|
||||
|
||||
gl_debug_init();
|
||||
|
||||
self->display =
|
||||
eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
|
||||
EGL_DEFAULT_DISPLAY, NULL);
|
||||
if (!self->display)
|
||||
return -1;
|
||||
|
||||
if (!eglInitialize(self->display, NULL, NULL))
|
||||
return -1;
|
||||
|
||||
static const EGLint cfg_attr[] = {
|
||||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
|
||||
EGL_ALPHA_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLConfig cfg;
|
||||
EGLint cfg_count;
|
||||
|
||||
if (!eglChooseConfig(self->display, cfg_attr, &cfg, 1, &cfg_count))
|
||||
return -1;
|
||||
|
||||
static const EGLint ctx_attr[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
self->context = eglCreateContext(self->display, cfg, EGL_NO_CONTEXT,
|
||||
ctx_attr);
|
||||
if (!self->context)
|
||||
return -1;
|
||||
|
||||
EGLint surf_attr[] = {
|
||||
EGL_WIDTH, width,
|
||||
EGL_HEIGHT, height,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
self->surface = eglCreatePbufferSurface(self->display, cfg, surf_attr);
|
||||
if (!self->surface)
|
||||
goto surface_failure;
|
||||
|
||||
if (!eglMakeCurrent(self->display, self->surface, self->surface,
|
||||
self->context))
|
||||
goto make_current_failure;
|
||||
|
||||
log_debug("%s\n", glGetString(GL_VERSION));
|
||||
|
||||
if (gl_load_late_extensions() < 0)
|
||||
goto late_extension_failure;
|
||||
|
||||
if (gl_compile_shader_program(&self->dmabuf_shader_program,
|
||||
dmabuf_vertex_src,
|
||||
dmabuf_fragment_src) < 0)
|
||||
goto shader_failure;
|
||||
|
||||
if (gl_compile_shader_program(&self->texture_shader_program,
|
||||
texture_vertex_src,
|
||||
texture_fragment_src) < 0)
|
||||
goto shader_failure;
|
||||
|
||||
self->width = width;
|
||||
self->height = height;
|
||||
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &self->read_format);
|
||||
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &self->read_type);
|
||||
|
||||
glViewport(0, 0, width, height);
|
||||
gl_clear();
|
||||
|
||||
return 0;
|
||||
|
||||
shader_failure:
|
||||
late_extension_failure:
|
||||
make_current_failure:
|
||||
eglDestroySurface(self->display, self->surface);
|
||||
surface_failure:
|
||||
eglDestroyContext(self->display, self->context);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline void append_attr(EGLint* dst, int* i, EGLint name, EGLint value)
|
||||
{
|
||||
dst[*i] = name;
|
||||
i[0] += 1;
|
||||
dst[*i] = value;
|
||||
i[0] += 1;
|
||||
}
|
||||
|
||||
static void dmabuf_attr_append_planes(EGLint* dst, int* i,
|
||||
struct dmabuf_frame* frame)
|
||||
{
|
||||
#define APPEND_PLANE_ATTR(n) \
|
||||
if (frame->n_planes <= n) \
|
||||
return; \
|
||||
\
|
||||
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_FD_EXT, frame->plane[n].fd); \
|
||||
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_OFFSET_EXT, frame->plane[n].offset); \
|
||||
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_PITCH_EXT, frame->plane[n].pitch); \
|
||||
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_MODIFIER_LO_EXT, frame->plane[n].modifier); \
|
||||
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_MODIFIER_HI_EXT, frame->plane[n].modifier >> 32); \
|
||||
|
||||
APPEND_PLANE_ATTR(0);
|
||||
APPEND_PLANE_ATTR(1);
|
||||
APPEND_PLANE_ATTR(2);
|
||||
APPEND_PLANE_ATTR(3);
|
||||
#undef APPEND_PLANE_ATTR
|
||||
}
|
||||
|
||||
int render_dmabuf_frame(struct renderer* self, struct dmabuf_frame* frame)
|
||||
{
|
||||
int index = 0;
|
||||
EGLint attr[6 + 10 * 4 + 1];
|
||||
|
||||
if (frame->n_planes == 0)
|
||||
return -1;
|
||||
|
||||
append_attr(attr, &index, EGL_WIDTH, frame->width);
|
||||
append_attr(attr, &index, EGL_HEIGHT, frame->height);
|
||||
append_attr(attr, &index, EGL_LINUX_DRM_FOURCC_EXT, frame->format);
|
||||
dmabuf_attr_append_planes(attr, &index, frame);
|
||||
attr[index++] = EGL_NONE;
|
||||
|
||||
EGLImageKHR image =
|
||||
eglCreateImageKHR(self->display, EGL_NO_CONTEXT,
|
||||
EGL_LINUX_DMA_BUF_EXT, NULL, attr);
|
||||
if (!image)
|
||||
return -1;
|
||||
|
||||
GLuint tex;
|
||||
glGenTextures(1, &tex);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
|
||||
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
|
||||
|
||||
glUseProgram(self->dmabuf_shader_program);
|
||||
gl_render();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDeleteTextures(1, &tex);
|
||||
eglDestroyImageKHR(self->display, image);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int render_framebuffer(struct renderer* self, const void* addr, uint32_t format,
|
||||
uint32_t width, uint32_t height, uint32_t stride)
|
||||
{
|
||||
GLuint tex;
|
||||
glGenTextures(1, &tex);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
|
||||
GLenum gl_format;
|
||||
if (gl_format_from_fourcc(&gl_format, format) < 0)
|
||||
return -1;
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / 4);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, self->read_format, width, height, 0,
|
||||
gl_format, GL_UNSIGNED_BYTE, addr);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
|
||||
|
||||
glUseProgram(self->texture_shader_program);
|
||||
gl_render();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDeleteTextures(1, &tex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void render_copy_pixels(struct renderer* self, void* dst, uint32_t y,
|
||||
uint32_t height)
|
||||
{
|
||||
assert(y + height <= self->height);
|
||||
|
||||
glReadPixels(0, y, self->width, height, self->read_format,
|
||||
self->read_type, dst);
|
||||
}
|
283
src/screencopy.c
283
src/screencopy.c
|
@ -22,72 +22,26 @@
|
|||
#include <wayland-client-protocol.h>
|
||||
#include <wayland-client.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <uv.h>
|
||||
#include <aml.h>
|
||||
#include <neatvnc.h>
|
||||
|
||||
#include "wlr-screencopy-unstable-v1.h"
|
||||
#include "buffer.h"
|
||||
#include "shm.h"
|
||||
#include "screencopy.h"
|
||||
#include "smooth.h"
|
||||
#include "time-util.h"
|
||||
#include "usdt.h"
|
||||
#include "pixels.h"
|
||||
#include "config.h"
|
||||
|
||||
#define RATE_LIMIT 20.0 // Hz
|
||||
#define DELAY_SMOOTHER_TIME_CONSTANT 0.5 // s
|
||||
|
||||
static uint32_t fourcc_from_wl_shm(enum wl_shm_format in)
|
||||
static void screencopy__stop(struct screencopy* self)
|
||||
{
|
||||
switch (in) {
|
||||
case WL_SHM_FORMAT_ARGB8888: return DRM_FORMAT_ARGB8888;
|
||||
case WL_SHM_FORMAT_XRGB8888: return DRM_FORMAT_XRGB8888;
|
||||
default: return in;
|
||||
}
|
||||
}
|
||||
aml_stop(aml_get_default(), self->timer);
|
||||
|
||||
static int screencopy_buffer_init(struct screencopy* self,
|
||||
enum wl_shm_format format, uint32_t width,
|
||||
uint32_t height, uint32_t stride)
|
||||
{
|
||||
if (self->buffer)
|
||||
return 0;
|
||||
|
||||
size_t size = stride * height;
|
||||
|
||||
int fd = shm_alloc_fd(size);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (!addr)
|
||||
goto mmap_failure;
|
||||
|
||||
struct wl_shm_pool* pool = wl_shm_create_pool(self->wl_shm, fd, size);
|
||||
if (!pool)
|
||||
goto shm_failure;
|
||||
|
||||
struct wl_buffer* buffer =
|
||||
wl_shm_pool_create_buffer(pool, 0, width, height, stride,
|
||||
format);
|
||||
wl_shm_pool_destroy(pool);
|
||||
if (!buffer)
|
||||
goto shm_failure;
|
||||
|
||||
self->buffer = buffer;
|
||||
self->pixels = addr;
|
||||
self->bufsize = size;
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
|
||||
shm_failure:
|
||||
munmap(addr, size);
|
||||
mmap_failure:
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void screencopy_stop(struct frame_capture* fc)
|
||||
{
|
||||
struct screencopy* self = (void*)fc;
|
||||
|
||||
uv_timer_stop(&self->timer);
|
||||
self->status = SCREENCOPY_STOPPED;
|
||||
|
||||
if (self->frame) {
|
||||
zwlr_screencopy_frame_v1_destroy(self->frame);
|
||||
|
@ -95,6 +49,76 @@ static void screencopy_stop(struct frame_capture* fc)
|
|||
}
|
||||
}
|
||||
|
||||
void screencopy_stop(struct screencopy* self)
|
||||
{
|
||||
if (self->front)
|
||||
wv_buffer_pool_release(self->pool, self->front);
|
||||
self->front = NULL;
|
||||
|
||||
return screencopy__stop(self);
|
||||
}
|
||||
|
||||
static void screencopy_linux_dmabuf(void* data,
|
||||
struct zwlr_screencopy_frame_v1* frame,
|
||||
uint32_t format, uint32_t width, uint32_t height)
|
||||
{
|
||||
#ifdef ENABLE_SCREENCOPY_DMABUF
|
||||
struct screencopy* self = data;
|
||||
|
||||
if (!(wv_buffer_get_available_types() & WV_BUFFER_DMABUF))
|
||||
return;
|
||||
|
||||
self->have_linux_dmabuf = true;
|
||||
self->dmabuf_width = width;
|
||||
self->dmabuf_height = height;
|
||||
self->fourcc = format;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void screencopy_buffer_done(void* data,
|
||||
struct zwlr_screencopy_frame_v1* frame)
|
||||
{
|
||||
struct screencopy* self = data;
|
||||
uint32_t width, height, stride, fourcc;
|
||||
enum wv_buffer_type type = WV_BUFFER_UNSPEC;
|
||||
|
||||
#ifdef ENABLE_SCREENCOPY_DMABUF
|
||||
if (self->have_linux_dmabuf && self->enable_linux_dmabuf) {
|
||||
width = self->dmabuf_width;
|
||||
height = self->dmabuf_height;
|
||||
stride = 0;
|
||||
fourcc = self->fourcc;
|
||||
type = WV_BUFFER_DMABUF;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
width = self->wl_shm_width;
|
||||
height = self->wl_shm_height;
|
||||
stride = self->wl_shm_stride;
|
||||
fourcc = fourcc_from_wl_shm(self->wl_shm_format);
|
||||
type = WV_BUFFER_SHM;
|
||||
}
|
||||
|
||||
wv_buffer_pool_resize(self->pool, type, width, height, stride, fourcc);
|
||||
|
||||
struct wv_buffer* buffer = wv_buffer_pool_acquire(self->pool);
|
||||
if (!buffer) {
|
||||
screencopy__stop(self);
|
||||
self->status = SCREENCOPY_FATAL;
|
||||
self->on_done(self);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!self->front);
|
||||
self->front = buffer;
|
||||
|
||||
if (self->is_immediate_copy)
|
||||
zwlr_screencopy_frame_v1_copy(self->frame, buffer->wl_buffer);
|
||||
else
|
||||
zwlr_screencopy_frame_v1_copy_with_damage(self->frame,
|
||||
buffer->wl_buffer);
|
||||
}
|
||||
|
||||
static void screencopy_buffer(void* data,
|
||||
struct zwlr_screencopy_frame_v1* frame,
|
||||
enum wl_shm_format format, uint32_t width,
|
||||
|
@ -102,51 +126,61 @@ static void screencopy_buffer(void* data,
|
|||
{
|
||||
struct screencopy* self = data;
|
||||
|
||||
if (screencopy_buffer_init(self, format, width, height, stride) < 0) {
|
||||
self->frame_capture.status = CAPTURE_FATAL;
|
||||
screencopy_stop(&self->frame_capture);
|
||||
self->frame_capture.on_done(&self->frame_capture);
|
||||
self->wl_shm_format = format;
|
||||
self->wl_shm_width = width;
|
||||
self->wl_shm_height = height;
|
||||
self->wl_shm_stride = stride;
|
||||
|
||||
int version = zwlr_screencopy_manager_v1_get_version(self->manager);
|
||||
if (version < 3) {
|
||||
self->have_linux_dmabuf = false;
|
||||
screencopy_buffer_done(data, frame);
|
||||
return;
|
||||
}
|
||||
|
||||
self->frame_capture.frame_info.fourcc_format =
|
||||
fourcc_from_wl_shm(format);
|
||||
self->frame_capture.frame_info.width = width;
|
||||
self->frame_capture.frame_info.height = height;
|
||||
self->frame_capture.frame_info.stride = stride;
|
||||
|
||||
zwlr_screencopy_frame_v1_copy_with_damage(self->frame, self->buffer);
|
||||
}
|
||||
|
||||
static void screencopy_flags(void* data,
|
||||
struct zwlr_screencopy_frame_v1* frame,
|
||||
uint32_t flags)
|
||||
{
|
||||
(void)data;
|
||||
(void)frame;
|
||||
(void)flags;
|
||||
|
||||
/* TODO. Assume y-invert for now */
|
||||
struct screencopy* self = data;
|
||||
|
||||
self->front->y_inverted =
|
||||
!!(flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT);
|
||||
}
|
||||
|
||||
static void screencopy_ready(void* data,
|
||||
struct zwlr_screencopy_frame_v1* frame,
|
||||
uint32_t sec_hi, uint32_t sec_lo, uint32_t nsec)
|
||||
{
|
||||
(void)sec_hi;
|
||||
(void)sec_lo;
|
||||
(void)nsec;
|
||||
|
||||
struct screencopy* self = data;
|
||||
|
||||
screencopy_stop(&self->frame_capture);
|
||||
uint64_t sec = (uint64_t)sec_hi << 32 | (uint64_t)sec_lo;
|
||||
uint64_t pts = sec * UINT64_C(1000000) + (uint64_t)nsec / UINT64_C(1000);
|
||||
|
||||
DTRACE_PROBE2(wayvnc, screencopy_ready, self, pts);
|
||||
|
||||
screencopy__stop(self);
|
||||
|
||||
self->last_time = gettime_us();
|
||||
|
||||
double delay = (self->last_time - self->start_time) * 1.0e-6;
|
||||
self->delay = smooth(&self->delay_smoother, delay);
|
||||
|
||||
self->frame_capture.status = CAPTURE_DONE;
|
||||
self->frame_capture.on_done(&self->frame_capture);
|
||||
if (self->is_immediate_copy)
|
||||
wv_buffer_damage_whole(self->front);
|
||||
|
||||
if (self->back)
|
||||
wv_buffer_pool_release(self->pool, self->back);
|
||||
self->back = self->front;
|
||||
self->front = NULL;
|
||||
|
||||
nvnc_fb_set_pts(self->back->nvnc_fb, pts);
|
||||
|
||||
self->status = SCREENCOPY_DONE;
|
||||
self->on_done(self);
|
||||
}
|
||||
|
||||
static void screencopy_failed(void* data,
|
||||
|
@ -154,9 +188,16 @@ static void screencopy_failed(void* data,
|
|||
{
|
||||
struct screencopy* self = data;
|
||||
|
||||
screencopy_stop(&self->frame_capture);
|
||||
self->frame_capture.status = CAPTURE_FAILED;
|
||||
self->frame_capture.on_done(&self->frame_capture);
|
||||
DTRACE_PROBE1(wayvnc, screencopy_failed, self);
|
||||
|
||||
screencopy__stop(self);
|
||||
|
||||
if (self->front)
|
||||
wv_buffer_pool_release(self->pool, self->front);
|
||||
self->front = NULL;
|
||||
|
||||
self->status = SCREENCOPY_FAILED;
|
||||
self->on_done(self);
|
||||
}
|
||||
|
||||
static void screencopy_damage(void* data,
|
||||
|
@ -166,18 +207,19 @@ static void screencopy_damage(void* data,
|
|||
{
|
||||
struct screencopy* self = data;
|
||||
|
||||
self->frame_capture.damage_hint.x = x;
|
||||
self->frame_capture.damage_hint.y = y;
|
||||
self->frame_capture.damage_hint.width = width;
|
||||
self->frame_capture.damage_hint.height = height;
|
||||
DTRACE_PROBE1(wayvnc, screencopy_damage, self);
|
||||
|
||||
wv_buffer_damage_rect(self->front, x, y, width, height);
|
||||
}
|
||||
|
||||
static int screencopy__start_capture(struct frame_capture* fc)
|
||||
static int screencopy__start_capture(struct screencopy* self)
|
||||
{
|
||||
struct screencopy* self = (void*)fc;
|
||||
DTRACE_PROBE1(wayvnc, screencopy_start, self);
|
||||
|
||||
static const struct zwlr_screencopy_frame_v1_listener frame_listener = {
|
||||
.buffer = screencopy_buffer,
|
||||
.linux_dmabuf = screencopy_linux_dmabuf,
|
||||
.buffer_done = screencopy_buffer_done,
|
||||
.flags = screencopy_flags,
|
||||
.ready = screencopy_ready,
|
||||
.failed = screencopy_failed,
|
||||
|
@ -186,10 +228,8 @@ static int screencopy__start_capture(struct frame_capture* fc)
|
|||
|
||||
self->start_time = gettime_us();
|
||||
|
||||
self->frame =
|
||||
zwlr_screencopy_manager_v1_capture_output(self->manager,
|
||||
fc->overlay_cursor,
|
||||
fc->wl_output);
|
||||
self->frame = zwlr_screencopy_manager_v1_capture_output(self->manager,
|
||||
self->overlay_cursor, self->wl_output);
|
||||
if (!self->frame)
|
||||
return -1;
|
||||
|
||||
|
@ -199,46 +239,67 @@ static int screencopy__start_capture(struct frame_capture* fc)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void screencopy__poll(uv_timer_t* timer)
|
||||
static void screencopy__poll(void* obj)
|
||||
{
|
||||
struct screencopy* self = wl_container_of(timer, self, timer);
|
||||
struct frame_capture* fc = (struct frame_capture*)self;
|
||||
struct screencopy* self = aml_get_userdata(obj);
|
||||
|
||||
screencopy__start_capture(fc);
|
||||
screencopy__start_capture(self);
|
||||
}
|
||||
|
||||
static int screencopy_start(struct frame_capture* fc)
|
||||
static int screencopy__start(struct screencopy* self, bool is_immediate_copy)
|
||||
{
|
||||
struct screencopy* self = (void*)fc;
|
||||
|
||||
if (fc->status == CAPTURE_IN_PROGRESS)
|
||||
if (self->status == SCREENCOPY_IN_PROGRESS)
|
||||
return -1;
|
||||
|
||||
self->is_immediate_copy = is_immediate_copy;
|
||||
|
||||
uint64_t now = gettime_us();
|
||||
double dt = (now - self->last_time) * 1.0e-6;
|
||||
double time_left = (1.0 / RATE_LIMIT - dt - self->delay) * 1.0e3;
|
||||
int32_t time_left = (1.0 / self->rate_limit - dt - self->delay) * 1.0e6;
|
||||
|
||||
fc->status = CAPTURE_IN_PROGRESS;
|
||||
self->status = SCREENCOPY_IN_PROGRESS;
|
||||
|
||||
return time_left > 0 ?
|
||||
uv_timer_start(&self->timer, screencopy__poll, time_left, 0) :
|
||||
screencopy__start_capture(fc);
|
||||
if (time_left > 0) {
|
||||
aml_set_duration(self->timer, time_left);
|
||||
return aml_start(aml_get_default(), self->timer);
|
||||
}
|
||||
|
||||
return screencopy__start_capture(self);
|
||||
}
|
||||
|
||||
int screencopy_start(struct screencopy* self)
|
||||
{
|
||||
return screencopy__start(self, false);
|
||||
}
|
||||
|
||||
int screencopy_start_immediate(struct screencopy* self)
|
||||
{
|
||||
return screencopy__start(self, true);
|
||||
}
|
||||
|
||||
void screencopy_init(struct screencopy* self)
|
||||
{
|
||||
uv_timer_init(uv_default_loop(), &self->timer);
|
||||
self->pool = wv_buffer_pool_create(0, 0, 0, 0, 0);
|
||||
assert(self->pool);
|
||||
|
||||
self->timer = aml_timer_new(0, screencopy__poll, self, NULL);
|
||||
assert(self->timer);
|
||||
|
||||
self->delay_smoother.time_constant = DELAY_SMOOTHER_TIME_CONSTANT;
|
||||
|
||||
self->frame_capture.backend.start = screencopy_start;
|
||||
self->frame_capture.backend.stop = screencopy_stop;
|
||||
}
|
||||
|
||||
void screencopy_destroy(struct screencopy* self)
|
||||
{
|
||||
uv_timer_stop(&self->timer);
|
||||
aml_stop(aml_get_default(), self->timer);
|
||||
aml_unref(self->timer);
|
||||
|
||||
if (self->buffer)
|
||||
wl_buffer_destroy(self->buffer);
|
||||
if (self->back)
|
||||
wv_buffer_pool_release(self->pool, self->back);
|
||||
if (self->front)
|
||||
wv_buffer_pool_release(self->pool, self->front);
|
||||
|
||||
self->back = NULL;
|
||||
self->front = NULL;
|
||||
|
||||
wv_buffer_pool_destroy(self->pool);
|
||||
}
|
||||
|
|
11
src/seat.c
11
src/seat.c
|
@ -97,6 +97,17 @@ struct seat* seat_find_by_id(struct wl_list* list, uint32_t id)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
struct seat* seat_find_unoccupied(struct wl_list* list)
|
||||
{
|
||||
struct seat* seat;
|
||||
|
||||
wl_list_for_each(seat, list, link)
|
||||
if (seat->occupancy == 0)
|
||||
return seat;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct seat* seat_first(struct wl_list* list)
|
||||
{
|
||||
struct seat* seat;
|
||||
|
|
36
src/shm.c
36
src/shm.c
|
@ -1,5 +1,17 @@
|
|||
/* The following is based on code puublished under public domain by Drew DeVault
|
||||
* here: https://wayland-book.com/book/surfaces/shared-memory.html
|
||||
/*
|
||||
* Copyright (c) 2019 - 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
@ -8,6 +20,18 @@
|
|||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
// Linux with glibc < 2.27 has no wrapper
|
||||
#if defined(HAVE_MEMFD) && !defined(HAVE_MEMFD_CREATE)
|
||||
#include <sys/syscall.h>
|
||||
|
||||
static inline int memfd_create(const char *name, unsigned int flags) {
|
||||
return syscall(SYS_memfd_create, name, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_MEMFD) && !defined(__FreeBSD__)
|
||||
static void randname(char *buf)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
@ -19,9 +43,16 @@ static void randname(char *buf)
|
|||
r >>= 5;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int create_shm_file(void)
|
||||
{
|
||||
#ifdef HAVE_MEMFD
|
||||
return memfd_create("wayvnc-shm", 0);
|
||||
#elif defined(__FreeBSD__)
|
||||
// memfd_create added in FreeBSD 13, but SHM_ANON has been supported for ages
|
||||
return shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||
#else
|
||||
int retries = 100;
|
||||
|
||||
do {
|
||||
|
@ -37,6 +68,7 @@ static int create_shm_file(void)
|
|||
} while (retries > 0 && errno == EEXIST);
|
||||
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int shm_alloc_fd(size_t size)
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Andri Yngvason
|
||||
* Copyright (c) 2023 Jim Ramsay
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "table-printer.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
static struct table_printer defaults = {
|
||||
.max_width = 80,
|
||||
.left_indent = 4,
|
||||
.column_offset = 8,
|
||||
.stream = NULL,
|
||||
.left_width = 0,
|
||||
};
|
||||
|
||||
void table_printer_set_defaults(int max_width, int left_indent,
|
||||
int column_offset)
|
||||
{
|
||||
defaults.max_width = max_width;
|
||||
defaults.left_indent = left_indent;
|
||||
defaults.column_offset = column_offset;
|
||||
}
|
||||
|
||||
void table_printer_init(struct table_printer* self, FILE* stream,
|
||||
int left_width)
|
||||
{
|
||||
memcpy(self, &defaults, sizeof(*self));
|
||||
self->stream = stream;
|
||||
self->left_width = left_width;
|
||||
}
|
||||
|
||||
int table_printer_reflow_text(char* dst, int dst_size, const char* src,
|
||||
int width)
|
||||
{
|
||||
int line_len = 0;
|
||||
int last_space_pos = 0;
|
||||
|
||||
int dst_len = 0;
|
||||
int i = 0;
|
||||
|
||||
while (true) {
|
||||
char c = src[i];
|
||||
if (line_len > width) {
|
||||
// first word > width
|
||||
assert(last_space_pos > 0);
|
||||
// subsequent word > width
|
||||
assert(dst[last_space_pos] != '\n');
|
||||
|
||||
dst_len -= i - last_space_pos;
|
||||
dst[dst_len++] = '\n';
|
||||
i = last_space_pos + 1;
|
||||
line_len = 0;
|
||||
continue;
|
||||
}
|
||||
if (!c)
|
||||
break;
|
||||
|
||||
if (c == ' ')
|
||||
last_space_pos = i;
|
||||
dst[dst_len++] = c;
|
||||
assert(dst_len < dst_size);
|
||||
++line_len;
|
||||
++i;
|
||||
|
||||
if (c == '\n')
|
||||
line_len = 0;
|
||||
}
|
||||
|
||||
dst[dst_len] = '\0';
|
||||
return dst_len;
|
||||
}
|
||||
|
||||
void table_printer_indent_and_reflow_text(FILE* stream, const char* src,
|
||||
int width, int first_line_indent, int subsequent_indent)
|
||||
{
|
||||
char buffer[256];
|
||||
table_printer_reflow_text(buffer, sizeof(buffer), src, width);
|
||||
|
||||
char* line = strtok(buffer, "\n");
|
||||
fprintf(stream, "%*s%s\n", first_line_indent, "", line);
|
||||
|
||||
while (true) {
|
||||
line = strtok(NULL, "\n");
|
||||
if (!line)
|
||||
break;
|
||||
|
||||
fprintf(stream, "%*s%s\n", subsequent_indent, "", line);
|
||||
}
|
||||
}
|
||||
|
||||
void table_printer_print_line(struct table_printer* self, const char* left_text,
|
||||
const char* right_text)
|
||||
{
|
||||
fprintf(self->stream, "%*s", self->left_indent, "");
|
||||
int field_len = fprintf(self->stream, "%s", left_text);
|
||||
fprintf(self->stream, "%*s", self->left_width - field_len + self->column_offset, "");
|
||||
int column_indent = self->left_indent + self->left_width + self->column_offset;
|
||||
int column_width = self->max_width - column_indent;
|
||||
table_printer_indent_and_reflow_text(self->stream,
|
||||
right_text,
|
||||
column_width, 0, column_indent);
|
||||
}
|
||||
|
||||
void table_printer_print_fmtline(struct table_printer* self,
|
||||
const char* right_text,
|
||||
const char* left_format, ...)
|
||||
{
|
||||
char buf[64];
|
||||
va_list args;
|
||||
va_start(args, left_format);
|
||||
vsnprintf(buf, sizeof(buf), left_format, args);
|
||||
va_end(args);
|
||||
table_printer_print_line(self, buf, right_text);
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* For code borrowed from wlroots:
|
||||
* Copyright (c) 2017, 2018 Drew DeVault
|
||||
* Copyright (c) 2014 Jari Vetoniemi
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <wayland-client.h>
|
||||
#include <pixman.h>
|
||||
|
||||
/* Note: This function yields the inverse pixman transform of the
|
||||
* wl_output_transform.
|
||||
*/
|
||||
void wv_pixman_transform_from_wl_output_transform(pixman_transform_t* dst,
|
||||
enum wl_output_transform src, int width, int height)
|
||||
{
|
||||
#define F1 pixman_fixed_1
|
||||
switch (src) {
|
||||
case WL_OUTPUT_TRANSFORM_NORMAL:
|
||||
{
|
||||
pixman_transform_t t = {{
|
||||
{ F1, 0, 0 },
|
||||
{ 0, F1, 0 },
|
||||
{ 0, 0, F1 },
|
||||
}};
|
||||
*dst = t;
|
||||
}
|
||||
return;
|
||||
case WL_OUTPUT_TRANSFORM_90:
|
||||
{
|
||||
pixman_transform_t t = {{
|
||||
{ 0, F1, 0 },
|
||||
{ -F1, 0, height * F1 },
|
||||
{ 0, 0, F1 },
|
||||
}};
|
||||
*dst = t;
|
||||
}
|
||||
return;
|
||||
case WL_OUTPUT_TRANSFORM_180:
|
||||
{
|
||||
pixman_transform_t t = {{
|
||||
{ -F1, 0, width * F1 },
|
||||
{ 0, -F1, height * F1 },
|
||||
{ 0, 0, F1 },
|
||||
}};
|
||||
*dst = t;
|
||||
}
|
||||
return;
|
||||
case WL_OUTPUT_TRANSFORM_270:
|
||||
{
|
||||
pixman_transform_t t = {{
|
||||
{ 0, -F1, width * F1 },
|
||||
{ F1, 0, 0 },
|
||||
{ 0, 0, F1 },
|
||||
}};
|
||||
*dst = t;
|
||||
}
|
||||
return;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED:
|
||||
{
|
||||
pixman_transform_t t = {{
|
||||
{ -F1, 0, width * F1 },
|
||||
{ 0, F1, 0 },
|
||||
{ 0, 0, F1 },
|
||||
}};
|
||||
*dst = t;
|
||||
}
|
||||
return;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
|
||||
{
|
||||
pixman_transform_t t = {{
|
||||
{ 0, F1, 0 },
|
||||
{ F1, 0, 0 },
|
||||
{ 0, 0, F1 },
|
||||
}};
|
||||
*dst = t;
|
||||
}
|
||||
return;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
|
||||
{
|
||||
pixman_transform_t t = {{
|
||||
{ F1, 0, 0 },
|
||||
{ 0, -F1, height * F1 },
|
||||
{ 0, 0, F1 },
|
||||
}};
|
||||
*dst = t;
|
||||
}
|
||||
return;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
|
||||
{
|
||||
pixman_transform_t t = {{
|
||||
{ 0, -F1, width * F1 },
|
||||
{ -F1, 0, height * F1 },
|
||||
{ 0, 0, F1 },
|
||||
}};
|
||||
*dst = t;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#undef F1
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
/* Borrowed these from wlroots */
|
||||
void wv_region_transform(struct pixman_region16* dst,
|
||||
struct pixman_region16* src, enum wl_output_transform transform,
|
||||
int width, int height)
|
||||
{
|
||||
if (transform == WL_OUTPUT_TRANSFORM_NORMAL) {
|
||||
pixman_region_copy(dst, src);
|
||||
return;
|
||||
}
|
||||
|
||||
int nrects = 0;
|
||||
pixman_box16_t* src_rects = pixman_region_rectangles(src, &nrects);
|
||||
|
||||
pixman_box16_t* dst_rects = malloc(nrects * sizeof(*dst_rects));
|
||||
if (dst_rects == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nrects; ++i) {
|
||||
switch (transform) {
|
||||
case WL_OUTPUT_TRANSFORM_NORMAL:
|
||||
dst_rects[i].x1 = src_rects[i].x1;
|
||||
dst_rects[i].y1 = src_rects[i].y1;
|
||||
dst_rects[i].x2 = src_rects[i].x2;
|
||||
dst_rects[i].y2 = src_rects[i].y2;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_90:
|
||||
dst_rects[i].x1 = height - src_rects[i].y2;
|
||||
dst_rects[i].y1 = src_rects[i].x1;
|
||||
dst_rects[i].x2 = height - src_rects[i].y1;
|
||||
dst_rects[i].y2 = src_rects[i].x2;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_180:
|
||||
dst_rects[i].x1 = width - src_rects[i].x2;
|
||||
dst_rects[i].y1 = height - src_rects[i].y2;
|
||||
dst_rects[i].x2 = width - src_rects[i].x1;
|
||||
dst_rects[i].y2 = height - src_rects[i].y1;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_270:
|
||||
dst_rects[i].x1 = src_rects[i].y1;
|
||||
dst_rects[i].y1 = width - src_rects[i].x2;
|
||||
dst_rects[i].x2 = src_rects[i].y2;
|
||||
dst_rects[i].y2 = width - src_rects[i].x1;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED:
|
||||
dst_rects[i].x1 = width - src_rects[i].x2;
|
||||
dst_rects[i].y1 = src_rects[i].y1;
|
||||
dst_rects[i].x2 = width - src_rects[i].x1;
|
||||
dst_rects[i].y2 = src_rects[i].y2;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
|
||||
dst_rects[i].x1 = src_rects[i].y1;
|
||||
dst_rects[i].y1 = src_rects[i].x1;
|
||||
dst_rects[i].x2 = src_rects[i].y2;
|
||||
dst_rects[i].y2 = src_rects[i].x2;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
|
||||
dst_rects[i].x1 = src_rects[i].x1;
|
||||
dst_rects[i].y1 = height - src_rects[i].y2;
|
||||
dst_rects[i].x2 = src_rects[i].x2;
|
||||
dst_rects[i].y2 = height - src_rects[i].y1;
|
||||
break;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
|
||||
dst_rects[i].x1 = height - src_rects[i].y2;
|
||||
dst_rects[i].y1 = width - src_rects[i].x2;
|
||||
dst_rects[i].x2 = height - src_rects[i].y1;
|
||||
dst_rects[i].y2 = width - src_rects[i].x1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pixman_region_fini(dst);
|
||||
pixman_region_init_rects(dst, dst_rects, nrects);
|
||||
free(dst_rects);
|
||||
}
|
||||
|
||||
enum wl_output_transform wv_output_transform_invert(enum wl_output_transform tr)
|
||||
{
|
||||
if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) {
|
||||
tr ^= WL_OUTPUT_TRANSFORM_180;
|
||||
}
|
||||
return tr;
|
||||
}
|
||||
|
||||
enum wl_output_transform wv_output_transform_compose(
|
||||
enum wl_output_transform tr_a, enum wl_output_transform tr_b)
|
||||
{
|
||||
uint32_t flipped = (tr_a ^ tr_b) & WL_OUTPUT_TRANSFORM_FLIPPED;
|
||||
uint32_t rotation_mask = WL_OUTPUT_TRANSFORM_90 | WL_OUTPUT_TRANSFORM_180;
|
||||
uint32_t rotated;
|
||||
if (tr_b & WL_OUTPUT_TRANSFORM_FLIPPED) {
|
||||
// When a rotation of k degrees is followed by a flip, the
|
||||
// equivalent transform is a flip followed by a rotation of
|
||||
// -k degrees.
|
||||
rotated = (tr_b - tr_a) & rotation_mask;
|
||||
} else {
|
||||
rotated = (tr_a + tr_b) & rotation_mask;
|
||||
}
|
||||
return flipped | rotated;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2022 Andri Yngvason
|
||||
* Copyright (c) 2022 Jim Ramsay
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
const char* wayvnc_version =
|
||||
#if defined(PROJECT_VERSION)
|
||||
PROJECT_VERSION;
|
||||
#else
|
||||
"UNKNOWN";
|
||||
#endif
|
||||
|
||||
const char* default_ctl_socket_path()
|
||||
{
|
||||
static char buffer[128];
|
||||
char* xdg_runtime = getenv("XDG_RUNTIME_DIR");
|
||||
if (xdg_runtime)
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"%s/wayvncctl", xdg_runtime);
|
||||
else
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"/tmp/wayvncctl-%d", getuid());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void advance_read_buffer(char (*buffer)[], size_t* current_len, size_t advance_by)
|
||||
{
|
||||
ssize_t remainder = *current_len - advance_by;
|
||||
if (remainder < 0)
|
||||
remainder = 0;
|
||||
else if (remainder > 0)
|
||||
memmove(*buffer, *buffer + advance_by, remainder);
|
||||
*current_len = remainder;
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023 Jim Ramsay
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "ctl-client.h"
|
||||
#include "option-parser.h"
|
||||
|
||||
#define MAYBE_UNUSED __attribute__((unused))
|
||||
|
||||
struct wayvncctl {
|
||||
bool do_exit;
|
||||
|
||||
struct ctl_client* ctl;
|
||||
};
|
||||
|
||||
static int wayvncctl_usage(FILE* stream, struct option_parser* options, int rc)
|
||||
{
|
||||
fprintf(stream, "Usage: wayvncctl");
|
||||
option_parser_print_usage(options, stream);
|
||||
fprintf(stream, " [parameters]\n");
|
||||
option_parser_print_cmd_summary(
|
||||
"Connects to and interacts with a running wayvnc instance.", stream);
|
||||
option_parser_print_options(options, stream);
|
||||
fprintf(stream, "\n");
|
||||
ctl_client_print_command_list(stream);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int show_version(void)
|
||||
{
|
||||
printf("wayvnc: %s\n", wayvnc_version);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
struct wayvncctl self = { 0 };
|
||||
|
||||
bool verbose = false;
|
||||
const char* socket_path = NULL;
|
||||
|
||||
unsigned flags = 0;
|
||||
|
||||
static const struct wv_option opts[] = {
|
||||
{ .positional = "command",
|
||||
.is_subcommand = true },
|
||||
{ 'S', "socket", "<path>",
|
||||
"Control socket path." },
|
||||
{ 'w', "wait", NULL,
|
||||
"Wait for wayvnc to start up if it's not already running." },
|
||||
{ 'r', "reconnect", NULL,
|
||||
"If disconnected while waiting for events, wait for wayvnc to restart." },
|
||||
{ 'j', "json", NULL,
|
||||
"Output json on stdout." },
|
||||
{ 'V', "version", NULL,
|
||||
"Show version info." },
|
||||
{ 'v', "verbose", NULL,
|
||||
"Be more verbose." },
|
||||
{ 'h', "help", NULL,
|
||||
"Get help (this text)." },
|
||||
{ }
|
||||
};
|
||||
|
||||
struct option_parser option_parser;
|
||||
option_parser_init(&option_parser, opts);
|
||||
if (option_parser_parse(&option_parser, argc,
|
||||
(const char* const*)argv) < 0)
|
||||
return wayvncctl_usage(stderr, &option_parser, 1);
|
||||
|
||||
if (option_parser_get_value(&option_parser, "help"))
|
||||
return wayvncctl_usage(stdout, &option_parser, 0);
|
||||
|
||||
if (option_parser_get_value(&option_parser, "version"))
|
||||
return show_version();
|
||||
|
||||
socket_path = option_parser_get_value(&option_parser, "socket");
|
||||
flags |= option_parser_get_value(&option_parser, "wait")
|
||||
? CTL_CLIENT_SOCKET_WAIT : 0;
|
||||
flags |= option_parser_get_value(&option_parser, "reconnect")
|
||||
? CTL_CLIENT_RECONNECT : 0;
|
||||
flags |= option_parser_get_value(&option_parser, "json")
|
||||
? CTL_CLIENT_PRINT_JSON : 0;
|
||||
verbose = !!option_parser_get_value(&option_parser, "verbose");
|
||||
|
||||
// No command; nothing to do...
|
||||
if (!option_parser_get_value(&option_parser, "command"))
|
||||
return wayvncctl_usage(stdout, &option_parser, 1);
|
||||
|
||||
ctl_client_debug_log(verbose);
|
||||
|
||||
self.ctl = ctl_client_new(socket_path, &self);
|
||||
if (!self.ctl)
|
||||
goto ctl_client_failure;
|
||||
|
||||
int result = ctl_client_run_command(self.ctl, &option_parser, flags);
|
||||
|
||||
ctl_client_destroy(self.ctl);
|
||||
|
||||
return result;
|
||||
|
||||
ctl_client_failure:
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
# Integration Testing
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The integration tests currently require that the following tools are installed:
|
||||
- sway (1.8 or later)
|
||||
- lsof
|
||||
- jq
|
||||
- bash
|
||||
- vncdotool
|
||||
|
||||
Most of these are available in your normal distro package manager, except
|
||||
vncdotool which is a python tool and installable via pip:
|
||||
|
||||
```
|
||||
pip install vncdotool
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
```
|
||||
./test/integration/integration.sh
|
||||
```
|
||||
|
||||
Two test suites are defined:
|
||||
|
||||
### Smoke test
|
||||
|
||||
Tests basic functionality such as:
|
||||
- Can wayvnc start and connect to wayland?
|
||||
- Does the wayvncctl IPC mechanism work (both control and events)?
|
||||
- Can a VNC client connect and send a keystroke through to sway?
|
||||
|
||||
### Multi-output test
|
||||
|
||||
Tests wayvnc with a multi-output sway, including:
|
||||
- Do we detect additions and removals of outputs?
|
||||
- Do the wayvncctl commands to cycle and switch outputs work?
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# This is free and unencumbered software released into the public domain.
|
||||
#
|
||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
# distribute this software, either in source code form or as a compiled
|
||||
# binary, for any purpose, commercial or non-commercial, and by any
|
||||
# means.
|
||||
#
|
||||
# In jurisdictions that recognize copyright laws, the author or authors
|
||||
# of this software dedicate any and all copyright interest in the
|
||||
# software to the public domain. We make this dedication for the benefit
|
||||
# of the public at large and to the detriment of our heirs and
|
||||
# successors. We intend this dedication to be an overt act of
|
||||
# relinquishment in perpetuity of all present and future rights to this
|
||||
# software under copyright law.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# For more information, please refer to <http://unlicense.org/>
|
||||
|
||||
# Integration test for wayvnc
|
||||
#
|
||||
# For now, this doesn't do much, but does check that some basic functionality isn't DOA
|
||||
#
|
||||
# Prerequisites:
|
||||
# - wayvnc and wayvncctl are built in ../build/, or in the $PATH
|
||||
# - Override by setting $WAYVNC and $WAYVNCCTL or $WAYVNC_BUILD_DIR
|
||||
# - sway and swaymsg are in the $PATH
|
||||
# - Override by setting $SWAY and $SWAYMSG
|
||||
# - jq for parsing json output is in the $PATH
|
||||
# - lsof for TCP port checking is in the $PATH
|
||||
# - vncdo for client testing is in the $PATH
|
||||
# (pip install vncdotool)
|
||||
|
||||
set -e
|
||||
|
||||
INTEGRATION_ROOT=$(realpath "$(dirname "$0")")
|
||||
REPO_ROOT=$(realpath "$INTEGRATION_ROOT/../..")
|
||||
WAYVNC_BUILD_DIR=${WAYVNC_BUILD_DIR:-$(realpath "$REPO_ROOT/build")}
|
||||
if [[ -d $WAYVNC_BUILD_DIR ]]; then
|
||||
export PATH=$WAYVNC_BUILD_DIR:$PATH
|
||||
fi
|
||||
echo "Looking for required binaries..."
|
||||
WAYVNC=${WAYVNC:-$(which wayvnc)}
|
||||
WAYVNCCTL=${WAYVNCCTL:-$(which wayvncctl)}
|
||||
SWAY=${SWAY:-$(which sway)}
|
||||
SWAYMSG=${SWAYMSG:-$(which swaymsg)}
|
||||
echo "Found: $WAYVNC $WAYVNCCTL $SWAY $SWAYMSG"
|
||||
$WAYVNC --version
|
||||
$SWAY --version
|
||||
IFS=" .-" read -r _ _ SWAYMAJOR SWAYMINOR _ < <($SWAY --version)
|
||||
VNCDO=${VNCDO:-$(which vncdo)}
|
||||
$VNCDO --version 2>/dev/null
|
||||
|
||||
export XDG_CONFIG_HOME=$INTEGRATION_ROOT/xdg_config
|
||||
export XDG_RUNTIME_DIR=/tmp/wayvnc-integration-$$
|
||||
|
||||
test_setup() {
|
||||
[[ -d "$XDG_RUNTIME_DIR" ]] && rm -rf "$XDG_RUNTIME_DIR"
|
||||
mkdir -p "$XDG_RUNTIME_DIR"
|
||||
echo "=============================================="
|
||||
echo "$*"
|
||||
echo "=============================================="
|
||||
}
|
||||
|
||||
TIMEOUT_COUNTER=0
|
||||
TIMEOUT_MAXCOUNT=1
|
||||
TIMEOUT_DELAY=0.1
|
||||
timeout_init() {
|
||||
TIMEOUT_COUNTER=0
|
||||
TIMEOUT_MAXCOUNT=${1:-5}
|
||||
TIMEOUT_DELAY=${2:-0.1}
|
||||
}
|
||||
|
||||
timeout_check() {
|
||||
if [[ $(( TIMEOUT_COUNTER++ )) -gt $TIMEOUT_MAXCOUNT ]]; then
|
||||
return 1
|
||||
fi
|
||||
sleep "$TIMEOUT_DELAY"
|
||||
}
|
||||
|
||||
wait_until() {
|
||||
timeout_init 10
|
||||
local last
|
||||
until last=$(eval "$*" 2>&1); do
|
||||
if ! timeout_check; then
|
||||
echo "Timeout waiting for $*" >&2
|
||||
printf "%s\n" "$last" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
[[ -z $last ]] || printf "%s\n" "$last"
|
||||
}
|
||||
|
||||
SWAY_ENV=$XDG_RUNTIME_DIR/sway.env
|
||||
SWAY_PID=
|
||||
start_sway() {
|
||||
echo "Starting sway..."
|
||||
SWAY_LOG=$XDG_RUNTIME_DIR/sway.log
|
||||
WLR_BACKENDS=headless \
|
||||
WLR_LIBINPUT_NO_DEVICES=1 \
|
||||
$SWAY &>"$SWAY_LOG" &
|
||||
SWAY_PID=$!
|
||||
wait_until [[ -f "$SWAY_ENV" ]] >/dev/null
|
||||
WAYLAND_DISPLAY=$(grep ^WAYLAND_DISPLAY= "$SWAY_ENV" | cut -d= -f2-)
|
||||
SWAYSOCK=$(grep ^SWAYSOCK= "$SWAY_ENV" | cut -d= -f2-)
|
||||
export WAYLAND_DISPLAY SWAYSOCK
|
||||
echo " sway is managing $WAYLAND_DISPLAY at $SWAYSOCK"
|
||||
}
|
||||
|
||||
stop_sway() {
|
||||
[[ -z $SWAY_PID ]] && return 0
|
||||
echo "Stopping sway ($SWAY_PID)"
|
||||
kill "$SWAY_PID"
|
||||
unset SWAY_PID WAYLAND_DISPLAY SWAYSOCK
|
||||
rm -f "$SWAY_ENV" || true
|
||||
}
|
||||
|
||||
WAYVNC_PID=
|
||||
WAYVNC_ADDRESS=127.0.0.1
|
||||
WAYVNC_PORT=5999
|
||||
start_wayvnc() {
|
||||
echo "Starting wayvnc..."
|
||||
WAYVNC_LOG=$XDG_RUNTIME_DIR/wayvnc.log
|
||||
$WAYVNC "$@" -L debug "$WAYVNC_ADDRESS" "$WAYVNC_PORT" &>$WAYVNC_LOG &
|
||||
WAYVNC_PID=$!
|
||||
# Wait for the VNC listening port
|
||||
echo " Started $WAYVNC_PID"
|
||||
wait_until lsof -a -p$WAYVNC_PID -iTCP@$WAYVNC_ADDRESS:$WAYVNC_PORT \
|
||||
-sTCP:LISTEN >/dev/null
|
||||
echo " Listening on $WAYVNC_ADDRESS:$WAYVNC_PORT"
|
||||
# Wait for the control socket
|
||||
wait_until [[ -S "$XDG_RUNTIME_DIR/wayvncctl" ]] >/dev/null
|
||||
echo " Control socket ready"
|
||||
}
|
||||
|
||||
stop_wayvnc() {
|
||||
[[ -z $WAYVNC_PID ]] && return 0
|
||||
echo "Stopping wayvnc ($WAYVNC_PID)"
|
||||
kill "$WAYVNC_PID"
|
||||
unset WAYVNC_PID
|
||||
}
|
||||
|
||||
WAYVNCCTL_PID=
|
||||
WAYVNCCTL_LOG=$XDG_RUNTIME_DIR/wayvncctl.log
|
||||
WAYVNCCTL_EVENTS=$XDG_RUNTIME_DIR/wayvncctl.events
|
||||
start_wayvncctl_events() {
|
||||
$WAYVNCCTL --verbose --wait --reconnect --json event-receive >"$WAYVNCCTL_EVENTS" 2>"$WAYVNCCTL_LOG" &
|
||||
WAYVNCCTL_PID=$!
|
||||
}
|
||||
|
||||
stop_wayvncctl_events() {
|
||||
[[ -z $WAYVNCCTL_PID ]] && return 0
|
||||
echo "Stopping wayvncctl event recorder ($WAYVNCCTL_PID)"
|
||||
kill "$WAYVNCCTL_PID"
|
||||
rm -f "$WAYVNCCTL_EVENTS" || true
|
||||
unset WAYVNCCTL_PID
|
||||
}
|
||||
|
||||
verify_events() {
|
||||
local expected=("$@")
|
||||
echo "Verifying recorded events"
|
||||
local name i=0
|
||||
while IFS= read -r EVT; do
|
||||
name=$(jq -r '.method' <<<"$EVT")
|
||||
ex=${expected[$((i++))]}
|
||||
echo " Event: $name=~$ex"
|
||||
[[ $name == "$ex" ]] || return 1
|
||||
done <"$WAYVNCCTL_EVENTS"
|
||||
if [[ $i -lt ${#expected[@]} ]]; then
|
||||
while [[ $i -lt ${#expected[@]} ]]; do
|
||||
echo " Missing: ${expected[$((i++))]}"
|
||||
done
|
||||
return 1
|
||||
fi
|
||||
echo "Ok"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
result=$?
|
||||
set +e
|
||||
stop_wayvnc
|
||||
stop_sway
|
||||
stop_wayvncctl_events
|
||||
if [[ $result != 0 ]]; then
|
||||
echo
|
||||
echo SWAY LOG
|
||||
echo --------
|
||||
cat "$SWAY_LOG"
|
||||
echo
|
||||
echo WAYVNC_LOG
|
||||
echo ----------
|
||||
cat "$WAYVNC_LOG"
|
||||
echo
|
||||
echo WAYVNCCTL_LOG
|
||||
echo ----------
|
||||
cat "$WAYVNCCTL_LOG"
|
||||
echo
|
||||
echo VNCDO_LOG
|
||||
echo ----------
|
||||
cat "$VNCDO_LOG"
|
||||
exit
|
||||
fi
|
||||
[[ -d "$XDG_RUNTIME_DIR" ]] && rm -rf "$XDG_RUNTIME_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
test_version_ipc() {
|
||||
echo "Checking version command"
|
||||
local version
|
||||
version=$($WAYVNCCTL --json version)
|
||||
[[ -n $version ]]
|
||||
echo " version IPC returned data"
|
||||
echo "ok"
|
||||
}
|
||||
|
||||
sway_active_outputs() {
|
||||
$SWAYMSG -t get_outputs | jq 'map(select(.active == true))'
|
||||
}
|
||||
|
||||
test_output_list_ipc() {
|
||||
local expected_capture=${1:-HEADLESS-1}
|
||||
echo "Checking output-list command"
|
||||
local sway_json wayvnc_json
|
||||
sway_json=$(sway_active_outputs)
|
||||
wayvnc_json=$($WAYVNCCTL --json output-list)
|
||||
local sway_list wayvnc_list
|
||||
sway_list=$(jq -r '.[].name' <<<"$sway_json" | sort -u)
|
||||
wayvnc_list=$(jq -r '.[].name' <<<"$wayvnc_json" | sort -u)
|
||||
[[ "$sway_list" == "$wayvnc_list" ]]
|
||||
echo " output-list IPC matches \`swaymsg -t get_outputs\`"
|
||||
wayvnc_capturing=$(jq -r '.[] | select(.captured == true).name' <<<"$wayvnc_json")
|
||||
echo " Capturing: $wayvnc_capturing=~$expected_capture"
|
||||
[[ $wayvnc_capturing == "$expected_capture" ]]
|
||||
echo "ok"
|
||||
}
|
||||
|
||||
verify_wayvnc_exited() {
|
||||
wait_until ! kill -0 $WAYVNC_PID >/dev/null
|
||||
unset WAYVNC_PID
|
||||
}
|
||||
|
||||
test_exit_ipc() {
|
||||
echo "Checking wayvnc-exit command"
|
||||
$WAYVNCCTL wayvnc-exit &>/dev/null
|
||||
verify_wayvnc_exited
|
||||
echo " wayvnc is shutdown"
|
||||
echo "ok"
|
||||
}
|
||||
|
||||
client() {
|
||||
VNCDO_LOG=$XDG_RUNTIME_DIR/vncdo.log
|
||||
$VNCDO -v --server=$WAYVNC_ADDRESS::$WAYVNC_PORT "$@" &>>$VNCDO_LOG
|
||||
}
|
||||
|
||||
test_client_connect() {
|
||||
echo "Connecting to send ctrl+t"
|
||||
client key ctrl-t
|
||||
echo " Looking for the result..."
|
||||
[[ -f $XDG_RUNTIME_DIR/test.txt ]]
|
||||
echo "Ok"
|
||||
}
|
||||
|
||||
output_count() {
|
||||
sway_active_outputs | jq 'length'
|
||||
}
|
||||
|
||||
sway_output_create() {
|
||||
local initial_count
|
||||
initial_count=$(output_count)
|
||||
echo "Creating new output"
|
||||
$SWAYMSG create_output &>/dev/null
|
||||
# shellcheck disable=SC2016
|
||||
wait_until [[ '$(output_count)' -gt "$initial_count" ]]
|
||||
echo " $(sway_active_outputs | jq -r '.[-1].name')"
|
||||
echo "Ok"
|
||||
}
|
||||
|
||||
sway_output_is_gone() {
|
||||
local output=$1
|
||||
$SWAYMSG -t get_outputs | jq -e "all(.name != \"$output\")"
|
||||
}
|
||||
|
||||
sway_output_destroy() {
|
||||
local output=$1
|
||||
echo "Removing output $output"
|
||||
$SWAYMSG output "$output" unplug >/dev/null
|
||||
wait_until sway_output_is_gone "$output" >/dev/null
|
||||
echo "Ok"
|
||||
}
|
||||
|
||||
smoke_test() {
|
||||
test_setup "smoke test"
|
||||
start_sway
|
||||
start_wayvncctl_events
|
||||
start_wayvnc
|
||||
test_version_ipc
|
||||
wait_until verify_events \
|
||||
wayvnc-startup
|
||||
test_output_list_ipc
|
||||
test_client_connect
|
||||
wait_until verify_events \
|
||||
wayvnc-startup \
|
||||
client-connected \
|
||||
client-disconnected
|
||||
test_exit_ipc
|
||||
wait_until verify_events \
|
||||
wayvnc-startup \
|
||||
client-connected \
|
||||
client-disconnected \
|
||||
wayvnc-shutdown
|
||||
stop_wayvncctl_events
|
||||
stop_sway
|
||||
}
|
||||
|
||||
multioutput_test() {
|
||||
test_setup "multioutput test"
|
||||
start_sway
|
||||
sway_output_create
|
||||
start_wayvncctl_events
|
||||
|
||||
# Ensure outout selection commandline works
|
||||
start_wayvnc -o HEADLESS-1
|
||||
wait_until verify_events \
|
||||
wayvnc-startup
|
||||
test_output_list_ipc HEADLESS-1
|
||||
|
||||
# Test outout-cycle
|
||||
$WAYVNCCTL output-cycle
|
||||
wait_until verify_events \
|
||||
wayvnc-startup \
|
||||
capture-changed
|
||||
test_output_list_ipc HEADLESS-2
|
||||
|
||||
# Test outout-cycle wraps
|
||||
$WAYVNCCTL output-cycle
|
||||
wait_until verify_events \
|
||||
wayvnc-startup \
|
||||
capture-changed \
|
||||
capture-changed
|
||||
test_output_list_ipc HEADLESS-1
|
||||
|
||||
# Add a new output, then switch to it
|
||||
sway_output_create
|
||||
wait_until test_output_list_ipc HEADLESS-1
|
||||
$WAYVNCCTL output-set HEADLESS-3
|
||||
wait_until verify_events \
|
||||
wayvnc-startup \
|
||||
capture-changed \
|
||||
capture-changed \
|
||||
capture-changed
|
||||
test_output_list_ipc HEADLESS-3
|
||||
|
||||
if [[ $SWAYMAJOR -le 1 && $SWAYMINOR -lt 8 ]]; then
|
||||
echo "Warning: sway-1.8 or later is needed for complete testing"
|
||||
return 0
|
||||
fi
|
||||
# Remove the output, and make sure we fallback properly
|
||||
sway_output_destroy HEADLESS-3
|
||||
wait_until verify_events \
|
||||
wayvnc-startup \
|
||||
capture-changed \
|
||||
capture-changed \
|
||||
capture-changed \
|
||||
capture-changed
|
||||
wait_until test_output_list_ipc HEADLESS-1
|
||||
stop_sway
|
||||
verify_wayvnc_exited
|
||||
stop_wayvncctl_events
|
||||
}
|
||||
|
||||
smoke_test
|
||||
#multioutput_test
|
|
@ -0,0 +1,3 @@
|
|||
xwayland disable
|
||||
bindsym Ctrl+t exec bash -c "echo OK > $XDG_RUNTIME_DIR/test.txt"
|
||||
exec bash -c "env > $XDG_RUNTIME_DIR/sway.env"
|
|
@ -0,0 +1,18 @@
|
|||
test('table-printer', executable('table-printer',
|
||||
[
|
||||
'table-printer-test.c',
|
||||
'../src/table-printer.c',
|
||||
],
|
||||
include_directories: inc,
|
||||
dependencies: [ ],
|
||||
))
|
||||
test('option-parser', executable('option-parser',
|
||||
[
|
||||
'option-parser-test.c',
|
||||
'../src/option-parser.c',
|
||||
'../src/table-printer.c',
|
||||
'../src/strlcpy.c',
|
||||
],
|
||||
include_directories: inc,
|
||||
dependencies: [ ],
|
||||
))
|
|
@ -0,0 +1,314 @@
|
|||
#include "tst.h"
|
||||
|
||||
#include "option-parser.h"
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||
|
||||
static const struct wv_option options[] = {
|
||||
{ .positional = "first" },
|
||||
{ .positional = "second" },
|
||||
{ .positional = "third" },
|
||||
{ .positional = "command", .is_subcommand = true },
|
||||
{ 'a', "option-a", NULL, "Description of a" },
|
||||
{ 'b', "option-b", NULL, "Description of b" },
|
||||
{ 'v', "value-option", "value", "Description of v" },
|
||||
{ },
|
||||
};
|
||||
|
||||
static const struct wv_option default_options[] = {
|
||||
{ .positional = "first" },
|
||||
{ .positional = "second", .default_ = "second_default" },
|
||||
{ 'v', "value-option", "value", "Description of v", .default_ = "v_default" },
|
||||
{ },
|
||||
};
|
||||
|
||||
static int test_simple(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
|
||||
const char* argv[] = {
|
||||
"executable",
|
||||
"-a",
|
||||
"-b",
|
||||
"pos 1",
|
||||
"pos 2",
|
||||
};
|
||||
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
|
||||
ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first"));
|
||||
ASSERT_STR_EQ("pos 2", option_parser_get_value(&parser, "second"));
|
||||
ASSERT_FALSE(option_parser_get_value(&parser, "third"));
|
||||
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
|
||||
ASSERT_TRUE(option_parser_get_value(&parser, "option-b"));
|
||||
ASSERT_FALSE(option_parser_get_value(&parser, "value-option"));
|
||||
ASSERT_INT_EQ(0, parser.remaining_argc);
|
||||
ASSERT_FALSE(parser.remaining_argv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_extra_positional_args(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
|
||||
const char* argv[] = {
|
||||
"executable",
|
||||
"pos 1",
|
||||
"pos 2",
|
||||
"-a",
|
||||
"pos 3",
|
||||
"-b",
|
||||
"pos 4",
|
||||
};
|
||||
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
|
||||
ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first"));
|
||||
ASSERT_STR_EQ("pos 2", option_parser_get_value(&parser, "second"));
|
||||
ASSERT_STR_EQ("pos 3", option_parser_get_value(&parser, "third"));
|
||||
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
|
||||
ASSERT_TRUE(option_parser_get_value(&parser, "option-b"));
|
||||
ASSERT_FALSE(option_parser_get_value(&parser, "value-option"));
|
||||
ASSERT_INT_EQ(1, parser.remaining_argc);
|
||||
ASSERT_STR_EQ("pos 4", parser.remaining_argv[0]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int test_short_value_option_with_space(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "-v", "value" };
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
|
||||
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_short_value_option_without_space(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "-vvalue" };
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
|
||||
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_short_value_option_with_eq(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "-v=value" };
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
|
||||
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_long_value_option_with_space(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "--value-option", "value" };
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
|
||||
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_long_value_option_without_space(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "--value-option=value" };
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
|
||||
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_multi_short_option(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "-ab" };
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
|
||||
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
|
||||
ASSERT_TRUE(option_parser_get_value(&parser, "b"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_multi_short_option_with_value(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "-abvthe-value" };
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
|
||||
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
|
||||
ASSERT_TRUE(option_parser_get_value(&parser, "b"));
|
||||
ASSERT_STR_EQ("the-value", option_parser_get_value(&parser, "v"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_stop(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "exec", "-a", "--", "-b"};
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
|
||||
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
|
||||
ASSERT_FALSE(option_parser_get_value(&parser, "b"));
|
||||
ASSERT_INT_EQ(1, parser.remaining_argc);
|
||||
ASSERT_STR_EQ("-b", parser.remaining_argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_unknown_short_option(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "-x" };
|
||||
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_unknown_long_option(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "--an-unknown-long-option" };
|
||||
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_missing_short_value(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "-v" };
|
||||
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_missing_long_value(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "--value-option" };
|
||||
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_subcommand_without_arguments(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "-ab", "first", "second", "third",
|
||||
"do-stuff" };
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
ASSERT_STR_EQ("do-stuff", option_parser_get_value(&parser, "command"));
|
||||
ASSERT_INT_EQ(1, parser.remaining_argc);
|
||||
ASSERT_STR_EQ("do-stuff", parser.remaining_argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_subcommand_with_arguments(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, options);
|
||||
const char* argv[] = { "executable", "-ab", "first", "second", "third",
|
||||
"do-stuff", "--some-option", "another-argument"};
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
ASSERT_STR_EQ("do-stuff", option_parser_get_value(&parser, "command"));
|
||||
ASSERT_INT_EQ(3, parser.remaining_argc);
|
||||
ASSERT_STR_EQ("do-stuff", parser.remaining_argv[0]);
|
||||
ASSERT_STR_EQ("another-argument", parser.remaining_argv[2]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_defaults_not_set(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, default_options);
|
||||
const char* argv[] = {
|
||||
"executable",
|
||||
"pos 1",
|
||||
};
|
||||
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first"));
|
||||
|
||||
ASSERT_STR_EQ("second_default", option_parser_get_value(&parser, "second"));
|
||||
ASSERT_FALSE(option_parser_get_value_no_default(&parser, "second"));
|
||||
|
||||
ASSERT_STR_EQ("v_default", option_parser_get_value(&parser, "value-option"));
|
||||
ASSERT_FALSE(option_parser_get_value_no_default(&parser, "value-option"));
|
||||
ASSERT_STR_EQ("v_default", option_parser_get_value(&parser, "v"));
|
||||
ASSERT_FALSE(option_parser_get_value_no_default(&parser, "v"));
|
||||
|
||||
ASSERT_INT_EQ(0, parser.remaining_argc);
|
||||
ASSERT_FALSE(parser.remaining_argv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_defaults_overridden(void)
|
||||
{
|
||||
struct option_parser parser;
|
||||
option_parser_init(&parser, default_options);
|
||||
const char* argv[] = {
|
||||
"executable",
|
||||
"pos 1",
|
||||
"pos 2",
|
||||
"-v",
|
||||
"v_set",
|
||||
};
|
||||
|
||||
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||
ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first"));
|
||||
|
||||
ASSERT_STR_EQ("pos 2", option_parser_get_value(&parser, "second"));
|
||||
ASSERT_STR_EQ("pos 2", option_parser_get_value_no_default(&parser, "second"));
|
||||
|
||||
ASSERT_STR_EQ("v_set", option_parser_get_value(&parser, "value-option"));
|
||||
ASSERT_STR_EQ("v_set", option_parser_get_value_no_default(&parser, "value-option"));
|
||||
ASSERT_STR_EQ("v_set", option_parser_get_value(&parser, "v"));
|
||||
ASSERT_STR_EQ("v_set", option_parser_get_value_no_default(&parser, "v"));
|
||||
|
||||
ASSERT_INT_EQ(0, parser.remaining_argc);
|
||||
ASSERT_FALSE(parser.remaining_argv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int r = 0;
|
||||
RUN_TEST(test_simple);
|
||||
RUN_TEST(test_extra_positional_args);
|
||||
RUN_TEST(test_short_value_option_with_space);
|
||||
RUN_TEST(test_short_value_option_without_space);
|
||||
RUN_TEST(test_short_value_option_with_eq);
|
||||
RUN_TEST(test_long_value_option_with_space);
|
||||
RUN_TEST(test_long_value_option_without_space);
|
||||
RUN_TEST(test_multi_short_option);
|
||||
RUN_TEST(test_multi_short_option_with_value);
|
||||
RUN_TEST(test_stop);
|
||||
RUN_TEST(test_unknown_short_option);
|
||||
RUN_TEST(test_unknown_long_option);
|
||||
RUN_TEST(test_missing_short_value);
|
||||
RUN_TEST(test_missing_long_value);
|
||||
RUN_TEST(test_subcommand_without_arguments);
|
||||
RUN_TEST(test_subcommand_with_arguments);
|
||||
RUN_TEST(test_defaults_not_set);
|
||||
RUN_TEST(test_defaults_overridden);
|
||||
return r;
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
#include "tst.h"
|
||||
#include "table-printer.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static int test_reflow_text(void)
|
||||
{
|
||||
char buf[20];
|
||||
const char* src = "one two three four";
|
||||
int len;
|
||||
|
||||
len = table_printer_reflow_text(buf, sizeof(buf), src, 20);
|
||||
ASSERT_INT_EQ(18, len);
|
||||
ASSERT_STR_EQ("one two three four", buf);
|
||||
|
||||
len = table_printer_reflow_text(buf, sizeof(buf), src, 18);
|
||||
ASSERT_INT_EQ(18, len);
|
||||
ASSERT_STR_EQ("one two three four", buf);
|
||||
|
||||
len = table_printer_reflow_text(buf, sizeof(buf), src, 17);
|
||||
ASSERT_INT_EQ(18, len);
|
||||
ASSERT_STR_EQ("one two three\nfour", buf);
|
||||
|
||||
len = table_printer_reflow_text(buf, sizeof(buf), src, 10);
|
||||
ASSERT_INT_EQ(18, len);
|
||||
ASSERT_STR_EQ("one two\nthree four", buf);
|
||||
|
||||
len = table_printer_reflow_text(buf, sizeof(buf), src, 8);
|
||||
ASSERT_INT_EQ(18, len);
|
||||
ASSERT_STR_EQ("one two\nthree\nfour", buf);
|
||||
|
||||
len = table_printer_reflow_text(buf, sizeof(buf), src, 7);
|
||||
ASSERT_INT_EQ(18, len);
|
||||
ASSERT_STR_EQ("one two\nthree\nfour", buf);
|
||||
|
||||
len = table_printer_reflow_text(buf, sizeof(buf), src, 6);
|
||||
ASSERT_INT_EQ(18, len);
|
||||
ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf);
|
||||
|
||||
len = table_printer_reflow_text(buf, sizeof(buf), src, 5);
|
||||
ASSERT_INT_EQ(18, len);
|
||||
ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf);
|
||||
|
||||
// width <= 4 cause aborts (if any word length > width)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_reflow_multiline(void)
|
||||
{
|
||||
char buf[20];
|
||||
const char* src = "one two\nthree four";
|
||||
|
||||
table_printer_reflow_text(buf, sizeof(buf), src, 20);
|
||||
ASSERT_STR_EQ("one two\nthree four", buf);
|
||||
|
||||
table_printer_reflow_text(buf, sizeof(buf), src, 18);
|
||||
ASSERT_STR_EQ("one two\nthree four", buf);
|
||||
|
||||
table_printer_reflow_text(buf, sizeof(buf), src, 17);
|
||||
ASSERT_STR_EQ("one two\nthree four", buf);
|
||||
|
||||
table_printer_reflow_text(buf, sizeof(buf), src, 10);
|
||||
ASSERT_STR_EQ("one two\nthree four", buf);
|
||||
|
||||
table_printer_reflow_text(buf, sizeof(buf), src, 9);
|
||||
ASSERT_STR_EQ("one two\nthree\nfour", buf);
|
||||
|
||||
table_printer_reflow_text(buf, sizeof(buf), src, 7);
|
||||
ASSERT_STR_EQ("one two\nthree\nfour", buf);
|
||||
|
||||
table_printer_reflow_text(buf, sizeof(buf), src, 6);
|
||||
ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf);
|
||||
|
||||
table_printer_reflow_text(buf, sizeof(buf), src, 5);
|
||||
ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_indent_and_reflow(void)
|
||||
{
|
||||
size_t len;
|
||||
char* buf;
|
||||
FILE* stream;
|
||||
|
||||
stream = open_memstream(&buf, &len);
|
||||
table_printer_indent_and_reflow_text(stream, "one two three four", 7, 2, 4);
|
||||
fclose(stream);
|
||||
// strlen(src)=18 + first=2 + subsequent=(2x4) + newline=1
|
||||
ASSERT_INT_EQ(29, len);
|
||||
ASSERT_STR_EQ(" one two\n three\n four\n", buf);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_defaults(void)
|
||||
{
|
||||
struct table_printer one;
|
||||
table_printer_init(&one, stdout, 1);
|
||||
table_printer_set_defaults(20, 2, 2);
|
||||
struct table_printer two;
|
||||
table_printer_init(&two, stderr, 2);
|
||||
ASSERT_INT_EQ(80, one.max_width);
|
||||
ASSERT_INT_EQ(4, one.left_indent);
|
||||
ASSERT_INT_EQ(8, one.column_offset);
|
||||
ASSERT_INT_EQ(1, one.left_width);
|
||||
ASSERT_PTR_EQ(stdout, one.stream);
|
||||
ASSERT_INT_EQ(20, two.max_width);
|
||||
ASSERT_INT_EQ(2, two.left_indent);
|
||||
ASSERT_INT_EQ(2, two.column_offset);
|
||||
ASSERT_INT_EQ(2, two.left_width);
|
||||
ASSERT_PTR_EQ(stderr, two.stream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_print_line(void)
|
||||
{
|
||||
size_t len;
|
||||
char* buf;
|
||||
struct table_printer printer = {
|
||||
.max_width = 20,
|
||||
.left_indent = 2,
|
||||
.left_width = 6,
|
||||
.column_offset = 2,
|
||||
};
|
||||
|
||||
printer.stream = open_memstream(&buf, &len);
|
||||
table_printer_print_line(&printer, "left", "right");
|
||||
fclose(printer.stream);
|
||||
ASSERT_STR_EQ(" left right\n", buf);
|
||||
free(buf);
|
||||
|
||||
printer.stream = open_memstream(&buf, &len);
|
||||
table_printer_print_line(&printer, "left", "right side will wrap");
|
||||
fclose(printer.stream);
|
||||
ASSERT_STR_EQ(" left right side\n"
|
||||
" will wrap\n", buf);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_print_fmtline(void)
|
||||
{
|
||||
size_t len;
|
||||
char* buf;
|
||||
struct table_printer printer = {
|
||||
.max_width = 20,
|
||||
.left_indent = 2,
|
||||
.left_width = 6,
|
||||
.column_offset = 2,
|
||||
};
|
||||
|
||||
printer.stream = open_memstream(&buf, &len);
|
||||
table_printer_print_fmtline(&printer, "right", "left");
|
||||
fclose(printer.stream);
|
||||
ASSERT_STR_EQ(" left right\n", buf);
|
||||
free(buf);
|
||||
|
||||
printer.stream = open_memstream(&buf, &len);
|
||||
table_printer_print_fmtline(&printer, "right side will wrap", "left%d", 2);
|
||||
fclose(printer.stream);
|
||||
ASSERT_STR_EQ(" left2 right side\n"
|
||||
" will wrap\n", buf);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int r = 0;
|
||||
RUN_TEST(test_reflow_text);
|
||||
RUN_TEST(test_reflow_multiline);
|
||||
RUN_TEST(test_indent_and_reflow);
|
||||
RUN_TEST(test_defaults);
|
||||
RUN_TEST(test_print_line);
|
||||
RUN_TEST(test_print_fmtline);
|
||||
return r;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
import math
|
||||
|
||||
stream = os.popen('perf script -F time,event')
|
||||
|
||||
is_in_update_fb = False
|
||||
|
||||
class StateTracker:
|
||||
def __init__(self, name, src, enter, leave):
|
||||
self.is_active = False
|
||||
self.name = name
|
||||
self.src = src
|
||||
self.enter = enter
|
||||
self.leave = leave
|
||||
self.n = 0
|
||||
self.dt_sum = 0.0
|
||||
self.dt_square_sum = 0.0
|
||||
self.dt_max = 0.0
|
||||
self.dt_min = math.inf
|
||||
|
||||
def add_dt(self, dt):
|
||||
self.n += 1
|
||||
self.dt_sum += dt
|
||||
self.dt_square_sum += dt ** 2
|
||||
self.dt_max = max(self.dt_max, dt)
|
||||
self.dt_min = min(self.dt_min, dt)
|
||||
|
||||
def apply(self, src, event, t):
|
||||
if self.is_active:
|
||||
if (src, event) == (self.src, self.leave):
|
||||
self.is_active = False
|
||||
self.add_dt(t - self.t0)
|
||||
else:
|
||||
if (src, event) == (self.src, self.enter):
|
||||
self.is_active = True
|
||||
self.t0 = t
|
||||
|
||||
def avg(self):
|
||||
return self.dt_sum / self.n
|
||||
|
||||
def var(self):
|
||||
return self.dt_square_sum / self.n - self.avg() ** 2
|
||||
|
||||
def stddev(self):
|
||||
return math.sqrt(self.var())
|
||||
|
||||
def report(self):
|
||||
if self.n == 0:
|
||||
return
|
||||
|
||||
print('{}:'.format(self.name))
|
||||
print('\tMin, max: {:.1f} ms, {:.1f} ms'.format(self.dt_min * 1e3, self.dt_max * 1e3))
|
||||
print('\tAverage, std.dev.: {:.1f} ms, {:.1f} ms'.format(self.avg() * 1e3, self.stddev() * 1e3))
|
||||
|
||||
trackers = [
|
||||
StateTracker('Framebuffer update', 'sdt_neatvnc', 'update_fb_start', 'update_fb_done'),
|
||||
StateTracker('Framebuffer update (only sending)', 'sdt_neatvnc', 'send_fb_start', 'send_fb_done'),
|
||||
StateTracker('Screencopy', 'sdt_wayvnc', 'screencopy_start', 'screencopy_ready'),
|
||||
StateTracker('Refine damage', 'sdt_wayvnc', 'refine_damage_start', 'refine_damage_end'),
|
||||
StateTracker('Render', 'sdt_wayvnc', 'render_start', 'render_end'),
|
||||
]
|
||||
|
||||
for line in stream:
|
||||
[t, src, event, _] = line.replace(' ', '').split(':')
|
||||
t = float(t)
|
||||
|
||||
for tracker in trackers:
|
||||
tracker.apply(src, event, t)
|
||||
|
||||
for tracker in trackers:
|
||||
tracker.report()
|
||||
print()
|
|
@ -0,0 +1,30 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
EVENTS="sdt_wayvnc:* sdt_neatvnc:*"
|
||||
|
||||
delete_all_events()
|
||||
{
|
||||
for e in $EVENTS; do
|
||||
sudo perf probe -d "$e" || true
|
||||
done
|
||||
}
|
||||
|
||||
add_all_events()
|
||||
{
|
||||
for e in $EVENTS; do
|
||||
sudo perf probe "$e"
|
||||
done
|
||||
}
|
||||
|
||||
sudo perf buildid-cache -a build/wayvnc
|
||||
sudo perf buildid-cache -a build/subprojects/neatvnc/libneatvnc.so
|
||||
|
||||
delete_all_events
|
||||
add_all_events
|
||||
|
||||
trap "sudo chown $USER:$USER perf.data*" EXIT
|
||||
|
||||
sudo perf record -aR -e ${EVENTS// /,}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
valgrind --leak-check=full \
|
||||
--show-leak-kinds=all \
|
||||
--suppressions=$SCRIPT_DIR/valgrind.supp \
|
||||
$@
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
Ignore dlopen bug.
|
||||
Memcheck:Leak
|
||||
...
|
||||
fun:_dl_*
|
||||
...
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
auth required pam_unix.so nodelay deny=3 unlock_time=600
|
||||
account required pam_unix.so nodelay deny=3 unlock_time=600
|
|
@ -0,0 +1,447 @@
|
|||
wayvnc(1)
|
||||
|
||||
# NAME
|
||||
|
||||
wayvnc - A VNC server for wlroots based Wayland compositors.
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
*wayvnc* [options] [address [port]]
|
||||
|
||||
# OPTIONS
|
||||
|
||||
*-C, --config=<path>*
|
||||
Select a config file.
|
||||
|
||||
*-g,--gpu*
|
||||
Enable features that require GPU.
|
||||
|
||||
*-o, --output=<name>*
|
||||
Select output to capture.
|
||||
|
||||
*-k, --keyboard=<layout>[-variant]*
|
||||
Select keyboard layout. The variant can be appended if needed.
|
||||
|
||||
*-s, --seat=<name>*
|
||||
Select seat by name.
|
||||
|
||||
*-S, --socket=<path>*
|
||||
Set wayvnc control socket path. Default: $XDG_RUNTIME_DIR/wayvncctl
|
||||
or /tmp/wayvncctl-$UID
|
||||
|
||||
*-r, --render-cursor*
|
||||
Enable overlay cursor rendering.
|
||||
|
||||
*-f, --max-fps=<fps>*
|
||||
Set the rate limit (default 30).
|
||||
|
||||
*-p, --show-performance*
|
||||
Show performance counters.
|
||||
|
||||
*-u, --unix-socket*
|
||||
Create a UNIX domain socket instead of TCP, treating the address as a
|
||||
path.
|
||||
|
||||
*-d, --disable-input*
|
||||
Disable all remote input. This allows using wayvnc without compositor
|
||||
support of virtual mouse / keyboard protocols.
|
||||
|
||||
*-V, --version*
|
||||
Show version info.
|
||||
|
||||
*-v,--verbose*
|
||||
Be more verbose. Same as setting `--log-level=info`.
|
||||
|
||||
*-w,--websocket*
|
||||
Create a websocket.
|
||||
|
||||
*-L,--log-level*
|
||||
Set log level. The levels are: error, warning, info, debug, trace and
|
||||
quiet.
|
||||
|
||||
*-h, --help*
|
||||
Get help.
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This is a VNC server for wlroots based Wayland compositors. It attaches to a
|
||||
running Wayland session, creates virtual input devices and exposes a single
|
||||
display via the RFB protocol. The Wayland session may be a headless one, so it
|
||||
is also possible to run wayvnc without a physical display attached.
|
||||
|
||||
## MULTIPLE OUTPUTS
|
||||
|
||||
If the Wayland session consists of multiple outputs, only one will be captured.
|
||||
By default this will be the first one, but can be specified by the _-o_ command
|
||||
line argument. The argument accepts the short name such as _eDP-1_ or _DP-4_.
|
||||
Running wayvnc in verbose mode (_-v_) will display the names of all outputs on
|
||||
startup, or you can query them at runtime via the *wayvncctl output-list*
|
||||
command.
|
||||
|
||||
You can also change which output is being captured on the fly via the *wayvncctl
|
||||
output-set* command.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
wayvnc searches for a config file in the location
|
||||
~/$XDG_CONFIG_HOME/wayvnc/config
|
||||
or if $XDG_CONFIG_HOME is not set
|
||||
~/.config/wayvnc/config
|
||||
|
||||
## SYNTAX
|
||||
|
||||
The configuration file is composed of key-value pairs separated with an *equal*
|
||||
sign. Whitespace around either the key or the value is insignificant and is not
|
||||
considered to be part of the key or the value.
|
||||
|
||||
## KEYWORDS
|
||||
|
||||
*address*
|
||||
The address to which the server shall bind, e.g. 0.0.0.0 or localhost.
|
||||
|
||||
*certificate_file*
|
||||
The path to the certificate file for encryption. Only applicable when
|
||||
*enable_auth*=true.
|
||||
|
||||
*enable_auth*
|
||||
Enable authentication and encryption. Setting this value to *true*
|
||||
requires also setting *certificate_file*, *private_key_file*,
|
||||
*username* and *password*.
|
||||
|
||||
*password*
|
||||
Choose a password for authentication.
|
||||
|
||||
*port*
|
||||
The port to which the server shall bind. Default is 5900.
|
||||
|
||||
*private_key_file_file*
|
||||
The path to the private key file for TLS encryption. Only applicable
|
||||
when *enable_auth*=true.
|
||||
|
||||
*relax_encryption*
|
||||
Don't require encryption after the user has been authenticated. This
|
||||
enables some security types such as Apple Diffie-Hellman.
|
||||
|
||||
*rsa_private_key_file*
|
||||
The path to the private key file for RSA-AES encryption. Only applicable
|
||||
when *enable_auth*=true.
|
||||
|
||||
*username*
|
||||
Choose a username for authentication.
|
||||
|
||||
*use_relative_paths*
|
||||
Make file paths relative to the location of the config file.
|
||||
|
||||
*xkb_layout*
|
||||
The keyboard layout to use for key code lookup.
|
||||
|
||||
Default: _XKB_DEFAULT_LAYOUT_ or system default.
|
||||
|
||||
*xkb_model*
|
||||
The keyboard model by which to interpret keycodes and LEDs.
|
||||
|
||||
Default: "pc105"
|
||||
|
||||
*xkb_options*
|
||||
A comma separated list of options, through which the user specifies
|
||||
non-layout related preferences such as which key is the Compose key.
|
||||
|
||||
Default: _XKB_DEFAULT_OPTIONS_ or system default.
|
||||
|
||||
*xkb_rules*
|
||||
The rules file describes how to interpret the values of the model,
|
||||
layout, variant and options fields.
|
||||
|
||||
Default: _XKB_DEFAULT_RULES_ or system default.
|
||||
|
||||
*xkb_variant*
|
||||
The keyboard variant to use for keycode lookup.
|
||||
|
||||
Default: _XKB_DEFAULT_VARIANT_ or system default.
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
```
|
||||
use_relative_paths=true
|
||||
address=0.0.0.0
|
||||
enable_auth=true
|
||||
username=luser
|
||||
password=p455w0rd
|
||||
rsa_private_key_file=rsa_key.pem
|
||||
private_key_file=tls_key.pem
|
||||
certificate_file=tls_cert.pem
|
||||
```
|
||||
|
||||
# WAYVNCCTL CONTROL SOCKET
|
||||
|
||||
To facilitate runtime interaction and control, wayvnc opens a unix domain socket
|
||||
at *$XDG_RUNTIME_DIR*/wayvncctl (or a fallback of /tmp/wayvncctl-*$UID*). A
|
||||
client can connect and exchange json-formatted IPC messages to query and control
|
||||
the running wayvnc instance.
|
||||
|
||||
## IPC COMMANDS
|
||||
|
||||
_HELP_
|
||||
|
||||
The *help* command, when issued without any parameters, lists the names of all
|
||||
available commands.
|
||||
|
||||
If an optional *command* parameter refers to one of those commands by name, the
|
||||
response data will be a detailed description of that command and its parameters.
|
||||
|
||||
_EVENT-RECEIVE_
|
||||
|
||||
The *event-receive* command registers for asynchronous server events. See the
|
||||
_EVENTS_ section below for details on the event message format, and the _IPC
|
||||
EVENTS_ section below for a description of all possible server events.
|
||||
|
||||
Event registration registers for all available server events and is scoped to
|
||||
the current connection only. If a client disconnects and reconnects, it must
|
||||
re-register for events.
|
||||
|
||||
_CLIENT-LIST_
|
||||
|
||||
The *client-list* command retrieves a list of all VNC clients currently
|
||||
connected to wayvnc.
|
||||
|
||||
_CLIENT-DISCONNECT_
|
||||
|
||||
The *client-disconnect* command disconnects a single VNC client.
|
||||
|
||||
Parameters:
|
||||
|
||||
*id*
|
||||
Required: The ID of the client to disconnect. This ID can be found from the
|
||||
_GET-CLIENTS_ command or receipt of a _CLIENT-CONNECTED_ event.
|
||||
|
||||
_OUTPUT-LIST_
|
||||
|
||||
The *output-list* command retrieves a list of all outputs known to wayvnc and
|
||||
whether or not each one is currently being captured.
|
||||
|
||||
_OUTPUT-CYCLE_
|
||||
|
||||
For multi-output wayland displays, the *output-cycle* command switches which
|
||||
output is actively captured by wayvnc. Running this once will switch to the next
|
||||
available output. If no more outputs are available, it cycles back to the first
|
||||
again.
|
||||
|
||||
_OUTPUT-SET_
|
||||
|
||||
For multi-output wayland displays, the *output-set* command switches which
|
||||
output is actively captured by wayvnc by name.
|
||||
|
||||
*output-name=name*
|
||||
Required: The name of the output to capture next.
|
||||
|
||||
_VERSION_
|
||||
|
||||
The *version* command queries the running wayvnc instance for its version
|
||||
information. Much like the _-V_ option, the response data will contain the
|
||||
version numbers of wayvnc, as well as the versions of the neatvnc and aml
|
||||
components.
|
||||
|
||||
_WAYVNC-EXIT_
|
||||
|
||||
The *wayvnc-exit* command disconnects all clients and shuts down wayvnc.
|
||||
|
||||
## IPC EVENTS
|
||||
|
||||
_CAPTURE_CHANGED_
|
||||
|
||||
The *capture-changed* event is sent when the currently captured output
|
||||
changes.
|
||||
|
||||
Parameters:
|
||||
|
||||
*output=...*
|
||||
The name of the output now being captured.
|
||||
|
||||
_CLIENT-CONNECTED_
|
||||
|
||||
The *client-connected* event is sent when a new VNC client connects to wayvnc.
|
||||
|
||||
Parameters:
|
||||
|
||||
*id=...*
|
||||
A unique identifier for this client.
|
||||
|
||||
*connection_count=...*
|
||||
The total number of connected VNC clients including this one.
|
||||
|
||||
*address=...*
|
||||
The IP address of this client. May be null.
|
||||
|
||||
*username=...*
|
||||
The username used to authenticate this client. May be null.
|
||||
|
||||
_CLIENT-DISCONNECTED_
|
||||
|
||||
The *client-disconnected* event is sent when a VNC cliwnt disconnects from
|
||||
wayvnc.
|
||||
|
||||
Parameters:
|
||||
|
||||
*id=...*
|
||||
A unique identifier for this client.
|
||||
|
||||
*connection_count=...*
|
||||
The total number of connected VNC clients not including this one.
|
||||
|
||||
*address=...*
|
||||
The IP address of this client. May be null.
|
||||
|
||||
*username=...*
|
||||
The username used to authenticate this client. May be null.
|
||||
|
||||
## IPC MESSAGE FORMAT
|
||||
|
||||
The *wayvncctl(1)* command line utility will construct properly-formatted json
|
||||
ipc messages, but any client will work. The client initiates the connection and
|
||||
sends one or more request objects, each of which will receive a corresponding
|
||||
response object.
|
||||
|
||||
*Note* This message format is unstable and may change substantially over the
|
||||
next few releases.
|
||||
|
||||
_REQUEST_
|
||||
|
||||
The general form of a json-ipc request message
|
||||
is:
|
||||
|
||||
```
|
||||
{
|
||||
"method": "command-name",
|
||||
"id": 123,
|
||||
"params": {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The *method* is the name of the command to be executed. Use the *help* method to
|
||||
query a list of all valid method names.
|
||||
|
||||
The *id* field is optional and may be any valid json number or string. If
|
||||
provided, the response to this request will contain the identical id value,
|
||||
which the client may use to coordinate multiple requests and responses.
|
||||
|
||||
The *params* object supplies optional parameters on a per-method basis, and may
|
||||
be omitted if empty.
|
||||
|
||||
_RESPONSE_
|
||||
|
||||
```
|
||||
{
|
||||
"id": 123,
|
||||
"code": 0,
|
||||
"data": {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the request had an id, the response will have an identical value for *id*.
|
||||
|
||||
The numerical *code* provides an indication of how the request was handled. A
|
||||
value of *0* always signifies success. Any other value means failure, and varies
|
||||
depending on the method in question.
|
||||
|
||||
The *data* object contains method-specific return data. This may be structured
|
||||
data in response to a query, a simple error string in the case of a failed
|
||||
request, or it may be omitted entirely if the error code alone is sufficient.
|
||||
|
||||
_EVENTS_
|
||||
|
||||
Events are aaynchronous messages sent from a server to all registered clients.
|
||||
The message format is identical to a _REQUEST_, but without an "id" field, and a
|
||||
client must not send a response.
|
||||
|
||||
Example event message:
|
||||
|
||||
```
|
||||
{
|
||||
"method": "event-name",
|
||||
"params": {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In order to receive any events, a client must first register to receive them by
|
||||
sending a _event-receive_ request IPC. Once the success response has been sent
|
||||
by the server, the client must expect that asynchronous event messages may be
|
||||
sent by the server at any time, even between a request and the associated
|
||||
response.
|
||||
|
||||
# ENVIRONMENT
|
||||
|
||||
The following environment variables have an effect on wayvnc:
|
||||
|
||||
_WAYLAND_DISPLAY_
|
||||
Specifies the name of the Wayland display that the compositor to which
|
||||
wayvnc shall bind is running on.
|
||||
|
||||
_XDG_CONFIG_HOME_
|
||||
Specifies the location of configuration files.
|
||||
|
||||
_XDG_RUNTIME_DIR_
|
||||
Specifies the default location for the wayvncctl control socket.
|
||||
|
||||
# FAQ
|
||||
|
||||
*Wayvnc complains that a protocol is not supported*
|
||||
|
||||
The error might look like this:
|
||||
```
|
||||
wl_registry@2: error 0: invalid version for global zxdg_output_manager_v1 (4): have 2, wanted 3
|
||||
ERROR: ../src/main.c: 388: Screencopy protocol not supported by compositor. Exiting. Refer to FAQ section in man page.
|
||||
ERROR: ../src/main.c: 1024: Failed to initialise wayland
|
||||
```
|
||||
|
||||
This means that your wayland compositor does not implement the
|
||||
screencopy protocol and wayvnc won't work with it. Screencopy is
|
||||
implemented by wlroots based compositors such as Sway and Wayfire.
|
||||
|
||||
*How can I run wayvnc in headless mode/over an SSH session?*
|
||||
|
||||
Set the environment variables _WLR_BACKENDS_=headless and
|
||||
_WLR_LIBINPUT_NO_DEVICES_=1 before starting the compositor, then run
|
||||
wayvnc as normal.
|
||||
|
||||
*How can I pass my mod-key from Sway to the remote desktop session?*
|
||||
|
||||
Create an almost empty mode in your sway config. Example:
|
||||
```
|
||||
mode passthrough {
|
||||
bindsym $mod+Pause mode default
|
||||
}
|
||||
bindsym $mod+Pause mode passthrough
|
||||
```
|
||||
This makes it so that when you press $mod+Pause, all keybindings, except
|
||||
the one to switch back, are disabled.
|
||||
|
||||
*Not all symbols show up when I'm typing. What can I do to fix this?*
|
||||
|
||||
Try setting the keyboard layout in wayvnc to the one that most closely
|
||||
matches the keyboard layout that you're using on the client side. An
|
||||
exact layout isn't needed, just one that has all the symbols that you
|
||||
use.
|
||||
|
||||
*How do I enable the Compose key?*
|
||||
|
||||
Set "xkb_options=compose:menu" in the config file. Any key that is not
|
||||
otherwise used will work. There just needs to be some key for wayvnc to
|
||||
match against.
|
||||
|
||||
# AUTHORS
|
||||
|
||||
Maintained by Andri Yngvason <andri@yngvason.is>. Up-to-date sources can be
|
||||
found at https://github.com/any1/wayvnc and bugs reports or patches can be
|
||||
submitted to GitHub's issue tracker.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*wayvncctl(1)*
|
|
@ -0,0 +1,160 @@
|
|||
wayvncctl(1)
|
||||
|
||||
# NAME
|
||||
|
||||
wayvncctl - A command line control client for wayvnc(1)
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
*wayvncctl* [options] [command [--parameter value ...]]
|
||||
|
||||
# OPTIONS
|
||||
|
||||
*-S, --socket=<path>*
|
||||
Set wayvnc control socket path. Default: $XDG_RUNTIME_DIR/wayvncctl
|
||||
or /tmp/wayvncctl-$UID
|
||||
|
||||
*-w, --wait*
|
||||
Wait for wayvnc to start up if it's not already running. Default: Exit
|
||||
immediately with an error if wayvnc is not running.
|
||||
|
||||
*-r,--reconnect*
|
||||
If disconnected while waiting for events, wait for wayvnc to restart and
|
||||
re-register for events. Default: Exit when wayvnc exits.
|
||||
|
||||
*-j, --json*
|
||||
Produce json output to stdout.
|
||||
|
||||
*-V, --version*
|
||||
Show version info.
|
||||
|
||||
*-v,--verbose*
|
||||
Be more verbose.
|
||||
|
||||
*-h, --help*
|
||||
Get help about the wayvncctl command itself (lists these options). Does
|
||||
not connect to the wayvncctl control socket.
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
*wayvnc(1)* allows runtime interaction via a unix socket json-ipc mechanism.
|
||||
This command line utility provides easy interaction with those commands.
|
||||
|
||||
This command is largely self-documenting:
|
||||
|
||||
- Running *wayvncctl --help* lists all supported IPC commands.
|
||||
- Running *wayvncctl command-name --help* returns a description of the given
|
||||
command and its available parameters.
|
||||
- Running *wayvncctl event-receive --help* includes a list of all supported event
|
||||
names.
|
||||
- Running *wayvncctl event-receive --show=event-name* returns a
|
||||
description of the given event and expected data fields.
|
||||
|
||||
# ASYNCHRONOUS EVENTS
|
||||
|
||||
While *wayvncctl* normally terminates after sending one request and receiving
|
||||
the corresponding reply, the *event-receive* command acts differently. Instead
|
||||
of exiting immediately, *wayvncctl* waits for any events from the server,
|
||||
printing each to stdout as they arrive. This mode of operation will block until
|
||||
either it receives a signal to terminate, or until the wayvnc server terminates.
|
||||
|
||||
In _--json_ mode, each event is printed on one line, with a newline character at
|
||||
the end, for ease in scripting:
|
||||
|
||||
```
|
||||
$ wayvncctl --json event-receive
|
||||
{"method":"client-connected","params":{"id":"0x10ef670","address":null,"username":null,"connection_count":1}}
|
||||
{"method":"client-disconnected","params":{"id":"0x10ef670","address":null,"username":null,"connection_count":0}}
|
||||
```
|
||||
|
||||
The default human-readible output is a multi-line yaml-like format, with two
|
||||
newline characters between each event:
|
||||
|
||||
```
|
||||
$ wayvncctl event-receive
|
||||
|
||||
client-connected:
|
||||
id: 0x10ef670
|
||||
address: 192.168.1.18
|
||||
connection_count: 1
|
||||
|
||||
client-disconnected:
|
||||
id: 0x10ef670
|
||||
address: 192.168.1.18
|
||||
connection_count: 0
|
||||
|
||||
```
|
||||
|
||||
## SPECIAL LOCAL EVENT TYPES
|
||||
|
||||
Especially useful when using _--wait_ or _--reconnect_ mode, wayvncctl will
|
||||
generate 2 additional events not documented in *wayvnc(1)*:
|
||||
|
||||
*wayvnc-startup*
|
||||
Sent when a successful wayvnc control connection is established and
|
||||
event registration has succeeded, both upon initial startup and on
|
||||
subsequent registrations with _--reconnect_.
|
||||
|
||||
No paramerers.
|
||||
|
||||
*wayvnc-shutdown*
|
||||
Sent when the wayvnc control connection is dropped, usually due to
|
||||
wayvnc exiting.
|
||||
|
||||
No paramerers.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
Get help on the "output-set" IPC command:
|
||||
|
||||
```
|
||||
$ wayvncctl output-set --help
|
||||
Usage: wayvncctl [options] output-set <output-name> [params]
|
||||
...
|
||||
```
|
||||
|
||||
Cycle to the next active output:
|
||||
|
||||
```
|
||||
$ wayvncctl output-cycle
|
||||
```
|
||||
|
||||
Get json-formatted version information:
|
||||
|
||||
```
|
||||
$ wayvncctl --json version
|
||||
{"wayvnc":"v0.5.0","neatvnc":"v0.5.1","aml":"v0.2.2"}
|
||||
```
|
||||
|
||||
A script that takes an action for each client connect and disconnect event:
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
connection_count_now() {
|
||||
echo "Total clients: $1"
|
||||
}
|
||||
|
||||
while IFS= read -r EVT; do
|
||||
case "$(jq -r '.method' <<<"$EVT")" in
|
||||
client-*onnected)
|
||||
count=$(jq -r '.params.connection_count' <<<"$EVT")
|
||||
connection_count_now "$count"
|
||||
;;
|
||||
wayvnc-shutdown)
|
||||
connection_count_now 0
|
||||
;;
|
||||
esac
|
||||
done < <(wayvncctl --wait --reconnect --json event-receive)
|
||||
```
|
||||
|
||||
# ENVIRONMENT
|
||||
|
||||
The following environment variables have an effect on wayvncctl:
|
||||
|
||||
_XDG_RUNTIME_DIR_
|
||||
Specifies the default location for the wayvncctl control socket.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*wayvnc(1)*
|
Loading…
Reference in New Issue