Compare commits

..

78 Commits

Author SHA1 Message Date
Jonas Letzbor 435efaab31
Implement copy and paste between client and server 2024-03-29 18:28:26 +01:00
Jonas Letzbor 23bd46bef9
Implement TLS and authentication for VeNCrypt 2024-03-28 21:39:35 +01:00
Mariusz Bialonczyk 2b9a886edd HandleVncAuth: revert asking for a password after 232bcad
Fixes #21
2023-01-05 20:04:21 +00:00
Andri Yngvason 64449b249a Adapt to aml api changes 2022-10-29 12:02:20 +00:00
Andri Yngvason ed52288aca Enable IPv6
Sometimes features can be added by removing code. ;)

Closes: #18
2022-10-23 18:28:09 +00:00
Andri Yngvason 232bcadd4a Remove vncrec suppport 2022-10-23 18:15:37 +00:00
Andri Yngvason 087a78e551 renderer-egl: Use glFlush instead of glFinish 2022-10-09 20:01:41 +00:00
Andri Yngvason 1baf3a902c Exit when the server goes away 2022-10-09 20:01:41 +00:00
Andri Yngvason 1baca2f9b4 Implement output scaling aware rendering 2022-10-09 17:26:04 +00:00
Andri Yngvason 9ef4184e87 main: Correct pointer scroll handling
closes: #15

Reported-by: retrotails
2022-07-30 11:02:28 +00:00
Andri Yngvason df203e5908 rfbproto: Request quality level with open-h264 2022-07-10 14:49:16 +00:00
Jan Beich ca6036f4cf meson: unbreak when libjpeg isn't found
ld: error: undefined symbol: jpeg_destroy_compress
>>> referenced by turbojpeg.c
>>>               wlvncc.p/src_turbojpeg.c.o:(tjDestroy)

ld: error: undefined symbol: jpeg_destroy_decompress
>>> referenced by turbojpeg.c
>>>               wlvncc.p/src_turbojpeg.c.o:(tjDestroy)

ld: error: undefined symbol: jpeg_std_error
>>> referenced by turbojpeg.c
>>>               wlvncc.p/src_turbojpeg.c.o:(_tjInitCompress)
>>> referenced by turbojpeg.c
>>>               wlvncc.p/src_turbojpeg.c.o:(_tjInitDecompress)

ld: error: undefined symbol: jpeg_CreateCompress
>>> referenced by turbojpeg.c
>>>               wlvncc.p/src_turbojpeg.c.o:(_tjInitCompress)

[...]
2022-07-10 13:56:28 +00:00
Andri Yngvason 81c9d8452c Remove rfb/ include prefix
This avoids accidentally including libvncclient's public API.
2022-07-06 21:17:01 +00:00
Andri Yngvason f1bba8e9bb Use quote-includes for all local headers 2022-07-06 21:11:19 +00:00
Andri Yngvason fcc6f737ea Remove redundant source file 2022-07-06 21:02:44 +00:00
Andri Yngvason c649bb8cb3 Add missing headers 2022-07-06 20:59:19 +00:00
Andri Yngvason 2e901f8e71 Don't outpace the wayland compositor 2022-07-03 10:47:27 +00:00
Andri Yngvason d4d5f2e75b vnc: Set a variable to tell that a framebuffer update is in progress 2022-07-03 10:46:49 +00:00
Andri Yngvason 73310b5669 rfbproto: Add StartingFramebufferUpdate callback 2022-07-02 22:01:12 +00:00
Andri Yngvason 178a10853c rfbproto: Extract framebuffer update handling into function 2022-07-02 21:51:54 +00:00
Andri Yngvason 6faab5d6d1 Pass rfbproto.c through clang-format 2022-07-02 20:55:41 +00:00
Andri Yngvason 88ff40f21c main: Fix typo 2022-07-02 15:32:49 +00:00
Andri Yngvason 589679f851 renderer-egl: Ignore alpha channel in shader 2022-07-02 10:47:35 +00:00
Andri Yngvason 5dfac14981 renderer-egl: Clear buffer damage after rendering
Otherwise, damage accumulates and everything is damaged all the time.
2022-07-02 10:47:35 +00:00
Andri Yngvason 29b33672ea renderer-egl: Don't clear before drawing
That interferes with damage tracking
2022-07-02 10:47:35 +00:00
Andri Yngvason 505b03a47e main: Extract function: window_damage_region 2022-07-02 10:47:35 +00:00
Andri Yngvason 47ba673c8b main: Extract function: apply_buffer_damage 2022-07-02 10:47:35 +00:00
Andri Yngvason d0818501f8 main: Extract function: get_frame_damage 2022-07-02 10:47:35 +00:00
Andri Yngvason 3a3d129e8f main: Remove redundant if 2022-07-02 10:47:35 +00:00
Andri Yngvason af59624f83 open-h264: Add padding to packet buffer
According to documentation this padding should be there and valgrind will
complain if it isn't.
2022-07-02 10:47:35 +00:00
Andri Yngvason f6a634f30b Free av_frames when done with them 2022-07-02 10:47:35 +00:00
Andri Yngvason 469db1ba3a renderer-egl: Clean up properly on exit 2022-07-02 10:47:35 +00:00
Andri Yngvason 37b3431e00 main: Add a canary ticker
This should help to notice when something is badly blocking the main loop.
2022-07-02 10:47:35 +00:00
Andri Yngvason cf56a1f416 Handle all buffered messages in the socket handler
Otherwise, some buffered messages will be left over
2022-07-02 10:46:00 +00:00
Andri Yngvason 2b99d0c019 sockets: Simplify write function 2022-07-02 10:46:00 +00:00
Andri Yngvason 1d9fcb5ebc Add dtrace probes for pts rects 2022-07-02 10:46:00 +00:00
Andri Yngvason 8970accb86 sockets: Replace select() with poll()
select() has been deprecated for a very long time and is considered harmful
2022-07-02 10:46:00 +00:00
Andri Yngvason d4faccba28 Remove useless feature test macros 2022-07-02 10:46:00 +00:00
Andri Yngvason 9a2a318991 Move SetFormatAndEncodings back into rfbproto.c 2022-07-02 10:46:00 +00:00
Andri Yngvason 760db16923 Remove dead code 2022-07-02 10:46:00 +00:00
Andri Yngvason f38e43e096 README: Remove libvncclient dependency 2022-07-02 10:46:00 +00:00
Andri Yngvason f4d40df8f6 Dispatch events while waiting for server data
Blocking the event loop is very bad
2022-07-02 10:46:00 +00:00
Andri Yngvason 23d1c82943 Merge libvncclient into the project 2022-07-02 10:46:00 +00:00
Andri Yngvason 3e8b6c311c .gitignore: Add .clang_complete 2022-07-02 10:45:34 +00:00
Daniel Lublin 0ea00b45ef Fail verbosely if we can't talk to wayland
Useful when mistakenly run in X
2022-06-30 15:26:13 +00:00
Jan Beich 83886345b1 vnc: consistently use builtin byteswap after b725a08b47
src/vnc.c:27:10: fatal error: 'byteswap.h' file not found
 #include <byteswap.h>
          ^~~~~~~~~~~~
2022-04-24 16:21:24 +00:00
Andri Yngvason b725a08b47 vnc: Add presentation timestamps 2022-04-14 18:41:34 +00:00
Andri Yngvason 3a96498113 vnc: Queue up two extra frambuffer updates
This way the server knows that the client can take up to 3 updates in a
row and doesn't have to wait for an update request after every single
frame.
2022-04-11 21:20:32 +00:00
Andri Yngvason 5b4694e1fd main: Remove dmabufs with alpha channel
They cause rendering issues
2022-04-10 17:00:09 +00:00
Andri Yngvason 7e89a36f43 main: Prioritise open-h264 encoding 2022-04-10 16:56:59 +00:00
Andri Yngvason 72c497e61d vnc: Re-implement libvncclient's SetFormatAndEncodings
Otherwise, we can't prioritise open-h264
2022-04-10 16:56:04 +00:00
Andri Yngvason 09c851750e open-h264: Fix frame parsing 2022-04-10 16:13:54 +00:00
Andri Yngvason 0b757d6623 main: User triple buffering
Apparently, double buffering isn't enough
2022-04-10 14:59:23 +00:00
Andri Yngvason 93869627ed open-h264: Add copyright notice 2022-04-10 13:56:11 +00:00
Andri Yngvason a69d5adc7a main: Pass AVFrames to egl renderer 2022-04-10 13:55:14 +00:00
Andri Yngvason c4ffd8eef6 renderer-egl: Add function to render AVFrames 2022-04-10 13:55:14 +00:00
Andri Yngvason 3e652be8d6 vnc: Add Open H.264 encoding extension 2022-04-10 13:55:14 +00:00
Andri Yngvason 14299b6cff main: Add option to turn off egl 2022-04-09 16:27:50 +00:00
Andri Yngvason ed4c8b1cd0 renderer-egl: Only copy damaged regions 2022-04-09 16:21:17 +00:00
Andri Yngvason 698ac6947b Pass frame damage to renderer via image struct 2022-04-09 16:19:57 +00:00
Andri Yngvason 040a87836d buffer: Clean up damage 2022-04-09 16:19:08 +00:00
Andri Yngvason 9afe099377 buffer: Apply full damage to new dmabuf 2022-04-09 16:18:35 +00:00
Andri Yngvason e6ea9068f0 Implement GL rendering 2022-04-09 15:21:24 +00:00
Andri Yngvason 07e8e5c303 Add dmabuf 2022-04-09 12:09:11 +00:00
Andri Yngvason 6bba21283f Move buffer functions into own file 2022-04-09 11:36:09 +00:00
Andri Yngvason ec51388fec Extract rendering into own unit 2022-04-03 22:17:27 +00:00
Andri Yngvason 9c7a4d6b07 Use drm format instead of wl_shm_format
Using the drm fourcc format as the main pixel format, helps when
adding linux-dmabuf support.
2022-04-03 19:00:19 +00:00
Andri Yngvason 15b5d5a33f Scale image to fit window 2022-04-02 12:51:37 +00:00
Andri Yngvason 545be152ce Add pixman region utils 2022-04-02 12:49:41 +00:00
Andri Yngvason 80d18ac6aa main: Use pixman for rendering 2022-03-30 21:48:54 +00:00
Andri Yngvason 767f7ce816 Add pixel format conversion utils 2022-03-30 21:48:21 +00:00
Andri Yngvason 1a713b6642 Use intermediate pixel buffer for VNC client 2022-03-28 21:40:44 +00:00
Andri Yngvason 1ca82ce2e2 meson: Don't require libvncserver as a subproject 2020-12-06 22:16:58 +00:00
Andri Yngvason 2414e23dd6 Use double-buffering
Without double-buffering there's a slight chance that the vnc code could
start writing to the buffer before the compositor is done with it.
2020-12-06 21:04:56 +00:00
Andri Yngvason 977f34224c Don't crash if cut_text cb isn't implemented 2020-12-06 20:00:49 +00:00
Andri Yngvason 86283fd8d1 vnc: Add cut-text events 2020-12-06 19:52:02 +00:00
Andri Yngvason 67676b224e vnc: Send regular key event if extended fails 2020-12-06 19:40:51 +00:00
Andri Yngvason 7c8c29beb2 Print an 'oops' when the wl_buffer is still attached during drawing 2020-12-06 19:30:46 +00:00
59 changed files with 18855 additions and 152 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
build*
subprojects
.clang_complete
.vscode

340
COPYING.GPL 100644
View File

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
Appendix: How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

View File

@ -6,7 +6,6 @@ Expect bugs and missing features.
## Runtime Dependencies
* aml
* libvncclient
* libwayland
* libxkbcommon
* pixman
@ -19,19 +18,13 @@ Expect bugs and missing features.
* wayland-protocols
## Building & Running
At the time of writing, an unreleased version of libvncclient is required. So,
you must either use bleeding edge git based packages for the project, or build
libvncclient as a subproject.
```
git clone https://github.com/any1/aml.git
git clone https://github.com/LibVNC/libvncserver.git
git clone https://github.com/any1/wlvncc.git
mkdir wlvncc/subprojects
cd wlvncc/subprojects
ln -s ../../aml .
ln -s ../../libvncserver .
cd -
meson build

55
include/buffer.h 100644
View File

@ -0,0 +1,55 @@
/*
* 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 <stdbool.h>
#include <unistd.h>
#include <stdint.h>
#include <pixman.h>
struct wl_buffer;
struct gbm_bo;
enum buffer_type {
BUFFER_UNSPEC = 0,
BUFFER_WL_SHM,
BUFFER_DMABUF,
};
struct buffer {
enum buffer_type type;
int width, height;
int32_t scale;
size_t size;
uint32_t format;
struct wl_buffer* wl_buffer;
bool is_attached;
bool please_clean_up;
struct pixman_region16 damage;
// wl_shm:
void* pixels;
int stride;
// dmabuf:
struct gbm_bo* bo;
};
struct buffer* buffer_create_shm(int width, int height, int stride, uint32_t format);
struct buffer* buffer_create_dmabuf(int width, int height, uint32_t format);
void buffer_destroy(struct buffer* self);

46
include/crypto.h 100644
View File

@ -0,0 +1,46 @@
#ifndef _RFB_CRYPTO_H
#define _RFB_CRYPTO_H 1
#include <stdint.h>
#include "config.h"
#define SHA1_HASH_SIZE 20
#define MD5_HASH_SIZE 16
/* Generates an MD5 hash of 'in' and writes it to 'out', which must be 16 bytes in size. */
int hash_md5(void *out, const void *in, const size_t in_len);
/* Generates an SHA1 hash of 'in' and writes it to 'out', which must be 20 bytes in size. */
int hash_sha1(void *out, const void *in, const size_t in_len);
/* Fill 'out' with 'len' random bytes. */
void random_bytes(void *out, size_t len);
/*
Takes the 8-byte key in 'key', reverses the bits in each byte of key as required by the RFB protocol,
encrypts 'in' with the resulting key using single-key 56-bit DES and writes the result to 'out'.
*/
int encrypt_rfbdes(void *out, int *out_len, const unsigned char key[8], const void *in, const size_t in_len);
/*
Takes the 8-byte key in 'key', reverses the bits in each byte of key as required by the RFB protocol,
decrypts 'in' with the resulting key using single-key 56-bit DES and writes the result to 'out'.
*/
int decrypt_rfbdes(void *out, int *out_len, const unsigned char key[8], const void *in, const size_t in_len);
/* Encrypts 'in' with the the 16-byte key in 'key' using AES-128-ECB and writes the result to 'out'. */
int encrypt_aes128ecb(void *out, int *out_len, const unsigned char key[16], const void *in, const size_t in_len);
/*
Generates a Diffie-Hellman public-private keypair using the generator value 'gen' and prime modulo
'prime', writing the result to 'pub_out' and 'priv_out', which must be 'keylen' in size.
*/
int dh_generate_keypair(uint8_t *priv_out, uint8_t *pub_out, const uint8_t *gen, const size_t gen_len, const uint8_t *prime, const size_t keylen);
/*
Computes the shared Diffie-Hellman secret using the private key 'priv', the other side's public
key 'pub' and the modulo prime 'prime' and writes it to 'shared_out', which must be 'keylen' in size.
*/
int dh_compute_shared_key(uint8_t *shared_out, const uint8_t *priv, const uint8_t *pub, const uint8_t *prime, const size_t keylen);
#endif

View File

@ -0,0 +1,24 @@
#include "wlr-data-control-unstable-v1.h"
#include "seat.h"
typedef void (*vnc_write_clipboard_t)(char* text, size_t size);
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;
vnc_write_clipboard_t vnc_write_clipboard;
};
void data_control_init(struct data_control* self, struct seat* seat, struct zwlr_data_control_manager_v1 *manager);
void data_control_to_clipboard(struct data_control* self, const char* text, size_t len);
void data_control_destroy(struct data_control* self);
#pragma once

1638
include/keysym.h 100644

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
/*
* 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
#define OPEN_H264_MAX_CONTEXTS 64
#include "rfbclient.h"
#include <stdbool.h>
struct AVFrame;
struct open_h264;
struct open_h264_context;
struct open_h264* open_h264_create(rfbClient* client);
void open_h264_destroy(struct open_h264*);
struct AVFrame* open_h264_decode_rect(struct open_h264* self,
rfbFramebufferUpdateRectHeader* message);

36
include/output.h 100644
View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2019 - 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 <wayland-client.h>
#include <stdint.h>
struct output {
struct wl_output* wl_output;
struct wl_list link;
uint32_t id;
int32_t scale;
};
struct output* output_new(struct wl_output* wl_output, uint32_t id);
void output_destroy(struct output* output);
void output_list_destroy(struct wl_list* list);
struct output* output_find_by_id(struct wl_list* list, uint32_t id);
int32_t output_list_get_max_scale(const struct wl_list* list);

25
include/pixels.h 100644
View File

@ -0,0 +1,25 @@
/*
* 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 <wayland-client.h>
#include <pixman.h>
#include <stdbool.h>
enum wl_shm_format drm_format_to_wl_shm(uint32_t in);
uint32_t drm_format_from_wl_shm(enum wl_shm_format in);
bool drm_format_to_pixman_fmt(pixman_format_code_t* dst, uint32_t src);

24
include/region.h 100644
View File

@ -0,0 +1,24 @@
/*
* 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
struct pixman_region16;
void region_scale(struct pixman_region16* dst, struct pixman_region16* src,
double scale);
void region_translate(struct pixman_region16* dst, struct pixman_region16* src,
int x, int y);

View File

@ -0,0 +1,14 @@
#pragma once
struct buffer;
struct image;
struct vnc_av_frame;
int egl_init(void);
void egl_finish(void);
void render_image_egl(struct buffer* dst, const struct image* src, double scale,
int pos_x, int pos_y);
void render_av_frames_egl(struct buffer* dst, struct vnc_av_frame** src,
int n_av_frames, double scale, int x_pos, int y_pos);

31
include/renderer.h 100644
View File

@ -0,0 +1,31 @@
/*
* 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 <pixman.h>
struct buffer;
struct image {
int width, height, stride;
uint32_t format;
void* pixels;
struct pixman_region16* damage;
};
void render_image(struct buffer* dst, const struct image* src, double scale,
int pos_x, int pos_y);

791
include/rfbclient.h 100644
View File

@ -0,0 +1,791 @@
#ifndef RFBCLIENT_H
#define RFBCLIENT_H
/**
* @defgroup libvncclient_api LibVNCClient API Reference
* @{
*/
/*
* Copyright (C) 2017 D. R. Commander. All Rights Reserved.
* Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved.
* Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/**
* @file rfbclient.h
*/
#include "rfbproto.h"
#include "keysym.h"
#include "threading.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#ifdef LIBVNCSERVER_HAVE_SASL
#include <sasl/sasl.h>
#endif /* LIBVNCSERVER_HAVE_SASL */
#define rfbClientSwap16IfLE(s) \
(*(char *)&client->endianTest ? ((((s) & 0xff) << 8) | (((s) >> 8) & 0xff)) : (s))
#define rfbClientSwap32IfLE(l) \
(*(char *)&client->endianTest ? ((((l) >> 24) & 0x000000ff) | \
(((l) & 0x00ff0000) >> 8) | \
(((l) & 0x0000ff00) << 8) | \
(((l) & 0x000000ff) << 24)) : (l))
#define rfbClientSwap64IfLE(l) \
(*(char *)&client->endianTest ? ((((l) >> 56 ) & 0x00000000000000ffULL) | \
(((l) & 0x00ff000000000000ULL) >> 40) | \
(((l) & 0x0000ff0000000000ULL) >> 24) | \
(((l) & 0x000000ff00000000ULL) >> 8) | \
(((l) & 0x00000000ff000000ULL) << 8) | \
(((l) & 0x0000000000ff0000ULL) << 24) | \
(((l) & 0x000000000000ff00ULL) << 40) | \
(((l) & 0x00000000000000ffULL) << 56)) : (l))
#define FLASH_PORT_OFFSET 5400
#define LISTEN_PORT_OFFSET 5500
#define TUNNEL_PORT_OFFSET 5500
#define SERVER_PORT_OFFSET 5900
#define DEFAULT_CONNECT_TIMEOUT 60
#define DEFAULT_READ_TIMEOUT 0
#define DEFAULT_SSH_CMD "/usr/bin/ssh"
#define DEFAULT_TUNNEL_CMD \
(DEFAULT_SSH_CMD " -f -L %L:localhost:%R %H sleep 20")
#define DEFAULT_VIA_CMD \
(DEFAULT_SSH_CMD " -f -L %L:%H:%R %G sleep 20")
#if(defined __cplusplus)
extern "C"
{
#endif
/** vncrec */
typedef struct {
FILE* file;
struct timeval tv;
rfbBool readTimestamp;
rfbBool doNotSleep;
} rfbVNCRec;
/** client data */
typedef struct rfbClientData {
void* tag;
void* data;
struct rfbClientData* next;
} rfbClientData;
/** app data (belongs into rfbClient?) */
typedef struct {
rfbBool shareDesktop;
rfbBool viewOnly;
const char* encodingsString;
rfbBool useBGR233;
int nColours;
rfbBool forceOwnCmap;
rfbBool forceTrueColour;
int requestedDepth;
int compressLevel;
int qualityLevel;
rfbBool enableJPEG;
rfbBool useRemoteCursor;
rfbBool palmVNC; /**< use palmvnc specific SetScale (vs ultravnc) */
int scaleSetting; /**< 0 means no scale set, else 1/scaleSetting */
} AppData;
/** For GetCredentialProc callback function to return */
typedef union _rfbCredential
{
/** X509 (VeNCrypt) */
struct
{
char *x509CACertFile;
char *x509CACrlFile;
char *x509ClientCertFile;
char *x509ClientKeyFile;
uint8_t x509CrlVerifyMode; /* Only required for OpenSSL - see meanings below */
} x509Credential;
/** Plain (VeNCrypt), MSLogon (UltraVNC) */
struct
{
char *username;
char *password;
} userCredential;
} rfbCredential;
#define rfbCredentialTypeX509 1
#define rfbCredentialTypeUser 2
/* When using OpenSSL, CRLs can be included in both the x509CACrlFile and appended
to the x509CACertFile as is common with OpenSSL. When rfbX509CrlVerifyAll is
specified the CRL list must include CRLs for all certificates in the chain */
#define rfbX509CrlVerifyNone 0 /* No CRL checking is performed */
#define rfbX509CrlVerifyClient 1 /* Only the leaf server certificate is checked */
#define rfbX509CrlVerifyAll 2 /* All certificates in the server chain are checked */
struct _rfbClient;
/**
* Handles a text chat message. If your application should accept text messages
* from the server, define a function with this prototype and set
* client->HandleTextChat to a pointer to that function subsequent to your
* rfbGetClient() call.
* @param client The client which called the text chat handler
* @param value text length if text != NULL, or one of rfbTextChatOpen,
* rfbTextChatClose, rfbTextChatFinished if text == NULL
* @param text The text message from the server
*/
typedef void (*HandleTextChatProc)(struct _rfbClient* client, int value, char *text);
/**
* Handles XVP server messages. If your application sends XVP messages to the
* server, you'll want to handle the server's XVP_FAIL and XVP_INIT responses.
* Define a function with this prototype and set client->HandleXvpMsg to a
* pointer to that function subsequent to your rfbGetClient() call.
* @param client The client which called the XVP message handler
* @param version The highest XVP extension version that the server supports
* @param opcode The opcode. 0 is XVP_FAIL, 1 is XVP_INIT
*/
typedef void (*HandleXvpMsgProc)(struct _rfbClient* client, uint8_t version, uint8_t opcode);
typedef void (*HandleKeyboardLedStateProc)(struct _rfbClient* client, int value, int pad);
typedef rfbBool (*HandleCursorPosProc)(struct _rfbClient* client, int x, int y);
typedef void (*SoftCursorLockAreaProc)(struct _rfbClient* client, int x, int y, int w, int h);
typedef void (*SoftCursorUnlockScreenProc)(struct _rfbClient* client);
/**
Callback indicating that a rectangular area of the client's framebuffer was updated.
As a server will usually send several rects per rfbFramebufferUpdate message, this
callback is usually called multiple times per rfbFramebufferUpdate message.
@param client The client whose framebuffer was (partially) updated
@param x The x-coordinate of the upper left corner of the updated rectangle
@param y The y-coordinate of the upper left corner of the updated rectangle
@param w The width of the updated rectangle
@param h The heigth of the updated rectangle
*/
typedef void (*GotFrameBufferUpdateProc)(struct _rfbClient* client, int x, int y, int w, int h);
/**
Callback indicating that a client has completely processed an rfbFramebufferUpdate
message sent by a server.
This is called exactly once per each handled rfbFramebufferUpdate message.
@param client The client which finished processing an rfbFramebufferUpdate
*/
typedef void (*FinishedFrameBufferUpdateProc)(struct _rfbClient* client);
typedef void (*StartingFrameBufferUpdateProc)(struct _rfbClient* client);
typedef void (*CancelledFrameBufferUpdateProc)(struct _rfbClient* client);
typedef char* (*GetPasswordProc)(struct _rfbClient* client);
typedef rfbCredential* (*GetCredentialProc)(struct _rfbClient* client, int credentialType);
typedef rfbBool (*MallocFrameBufferProc)(struct _rfbClient* client);
typedef void (*GotXCutTextProc)(struct _rfbClient* client, const char *text, int textlen);
typedef void (*BellProc)(struct _rfbClient* client);
/**
Called when a cursor shape update was received from the server. The decoded cursor shape
will be in client->rcSource. It's up to the application to do something with this, e.g. draw
into a viewer's window. If you want the server to draw the cursor into the framebuffer, be
careful not to announce remote cursor support, i.e. not include rfbEncodingXCursor or
rfbEncodingRichCursor in SetFormatAndEncodings().
*/
typedef void (*GotCursorShapeProc)(struct _rfbClient* client, int xhot, int yhot, int width, int height, int bytesPerPixel);
typedef void (*GotCopyRectProc)(struct _rfbClient* client, int src_x, int src_y, int w, int h, int dest_x, int dest_y);
typedef void (*GotFillRectProc)(struct _rfbClient* client, int x, int y, int w, int h, uint32_t colour);
typedef void (*GotBitmapProc)(struct _rfbClient* client, const uint8_t* buffer, int x, int y, int w, int h);
typedef rfbBool (*GotJpegProc)(struct _rfbClient* client, const uint8_t* buffer, int length, int x, int y, int w, int h);
typedef rfbBool (*LockWriteToTLSProc)(struct _rfbClient* client); /** @deprecated */
typedef rfbBool (*UnlockWriteToTLSProc)(struct _rfbClient* client); /** @deprecated */
#ifdef LIBVNCSERVER_HAVE_SASL
typedef char* (*GetUserProc)(struct _rfbClient* client);
typedef char* (*GetSASLMechanismProc)(struct _rfbClient* client, char* mechlist);
#endif /* LIBVNCSERVER_HAVE_SASL */
typedef struct _rfbClient {
uint8_t* frameBuffer;
int width, height;
int endianTest;
AppData appData;
const char* programName;
char* serverHost;
int serverPort;
rfbBool listenSpecified;
int listenPort, flashPort;
struct {
int x, y, w, h;
} updateRect;
/** Note that the CoRRE encoding uses this buffer and assumes it is big enough
to hold 255 * 255 * 32 bits -> 260100 bytes. 640*480 = 307200 bytes.
Hextile also assumes it is big enough to hold 16 * 16 * 32 bits.
Tight encoding assumes BUFFER_SIZE is at least 16384 bytes. */
#define RFB_BUFFER_SIZE (640*480)
char buffer[RFB_BUFFER_SIZE];
/* rfbproto.c */
rfbSocket sock;
rfbBool canUseCoRRE;
rfbBool canUseHextile;
char *desktopName;
rfbPixelFormat format;
rfbServerInitMsg si;
/* sockets.c */
#define RFB_BUF_SIZE 8192
char buf[RFB_BUF_SIZE];
char *bufoutptr;
unsigned int buffered;
/* The zlib encoding requires expansion/decompression/deflation of the
compressed data in the "buffer" above into another, result buffer.
However, the size of the result buffer can be determined precisely
based on the bitsPerPixel, height and width of the rectangle. We
allocate this buffer one time to be the full size of the buffer. */
/* Ultra Encoding uses this buffer too */
int ultra_buffer_size;
char *ultra_buffer;
int raw_buffer_size;
char *raw_buffer;
#ifdef LIBVNCSERVER_HAVE_LIBZ
z_stream decompStream;
rfbBool decompStreamInited;
#endif
#ifdef LIBVNCSERVER_HAVE_LIBZ
/*
* Variables for the ``tight'' encoding implementation.
*/
/** Separate buffer for compressed data. */
#define ZLIB_BUFFER_SIZE 30000
char zlib_buffer[ZLIB_BUFFER_SIZE];
/* Four independent compression streams for zlib library. */
z_stream zlibStream[4];
rfbBool zlibStreamActive[4];
/* Filter stuff. Should be initialized by filter initialization code. */
rfbBool cutZeros;
int rectWidth, rectColors;
char tightPalette[256*4];
uint8_t tightPrevRow[2048*3*sizeof(uint16_t)];
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
/** JPEG decoder state (obsolete-- do not use). */
rfbBool jpegError;
struct jpeg_source_mgr* jpegSrcManager;
void* jpegBufferPtr;
size_t jpegBufferLen;
#endif
#endif
/* cursor.c */
/** Holds cursor shape data when received from server. */
uint8_t *rcSource, *rcMask;
/** private data pointer */
rfbClientData* clientData;
rfbVNCRec* vncRec;
/* Keyboard State support (is 'Caps Lock' set on the remote display???) */
int KeyboardLedStateEnabled;
int CurrentKeyboardLedState;
int canHandleNewFBSize;
/* hooks */
HandleTextChatProc HandleTextChat;
HandleKeyboardLedStateProc HandleKeyboardLedState;
HandleCursorPosProc HandleCursorPos;
SoftCursorLockAreaProc SoftCursorLockArea;
SoftCursorUnlockScreenProc SoftCursorUnlockScreen;
GotFrameBufferUpdateProc GotFrameBufferUpdate;
/** the pointer returned by GetPassword will be freed after use! */
GetPasswordProc GetPassword;
MallocFrameBufferProc MallocFrameBuffer;
GotXCutTextProc GotXCutText;
BellProc Bell;
GotCursorShapeProc GotCursorShape;
GotCopyRectProc GotCopyRect;
/** Which messages are supported by the server
* This is a *guess* for most servers.
* (If we can even detect the type of server)
*
* If the server supports the "rfbEncodingSupportedMessages"
* then this will be updated when the encoding is received to
* accurately reflect the servers capabilities.
*/
rfbSupportedMessages supportedMessages;
/** negotiated protocol version */
int major, minor;
/** The selected security types */
uint32_t authScheme, subAuthScheme;
/** The TLS session for Anonymous TLS and VeNCrypt */
void* tlsSession;
/** To support security types that requires user input (except VNC password
* authentication), for example VeNCrypt and MSLogon, this callback function
* must be set before the authentication. Otherwise, it implicates that the
* caller application does not support it and related security types should
* be bypassed.
*/
GetCredentialProc GetCredential;
/** The 0-terminated security types supported by the client.
* Set by function SetClientAuthSchemes() */
uint32_t *clientAuthSchemes;
/** When the server is a repeater, this specifies the final destination */
char *destHost;
int destPort;
/** the QoS IP DSCP for this client */
int QoS_DSCP;
/** hook to handle xvp server messages */
HandleXvpMsgProc HandleXvpMsg;
/* listen.c */
rfbSocket listenSock;
FinishedFrameBufferUpdateProc FinishedFrameBufferUpdate;
char *listenAddress;
/* IPv6 listen socket, address and port*/
rfbSocket listen6Sock;
char* listen6Address;
int listen6Port;
/* Output Window ID. When set, client application enables libvncclient to perform direct rendering in its window */
unsigned long outputWindow;
/**
* These lock/unlock hooks are not used anymore. LibVNCClient will now use
* platform-specific synchronization library to protect concurrent TLS R/W.
*
* @deprecated
*/
LockWriteToTLSProc LockWriteToTLS;
UnlockWriteToTLSProc UnlockWriteToTLS;
/** Hooks for custom rendering
*
* VNC rendering boils down to 3 activities:
* - GotCopyRect: copy an area of the framebuffer
* - GotFillRect: fill an area of the framebuffer with a solid color
* - GotBitmap: copy the bitmap in the buffer into the framebuffer
* The client application should either set all three of these or none!
*/
GotFillRectProc GotFillRect;
GotBitmapProc GotBitmap;
/** Hook for custom JPEG decoding and rendering */
GotJpegProc GotJpeg;
#ifdef LIBVNCSERVER_HAVE_SASL
sasl_conn_t *saslconn;
const char *saslDecoded;
unsigned int saslDecodedLength;
unsigned int saslDecodedOffset;
sasl_secret_t *saslSecret;
/* Callback to allow the client to choose a preferred mechanism. The string returned will
be freed once no longer required. */
GetSASLMechanismProc GetSASLMechanism;
GetUserProc GetUser;
#endif /* LIBVNCSERVER_HAVE_SASL */
#ifdef LIBVNCSERVER_HAVE_LIBZ
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
/** JPEG decoder state. */
void *tjhnd;
#endif
#endif
/* timeout in seconds for select() after connect() */
unsigned int connectTimeout;
/* timeout in seconds when reading from half-open connections in
* ReadFromRFBServer() - keep at 0 to disable timeout detection and handling */
unsigned int readTimeout;
/**
* Mutex to protect concurrent TLS read/write.
* For internal use only.
*/
MUTEX(tlsRwMutex);
rfbBool requestedResize;
/**
* Used for intended dimensions, rfbClient.width and rfbClient.height are used to manage the real framebuffer dimensions.
*/
rfbExtDesktopScreen screen;
StartingFrameBufferUpdateProc StartingFrameBufferUpdate;
CancelledFrameBufferUpdateProc CancelledFrameBufferUpdate;
} rfbClient;
/* cursor.c */
/**
* Handles XCursor and RichCursor shape updates from the server.
* We emulate cursor operating on the frame buffer (that is
* why we call it "software cursor"). This decodes the received cursor
* shape and hands it over to GotCursorShapeProc, if set.
*/
extern rfbBool HandleCursorShape(rfbClient* client,int xhot, int yhot, int width, int height, uint32_t enc);
/* listen.c */
extern void listenForIncomingConnections(rfbClient* viewer);
extern int listenForIncomingConnectionsNoFork(rfbClient* viewer, int usec_timeout);
/* rfbproto.c */
extern rfbBool rfbEnableClientLogging;
typedef void (*rfbClientLogProc)(const char *format, ...);
extern rfbClientLogProc rfbClientLog,rfbClientErr;
extern rfbBool ConnectToRFBServer(rfbClient* client,const char *hostname, int port);
extern rfbBool ConnectToRFBRepeater(rfbClient* client,const char *repeaterHost, int repeaterPort, const char *destHost, int destPort);
extern void SetClientAuthSchemes(rfbClient* client,const uint32_t *authSchemes, int size);
extern rfbBool InitialiseRFBConnection(rfbClient* client);
/**
* Sends format and encoding parameters to the server. Your application can
* modify the 'client' data structure directly. However some changes to this
* structure must be communicated back to the server. For instance, if you
* change the encoding to hextile, the server needs to know that it should send
* framebuffer updates in hextile format. Likewise if you change the pixel
* format of the framebuffer, the server must be notified about this as well.
* Call this function to propagate your changes of the local 'client' structure
* over to the server.
* @li Encoding type
* @li RFB protocol extensions announced via pseudo-encodings
* @li Framebuffer pixel format (like RGB vs ARGB)
* @li Remote cursor support
* @param client The client in which the format or encodings have been changed
* @return true if the format or encodings were sent to the server successfully,
* false otherwise
*/
extern rfbBool SetFormatAndEncodings(rfbClient* client);
extern rfbBool SendIncrementalFramebufferUpdateRequest(rfbClient* client);
/**
* Sends a framebuffer update request to the server. A VNC client may request an
* update from the server at any time. You can also specify which portions of
* the screen you want updated. This can be handy if a pointer is at certain
* location and the user pressed a mouse button, for instance. Then you can
* immediately request an update of the region around the pointer from the
* server.
* @note The coordinate system is a left-handed Cartesian coordinate system with
* the Z axis (unused) pointing out of the screen. Alternately you can think of
* it as a right-handed Cartesian coordinate system with the Z axis pointing
* into the screen. The origin is at the upper left corner of the framebuffer.
* @param client The client through which to send the request
* @param x The horizontal position of the update request rectangle
* @param y The vertical position of the update request rectangle
* @param w The width of the update request rectangle
* @param h The height of the update request rectangle
* @param incremental false: server sends rectangle even if nothing changed.
* true: server only sends changed parts of rectangle.
* @return true if the update request was sent successfully, false otherwise
*/
extern rfbBool SendFramebufferUpdateRequest(rfbClient* client,
int x, int y, int w, int h,
rfbBool incremental);
extern rfbBool SendScaleSetting(rfbClient* client,int scaleSetting);
/**
* Sends a pointer event to the server. A pointer event includes a cursor
* location and a button mask. The button mask indicates which buttons on the
* pointing device are pressed. Each button is represented by a bit in the
* button mask. A 1 indicates the button is pressed while a 0 indicates that it
* is not pressed. You may use these pre-defined button masks by ORing them
* together: rfbButton1Mask, rfbButton2Mask, rfbButton3Mask, rfbButton4Mask
* rfbButton5Mask
* @note The cursor location is relative to the client's framebuffer, not the
* client's screen itself.
* @note The coordinate system is a left-handed Cartesian coordinate system with
* the Z axis (unused) pointing out of the screen. Alternately you can think of
* it as a right-handed Cartesian coordinate system with the Z axis pointing
* into the screen. The origin is at the upper left corner of the screen.
* @param client The client through which to send the pointer event
* @param x the horizontal location of the cursor
* @param y the vertical location of the cursor
* @param buttonMask the button mask indicating which buttons are pressed
* @return true if the pointer event was sent successfully, false otherwise
*/
extern rfbBool SendPointerEvent(rfbClient* client,int x, int y, int buttonMask);
/**
* Sends a SetDesktopSize event to the server.
* @param client The client through which to send the SetDesktopSize event
* @param width The width of the update request rectangle
* @param height The height of the update request rectangle
* @return true if the SetDesktopSize event was send successfully, false otherwise
*/
extern rfbBool SendExtDesktopSize(rfbClient* client, uint16_t width, uint16_t height);
/**
* Sends a key event to the server. If your application is not merely a VNC
* viewer (i.e. it controls the server), you'll want to send the keys that the
* user presses to the server. Use this function to do that.
* @param client The client through which to send the key event
* @param key An rfbKeySym defined in keysym.h
* @param down true if this was a key down event, false otherwise
* @return true if the key event was send successfully, false otherwise
*/
extern rfbBool SendKeyEvent(rfbClient* client,uint32_t key, rfbBool down);
/**
* The same as SendKeyEvent, except a key code will be sent along with the
* symbol if the server supports extended key events.
* @param client The client through which to send the key event
* @param keysym An rfbKeySym defined in keysym.h
* @param keycode An XT key code
* @param down true if this was a key down event, false otherwise
* @return true if the extended key event is supported and was sent
* successfully, false otherwise
*/
extern rfbBool SendExtendedKeyEvent(rfbClient* client, uint32_t keysym, uint32_t keycode, rfbBool down);
/**
* Places a string on the server's clipboard. Use this function if you want to
* be able to copy and paste between the server and your application. For
* instance, when your application is notified that the user copied some text
* onto the clipboard, you would call this function to synchronize the server's
* clipboard with your local clipboard.
* @param client The client structure through which to send the client cut text
* message
* @param str The string to send (doesn't need to be NULL terminated)
* @param len The length of the string
* @return true if the client cut message was sent successfully, false otherwise
*/
extern rfbBool SendClientCutText(rfbClient* client,char *str, int len);
/**
* Handles messages from the RFB server. You must call this function
* intermittently so LibVNCClient can parse messages from the server. For
* example, if your app has a draw loop, you could place a call to this
* function within that draw loop.
* @note You must call WaitForMessage() before you call this function.
* @param client The client which will handle the RFB server messages
* @return true if the client was able to handle the RFB server messages, false
* otherwise
*/
extern rfbBool HandleRFBServerMessage(rfbClient* client);
extern rfbBool ReadToBuffer(rfbClient* client);
/**
* Sends a text chat message to the server.
* @param client The client through which to send the message
* @param text The text to send
* @return true if the text was sent successfully, false otherwise
*/
extern rfbBool TextChatSend(rfbClient* client, char *text);
/**
* Opens a text chat window on the server.
* @param client The client through which to send the message
* @return true if the window was opened successfully, false otherwise
*/
extern rfbBool TextChatOpen(rfbClient* client);
/**
* Closes the text chat window on the server.
* @param client The client through which to send the message
* @return true if the window was closed successfully, false otherwise
*/
extern rfbBool TextChatClose(rfbClient* client);
extern rfbBool TextChatFinish(rfbClient* client);
extern rfbBool PermitServerInput(rfbClient* client, int enabled);
extern rfbBool SendXvpMsg(rfbClient* client, uint8_t version, uint8_t code);
extern void PrintPixelFormat(rfbPixelFormat *format);
extern rfbBool SupportsClient2Server(rfbClient* client, int messageType);
extern rfbBool SupportsServer2Client(rfbClient* client, int messageType);
/* client data */
/**
* Associates a client data tag with the given pointer. LibVNCClient has
* several events to which you can associate your own handlers. These handlers
* have the client structure as one of their parameters. Sometimes, you may want
* to make data from elsewhere in your application available to these handlers
* without using a global variable. To do this, you call
* rfbClientSetClientData() and associate the data with a tag. Then, your
* handler can call rfbClientGetClientData() and get the a pointer to the data
* associated with that tag.
* @param client The client in which to set the client data
* @param tag A unique tag which identifies the data
* @param data A pointer to the data to associate with the tag
*/
void rfbClientSetClientData(rfbClient* client, void* tag, void* data);
/**
* Returns a pointer to the client data associated with the given tag. See the
* the documentation for rfbClientSetClientData() for a discussion of how you
* can use client data.
* @param client The client from which to get the client data
* @param tag The tag which identifies the client data
* @return a pointer to the client data
*/
void* rfbClientGetClientData(rfbClient* client, void* tag);
/* protocol extensions */
typedef struct _rfbClientProtocolExtension {
int* encodings;
/** returns TRUE if the encoding was handled */
rfbBool (*handleEncoding)(rfbClient* cl,
rfbFramebufferUpdateRectHeader* rect);
/** returns TRUE if it handled the message */
rfbBool (*handleMessage)(rfbClient* cl,
rfbServerToClientMsg* message);
struct _rfbClientProtocolExtension* next;
uint32_t const* securityTypes;
/** returns TRUE if it handled the authentication */
rfbBool (*handleAuthentication)(rfbClient* cl, uint32_t authScheme);
} rfbClientProtocolExtension;
void rfbClientRegisterExtension(rfbClientProtocolExtension* e);
/* sockets.c */
extern rfbBool errorMessageOnReadFailure;
extern rfbBool ReadFromRFBServer(rfbClient* client, char *out, unsigned int n);
extern rfbBool WriteToRFBServer(rfbClient* client, const char *buf, unsigned int n);
/**
Tries to connect to an IPv4 host.
@param host Binary IPv4 address
@param port Port
@return A blocking socket or RFB_INVALID_SOCKET if the connection failed
*/
extern rfbSocket ConnectClientToTcpAddr(unsigned int host, int port);
/**
Tries to connect to an IPv4 or IPv6 host.
@param hostname A hostname or IP address
@param port Port
@return A blocking socket or RFB_INVALID_SOCKET if the connection failed
*/
extern rfbSocket ConnectClientToTcpAddr6(const char *hostname, int port);
/**
Tries to connect to a Unix socket.
@param sockFile Path of the socket file
@return A blocking socket or RFB_INVALID_SOCKET if the connection failed
*/
extern rfbSocket ConnectClientToUnixSock(const char *sockFile);
/**
Tries to connect to an IPv4 host using the given timeout value.
@param host Binary IPv4 address
@param port Port
@param timeout The time in seconds to wait for a connection
@return A nonblocking socket or RFB_INVALID_SOCKET if the connection failed
*/
extern rfbSocket ConnectClientToTcpAddrWithTimeout(unsigned int host, int port, unsigned int timeout);
/**
Tries to connect to an IPv4 or IPv6 host using the given timeout value.
@param hostname A hostname or IP address
@param port Port
@param timeout The time in seconds to wait for a connection
@return A nonblocking socket or RFB_INVALID_SOCKET if the connection failed
*/
extern rfbSocket ConnectClientToTcpAddr6WithTimeout(const char *hostname, int port, unsigned int timeout);
/**
Tries to connect to a Unix socket using the given timeout value.
@param sockFile Path of the socket file
@param timeout The time in seconds to wait for a connection
@return A nonblocking socket or RFB_INVALID_SOCKET if the connection failed
*/
extern rfbSocket ConnectClientToUnixSockWithTimeout(const char *sockFile, unsigned int timeout);
extern rfbBool SetNonBlocking(rfbSocket sock);
extern rfbBool SetBlocking(rfbSocket sock);
extern rfbBool SetDSCP(rfbSocket sock, int dscp);
extern rfbBool SameMachine(rfbSocket sock);
/* vncviewer.c */
/**
* Allocates and returns a pointer to an rfbClient structure. This will probably
* be the first LibVNCClient function your client code calls. Most libVNCClient
* functions operate on an rfbClient structure, and this function allocates
* memory for that structure. When you're done with the rfbClient structure
* pointer this function returns, you should free the memory rfbGetClient()
* allocated by calling rfbClientCleanup().
*
* A pixel is one dot on the screen. The number of bytes in a pixel will depend
* on the number of samples in that pixel and the number of bits in each sample.
* A sample represents one of the primary colors in a color model. The RGB
* color model uses red, green, and blue samples respectively. Suppose you
* wanted to use 16-bit RGB color: You would have three samples per pixel (one
* for each primary color), five bits per sample (the quotient of 16 RGB bits
* divided by three samples), and two bytes per pixel (the smallest multiple of
* eight bits in which the 16-bit pixel will fit). If you wanted 32-bit RGB
* color, you would have three samples per pixel again, eight bits per sample
* (since that's how 32-bit color is defined), and four bytes per pixel (the
* smallest multiple of eight bits in which the 32-bit pixel will fit.
* @param bitsPerSample The number of bits in a sample
* @param samplesPerPixel The number of samples in a pixel
* @param bytesPerPixel The number of bytes in a pixel
* @return a pointer to the allocated rfbClient structure
*/
rfbClient* rfbGetClient(int bitsPerSample,int samplesPerPixel,int bytesPerPixel);
/**
* Cleans up the client structure and releases the memory allocated for it. You
* should call this when you're done with the rfbClient structure that you
* allocated with rfbGetClient().
* @note rfbClientCleanup() does not touch client->frameBuffer.
* @param client The client to clean up
*/
void rfbClientCleanup(rfbClient* client);
#if(defined __cplusplus)
}
#endif
/**
* @}
*/
/**
@page libvncclient_doc LibVNCClient Documentation
@section example_code Example Code
See SDLvncviewer.c for a rather complete client example.
*/
#endif

1524
include/rfbproto.h 100644

File diff suppressed because it is too large Load Diff

39
include/sasl.h 100644
View File

@ -0,0 +1,39 @@
#ifndef RFBSASL_H
#define RFBSASL_H
/*
* Copyright (C) 2017 S. Waterman. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifdef LIBVNCSERVER_HAVE_SASL
#include "rfbclient.h"
/*
* Perform the SASL authentication process
*/
rfbBool HandleSASLAuth(rfbClient *client);
/*
* Read from SASL when the SASL SSF is in use.
*/
int ReadFromSASL(rfbClient* client, char *out, unsigned int n);
#endif /* LIBVNCSERVER_HAVE_SASL */
#endif /* RFBSASL_H */

32
include/sockets.h 100644
View File

@ -0,0 +1,32 @@
/*
* LibVNCServer/LibVNCClient common platform socket defines and includes.
*
* Copyright (C) 2020 Christian Beier <dontmind@freeshell.org>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifndef _RFB_COMMON_SOCKETS_H
#define _RFB_COMMON_SOCKETS_H
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <netdb.h>
#endif /* _RFB_COMMON_SOCKETS_H */

View File

@ -0,0 +1,76 @@
/*
* LibVNCServer/LibVNCClient common platform threading defines and includes.
*
* Copyright (C) 2020 Christian Beier <dontmind@freeshell.org>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifndef _RFB_COMMON_THREADING_H
#define _RFB_COMMON_THREADING_H
#include "config.h"
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
#include <pthread.h>
#if 0 /* debugging */
#define LOCK(mutex) (rfbLog("%s:%d LOCK(%s,0x%x)\n",__FILE__,__LINE__,#mutex,&(mutex)), pthread_mutex_lock(&(mutex)))
#define UNLOCK(mutex) (rfbLog("%s:%d UNLOCK(%s,0x%x)\n",__FILE__,__LINE__,#mutex,&(mutex)), pthread_mutex_unlock(&(mutex)))
#define MUTEX(mutex) pthread_mutex_t (mutex)
#define INIT_MUTEX(mutex) (rfbLog("%s:%d INIT_MUTEX(%s,0x%x)\n",__FILE__,__LINE__,#mutex,&(mutex)), pthread_mutex_init(&(mutex),NULL))
#define TINI_MUTEX(mutex) (rfbLog("%s:%d TINI_MUTEX(%s)\n",__FILE__,__LINE__,#mutex), pthread_mutex_destroy(&(mutex)))
#define TSIGNAL(cond) (rfbLog("%s:%d TSIGNAL(%s)\n",__FILE__,__LINE__,#cond), pthread_cond_signal(&(cond)))
#define WAIT(cond,mutex) (rfbLog("%s:%d WAIT(%s,%s)\n",__FILE__,__LINE__,#cond,#mutex), pthread_cond_wait(&(cond),&(mutex)))
#define COND(cond) pthread_cond_t (cond)
#define INIT_COND(cond) (rfbLog("%s:%d INIT_COND(%s)\n",__FILE__,__LINE__,#cond), pthread_cond_init(&(cond),NULL))
#define TINI_COND(cond) (rfbLog("%s:%d TINI_COND(%s)\n",__FILE__,__LINE__,#cond), pthread_cond_destroy(&(cond)))
#define IF_PTHREADS(x) x
#else
#if !NONETWORK
#define LOCK(mutex) pthread_mutex_lock(&(mutex))
#define UNLOCK(mutex) pthread_mutex_unlock(&(mutex))
#endif
#define MUTEX(mutex) pthread_mutex_t (mutex)
#define MUTEX_SIZE (sizeof(pthread_mutex_t))
#define INIT_MUTEX(mutex) pthread_mutex_init(&(mutex),NULL)
#define TINI_MUTEX(mutex) pthread_mutex_destroy(&(mutex))
#define TSIGNAL(cond) pthread_cond_signal(&(cond))
#define WAIT(cond,mutex) pthread_cond_wait(&(cond),&(mutex))
#define COND(cond) pthread_cond_t (cond)
#define INIT_COND(cond) pthread_cond_init(&(cond),NULL)
#define TINI_COND(cond) pthread_cond_destroy(&(cond))
#define IF_PTHREADS(x) x
#define THREAD_ROUTINE_RETURN_TYPE void*
#define THREAD_ROUTINE_RETURN_VALUE NULL
#define THREAD_SLEEP_MS(ms) usleep(ms*1000)
#define THREAD_JOIN(thread) pthread_join(thread, NULL)
#define CURRENT_THREAD_ID pthread_self()
#endif
#else
#define LOCK(mutex)
#define UNLOCK(mutex)
#define MUTEX(mutex)
#define INIT_MUTEX(mutex)
#define TINI_MUTEX(mutex)
#define TSIGNAL(cond)
#define WAIT(cond,mutex) this_is_unsupported
#define COND(cond)
#define INIT_COND(cond)
#define TINI_COND(cond)
#define IF_PTHREADS(x)
#endif
#endif /* _RFB_COMMON_THREADING_H */

View File

@ -0,0 +1,46 @@
/*
* 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 <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 timespec_to_us(&ts);
}
static inline uint64_t gettime_ms(void)
{
struct timespec ts = { 0 };
clock_gettime(CLOCK_MONOTONIC, &ts);
return timespec_to_ms(&ts);
}

56
include/tls.h 100644
View File

@ -0,0 +1,56 @@
#ifndef TLS_H
#define TLS_H
/*
* Copyright (C) 2009 Vic Lee.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/* Handle Anonymous TLS Authentication (18) with the server.
* After authentication, client->tlsSession will be set.
*/
rfbBool HandleAnonTLSAuth(rfbClient* client);
/* Handle VeNCrypt Authentication (19) with the server.
* The callback function GetX509Credential will be called.
* After authentication, client->tlsSession will be set.
*/
rfbBool HandleVeNCryptAuth(rfbClient* client);
/* Read desired bytes from TLS session.
* It's a wrapper function over gnutls_record_recv() and return values
* are same as read(), that is, >0 for actual bytes read, 0 for EOF,
* or EAGAIN, EINTR.
* This should be a non-blocking call. Blocking is handled in sockets.c.
*/
int ReadFromTLS(rfbClient* client, char *out, unsigned int n);
/* Write desired bytes to TLS session.
* It's a wrapper function over gnutls_record_send() and it will be
* blocking call, until all bytes are written or error returned.
*/
int WriteToTLS(rfbClient* client, const char *buf, unsigned int n);
/* Free TLS resources */
void FreeTLS(rfbClient* client);
#ifdef LIBVNCSERVER_HAVE_SASL
/* Get the number of bits in the current cipher */
int GetTLSCipherBits(rfbClient* client);
#endif /* LIBVNCSERVER_HAVE_SASL */
#endif /* TLS_H */

534
include/turbojpeg.h 100644
View File

@ -0,0 +1,534 @@
/*
* Copyright (C)2009-2012 D. R. Commander. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the libjpeg-turbo Project nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __TURBOJPEG_H__
#define __TURBOJPEG_H__
#if defined(_WIN32) && defined(DLLDEFINE)
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT
#endif
#define DLLCALL
#ifdef _MSC_VER
#pragma warning(disable:4996)
#endif
/**
* @addtogroup TurboJPEG Lite
* TurboJPEG API. This API provides an interface for generating and decoding
* JPEG images in memory.
*
* @{
*/
/**
* The number of chrominance subsampling options
*/
#define TJ_NUMSAMP 5
/**
* Chrominance subsampling options.
* When an image is converted from the RGB to the YCbCr colorspace as part of
* the JPEG compression process, some of the Cb and Cr (chrominance) components
* can be discarded or averaged together to produce a smaller image with little
* perceptible loss of image clarity (the human eye is more sensitive to small
* changes in brightness than small changes in color.) This is called
* "chrominance subsampling".
*/
enum TJSAMP
{
/**
* 4:4:4 chrominance subsampling (no chrominance subsampling). The JPEG or
* YUV image will contain one chrominance component for every pixel in the
* source image.
*/
TJSAMP_444=0,
/**
* 4:2:2 chrominance subsampling. The JPEG or YUV image will contain one
* chrominance component for every 2x1 block of pixels in the source image.
*/
TJSAMP_422,
/**
* 4:2:0 chrominance subsampling. The JPEG or YUV image will contain one
* chrominance component for every 2x2 block of pixels in the source image.
*/
TJSAMP_420,
/**
* Grayscale. The JPEG or YUV image will contain no chrominance components.
*/
TJSAMP_GRAY,
/**
* 4:4:0 chrominance subsampling. The JPEG or YUV image will contain one
* chrominance component for every 1x2 block of pixels in the source image.
*/
TJSAMP_440
};
/**
* MCU block width (in pixels) for a given level of chrominance subsampling.
* MCU block sizes:
* - 8x8 for no subsampling or grayscale
* - 16x8 for 4:2:2
* - 8x16 for 4:4:0
* - 16x16 for 4:2:0
*/
static const int tjMCUWidth[TJ_NUMSAMP] = {8, 16, 16, 8, 8};
/**
* MCU block height (in pixels) for a given level of chrominance subsampling.
* MCU block sizes:
* - 8x8 for no subsampling or grayscale
* - 16x8 for 4:2:2
* - 8x16 for 4:4:0
* - 16x16 for 4:2:0
*/
static const int tjMCUHeight[TJ_NUMSAMP] = {8, 8, 16, 8, 16};
/**
* The number of pixel formats
*/
#define TJ_NUMPF 11
/**
* Pixel formats
*/
enum TJPF
{
/**
* RGB pixel format. The red, green, and blue components in the image are
* stored in 3-byte pixels in the order R, G, B from lowest to highest byte
* address within each pixel.
*/
TJPF_RGB=0,
/**
* BGR pixel format. The red, green, and blue components in the image are
* stored in 3-byte pixels in the order B, G, R from lowest to highest byte
* address within each pixel.
*/
TJPF_BGR,
/**
* RGBX pixel format. The red, green, and blue components in the image are
* stored in 4-byte pixels in the order R, G, B from lowest to highest byte
* address within each pixel. The X component is ignored when compressing
* and undefined when decompressing.
*/
TJPF_RGBX,
/**
* BGRX pixel format. The red, green, and blue components in the image are
* stored in 4-byte pixels in the order B, G, R from lowest to highest byte
* address within each pixel. The X component is ignored when compressing
* and undefined when decompressing.
*/
TJPF_BGRX,
/**
* XBGR pixel format. The red, green, and blue components in the image are
* stored in 4-byte pixels in the order R, G, B from highest to lowest byte
* address within each pixel. The X component is ignored when compressing
* and undefined when decompressing.
*/
TJPF_XBGR,
/**
* XRGB pixel format. The red, green, and blue components in the image are
* stored in 4-byte pixels in the order B, G, R from highest to lowest byte
* address within each pixel. The X component is ignored when compressing
* and undefined when decompressing.
*/
TJPF_XRGB,
/**
* Grayscale pixel format. Each 1-byte pixel represents a luminance
* (brightness) level from 0 to 255.
*/
TJPF_GRAY,
/**
* RGBA pixel format. This is the same as @ref TJPF_RGBX, except that when
* decompressing, the X component is guaranteed to be 0xFF, which can be
* interpreted as an opaque alpha channel.
*/
TJPF_RGBA,
/**
* BGRA pixel format. This is the same as @ref TJPF_BGRX, except that when
* decompressing, the X component is guaranteed to be 0xFF, which can be
* interpreted as an opaque alpha channel.
*/
TJPF_BGRA,
/**
* ABGR pixel format. This is the same as @ref TJPF_XBGR, except that when
* decompressing, the X component is guaranteed to be 0xFF, which can be
* interpreted as an opaque alpha channel.
*/
TJPF_ABGR,
/**
* ARGB pixel format. This is the same as @ref TJPF_XRGB, except that when
* decompressing, the X component is guaranteed to be 0xFF, which can be
* interpreted as an opaque alpha channel.
*/
TJPF_ARGB
};
/**
* Red offset (in bytes) for a given pixel format. This specifies the number
* of bytes that the red component is offset from the start of the pixel. For
* instance, if a pixel of format TJ_BGRX is stored in <tt>char pixel[]</tt>,
* then the red component will be <tt>pixel[tjRedOffset[TJ_BGRX]]</tt>.
*/
static const int tjRedOffset[TJ_NUMPF] = {0, 2, 0, 2, 3, 1, 0, 0, 2, 3, 1};
/**
* Green offset (in bytes) for a given pixel format. This specifies the number
* of bytes that the green component is offset from the start of the pixel.
* For instance, if a pixel of format TJ_BGRX is stored in
* <tt>char pixel[]</tt>, then the green component will be
* <tt>pixel[tjGreenOffset[TJ_BGRX]]</tt>.
*/
static const int tjGreenOffset[TJ_NUMPF] = {1, 1, 1, 1, 2, 2, 0, 1, 1, 2, 2};
/**
* Blue offset (in bytes) for a given pixel format. This specifies the number
* of bytes that the Blue component is offset from the start of the pixel. For
* instance, if a pixel of format TJ_BGRX is stored in <tt>char pixel[]</tt>,
* then the blue component will be <tt>pixel[tjBlueOffset[TJ_BGRX]]</tt>.
*/
static const int tjBlueOffset[TJ_NUMPF] = {2, 0, 2, 0, 1, 3, 0, 2, 0, 1, 3};
/**
* Pixel size (in bytes) for a given pixel format.
*/
static const int tjPixelSize[TJ_NUMPF] = {3, 3, 4, 4, 4, 4, 1, 4, 4, 4, 4};
/**
* The uncompressed source/destination image is stored in bottom-up (Windows,
* OpenGL) order, not top-down (X11) order.
*/
#define TJFLAG_BOTTOMUP 2
/**
* Turn off CPU auto-detection and force TurboJPEG to use MMX code (IPP and
* 32-bit libjpeg-turbo versions only.)
*/
#define TJFLAG_FORCEMMX 8
/**
* Turn off CPU auto-detection and force TurboJPEG to use SSE code (32-bit IPP
* and 32-bit libjpeg-turbo versions only)
*/
#define TJFLAG_FORCESSE 16
/**
* Turn off CPU auto-detection and force TurboJPEG to use SSE2 code (32-bit IPP
* and 32-bit libjpeg-turbo versions only)
*/
#define TJFLAG_FORCESSE2 32
/**
* Turn off CPU auto-detection and force TurboJPEG to use SSE3 code (64-bit IPP
* version only)
*/
#define TJFLAG_FORCESSE3 128
/**
* Use fast, inaccurate chrominance upsampling routines in the JPEG
* decompressor (libjpeg and libjpeg-turbo versions only)
*/
#define TJFLAG_FASTUPSAMPLE 256
/**
* Scaling factor
*/
typedef struct
{
/**
* Numerator
*/
int num;
/**
* Denominator
*/
int denom;
} tjscalingfactor;
/**
* TurboJPEG instance handle
*/
typedef void* tjhandle;
/**
* Pad the given width to the nearest 32-bit boundary
*/
#define TJPAD(width) (((width)+3)&(~3))
/**
* Compute the scaled value of <tt>dimension</tt> using the given scaling
* factor. This macro performs the integer equivalent of <tt>ceil(dimension *
* scalingFactor)</tt>.
*/
#define TJSCALED(dimension, scalingFactor) ((dimension * scalingFactor.num \
+ scalingFactor.denom - 1) / scalingFactor.denom)
#ifdef __cplusplus
extern "C" {
#endif
/**
* Create a TurboJPEG compressor instance.
*
* @return a handle to the newly-created instance, or NULL if an error
* occurred (see #tjGetErrorStr().)
*/
DLLEXPORT tjhandle DLLCALL tjInitCompress(void);
/**
* Compress an RGB or grayscale image into a JPEG image.
*
* @param handle a handle to a TurboJPEG compressor or transformer instance
* @param srcBuf pointer to an image buffer containing RGB or grayscale pixels
* to be compressed
* @param width width (in pixels) of the source image
* @param pitch bytes per line of the source image. Normally, this should be
* <tt>width * #tjPixelSize[pixelFormat]</tt> if the image is unpadded,
* or <tt>#TJPAD(width * #tjPixelSize[pixelFormat])</tt> if each line of
* the image is padded to the nearest 32-bit boundary, as is the case
* for Windows bitmaps. You can also be clever and use this parameter
* to skip lines, etc. Setting this parameter to 0 is the equivalent of
* setting it to <tt>width * #tjPixelSize[pixelFormat]</tt>.
* @param height height (in pixels) of the source image
* @param pixelFormat pixel format of the source image (see @ref TJPF
* "Pixel formats".)
* @param jpegBuf address of a pointer to an image buffer that will receive the
* JPEG image. TurboJPEG has the ability to reallocate the JPEG buffer
* to accommodate the size of the JPEG image. Thus, you can choose to:
* -# pre-allocate the JPEG buffer with an arbitrary size using
* #tjAlloc() and let TurboJPEG grow the buffer as needed,
* -# set <tt>*jpegBuf</tt> to NULL to tell TurboJPEG to allocate the
* buffer for you, or
* -# pre-allocate the buffer to a "worst case" size determined by
* calling #tjBufSize(). This should ensure that the buffer never has
* to be re-allocated (setting #TJFLAG_NOREALLOC guarantees this.)
* .
* If you choose option 1, <tt>*jpegSize</tt> should be set to the
* size of your pre-allocated buffer. In any case, unless you have
* set #TJFLAG_NOREALLOC, you should always check <tt>*jpegBuf</tt> upon
* return from this function, as it may have changed.
* @param jpegSize pointer to an unsigned long variable that holds the size of
* the JPEG image buffer. If <tt>*jpegBuf</tt> points to a
* pre-allocated buffer, then <tt>*jpegSize</tt> should be set to the
* size of the buffer. Upon return, <tt>*jpegSize</tt> will contain the
* size of the JPEG image (in bytes.)
* @param jpegSubsamp the level of chrominance subsampling to be used when
* generating the JPEG image (see @ref TJSAMP
* "Chrominance subsampling options".)
* @param jpegQual the image quality of the generated JPEG image (1 = worst,
100 = best)
* @param flags the bitwise OR of one or more of the @ref TJFLAG_BOTTOMUP
* "flags".
*
* @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr().)
*/
DLLEXPORT int DLLCALL tjCompress2(tjhandle handle, unsigned char *srcBuf,
int width, int pitch, int height, int pixelFormat, unsigned char **jpegBuf,
unsigned long *jpegSize, int jpegSubsamp, int jpegQual, int flags);
/**
* The maximum size of the buffer (in bytes) required to hold a JPEG image with
* the given parameters. The number of bytes returned by this function is
* larger than the size of the uncompressed source image. The reason for this
* is that the JPEG format uses 16-bit coefficients, and it is thus possible
* for a very high-quality JPEG image with very high frequency content to
* expand rather than compress when converted to the JPEG format. Such images
* represent a very rare corner case, but since there is no way to predict the
* size of a JPEG image prior to compression, the corner case has to be
* handled.
*
* @param width width of the image (in pixels)
* @param height height of the image (in pixels)
* @param jpegSubsamp the level of chrominance subsampling to be used when
* generating the JPEG image (see @ref TJSAMP
* "Chrominance subsampling options".)
*
* @return the maximum size of the buffer (in bytes) required to hold the
* image, or -1 if the arguments are out of bounds.
*/
DLLEXPORT unsigned long DLLCALL tjBufSize(int width, int height,
int jpegSubsamp);
/**
* Create a TurboJPEG decompressor instance.
*
* @return a handle to the newly-created instance, or NULL if an error
* occurred (see #tjGetErrorStr().)
*/
DLLEXPORT tjhandle DLLCALL tjInitDecompress(void);
/**
* Retrieve information about a JPEG image without decompressing it.
*
* @param handle a handle to a TurboJPEG decompressor or transformer instance
* @param jpegBuf pointer to a buffer containing a JPEG image
* @param jpegSize size of the JPEG image (in bytes)
* @param width pointer to an integer variable that will receive the width (in
* pixels) of the JPEG image
* @param height pointer to an integer variable that will receive the height
* (in pixels) of the JPEG image
* @param jpegSubsamp pointer to an integer variable that will receive the
* level of chrominance subsampling used when compressing the JPEG image
* (see @ref TJSAMP "Chrominance subsampling options".)
*
* @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr().)
*/
DLLEXPORT int DLLCALL tjDecompressHeader2(tjhandle handle,
unsigned char *jpegBuf, unsigned long jpegSize, int *width, int *height,
int *jpegSubsamp);
/**
* Returns a list of fractional scaling factors that the JPEG decompressor in
* this implementation of TurboJPEG supports.
*
* @param numscalingfactors pointer to an integer variable that will receive
* the number of elements in the list
*
* @return a pointer to a list of fractional scaling factors, or NULL if an
* error is encountered (see #tjGetErrorStr().)
*/
DLLEXPORT tjscalingfactor* DLLCALL tjGetScalingFactors(int *numscalingfactors);
/**
* Decompress a JPEG image to an RGB or grayscale image.
*
* @param handle a handle to a TurboJPEG decompressor or transformer instance
* @param jpegBuf pointer to a buffer containing the JPEG image to decompress
* @param jpegSize size of the JPEG image (in bytes)
* @param dstBuf pointer to an image buffer that will receive the decompressed
* image. This buffer should normally be <tt>pitch * scaledHeight</tt>
* bytes in size, where <tt>scaledHeight</tt> can be determined by
* calling #TJSCALED() with the JPEG image height and one of the scaling
* factors returned by #tjGetScalingFactors(). The dstBuf pointer may
* also be used to decompress into a specific region of a larger buffer.
* @param width desired width (in pixels) of the destination image. If this is
* smaller than the width of the JPEG image being decompressed, then
* TurboJPEG will use scaling in the JPEG decompressor to generate the
* largest possible image that will fit within the desired width. If
* width is set to 0, then only the height will be considered when
* determining the scaled image size.
* @param pitch bytes per line of the destination image. Normally, this is
* <tt>scaledWidth * #tjPixelSize[pixelFormat]</tt> if the decompressed
* image is unpadded, else <tt>#TJPAD(scaledWidth *
* #tjPixelSize[pixelFormat])</tt> if each line of the decompressed
* image is padded to the nearest 32-bit boundary, as is the case for
* Windows bitmaps. (NOTE: <tt>scaledWidth</tt> can be determined by
* calling #TJSCALED() with the JPEG image width and one of the scaling
* factors returned by #tjGetScalingFactors().) You can also be clever
* and use the pitch parameter to skip lines, etc. Setting this
* parameter to 0 is the equivalent of setting it to <tt>scaledWidth
* * #tjPixelSize[pixelFormat]</tt>.
* @param height desired height (in pixels) of the destination image. If this
* is smaller than the height of the JPEG image being decompressed, then
* TurboJPEG will use scaling in the JPEG decompressor to generate the
* largest possible image that will fit within the desired height. If
* height is set to 0, then only the width will be considered when
* determining the scaled image size.
* @param pixelFormat pixel format of the destination image (see @ref
* TJPF "Pixel formats".)
* @param flags the bitwise OR of one or more of the @ref TJFLAG_BOTTOMUP
* "flags".
*
* @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr().)
*/
DLLEXPORT int DLLCALL tjDecompress2(tjhandle handle,
unsigned char *jpegBuf, unsigned long jpegSize, unsigned char *dstBuf,
int width, int pitch, int height, int pixelFormat, int flags);
/**
* Destroy a TurboJPEG compressor, decompressor, or transformer instance.
*
* @param handle a handle to a TurboJPEG compressor, decompressor or
* transformer instance
*
* @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr().)
*/
DLLEXPORT int DLLCALL tjDestroy(tjhandle handle);
/**
* Returns a descriptive error message explaining why the last command failed.
*
* @return a descriptive error message explaining why the last command failed.
*/
DLLEXPORT char* DLLCALL tjGetErrorStr(void);
/* Backward compatibility functions and macros (nothing to see here) */
#define NUMSUBOPT TJ_NUMSAMP
#define TJ_444 TJSAMP_444
#define TJ_422 TJSAMP_422
#define TJ_420 TJSAMP_420
#define TJ_411 TJSAMP_420
#define TJ_GRAYSCALE TJSAMP_GRAY
#define TJ_BGR 1
#define TJ_BOTTOMUP TJFLAG_BOTTOMUP
#define TJ_FORCEMMX TJFLAG_FORCEMMX
#define TJ_FORCESSE TJFLAG_FORCESSE
#define TJ_FORCESSE2 TJFLAG_FORCESSE2
#define TJ_ALPHAFIRST 64
#define TJ_FORCESSE3 TJFLAG_FORCESSE3
#define TJ_FASTUPSAMPLE TJFLAG_FASTUPSAMPLE
DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height);
DLLEXPORT int DLLCALL tjCompress(tjhandle handle, unsigned char *srcBuf,
int width, int pitch, int height, int pixelSize, unsigned char *dstBuf,
unsigned long *compressedSize, int jpegSubsamp, int jpegQual, int flags);
DLLEXPORT int DLLCALL tjDecompressHeader(tjhandle handle,
unsigned char *jpegBuf, unsigned long jpegSize, int *width, int *height);
DLLEXPORT int DLLCALL tjDecompress(tjhandle handle,
unsigned char *jpegBuf, unsigned long jpegSize, unsigned char *dstBuf,
int width, int pitch, int height, int pixelSize, int flags);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif

31
include/usdt.h 100644
View File

@ -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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Andri Yngvason
* Copyright (c) 2020 - 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
@ -16,28 +16,52 @@
#pragma once
#include "rfbclient.h"
#include "data-control.h"
#include <stdbool.h>
#include <unistd.h>
#include <pixman.h>
#include <wayland-client.h>
#include <rfb/rfbclient.h>
#define VNC_CLIENT_MAX_AV_FRAMES 64
struct open_h264;
struct AVFrame;
struct vnc_av_frame {
struct AVFrame* frame;
int x, y, width, height;
};
struct vnc_client {
rfbClient* client;
struct data_control* data_control;
struct open_h264* open_h264;
bool current_rect_is_av_frame;
struct vnc_av_frame* av_frames[VNC_CLIENT_MAX_AV_FRAMES];
int n_av_frames;
uint64_t pts;
int (*alloc_fb)(struct vnc_client*);
void (*update_fb)(struct vnc_client*);
void (*cut_text)(struct vnc_client*, const char*, size_t);
void* userdata;
struct pixman_region16 damage;
bool handler_lock;
bool is_updating;
};
struct vnc_client* vnc_client_create(void);
struct vnc_client* vnc_client_create(struct data_control* data_control);
void vnc_client_destroy(struct vnc_client* self);
int vnc_client_connect(struct vnc_client* self, const char* address, int port);
int vnc_client_init(struct vnc_client* self);
int vnc_client_set_pixel_format(struct vnc_client* self,
enum wl_shm_format format);
int vnc_client_set_pixel_format(struct vnc_client* self, uint32_t format);
int vnc_client_get_fd(const struct vnc_client* self);
int vnc_client_get_width(const struct vnc_client* self);
@ -54,3 +78,8 @@ void vnc_client_send_keyboard_event(struct vnc_client* self, uint32_t symbol,
void vnc_client_set_encodings(struct vnc_client* self, const char* encodings);
void vnc_client_set_quality_level(struct vnc_client* self, int value);
void vnc_client_set_compression_level(struct vnc_client* self, int value);
void vnc_client_send_cut_text(struct vnc_client* self, const char* text,
size_t len);
void vnc_client_clear_av_frames(struct vnc_client* self);
rfbCredential* handle_vnc_authentication(struct _rfbClient *client, int credentialType);
void cut_text (struct vnc_client* self, const char* text, size_t size);

View File

@ -16,6 +16,7 @@ prefix = get_option('prefix')
c_args = [
'-D_GNU_SOURCE',
'-DAML_UNSTABLE_API=1',
]
if buildtype != 'debug' and buildtype != 'debugoptimized'
@ -28,24 +29,26 @@ cc = meson.get_compiler('c')
libm = cc.find_library('m', required: false)
librt = cc.find_library('rt', required: false)
pthread = cc.find_library('pthread', required: false)
xkbcommon = dependency('xkbcommon')
pixman = dependency('pixman-1')
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
libvncserver_opt = cmake.subproject_options()
libvncserver_opt.add_cmake_defines({
'BUILD_SHARED_LIBS': false,
'LIBVNCSERVER_INSTALL': false,
})
libvncserver_project = cmake.subproject('libvncserver', options: libvncserver_opt)
if libvncserver_project.found()
libvncclient = libvncserver_project.dependency('vncclient')
else
libvncclient = dependency('libvncclient')
endif
drm = dependency('libdrm')
gbm = dependency('gbm')
egl = dependency('egl')
glesv2 = dependency('glesv2')
lavc = dependency('libavcodec')
lavu = dependency('libavutil')
gcrypt = dependency('libgcrypt', required: false)
openssl = dependency('openssl', required: false)
gnutls = dependency('gnutls', required: false)
sasl = dependency('libsasl2', required: false)
libjpeg = dependency('libjpeg', required: false)
libpng = dependency('libpng', required: false)
lzo = dependency('lzo2', required: false)
libz = dependency('zlib', required: false)
aml_project = subproject('aml', required: false,
default_options: ['default_library=static'])
@ -55,7 +58,7 @@ else
aml = dependency('aml')
endif
inc = include_directories('include')
inc = include_directories('include', 'src/encodings')
subdir('protocols')
@ -63,11 +66,23 @@ sources = [
'src/main.c',
'src/shm.c',
'src/seat.c',
'src/output.c',
'src/pointer.c',
'src/keyboard.c',
'src/vnc.c',
'src/data-control.c',
'src/strlcpy.c',
'src/evdev-to-qnum.c',
'src/pixels.c',
'src/region.c',
'src/renderer.c',
'src/renderer-egl.c',
'src/buffer.c',
'src/open-h264.c',
'src/cursor.c',
'src/rfbproto.c',
'src/sockets.c',
'src/vncviewer.c',
]
dependencies = [
@ -78,7 +93,12 @@ dependencies = [
aml,
wayland_client,
wayland_cursor,
libvncclient,
drm,
gbm,
egl,
glesv2,
lavc,
lavu,
client_protos,
]
@ -86,6 +106,60 @@ config = configuration_data()
config.set('PREFIX', '"' + prefix + '"')
if gcrypt.found()
sources += 'src/crypto_libgcrypt.c'
dependencies += gcrypt
elif openssl.found()
sources += 'src/crypto_openssl.c'
dependencies += openssl
else
sources += 'src/crypto_included.c'
endif
if gnutls.found()
sources += 'src/tls_gnutls.c'
dependencies += gnutls
config.set('LIBVNCSERVER_HAVE_GNUTLS', true)
elif openssl.found()
sources += 'src/tls_openssl.c'
dependencies += openssl
config.set('LIBVNCSERVER_HAVE_LIBSSL', true)
else
sources += 'src/tls_none.c'
endif
if sasl.found()
dependencies += sasl
sources += 'src/sasl.c'
config.set('LIBVNCSERVER_HAVE_SASL', true)
endif
if libjpeg.found()
sources += 'src/turbojpeg.c'
dependencies += libjpeg
config.set('LIBVNCSERVER_HAVE_LIBJPEG', true)
endif
if libpng.found()
dependencies += libpng
config.set('LIBVNCSERVER_HAVE_LIBPNG', true)
endif
if pthread.found()
dependencies += pthread
config.set('LIBVNCSERVER_HAVE_PTHREAD', true)
endif
if libz.found()
dependencies += libz
config.set('LIBVNCSERVER_HAVE_LIBZ', true)
endif
if lzo.found()
dependencies += lzo
config.set('LIBVNCSERVER_HAVE_LZO', true)
endif
if host_system == 'linux' and cc.has_header('sys/sdt.h')
config.set('HAVE_USDT', true)
endif

View File

@ -0,0 +1,586 @@
<?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="4">
<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.
Clients can use the get_surface_feedback request to get dmabuf feedback
for a particular surface. If the client wants to retrieve feedback not
tied to a surface, they can use the get_default_feedback request.
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.
However, when the underlying graphics stack fails to deliver the
promise, because of e.g. a device hot-unplug which raises internal
errors, after the wl_buffer has been successfully created the
compositor must not raise protocol errors to the client when dmabuf
import later fails.
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.
For all DRM formats and unless specified in another protocol extension,
pre-multiplied alpha is used for pixel values.
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.
Starting version 4, the format event is deprecated and must not be
sent by compositors. Instead, use get_default_feedback or
get_surface_feedback.
</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.
A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for
a given format supports both explicit modifiers and implicit modifiers.
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.
Starting version 4, the modifier event is deprecated and must not be
sent by compositors. Instead, use get_default_feedback or
get_surface_feedback.
</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>
<!-- Version 4 additions -->
<request name="get_default_feedback" since="4">
<description summary="get default feedback">
This request creates a new wp_linux_dmabuf_feedback object not bound
to a particular surface. This object will deliver feedback about dmabuf
parameters to use if the client doesn't support per-surface feedback
(see get_surface_feedback).
</description>
<arg name="id" type="new_id" interface="zwp_linux_dmabuf_feedback_v1"/>
</request>
<request name="get_surface_feedback" since="4">
<description summary="get feedback for a surface">
This request creates a new wp_linux_dmabuf_feedback object for the
specified wl_surface. This object will deliver feedback about dmabuf
parameters to use for buffers attached to this surface.
If the surface is destroyed before the wp_linux_dmabuf_feedback object,
the feedback object becomes inert.
</description>
<arg name="id" type="new_id" interface="zwp_linux_dmabuf_feedback_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
</interface>
<interface name="zwp_linux_buffer_params_v1" version="4">
<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.
Starting from version 4, the invalid_format protocol error is sent if
the format + modifier pair was not advertised as supported.
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" bitfield="true">
<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" enum="flags" 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" enum="flags" summary="see enum flags"/>
</request>
</interface>
<interface name="zwp_linux_dmabuf_feedback_v1" version="4">
<description summary="dmabuf feedback">
This object advertises dmabuf parameters feedback. This includes the
preferred devices and the supported formats/modifiers.
The parameters are sent once when this object is created and whenever they
change. The done event is always sent once after all parameters have been
sent. When a single parameter changes, all parameters are re-sent by the
compositor.
Compositors can re-send the parameters when the current client buffer
allocations are sub-optimal. Compositors should not re-send the
parameters if re-allocating the buffers would not result in a more optimal
configuration. In particular, compositors should avoid sending the exact
same parameters multiple times in a row.
The tranche_target_device and tranche_modifier events are grouped by
tranches of preference. For each tranche, a tranche_target_device, one
tranche_flags and one or more tranche_modifier events are sent, followed
by a tranche_done event finishing the list. The tranches are sent in
descending order of preference. All formats and modifiers in the same
tranche have the same preference.
To send parameters, the compositor sends one main_device event, tranches
(each consisting of one tranche_target_device event, one tranche_flags
event, tranche_modifier events and then a tranche_done event), then one
done event.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the feedback object">
Using this request a client can tell the server that it is not going to
use the wp_linux_dmabuf_feedback object anymore.
</description>
</request>
<event name="done">
<description summary="all feedback has been sent">
This event is sent after all parameters of a wp_linux_dmabuf_feedback
object have been sent.
This allows changes to the wp_linux_dmabuf_feedback parameters to be
seen as atomic, even if they happen via multiple events.
</description>
</event>
<event name="format_table">
<description summary="format and modifier table">
This event provides a file descriptor which can be memory-mapped to
access the format and modifier table.
The table contains a tightly packed array of consecutive format +
modifier pairs. Each pair is 16 bytes wide. It contains a format as a
32-bit unsigned integer, followed by 4 bytes of unused padding, and a
modifier as a 64-bit unsigned integer. The native endianness is used.
The client must map the file descriptor in read-only private mode.
Compositors are not allowed to mutate the table file contents once this
event has been sent. Instead, compositors must create a new, separate
table file and re-send feedback parameters. Compositors are allowed to
store duplicate format + modifier pairs in the table.
</description>
<arg name="fd" type="fd" summary="table file descriptor"/>
<arg name="size" type="uint" summary="table size, in bytes"/>
</event>
<event name="main_device">
<description summary="preferred main device">
This event advertises the main device that the server prefers to use
when direct scan-out to the target device isn't possible. The
advertised main device may be different for each
wp_linux_dmabuf_feedback object, and may change over time.
There is exactly one main device. The compositor must send at least
one preference tranche with tranche_target_device equal to main_device.
Clients need to create buffers that the main device can import and
read from, otherwise creating the dmabuf wl_buffer will fail (see the
wp_linux_buffer_params.create and create_immed requests for details).
The main device will also likely be kept active by the compositor,
so clients can use it instead of waking up another device for power
savings.
In general the device is a DRM node. The DRM node type (primary vs.
render) is unspecified. Clients must not rely on the compositor sending
a particular node type. Clients cannot check two devices for equality
by comparing the dev_t value.
If explicit modifiers are not supported and the client performs buffer
allocations on a different device than the main device, then the client
must force the buffer to have a linear layout.
</description>
<arg name="device" type="array" summary="device dev_t value"/>
</event>
<event name="tranche_done">
<description summary="a preference tranche has been sent">
This event splits tranche_target_device and tranche_modifier events in
preference tranches. It is sent after a set of tranche_target_device
and tranche_modifier events; it represents the end of a tranche. The
next tranche will have a lower preference.
</description>
</event>
<event name="tranche_target_device">
<description summary="target device">
This event advertises the target device that the server prefers to use
for a buffer created given this tranche. The advertised target device
may be different for each preference tranche, and may change over time.
There is exactly one target device per tranche.
The target device may be a scan-out device, for example if the
compositor prefers to directly scan-out a buffer created given this
tranche. The target device may be a rendering device, for example if
the compositor prefers to texture from said buffer.
The client can use this hint to allocate the buffer in a way that makes
it accessible from the target device, ideally directly. The buffer must
still be accessible from the main device, either through direct import
or through a potentially more expensive fallback path. If the buffer
can't be directly imported from the main device then clients must be
prepared for the compositor changing the tranche priority or making
wl_buffer creation fail (see the wp_linux_buffer_params.create and
create_immed requests for details).
If the device is a DRM node, the DRM node type (primary vs. render) is
unspecified. Clients must not rely on the compositor sending a
particular node type. Clients cannot check two devices for equality by
comparing the dev_t value.
This event is tied to a preference tranche, see the tranche_done event.
</description>
<arg name="device" type="array" summary="device dev_t value"/>
</event>
<event name="tranche_formats">
<description summary="supported buffer format modifier">
This event advertises the format + modifier combinations that the
compositor supports.
It carries an array of indices, each referring to a format + modifier
pair in the last received format table (see the format_table event).
Each index is a 16-bit unsigned integer in native endianness.
For legacy support, DRM_FORMAT_MOD_INVALID is an allowed modifier.
It indicates that the server can support the format with an implicit
modifier. When a buffer 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.
A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for
a given format supports both explicit modifiers and implicit modifiers.
Compositors must not send duplicate format + modifier pairs within the
same tranche or across two different tranches with the same target
device and flags.
This event is tied to a preference tranche, see the tranche_done event.
For the definition of the format and modifier codes, see the
wp_linux_buffer_params.create request.
</description>
<arg name="indices" type="array" summary="array of 16-bit indexes"/>
</event>
<enum name="tranche_flags" bitfield="true">
<entry name="scanout" value="1" summary="direct scan-out tranche"/>
</enum>
<event name="tranche_flags">
<description summary="tranche flags">
This event sets tranche-specific flags.
The scanout flag is a hint that direct scan-out may be attempted by the
compositor on the target device if the client appropriately allocates a
buffer. How to allocate a buffer that can be scanned out on the target
device is implementation-defined.
This event is tied to a preference tranche, see the tranche_done event.
</description>
<arg name="flags" type="uint" enum="tranche_flags" summary="tranche flags"/>
</event>
</interface>
</protocol>

View File

@ -15,6 +15,8 @@ wayland_scanner_client = generator(
client_protocols = [
'xdg-shell.xml',
'linux-dmabuf-unstable-v1.xml',
'wlr-data-control-unstable-v1.xml'
]
client_protos_src = []

View File

@ -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>

181
src/buffer.c 100644
View File

@ -0,0 +1,181 @@
/*
* 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 "buffer.h"
#include "shm.h"
#include "pixels.h"
#include "linux-dmabuf-unstable-v1.h"
#include <stdlib.h>
#include <sys/mman.h>
#include <wayland-client.h>
#include <gbm.h>
#include <assert.h>
/* Origin: main.c */
extern struct wl_shm* wl_shm;
extern struct gbm_device* gbm_device;
extern struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1;
static void buffer_release(void* data, struct wl_buffer* wl_buffer)
{
(void)wl_buffer;
struct buffer* self = data;
self->is_attached = false;
if (self->please_clean_up) {
buffer_destroy(self);
}
}
static const struct wl_buffer_listener buffer_listener = {
.release = buffer_release,
};
struct buffer* buffer_create_shm(int width, int height, int stride,
uint32_t format)
{
assert(wl_shm);
struct buffer* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->type = BUFFER_WL_SHM;
self->width = width;
self->height = height;
self->stride = stride;
self->format = format;
pixman_region_init_rect(&self->damage, 0, 0, width, height);
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, drm_format_to_wl_shm(format));
wl_shm_pool_destroy(pool);
if (!self->wl_buffer)
goto shm_failure;
close(fd);
wl_buffer_add_listener(self->wl_buffer, &buffer_listener, self);
return self;
shm_failure:
pool_failure:
munmap(self->pixels, self->size);
mmap_failure:
close(fd);
failure:
free(self);
return NULL;
}
struct buffer* buffer_create_dmabuf(int width, int height, uint32_t format)
{
assert(gbm_device && zwp_linux_dmabuf_v1);
struct buffer* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->type = BUFFER_DMABUF;
self->width = width;
self->height = height;
self->format = format;
pixman_region_init_rect(&self->damage, 0, 0, width, height);
self->bo = gbm_bo_create(gbm_device, width, height, format,
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_v1);
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, format, /* flags */ 0);
zwp_linux_buffer_params_v1_destroy(params);
close(fd);
if (!self->wl_buffer)
goto buffer_failure;
wl_buffer_add_listener(self->wl_buffer, &buffer_listener, self);
return self;
buffer_failure:
fd_failure:
zwp_linux_buffer_params_v1_destroy(params);
params_failure:
gbm_bo_destroy(self->bo);
bo_failure:
free(self);
return NULL;
}
void buffer_destroy(struct buffer* self)
{
if (!self)
return;
if (self->is_attached)
self->please_clean_up = true;
pixman_region_fini(&self->damage);
wl_buffer_destroy(self->wl_buffer);
switch (self->type) {
case BUFFER_WL_SHM:
munmap(self->pixels, self->size);
break;
case BUFFER_DMABUF:
gbm_bo_destroy(self->bo);
break;
default:
abort();
break;
}
free(self);
}

View File

@ -0,0 +1,93 @@
/*
* crypto_included.c - Crypto wrapper (included version)
*/
/*
* Copyright (C) 2011 Gernot Tenchio
* Copyright (C) 2019 Christian Beier
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include <string.h>
#include "sha.h"
#include "d3des.h"
#include "crypto.h"
int hash_md5(void *out, const void *in, const size_t in_len)
{
return 0;
}
int hash_sha1(void *out, const void *in, const size_t in_len)
{
SHA1Context sha1;
if(SHA1Reset(&sha1) != shaSuccess)
return 0;
if(SHA1Input(&sha1, in, in_len) != shaSuccess)
return 0;
if(SHA1Result(&sha1, out) != shaSuccess)
return 0;
return 1;
}
void random_bytes(void *out, size_t len)
{
}
int encrypt_rfbdes(void *out, int *out_len, const unsigned char key[8], const void *in, const size_t in_len)
{
int eightbyteblocks = in_len/8;
int i;
rfbDesKey((unsigned char*)key, EN0);
for(i = 0; i < eightbyteblocks; ++i)
rfbDes((unsigned char*)in + i*8, (unsigned char*)out + i*8);
*out_len = in_len;
return 1;
}
int decrypt_rfbdes(void *out, int *out_len, const unsigned char key[8], const void *in, const size_t in_len)
{
int eightbyteblocks = in_len/8;
int i;
rfbDesKey((unsigned char*)key, DE1);
for(i = 0; i < eightbyteblocks; ++i)
rfbDes((unsigned char*)in + i*8, (unsigned char*)out + i*8);
*out_len = in_len;
return 1;
}
int encrypt_aes128ecb(void *out, int *out_len, const unsigned char key[16], const void *in, const size_t in_len)
{
return 0;
}
int dh_generate_keypair(uint8_t *priv_out, uint8_t *pub_out, const uint8_t *gen, const size_t gen_len, const uint8_t *prime, const size_t keylen)
{
return 0;
}
int dh_compute_shared_key(uint8_t *shared_out, const uint8_t *priv, const uint8_t *pub, const uint8_t *prime, const size_t keylen)
{
return 0;
}

View File

@ -0,0 +1,271 @@
/*
* crypto_gnutls.c - Crypto wrapper (libgcrypt version)
*/
/*
* Copyright (C) 2011 Gernot Tenchio
* Copyright (C) 2019 Christian Beier
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include <string.h>
#include <gcrypt.h>
#include "crypto.h"
static int mpiToBytes(const gcry_mpi_t value, uint8_t *result, size_t size)
{
gcry_error_t error;
size_t len;
int i;
error = gcry_mpi_print(GCRYMPI_FMT_USG, result, size, &len, value);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
return 0;
for (i=size-1;i>(int)size-1-(int)len;--i)
result[i] = result[i-size+len];
for (;i>=0;--i)
result[i] = 0;
return 1;
}
static unsigned char reverseByte(unsigned char b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
int hash_md5(void *out, const void *in, const size_t in_len)
{
int result = 0;
gcry_error_t error;
gcry_md_hd_t md5 = NULL;
void *digest;
error = gcry_md_open(&md5, GCRY_MD_MD5, 0);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
gcry_md_write(md5, in, in_len);
if(!(digest = gcry_md_read(md5, GCRY_MD_MD5)))
goto out;
memcpy(out, digest, gcry_md_get_algo_dlen(GCRY_MD_MD5));
result = 1;
out:
gcry_md_close(md5);
return result;
}
int hash_sha1(void *out, const void *in, const size_t in_len)
{
int result = 0;
gcry_error_t error;
gcry_md_hd_t sha1 = NULL;
void *digest;
error = gcry_md_open(&sha1, GCRY_MD_SHA1, 0);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
gcry_md_write(sha1, in, in_len);
if(!(digest = gcry_md_read(sha1, GCRY_MD_SHA1)))
goto out;
memcpy(out, digest, gcry_md_get_algo_dlen(GCRY_MD_SHA1));
result = 1;
out:
gcry_md_close(sha1);
return result;
}
void random_bytes(void *out, size_t len)
{
gcry_randomize(out, len, GCRY_STRONG_RANDOM);
}
int encrypt_rfbdes(void *out, int *out_len, const unsigned char key[8], const void *in, const size_t in_len)
{
int result = 0;
gcry_error_t error;
gcry_cipher_hd_t des = NULL;
unsigned char mungedkey[8];
int i;
for (i = 0; i < 8; i++)
mungedkey[i] = reverseByte(key[i]);
error = gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
error = gcry_cipher_setkey(des, mungedkey, 8);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
error = gcry_cipher_encrypt(des, out, in_len, in, in_len);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
*out_len = in_len;
result = 1;
out:
gcry_cipher_close(des);
return result;
}
int decrypt_rfbdes(void *out, int *out_len, const unsigned char key[8], const void *in, const size_t in_len)
{
int result = 0;
gcry_error_t error;
gcry_cipher_hd_t des = NULL;
unsigned char mungedkey[8];
int i;
for (i = 0; i < 8; i++)
mungedkey[i] = reverseByte(key[i]);
error = gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
error = gcry_cipher_setkey(des, mungedkey, 8);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
error = gcry_cipher_decrypt(des, out, in_len, in, in_len);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
*out_len = in_len;
result = 1;
out:
gcry_cipher_close(des);
return result;
}
int encrypt_aes128ecb(void *out, int *out_len, const unsigned char key[16], const void *in, const size_t in_len)
{
int result = 0;
gcry_error_t error;
gcry_cipher_hd_t aes = NULL;
error = gcry_cipher_open(&aes, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_ECB, 0);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
error = gcry_cipher_setkey(aes, key, 16);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
error = gcry_cipher_encrypt(aes, out, in_len, in, in_len);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
*out_len = in_len;
result = 1;
out:
gcry_cipher_close(aes);
return result;
}
int dh_generate_keypair(uint8_t *priv_out, uint8_t *pub_out, const uint8_t *gen, const size_t gen_len, const uint8_t *prime, const size_t keylen)
{
int result = 0;
gcry_error_t error;
gcry_mpi_t genmpi = NULL, modmpi = NULL, privmpi = NULL, pubmpi = NULL;
error = gcry_mpi_scan(&genmpi, GCRYMPI_FMT_USG, gen, gen_len, NULL);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
error = gcry_mpi_scan(&modmpi, GCRYMPI_FMT_USG, prime, keylen, NULL);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
privmpi = gcry_mpi_new(keylen);
if (!privmpi)
goto out;
gcry_mpi_randomize(privmpi, (keylen/8)*8, GCRY_STRONG_RANDOM);
pubmpi = gcry_mpi_new(keylen);
if (!pubmpi)
goto out;
gcry_mpi_powm(pubmpi, genmpi, privmpi, modmpi);
if (!mpiToBytes(pubmpi, pub_out, keylen))
goto out;
if (!mpiToBytes(privmpi, priv_out, keylen))
goto out;
result = 1;
out:
gcry_mpi_release(genmpi);
gcry_mpi_release(modmpi);
gcry_mpi_release(privmpi);
gcry_mpi_release(pubmpi);
return result;
}
int dh_compute_shared_key(uint8_t *shared_out, const uint8_t *priv, const uint8_t *pub, const uint8_t *prime, const size_t keylen)
{
int result = 1;
gcry_error_t error;
gcry_mpi_t keympi = NULL, modmpi = NULL, privmpi = NULL, pubmpi = NULL;
error = gcry_mpi_scan(&privmpi, GCRYMPI_FMT_USG, priv, keylen, NULL);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
error = gcry_mpi_scan(&pubmpi, GCRYMPI_FMT_USG, pub, keylen, NULL);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
error = gcry_mpi_scan(&modmpi, GCRYMPI_FMT_USG, prime, keylen, NULL);
if (gcry_err_code(error) != GPG_ERR_NO_ERROR)
goto out;
keympi = gcry_mpi_new(keylen);
if (!keympi)
goto out;
gcry_mpi_powm(keympi, pubmpi, privmpi, modmpi);
if (!mpiToBytes(keympi, shared_out, keylen))
goto out;
result = 1;
out:
gcry_mpi_release(keympi);
gcry_mpi_release(modmpi);
gcry_mpi_release(privmpi);
gcry_mpi_release(pubmpi);
return result;
}

View File

@ -0,0 +1,205 @@
/*
* crypto_openssl.c - Crypto wrapper (openssl version)
*/
/*
* Copyright (C) 2011 Gernot Tenchio
* Copyright (C) 2019 Christian Beier
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include <string.h>
#include <openssl/sha.h>
#include <openssl/md5.h>
#include <openssl/dh.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include "crypto.h"
static unsigned char reverseByte(unsigned char b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
int hash_md5(void *out, const void *in, const size_t in_len)
{
MD5_CTX md5;
if(!MD5_Init(&md5))
return 0;
if(!MD5_Update(&md5, in, in_len))
return 0;
if(!MD5_Final(out, &md5))
return 0;
return 1;
}
int hash_sha1(void *out, const void *in, const size_t in_len)
{
SHA_CTX sha1;
if(!SHA1_Init(&sha1))
return 0;
if(!SHA1_Update(&sha1, in, in_len))
return 0;
if(!SHA1_Final(out, &sha1))
return 0;
return 1;
}
void random_bytes(void *out, size_t len)
{
RAND_bytes(out, len);
}
int encrypt_rfbdes(void *out, int *out_len, const unsigned char key[8], const void *in, const size_t in_len)
{
int result = 0;
EVP_CIPHER_CTX *des;
unsigned char mungedkey[8];
int i;
for (i = 0; i < 8; i++)
mungedkey[i] = reverseByte(key[i]);
if(!(des = EVP_CIPHER_CTX_new()))
goto out;
if(!EVP_EncryptInit_ex(des, EVP_des_ecb(), NULL, mungedkey, NULL))
goto out;
if(!EVP_EncryptUpdate(des, out, out_len, in, in_len))
goto out;
result = 1;
out:
EVP_CIPHER_CTX_free(des);
return result;
}
int decrypt_rfbdes(void *out, int *out_len, const unsigned char key[8], const void *in, const size_t in_len)
{
int result = 0;
EVP_CIPHER_CTX *des;
unsigned char mungedkey[8];
int i;
for (i = 0; i < 8; i++)
mungedkey[i] = reverseByte(key[i]);
if(!(des = EVP_CIPHER_CTX_new()))
goto out;
if(!EVP_DecryptInit_ex(des, EVP_des_ecb(), NULL, mungedkey, NULL))
goto out;
if(!EVP_DecryptUpdate(des, out, out_len, in, in_len))
goto out;
result = 1;
out:
EVP_CIPHER_CTX_free(des);
return result;
}
int encrypt_aes128ecb(void *out, int *out_len, const unsigned char key[16], const void *in, const size_t in_len)
{
int result = 0;
EVP_CIPHER_CTX *aes;
if(!(aes = EVP_CIPHER_CTX_new()))
goto out;
EVP_CIPHER_CTX_set_padding(aes, 0);
if(!EVP_EncryptInit_ex(aes, EVP_aes_128_ecb(), NULL, key, NULL))
goto out;
if(!EVP_EncryptUpdate(aes, out, out_len, in, in_len))
goto out;
result = 1;
out:
EVP_CIPHER_CTX_free(aes);
return result;
}
int dh_generate_keypair(uint8_t *priv_out, uint8_t *pub_out, const uint8_t *gen, const size_t gen_len, const uint8_t *prime, const size_t keylen)
{
int result = 0;
DH *dh;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L || \
(defined (LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER >= 0x30500000)
const BIGNUM *pub_key = NULL;
const BIGNUM *priv_key = NULL;
#endif
if(!(dh = DH_new()))
goto out;
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
(defined (LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x30500000)
dh->p = BN_bin2bn(prime, keylen, NULL);
dh->g = BN_bin2bn(gen, gen_len, NULL);
#else
if(!DH_set0_pqg(dh, BN_bin2bn(prime, keylen, NULL), NULL, BN_bin2bn(gen, gen_len, NULL)))
goto out;
#endif
if(!DH_generate_key(dh))
goto out;
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
(defined (LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x30500000)
if(BN_bn2bin(dh->priv_key, priv_out) == 0)
goto out;
if(BN_bn2bin(dh->pub_key, pub_out) == 0)
goto out;
#else
DH_get0_key(dh, &pub_key, &priv_key);
if(BN_bn2binpad(priv_key, priv_out, keylen) == -1)
goto out;
if(BN_bn2binpad(pub_key, pub_out, keylen) == -1)
goto out;
#endif
result = 1;
out:
DH_free(dh);
return result;
}
int dh_compute_shared_key(uint8_t *shared_out, const uint8_t *priv, const uint8_t *pub, const uint8_t *prime, const size_t keylen)
{
int result = 0;
DH *dh;
if(!(dh = DH_new()))
goto out;
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
(defined LIBRESSL_VERSION_NUMBER && LIBRESSL_VERSION_NUMBER < 0x30500000)
dh->p = BN_bin2bn(prime, keylen, NULL);
dh->priv_key = BN_bin2bn(priv, keylen, NULL);
#else
if(!DH_set0_pqg(dh, BN_bin2bn(prime, keylen, NULL), NULL, BN_new()))
goto out;
if(!DH_set0_key(dh, NULL, BN_bin2bn(priv, keylen, NULL)))
goto out;
#endif
if(DH_compute_key(shared_out, BN_bin2bn(pub, keylen, NULL), dh) == -1)
goto out;
result = 1;
out:
DH_free(dh);
return result;
}

177
src/cursor.c 100644
View File

@ -0,0 +1,177 @@
/*
* Copyright (C) 2001,2002 Constantin Kaplinsky. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/*
* cursor.c - code to support cursor shape updates (XCursor and
* RichCursor preudo-encodings).
*/
#include "rfbclient.h"
#define OPER_SAVE 0
#define OPER_RESTORE 1
#define MAX_CURSOR_SIZE 1024
#define RGB24_TO_PIXEL(bpp,r,g,b) \
((((uint##bpp##_t)(r) & 0xFF) * client->format.redMax + 127) / 255 \
<< client->format.redShift | \
(((uint##bpp##_t)(g) & 0xFF) * client->format.greenMax + 127) / 255 \
<< client->format.greenShift | \
(((uint##bpp##_t)(b) & 0xFF) * client->format.blueMax + 127) / 255 \
<< client->format.blueShift)
rfbBool HandleCursorShape(rfbClient* client,int xhot, int yhot, int width, int height, uint32_t enc)
{
int bytesPerPixel;
size_t bytesPerRow, bytesMaskData;
rfbXCursorColors rgb;
uint32_t colors[2];
char *buf;
uint8_t *ptr;
int x, y, b;
bytesPerPixel = client->format.bitsPerPixel / 8;
bytesPerRow = (width + 7) / 8;
bytesMaskData = bytesPerRow * height;
if (width * height == 0)
return TRUE;
if (width >= MAX_CURSOR_SIZE || height >= MAX_CURSOR_SIZE)
return FALSE;
/* Allocate memory for pixel data and temporary mask data. */
if(client->rcSource)
free(client->rcSource);
client->rcSource = malloc((size_t)width * height * bytesPerPixel);
if (client->rcSource == NULL)
return FALSE;
buf = malloc(bytesMaskData);
if (buf == NULL) {
free(client->rcSource);
client->rcSource = NULL;
return FALSE;
}
/* Read and decode cursor pixel data, depending on the encoding type. */
if (enc == rfbEncodingXCursor) {
/* Read and convert background and foreground colors. */
if (!ReadFromRFBServer(client, (char *)&rgb, sz_rfbXCursorColors)) {
free(client->rcSource);
client->rcSource = NULL;
free(buf);
return FALSE;
}
colors[0] = RGB24_TO_PIXEL(32, rgb.backRed, rgb.backGreen, rgb.backBlue);
colors[1] = RGB24_TO_PIXEL(32, rgb.foreRed, rgb.foreGreen, rgb.foreBlue);
/* Read 1bpp pixel data into a temporary buffer. */
if (!ReadFromRFBServer(client, buf, bytesMaskData)) {
free(client->rcSource);
client->rcSource = NULL;
free(buf);
return FALSE;
}
/* Convert 1bpp data to byte-wide color indices. */
ptr = client->rcSource;
for (y = 0; y < height; y++) {
for (x = 0; x < width / 8; x++) {
for (b = 7; b >= 0; b--) {
*ptr = buf[y * bytesPerRow + x] >> b & 1;
ptr += bytesPerPixel;
}
}
for (b = 7; b > 7 - width % 8; b--) {
*ptr = buf[y * bytesPerRow + x] >> b & 1;
ptr += bytesPerPixel;
}
}
/* Convert indices into the actual pixel values. */
switch (bytesPerPixel) {
case 1:
for (x = 0; x < width * height; x++)
client->rcSource[x] = (uint8_t)colors[client->rcSource[x]];
break;
case 2:
for (x = 0; x < width * height; x++)
((uint16_t *)client->rcSource)[x] = (uint16_t)colors[client->rcSource[x * 2]];
break;
case 4:
for (x = 0; x < width * height; x++)
((uint32_t *)client->rcSource)[x] = colors[client->rcSource[x * 4]];
break;
}
} else { /* enc == rfbEncodingRichCursor */
if (!ReadFromRFBServer(client, (char *)client->rcSource, width * height * bytesPerPixel)) {
free(client->rcSource);
client->rcSource = NULL;
free(buf);
return FALSE;
}
}
/* Read and decode mask data. */
if (!ReadFromRFBServer(client, buf, bytesMaskData)) {
free(client->rcSource);
client->rcSource = NULL;
free(buf);
return FALSE;
}
client->rcMask = malloc((size_t)width * height);
if (client->rcMask == NULL) {
free(client->rcSource);
client->rcSource = NULL;
free(buf);
return FALSE;
}
ptr = client->rcMask;
for (y = 0; y < height; y++) {
for (x = 0; x < width / 8; x++) {
for (b = 7; b >= 0; b--) {
*ptr++ = buf[y * bytesPerRow + x] >> b & 1;
}
}
for (b = 7; b > 7 - width % 8; b--) {
*ptr++ = buf[y * bytesPerRow + x] >> b & 1;
}
}
if (client->GotCursorShape != NULL) {
client->GotCursorShape(client, xhot, yhot, width, height, bytesPerPixel);
}
free(buf);
return TRUE;
}

308
src/data-control.c 100644
View File

@ -0,0 +1,308 @@
#include "data-control.h"
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <getopt.h>
#include <aml.h>
#include <seat.h>
#include <vnc.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;
struct data_control* self;
};
static char* mime_type = "text/plain;charset=utf-8";
static bool isStringEqual(int lena, char* a, int lenb, char* b) {
if (lena != lenb) {
return false;
}
// Compare every character
for (size_t i = 0; i < lena; ++i) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
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 && ctx->self->vnc_write_clipboard) {
// If we receive clipboard data from the VNC server, we set the clipboard of the client.
// This "change" of clipboard data results into this "on_receive" event. That would leed
// to an endless loop...
// So we check if we send exactle that clipboard string to avoid an loop
if (!ctx->self->cb_data || !isStringEqual(ctx->self->cb_len, ctx->self->cb_data, strlen(ctx->mem_data), ctx->mem_data)) {
//printf("Received data FROM clipboard: %s\n", ctx->mem_data);
ctx->self->vnc_write_clipboard(ctx->mem_data, ctx->mem_size);
}
}
aml_stop(aml_get_default(), handler);
}
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 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) {
printf("pipe() failed");
return;
}
struct receive_context* ctx = calloc(1, sizeof(*ctx));
if (!ctx) {
printf("OOM");
close(pipe_fd[0]);
close(pipe_fd[1]);
return;
}
zwlr_data_control_offer_v1_receive(self->offer, mime_type, pipe_fd[1]);
//wl_display_flush(self->wl_display);
close(pipe_fd[1]);
ctx->self = self;
ctx->fd = pipe_fd[0];
//ctx->data_control = self;
ctx->offer = self->offer;
ctx->mem_fp = open_memstream(&ctx->mem_data, &ctx->mem_size);
if (!ctx->mem_fp) {
close(ctx->fd);
free(ctx);
printf("open_memstream() failed");
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, 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_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 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 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)
printf("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) {
printf("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, 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 seat* seat, struct zwlr_data_control_manager_v1 *manager) {
self->manager = manager;
self->device = zwlr_data_control_manager_v1_get_data_device(self->manager, seat->wl_seat);
self->cb_data = NULL;
self->cb_len = 0;
zwlr_data_control_device_v1_add_listener(self->device, &data_control_device_listener, self);
}
void data_control_to_clipboard(struct data_control* self, const char* text, size_t len)
{
//printf("Writing text TO CLIPBOARD: %s\n", text);
if (!len) {
printf("%s called with 0 length", __func__);
return;
}
if (self->cb_data) {
free(self->cb_data);
}
self->cb_data = malloc(len);
if (!self->cb_data) {
printf("OOM");
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);
}
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);
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/*
* corre.c - handle CoRRE encoding.
*
* This file shouldn't be compiled directly. It is included multiple times by
* rfbproto.c, each time with a different definition of the macro BPP. For
* each value of BPP, this file defines a function which handles a CoRRE
* encoded rectangle with BPP bits per pixel.
*/
#define HandleCoRREBPP CONCAT2E(HandleCoRRE,BPP)
#define CARDBPP CONCAT3E(uint,BPP,_t)
static rfbBool
HandleCoRREBPP (rfbClient* client, int rx, int ry, int rw, int rh)
{
rfbRREHeader hdr;
int i;
CARDBPP pix;
uint8_t *ptr;
int x, y, w, h;
if (!ReadFromRFBServer(client, (char *)&hdr, sz_rfbRREHeader))
return FALSE;
hdr.nSubrects = rfbClientSwap32IfLE(hdr.nSubrects);
if (!ReadFromRFBServer(client, (char *)&pix, sizeof(pix)))
return FALSE;
client->GotFillRect(client, rx, ry, rw, rh, pix);
if (hdr.nSubrects > RFB_BUFFER_SIZE / (4 + (BPP / 8)) || !ReadFromRFBServer(client, client->buffer, hdr.nSubrects * (4 + (BPP / 8))))
return FALSE;
ptr = (uint8_t *)client->buffer;
for (i = 0; i < hdr.nSubrects; i++) {
pix = *(CARDBPP *)ptr;
ptr += BPP/8;
x = *ptr++;
y = *ptr++;
w = *ptr++;
h = *ptr++;
client->GotFillRect(client, rx+x, ry+y, w, h, pix);
}
return TRUE;
}
#undef CARDBPP

View File

@ -0,0 +1,127 @@
/*
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/*
* hextile.c - handle hextile encoding.
*
* This file shouldn't be compiled directly. It is included multiple times by
* rfbproto.c, each time with a different definition of the macro BPP. For
* each value of BPP, this file defines a function which handles a hextile
* encoded rectangle with BPP bits per pixel.
*/
#define HandleHextileBPP CONCAT2E(HandleHextile,BPP)
#define CARDBPP CONCAT3E(uint,BPP,_t)
static rfbBool
HandleHextileBPP (rfbClient* client, int rx, int ry, int rw, int rh)
{
CARDBPP bg = 0, fg;
int i;
uint8_t *ptr;
int x, y, w, h;
int sx, sy, sw, sh;
uint8_t subencoding;
uint8_t nSubrects;
for (y = ry; y < ry+rh; y += 16) {
for (x = rx; x < rx+rw; x += 16) {
w = h = 16;
if (rx+rw - x < 16)
w = rx+rw - x;
if (ry+rh - y < 16)
h = ry+rh - y;
if (!ReadFromRFBServer(client, (char *)&subencoding, 1))
return FALSE;
if (subencoding & rfbHextileRaw) {
if (!ReadFromRFBServer(client, client->buffer, w * h * (BPP / 8)))
return FALSE;
client->GotBitmap(client, (uint8_t *)client->buffer, x, y, w, h);
continue;
}
if (subencoding & rfbHextileBackgroundSpecified)
if (!ReadFromRFBServer(client, (char *)&bg, sizeof(bg)))
return FALSE;
client->GotFillRect(client, x, y, w, h, bg);
if (subencoding & rfbHextileForegroundSpecified)
if (!ReadFromRFBServer(client, (char *)&fg, sizeof(fg)))
return FALSE;
if (!(subencoding & rfbHextileAnySubrects)) {
continue;
}
if (!ReadFromRFBServer(client, (char *)&nSubrects, 1))
return FALSE;
ptr = (uint8_t*)client->buffer;
if (subencoding & rfbHextileSubrectsColoured) {
if (!ReadFromRFBServer(client, client->buffer, nSubrects * (2 + (BPP / 8))))
return FALSE;
for (i = 0; i < nSubrects; i++) {
#if BPP==8
GET_PIXEL8(fg, ptr);
#elif BPP==16
GET_PIXEL16(fg, ptr);
#elif BPP==32
GET_PIXEL32(fg, ptr);
#else
#error "Invalid BPP"
#endif
sx = rfbHextileExtractX(*ptr);
sy = rfbHextileExtractY(*ptr);
ptr++;
sw = rfbHextileExtractW(*ptr);
sh = rfbHextileExtractH(*ptr);
ptr++;
client->GotFillRect(client, x+sx, y+sy, sw, sh, fg);
}
} else {
if (!ReadFromRFBServer(client, client->buffer, nSubrects * 2))
return FALSE;
for (i = 0; i < nSubrects; i++) {
sx = rfbHextileExtractX(*ptr);
sy = rfbHextileExtractY(*ptr);
ptr++;
sw = rfbHextileExtractW(*ptr);
sh = rfbHextileExtractH(*ptr);
ptr++;
client->GotFillRect(client, x+sx, y+sy, sw, sh, fg);
}
}
}
}
return TRUE;
}
#undef CARDBPP

View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/*
* rre.c - handle RRE encoding.
*
* This file shouldn't be compiled directly. It is included multiple times by
* rfbproto.c, each time with a different definition of the macro BPP. For
* each value of BPP, this file defines a function which handles an RRE
* encoded rectangle with BPP bits per pixel.
*/
#define HandleRREBPP CONCAT2E(HandleRRE,BPP)
#define CARDBPP CONCAT3E(uint,BPP,_t)
static rfbBool
HandleRREBPP (rfbClient* client, int rx, int ry, int rw, int rh)
{
rfbRREHeader hdr;
int i;
CARDBPP pix;
rfbRectangle subrect;
if (!ReadFromRFBServer(client, (char *)&hdr, sz_rfbRREHeader))
return FALSE;
hdr.nSubrects = rfbClientSwap32IfLE(hdr.nSubrects);
if (!ReadFromRFBServer(client, (char *)&pix, sizeof(pix)))
return FALSE;
client->GotFillRect(client, rx, ry, rw, rh, pix);
for (i = 0; i < hdr.nSubrects; i++) {
if (!ReadFromRFBServer(client, (char *)&pix, sizeof(pix)))
return FALSE;
if (!ReadFromRFBServer(client, (char *)&subrect, sz_rfbRectangle))
return FALSE;
subrect.x = rfbClientSwap16IfLE(subrect.x);
subrect.y = rfbClientSwap16IfLE(subrect.y);
subrect.w = rfbClientSwap16IfLE(subrect.w);
subrect.h = rfbClientSwap16IfLE(subrect.h);
client->GotFillRect(client, rx+subrect.x, ry+subrect.y, subrect.w, subrect.h, pix);
}
return TRUE;
}
#undef CARDBPP

View File

@ -0,0 +1,670 @@
/*
* Copyright (C) 2017, 2019 D. R. Commander. All Rights Reserved.
* Copyright (C) 2004-2008 Sun Microsystems, Inc. All Rights Reserved.
* Copyright (C) 2004 Landmark Graphics Corporation. All Rights Reserved.
* Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifdef LIBVNCSERVER_HAVE_LIBZ
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
#include "turbojpeg.h"
/*
* tight.c - handle ``tight'' encoding.
*
* This file shouldn't be compiled directly. It is included multiple
* times by rfbproto.c, each time with a different definition of the
* macro BPP. For each value of BPP, this file defines a function
* which handles a tight-encoded rectangle with BPP bits per pixel.
*
*/
#define TIGHT_MIN_TO_COMPRESS 12
#define CARDBPP CONCAT3E(uint,BPP,_t)
#define filterPtrBPP CONCAT2E(filterPtr,BPP)
#define HandleTightBPP CONCAT2E(HandleTight,BPP)
#define InitFilterCopyBPP CONCAT2E(InitFilterCopy,BPP)
#define InitFilterPaletteBPP CONCAT2E(InitFilterPalette,BPP)
#define InitFilterGradientBPP CONCAT2E(InitFilterGradient,BPP)
#define FilterCopyBPP CONCAT2E(FilterCopy,BPP)
#define FilterPaletteBPP CONCAT2E(FilterPalette,BPP)
#define FilterGradientBPP CONCAT2E(FilterGradient,BPP)
#if BPP != 8
#define DecompressJpegRectBPP CONCAT2E(DecompressJpegRect,BPP)
#endif
#ifndef RGB_TO_PIXEL
#define RGB_TO_PIXEL(bpp,r,g,b) \
(((CARD##bpp)(r) & client->format.redMax) << client->format.redShift | \
((CARD##bpp)(g) & client->format.greenMax) << client->format.greenShift | \
((CARD##bpp)(b) & client->format.blueMax) << client->format.blueShift)
#define RGB24_TO_PIXEL(bpp,r,g,b) \
((((CARD##bpp)(r) & 0xFF) * client->format.redMax + 127) / 255 \
<< client->format.redShift | \
(((CARD##bpp)(g) & 0xFF) * client->format.greenMax + 127) / 255 \
<< client->format.greenShift | \
(((CARD##bpp)(b) & 0xFF) * client->format.blueMax + 127) / 255 \
<< client->format.blueShift)
#define RGB24_TO_PIXEL32(r,g,b) \
(((uint32_t)(r) & 0xFF) << client->format.redShift | \
((uint32_t)(g) & 0xFF) << client->format.greenShift | \
((uint32_t)(b) & 0xFF) << client->format.blueShift)
#endif
/* Type declarations */
typedef void (*filterPtrBPP)(rfbClient* client, int, int, int);
/* Prototypes */
static int InitFilterCopyBPP (rfbClient* client, int rw, int rh);
static int InitFilterPaletteBPP (rfbClient* client, int rw, int rh);
static int InitFilterGradientBPP (rfbClient* client, int rw, int rh);
static void FilterCopyBPP (rfbClient* client, int srcx, int srcy, int numRows);
static void FilterPaletteBPP (rfbClient* client, int srcx, int srcy, int numRows);
static void FilterGradientBPP (rfbClient* client, int srcx, int srcy, int numRows);
#if BPP != 8
static rfbBool DecompressJpegRectBPP(rfbClient* client, int x, int y, int w, int h);
#endif
/* Definitions */
static rfbBool
HandleTightBPP (rfbClient* client, int rx, int ry, int rw, int rh)
{
CARDBPP fill_colour;
uint8_t comp_ctl;
uint8_t filter_id;
filterPtrBPP filterFn;
z_streamp zs;
int err, stream_id, compressedLen, bitsPixel;
int bufferSize, rowSize, numRows, portionLen, rowsProcessed, extraBytes;
rfbBool readUncompressed = FALSE;
if (client->frameBuffer == NULL)
return FALSE;
if (rx + rw > client->width || ry + rh > client->height) {
rfbClientLog("Rect out of bounds: %dx%d at (%d, %d)\n", rx, ry, rw, rh);
return FALSE;
}
if (!ReadFromRFBServer(client, (char *)&comp_ctl, 1))
return FALSE;
/* Flush zlib streams if we are told by the server to do so. */
for (stream_id = 0; stream_id < 4; stream_id++) {
if ((comp_ctl & 1) && client->zlibStreamActive[stream_id]) {
if (inflateEnd (&client->zlibStream[stream_id]) != Z_OK &&
client->zlibStream[stream_id].msg != NULL)
rfbClientLog("inflateEnd: %s\n", client->zlibStream[stream_id].msg);
client->zlibStreamActive[stream_id] = FALSE;
}
comp_ctl >>= 1;
}
if ((comp_ctl & rfbTightNoZlib) == rfbTightNoZlib) {
comp_ctl &= ~(rfbTightNoZlib);
readUncompressed = TRUE;
}
/* Handle solid rectangles. */
if (comp_ctl == rfbTightFill) {
#if BPP == 32
if (client->format.depth == 24 && client->format.redMax == 0xFF &&
client->format.greenMax == 0xFF && client->format.blueMax == 0xFF) {
if (!ReadFromRFBServer(client, client->buffer, 3))
return FALSE;
fill_colour = RGB24_TO_PIXEL32(client->buffer[0], client->buffer[1], client->buffer[2]);
} else {
if (!ReadFromRFBServer(client, (char*)&fill_colour, sizeof(fill_colour)))
return FALSE;
}
#else
if (!ReadFromRFBServer(client, (char*)&fill_colour, sizeof(fill_colour)))
return FALSE;
#endif
client->GotFillRect(client, rx, ry, rw, rh, fill_colour);
return TRUE;
}
#if BPP == 8
if (comp_ctl == rfbTightJpeg) {
rfbClientLog("Tight encoding: JPEG is not supported in 8 bpp mode.\n");
return FALSE;
}
#else
if (comp_ctl == rfbTightJpeg) {
return DecompressJpegRectBPP(client, rx, ry, rw, rh);
}
#endif
/* Quit on unsupported subencoding value. */
if (comp_ctl > rfbTightMaxSubencoding) {
rfbClientLog("Tight encoding: bad subencoding value received.\n");
return FALSE;
}
/*
* Here primary compression mode handling begins.
* Data was processed with optional filter + zlib compression.
*/
/* First, we should identify a filter to use. */
if ((comp_ctl & rfbTightExplicitFilter) != 0) {
if (!ReadFromRFBServer(client, (char*)&filter_id, 1))
return FALSE;
switch (filter_id) {
case rfbTightFilterCopy:
filterFn = FilterCopyBPP;
bitsPixel = InitFilterCopyBPP(client, rw, rh);
break;
case rfbTightFilterPalette:
filterFn = FilterPaletteBPP;
bitsPixel = InitFilterPaletteBPP(client, rw, rh);
break;
case rfbTightFilterGradient:
filterFn = FilterGradientBPP;
bitsPixel = InitFilterGradientBPP(client, rw, rh);
break;
default:
rfbClientLog("Tight encoding: unknown filter code received.\n");
return FALSE;
}
} else {
filterFn = FilterCopyBPP;
bitsPixel = InitFilterCopyBPP(client, rw, rh);
}
if (bitsPixel == 0) {
rfbClientLog("Tight encoding: error receiving palette.\n");
return FALSE;
}
/* Determine if the data should be decompressed or just copied. */
rowSize = (rw * bitsPixel + 7) / 8;
if (rh * rowSize < TIGHT_MIN_TO_COMPRESS) {
if (!ReadFromRFBServer(client, (char*)client->buffer, rh * rowSize))
return FALSE;
filterFn(client, rx, ry, rh);
return TRUE;
}
/* Read the length (1..3 bytes) of compressed data following. */
compressedLen = (int)ReadCompactLen(client);
if (compressedLen <= 0) {
rfbClientLog("Incorrect data received from the server.\n");
return FALSE;
}
if (readUncompressed) {
if (compressedLen > RFB_BUFFER_SIZE) {
rfbClientErr("Received uncompressed byte count exceeds our buffer size.\n");
return FALSE;
}
if (!ReadFromRFBServer(client, (char*)client->buffer, compressedLen))
return FALSE;
filterFn(client, rx, ry, rh);
return TRUE;
}
/* Now let's initialize compression stream if needed. */
stream_id = comp_ctl & 0x03;
zs = &client->zlibStream[stream_id];
if (!client->zlibStreamActive[stream_id]) {
zs->zalloc = Z_NULL;
zs->zfree = Z_NULL;
zs->opaque = Z_NULL;
err = inflateInit(zs);
if (err != Z_OK) {
if (zs->msg != NULL)
rfbClientLog("InflateInit error: %s.\n", zs->msg);
return FALSE;
}
client->zlibStreamActive[stream_id] = TRUE;
}
/* Read, decode and draw actual pixel data in a loop. */
bufferSize = RFB_BUFFER_SIZE * bitsPixel / (bitsPixel + BPP) & 0xFFFFFFFC;
if (rowSize > bufferSize) {
/* Should be impossible when RFB_BUFFER_SIZE >= 16384 */
rfbClientLog("Internal error: incorrect buffer size.\n");
return FALSE;
}
rowsProcessed = 0;
extraBytes = 0;
while (compressedLen > 0) {
if (compressedLen > ZLIB_BUFFER_SIZE)
portionLen = ZLIB_BUFFER_SIZE;
else
portionLen = compressedLen;
if (!ReadFromRFBServer(client, (char*)client->zlib_buffer, portionLen))
return FALSE;
compressedLen -= portionLen;
zs->next_in = (Bytef *)client->zlib_buffer;
zs->avail_in = portionLen;
do {
zs->next_out = (Bytef *)&client->buffer[extraBytes];
zs->avail_out = bufferSize - extraBytes;
err = inflate(zs, Z_SYNC_FLUSH);
if (err == Z_BUF_ERROR) /* Input exhausted -- no problem. */
break;
if (err != Z_OK && err != Z_STREAM_END) {
if (zs->msg != NULL) {
rfbClientLog("Inflate error: %s.\n", zs->msg);
} else {
rfbClientLog("Inflate error: %d.\n", err);
}
return FALSE;
}
numRows = (bufferSize - zs->avail_out) / rowSize;
filterFn(client, rx, ry+rowsProcessed, numRows);
extraBytes = bufferSize - zs->avail_out - numRows * rowSize;
if (extraBytes > 0)
memcpy(client->buffer, &client->buffer[numRows * rowSize], extraBytes);
rowsProcessed += numRows;
}
while (zs->avail_out == 0);
}
if (rowsProcessed != rh) {
rfbClientLog("Incorrect number of scan lines after decompression.\n");
return FALSE;
}
return TRUE;
}
/*----------------------------------------------------------------------------
*
* Filter stuff.
*
*/
static int
InitFilterCopyBPP (rfbClient* client, int rw, int rh)
{
client->rectWidth = rw;
#if BPP == 32
if (client->format.depth == 24 && client->format.redMax == 0xFF &&
client->format.greenMax == 0xFF && client->format.blueMax == 0xFF) {
client->cutZeros = TRUE;
return 24;
} else {
client->cutZeros = FALSE;
}
#endif
return BPP;
}
static void
FilterCopyBPP (rfbClient* client, int srcx, int srcy, int numRows)
{
CARDBPP *dst =
(CARDBPP *)&client->frameBuffer[(srcy * client->width + srcx) * BPP / 8];
int y;
#if BPP == 32
int x;
if (client->cutZeros) {
for (y = 0; y < numRows; y++) {
for (x = 0; x < client->rectWidth; x++) {
dst[y*client->width+x] =
RGB24_TO_PIXEL32(client->buffer[(y*client->rectWidth+x)*3],
client->buffer[(y*client->rectWidth+x)*3+1],
client->buffer[(y*client->rectWidth+x)*3+2]);
}
}
return;
}
#endif
for (y = 0; y < numRows; y++)
memcpy (&dst[y*client->width],
&client->buffer[y * client->rectWidth * (BPP / 8)],
client->rectWidth * (BPP / 8));
}
static int
InitFilterGradientBPP (rfbClient* client, int rw, int rh)
{
int bits;
bits = InitFilterCopyBPP(client, rw, rh);
if (client->cutZeros)
memset(client->tightPrevRow, 0, rw * 3);
else
memset(client->tightPrevRow, 0, rw * 3 * sizeof(uint16_t));
return bits;
}
#if BPP == 32
static void
FilterGradient24 (rfbClient* client, int srcx, int srcy, int numRows)
{
CARDBPP *dst =
(CARDBPP *)&client->frameBuffer[(srcy * client->width + srcx) * BPP / 8];
int x, y, c;
uint8_t thisRow[2048*3];
uint8_t pix[3];
int est[3];
for (y = 0; y < numRows; y++) {
/* First pixel in a row */
for (c = 0; c < 3; c++) {
pix[c] = client->tightPrevRow[c] + client->buffer[y*client->rectWidth*3+c];
thisRow[c] = pix[c];
}
dst[y*client->width] = RGB24_TO_PIXEL32(pix[0], pix[1], pix[2]);
/* Remaining pixels of a row */
for (x = 1; x < client->rectWidth; x++) {
for (c = 0; c < 3; c++) {
est[c] = (int)client->tightPrevRow[x*3+c] + (int)pix[c] -
(int)client->tightPrevRow[(x-1)*3+c];
if (est[c] > 0xFF) {
est[c] = 0xFF;
} else if (est[c] < 0x00) {
est[c] = 0x00;
}
pix[c] = (uint8_t)est[c] + client->buffer[(y*client->rectWidth+x)*3+c];
thisRow[x*3+c] = pix[c];
}
dst[y*client->width+x] = RGB24_TO_PIXEL32(pix[0], pix[1], pix[2]);
}
memcpy(client->tightPrevRow, thisRow, client->rectWidth * 3);
}
}
#endif
static void
FilterGradientBPP (rfbClient* client, int srcx, int srcy, int numRows)
{
CARDBPP *dst =
(CARDBPP *)&client->frameBuffer[(srcy * client->width + srcx) * BPP / 8];
int x, y, c;
CARDBPP *src = (CARDBPP *)client->buffer;
uint16_t *thatRow = (uint16_t *)client->tightPrevRow;
uint16_t thisRow[2048*3];
uint16_t pix[3];
uint16_t max[3];
int shift[3];
int est[3];
#if BPP == 32
if (client->cutZeros) {
FilterGradient24(client, srcx, srcy, numRows);
return;
}
#endif
max[0] = client->format.redMax;
max[1] = client->format.greenMax;
max[2] = client->format.blueMax;
shift[0] = client->format.redShift;
shift[1] = client->format.greenShift;
shift[2] = client->format.blueShift;
for (y = 0; y < numRows; y++) {
/* First pixel in a row */
for (c = 0; c < 3; c++) {
pix[c] = (uint16_t)(((src[y*client->rectWidth] >> shift[c]) + thatRow[c]) & max[c]);
thisRow[c] = pix[c];
}
dst[y*client->width] = RGB_TO_PIXEL(BPP, pix[0], pix[1], pix[2]);
/* Remaining pixels of a row */
for (x = 1; x < client->rectWidth; x++) {
for (c = 0; c < 3; c++) {
est[c] = (int)thatRow[x*3+c] + (int)pix[c] - (int)thatRow[(x-1)*3+c];
if (est[c] > (int)max[c]) {
est[c] = (int)max[c];
} else if (est[c] < 0) {
est[c] = 0;
}
pix[c] = (uint16_t)(((src[y*client->rectWidth+x] >> shift[c]) + est[c]) & max[c]);
thisRow[x*3+c] = pix[c];
}
dst[y*client->width+x] = RGB_TO_PIXEL(BPP, pix[0], pix[1], pix[2]);
}
memcpy(thatRow, thisRow, client->rectWidth * 3 * sizeof(uint16_t));
}
}
static int
InitFilterPaletteBPP (rfbClient* client, int rw, int rh)
{
uint8_t numColors;
#if BPP == 32
int i;
CARDBPP *palette = (CARDBPP *)client->tightPalette;
#endif
client->rectWidth = rw;
if (!ReadFromRFBServer(client, (char*)&numColors, 1))
return 0;
client->rectColors = (int)numColors;
if (++client->rectColors < 2)
return 0;
#if BPP == 32
if (client->format.depth == 24 && client->format.redMax == 0xFF &&
client->format.greenMax == 0xFF && client->format.blueMax == 0xFF) {
if (!ReadFromRFBServer(client, (char*)&client->tightPalette, client->rectColors * 3))
return 0;
for (i = client->rectColors - 1; i >= 0; i--) {
palette[i] = RGB24_TO_PIXEL32(client->tightPalette[i*3],
client->tightPalette[i*3+1],
client->tightPalette[i*3+2]);
}
return (client->rectColors == 2) ? 1 : 8;
}
#endif
if (!ReadFromRFBServer(client, (char*)&client->tightPalette, client->rectColors * (BPP / 8)))
return 0;
return (client->rectColors == 2) ? 1 : 8;
}
static void
FilterPaletteBPP (rfbClient* client, int srcx, int srcy, int numRows)
{
int x, y, b, w;
CARDBPP *dst =
(CARDBPP *)&client->frameBuffer[(srcy * client->width + srcx) * BPP / 8];
uint8_t *src = (uint8_t *)client->buffer;
CARDBPP *palette = (CARDBPP *)client->tightPalette;
if (client->rectColors == 2) {
w = (client->rectWidth + 7) / 8;
for (y = 0; y < numRows; y++) {
for (x = 0; x < client->rectWidth / 8; x++) {
for (b = 7; b >= 0; b--)
dst[y*client->width+x*8+7-b] = palette[src[y*w+x] >> b & 1];
}
for (b = 7; b >= 8 - client->rectWidth % 8; b--) {
dst[y*client->width+x*8+7-b] = palette[src[y*w+x] >> b & 1];
}
}
} else {
for (y = 0; y < numRows; y++)
for (x = 0; x < client->rectWidth; x++)
dst[y*client->width+x] = palette[(int)src[y*client->rectWidth+x]];
}
}
#if BPP != 8
/*----------------------------------------------------------------------------
*
* JPEG decompression.
*
*/
static rfbBool
DecompressJpegRectBPP(rfbClient* client, int x, int y, int w, int h)
{
int compressedLen;
uint8_t *compressedData, *dst;
int pixelSize, pitch, flags = 0;
compressedLen = (int)ReadCompactLen(client);
if (compressedLen <= 0) {
rfbClientLog("Incorrect data received from the server.\n");
return FALSE;
}
compressedData = malloc(compressedLen);
if (compressedData == NULL) {
rfbClientLog("Memory allocation error.\n");
return FALSE;
}
if (!ReadFromRFBServer(client, (char*)compressedData, compressedLen)) {
free(compressedData);
return FALSE;
}
if(client->GotJpeg != NULL)
return client->GotJpeg(client, compressedData, compressedLen, x, y, w, h);
if (!client->tjhnd) {
if ((client->tjhnd = tjInitDecompress()) == NULL) {
rfbClientLog("TurboJPEG error: %s\n", tjGetErrorStr());
free(compressedData);
return FALSE;
}
}
#if BPP == 16
flags = 0;
pixelSize = 3;
pitch = w * pixelSize;
dst = (uint8_t *)client->buffer;
#else
if (client->format.bigEndian) flags |= TJ_ALPHAFIRST;
if (client->format.redShift == 16 && client->format.blueShift == 0)
flags |= TJ_BGR;
if (client->format.bigEndian) flags ^= TJ_BGR;
pixelSize = BPP / 8;
pitch = client->width * pixelSize;
dst = &client->frameBuffer[y * pitch + x * pixelSize];
#endif
if (tjDecompress(client->tjhnd, compressedData, (unsigned long)compressedLen,
dst, w, pitch, h, pixelSize, flags)==-1) {
rfbClientLog("TurboJPEG error: %s\n", tjGetErrorStr());
free(compressedData);
return FALSE;
}
free(compressedData);
#if BPP == 16
pixelSize = BPP / 8;
pitch = client->width * pixelSize;
dst = &client->frameBuffer[y * pitch + x * pixelSize];
{
CARDBPP *dst16=(CARDBPP *)dst, *dst2;
char *src = client->buffer;
int i, j;
for (j = 0; j < h; j++) {
for (i = 0, dst2 = dst16; i < w; i++, dst2++, src += 3) {
*dst2 = RGB24_TO_PIXEL(BPP, src[0], src[1], src[2]);
}
dst16 += client->width;
}
}
#endif
return TRUE;
}
#else
static long
ReadCompactLen (rfbClient* client)
{
long len;
uint8_t b;
if (!ReadFromRFBServer(client, (char *)&b, 1))
return -1;
len = (int)b & 0x7F;
if (b & 0x80) {
if (!ReadFromRFBServer(client, (char *)&b, 1))
return -1;
len |= ((int)b & 0x7F) << 7;
if (b & 0x80) {
if (!ReadFromRFBServer(client, (char *)&b, 1))
return -1;
len |= ((int)b & 0xFF) << 14;
}
}
return len;
}
#endif
#undef CARDBPP
/* LIBVNCSERVER_HAVE_LIBZ and LIBVNCSERVER_HAVE_LIBJPEG */
#endif
#endif

View File

@ -0,0 +1,299 @@
/*
* Copyright (C) 2017 Wiki Wang <wikiwang@live.com>. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/*
* trle.c - handle trle encoding.
*
* This file shouldn't be compiled directly. It is included multiple times by
* rfbproto.c, each time with a different definition of the macro BPP. For
* each value of BPP, this file defines a function which handles a trle
* encoded rectangle with BPP bits per pixel.
*/
#ifndef REALBPP
#define REALBPP BPP
#endif
#if !defined(UNCOMP) || UNCOMP == 0
#define HandleTRLE CONCAT2E(HandleTRLE, REALBPP)
#elif UNCOMP > 0
#define HandleTRLE CONCAT3E(HandleTRLE, REALBPP, Down)
#else
#define HandleTRLE CONCAT3E(HandleTRLE, REALBPP, Up)
#endif
#define CARDBPP CONCAT3E(uint, BPP, _t)
#define CARDREALBPP CONCAT3E(uint, REALBPP, _t)
#if REALBPP != BPP && defined(UNCOMP) && UNCOMP != 0
#if UNCOMP > 0
#define UncompressCPixel(pointer) ((*(CARDBPP *)pointer) >> UNCOMP)
#else
#define UncompressCPixel(pointer) ((*(CARDBPP *)pointer) << (-(UNCOMP)))
#endif
#else
#define UncompressCPixel(pointer) (*(CARDBPP *)pointer)
#endif
static rfbBool HandleTRLE(rfbClient *client, int rx, int ry, int rw, int rh) {
int x, y, w, h;
uint8_t type, last_type = 0;
int min_buffer_size = 16 * 16 * (REALBPP / 8) * 2;
uint8_t *buffer;
CARDBPP palette[128];
int bpp = 0, mask = 0, divider = 0;
CARDBPP color = 0;
/* First make sure we have a large enough raw buffer to hold the
* decompressed data. In practice, with a fixed REALBPP, fixed frame
* buffer size and the first update containing the entire frame
* buffer, this buffer allocation should only happen once, on the
* first update.
*/
if (client->raw_buffer_size < min_buffer_size) {
if (client->raw_buffer != NULL) {
free(client->raw_buffer);
}
client->raw_buffer_size = min_buffer_size;
client->raw_buffer = (char *)malloc(client->raw_buffer_size);
}
rfbClientLog("Update %d %d %d %d\n", rx, ry, rw, rh);
for (y = ry; y < ry + rh; y += 16) {
for (x = rx; x < rx + rw; x += 16) {
w = h = 16;
if (rx + rw - x < 16)
w = rx + rw - x;
if (ry + rh - y < 16)
h = ry + rh - y;
if (!ReadFromRFBServer(client, (char *)(&type), 1))
return FALSE;
buffer = (uint8_t*)(client->raw_buffer);
switch (type) {
case 0: {
if (!ReadFromRFBServer(client, (char *)buffer, w * h * REALBPP / 8))
return FALSE;
#if REALBPP != BPP
int i, j;
for (j = y * client->width; j < (y + h) * client->width;
j += client->width)
for (i = x; i < x + w; i++, buffer += REALBPP / 8)
((CARDBPP *)client->frameBuffer)[j + i] = UncompressCPixel(buffer);
#else
client->GotBitmap(client, buffer, x, y, w, h);
#endif
type = last_type;
break;
}
case 1: {
if (!ReadFromRFBServer(client, (char *)buffer, REALBPP / 8))
return FALSE;
color = UncompressCPixel(buffer);
client->GotFillRect(client, x, y, w, h, color);
last_type = type;
break;
}
case_127:
case 127:
switch (last_type) {
case 0:
return FALSE;
case 1:
client->GotFillRect(client, x, y, w, h, color);
type = last_type;
break;
case 128:
return FALSE;
default:
if (last_type >= 130) {
last_type = last_type & 0x7f;
bpp = (last_type > 4 ? (last_type > 16 ? 8 : 4)
: (last_type > 2 ? 2 : 1)),
mask = (1 << bpp) - 1, divider = (8 / bpp);
}
if (last_type <= 16) {
int i, j, shift;
if (!ReadFromRFBServer(client, (char*)buffer,
(w + divider - 1) / divider * h))
return FALSE;
/* read palettized pixels */
for (j = y * client->width; j < (y + h) * client->width;
j += client->width) {
for (i = x, shift = 8 - bpp; i < x + w; i++) {
((CARDBPP *)client->frameBuffer)[j + i] =
palette[((*buffer) >> shift) & mask];
shift -= bpp;
if (shift < 0) {
shift = 8 - bpp;
buffer++;
}
}
if (shift < 8 - bpp)
buffer++;
type = last_type;
}
} else
return FALSE;
}
break;
case 128: {
int i = 0, j = 0;
while (j < h) {
int color, length, buffer_pos = 0;
/* read color */
if (!ReadFromRFBServer(client, (char*)buffer, REALBPP / 8 + 1))
return FALSE;
color = UncompressCPixel(buffer);
buffer += REALBPP / 8;
buffer_pos += REALBPP / 8;
/* read run length */
length = 1;
while (*buffer == 0xff && buffer_pos < client->raw_buffer_size-1) {
if (!ReadFromRFBServer(client, (char*)buffer + 1, 1))
return FALSE;
length += *buffer;
buffer++;
buffer_pos++;
}
length += *buffer;
buffer++;
while (j < h && length > 0) {
((CARDBPP *)client->frameBuffer)[(y + j) * client->width + x + i] =
color;
length--;
i++;
if (i >= w) {
i = 0;
j++;
}
}
if (length > 0)
rfbClientLog("Warning: possible TRLE corruption\n");
}
type = last_type;
break;
}
case_129:
case 129: {
int i, j;
/* read palettized pixels */
i = j = 0;
while (j < h) {
int color, length, buffer_pos = 0;
/* read color */
if (!ReadFromRFBServer(client, (char *)buffer, 1))
return FALSE;
color = palette[(*buffer) & 0x7f];
length = 1;
if (*buffer & 0x80) {
if (!ReadFromRFBServer(client, (char *)buffer + 1, 1))
return FALSE;
buffer++;
buffer_pos++;
/* read run length */
while (*buffer == 0xff && buffer_pos < client->raw_buffer_size-1) {
if (!ReadFromRFBServer(client, (char *)buffer + 1, 1))
return FALSE;
length += *buffer;
buffer++;
buffer_pos++;
}
length += *buffer;
}
buffer++;
while (j < h && length > 0) {
((CARDBPP *)client->frameBuffer)[(y + j) * client->width + x + i] =
color;
length--;
i++;
if (i >= w) {
i = 0;
j++;
}
}
if (length > 0)
rfbClientLog("Warning: possible TRLE corruption\n");
}
if (type == 129) {
type = last_type;
}
break;
}
default:
if (type <= 16) {
int i;
bpp = (type > 4 ? 4 : (type > 2 ? 2 : 1)),
mask = (1 << bpp) - 1, divider = (8 / bpp);
if (!ReadFromRFBServer(client, (char *)buffer, type * REALBPP / 8))
return FALSE;
/* read palette */
for (i = 0; i < type; i++, buffer += REALBPP / 8)
palette[i] = UncompressCPixel(buffer);
last_type = type;
goto case_127;
} else if (type >= 130) {
int i;
if (!ReadFromRFBServer(client, (char *)buffer, (type - 128) * REALBPP / 8))
return FALSE;
/* read palette */
for (i = 0; i < type - 128; i++, buffer += REALBPP / 8)
palette[i] = UncompressCPixel(buffer);
last_type = type;
goto case_129;
} else
return FALSE;
}
last_type = type;
}
}
return TRUE;
}
#undef CARDBPP
#undef CARDREALBPP
#undef HandleTRLE
#undef UncompressCPixel
#undef REALBPP
#undef UNCOMP

View File

@ -0,0 +1,224 @@
/*
* Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/*
* ultrazip.c - handle ultrazip encoding.
*
* This file shouldn't be compiled directly. It is included multiple times by
* rfbproto.c, each time with a different definition of the macro BPP. For
* each value of BPP, this file defines a function which handles an zlib
* encoded rectangle with BPP bits per pixel.
*/
#define HandleUltraZipBPP CONCAT2E(HandleUltraZip,BPP)
#define HandleUltraBPP CONCAT2E(HandleUltra,BPP)
#define CARDBPP CONCAT3E(uint,BPP,_t)
static rfbBool
HandleUltraBPP (rfbClient* client, int rx, int ry, int rw, int rh)
{
rfbZlibHeader hdr;
int toRead=0;
int inflateResult=0;
lzo_uint uncompressedBytes = (( rw * rh ) * ( BPP / 8 ));
if (!ReadFromRFBServer(client, (char *)&hdr, sz_rfbZlibHeader))
return FALSE;
toRead = rfbClientSwap32IfLE(hdr.nBytes);
if (toRead==0) return TRUE;
if (toRead < 0) {
rfbClientErr("ultra error: remote sent negative payload size\n");
return FALSE;
}
if (uncompressedBytes==0)
{
rfbClientLog("ultra error: rectangle has 0 uncomressed bytes ((%dw * %dh) * (%d / 8))\n", rw, rh, BPP);
return FALSE;
}
/* First make sure we have a large enough raw buffer to hold the
* decompressed data. In practice, with a fixed BPP, fixed frame
* buffer size and the first update containing the entire frame
* buffer, this buffer allocation should only happen once, on the
* first update.
*/
if ( client->raw_buffer_size < (int)uncompressedBytes) {
if ( client->raw_buffer != NULL ) {
free( client->raw_buffer );
}
client->raw_buffer_size = uncompressedBytes;
/* buffer needs to be aligned on 4-byte boundaries */
if ((client->raw_buffer_size % 4)!=0)
client->raw_buffer_size += (4-(client->raw_buffer_size % 4));
client->raw_buffer = (char*) malloc( client->raw_buffer_size );
if(client->raw_buffer == NULL)
return FALSE;
}
/* allocate enough space to store the incoming compressed packet */
if ( client->ultra_buffer_size < toRead ) {
if ( client->ultra_buffer != NULL ) {
free( client->ultra_buffer );
}
client->ultra_buffer_size = toRead;
/* buffer needs to be aligned on 4-byte boundaries */
if ((client->ultra_buffer_size % 4)!=0)
client->ultra_buffer_size += (4-(client->ultra_buffer_size % 4));
client->ultra_buffer = (char*) malloc( client->ultra_buffer_size );
}
/* Fill the buffer, obtaining data from the server. */
if (!ReadFromRFBServer(client, client->ultra_buffer, toRead))
return FALSE;
/* uncompress the data */
uncompressedBytes = client->raw_buffer_size;
inflateResult = lzo1x_decompress_safe(
(lzo_byte *)client->ultra_buffer, toRead,
(lzo_byte *)client->raw_buffer, (lzo_uintp) &uncompressedBytes,
NULL);
/* Note that uncompressedBytes will be 0 on output overrun */
if ((rw * rh * (BPP / 8)) != uncompressedBytes)
rfbClientLog("Ultra decompressed unexpected amount of data (%d != %d)\n", (rw * rh * (BPP / 8)), uncompressedBytes);
/* Put the uncompressed contents of the update on the screen. */
if ( inflateResult == LZO_E_OK )
{
client->GotBitmap(client, (unsigned char *)client->raw_buffer, rx, ry, rw, rh);
}
else
{
rfbClientLog("ultra decompress returned error: %d\n",
inflateResult);
return FALSE;
}
return TRUE;
}
/* UltraZip is like rre in that it is composed of subrects */
static rfbBool
HandleUltraZipBPP (rfbClient* client, int rx, int ry, int rw, int rh)
{
rfbZlibHeader hdr;
int i=0;
int toRead=0;
int inflateResult=0;
unsigned char *ptr=NULL;
lzo_uint uncompressedBytes = ry + (rw * 65535);
unsigned int numCacheRects = rx;
if (!ReadFromRFBServer(client, (char *)&hdr, sz_rfbZlibHeader))
return FALSE;
toRead = rfbClientSwap32IfLE(hdr.nBytes);
if (toRead==0) return TRUE;
if (toRead < 0) {
rfbClientErr("ultrazip error: remote sent negative payload size\n");
return FALSE;
}
if (uncompressedBytes==0)
{
rfbClientLog("ultrazip error: rectangle has 0 uncomressed bytes (%dy + (%dw * 65535)) (%d rectangles)\n", ry, rw, rx);
return FALSE;
}
/* First make sure we have a large enough raw buffer to hold the
* decompressed data. In practice, with a fixed BPP, fixed frame
* buffer size and the first update containing the entire frame
* buffer, this buffer allocation should only happen once, on the
* first update.
*/
if ( client->raw_buffer_size < (int)(uncompressedBytes + 500)) {
if ( client->raw_buffer != NULL ) {
free( client->raw_buffer );
}
client->raw_buffer_size = uncompressedBytes + 500;
/* buffer needs to be aligned on 4-byte boundaries */
if ((client->raw_buffer_size % 4)!=0)
client->raw_buffer_size += (4-(client->raw_buffer_size % 4));
client->raw_buffer = (char*) malloc( client->raw_buffer_size );
if(client->raw_buffer == NULL)
return FALSE;
}
/* allocate enough space to store the incoming compressed packet */
if ( client->ultra_buffer_size < toRead ) {
if ( client->ultra_buffer != NULL ) {
free( client->ultra_buffer );
}
client->ultra_buffer_size = toRead;
client->ultra_buffer = (char*) malloc( client->ultra_buffer_size );
}
/* Fill the buffer, obtaining data from the server. */
if (!ReadFromRFBServer(client, client->ultra_buffer, toRead))
return FALSE;
/* uncompress the data */
uncompressedBytes = client->raw_buffer_size;
inflateResult = lzo1x_decompress_safe(
(lzo_byte *)client->ultra_buffer, toRead,
(lzo_byte *)client->raw_buffer, &uncompressedBytes, NULL);
if ( inflateResult != LZO_E_OK )
{
rfbClientLog("ultra decompress returned error: %d\n",
inflateResult);
return FALSE;
}
/* Put the uncompressed contents of the update on the screen. */
ptr = (unsigned char *)client->raw_buffer;
for (i=0; i<numCacheRects; i++)
{
unsigned short sx, sy, sw, sh;
unsigned int se;
memcpy((char *)&sx, ptr, 2); ptr += 2;
memcpy((char *)&sy, ptr, 2); ptr += 2;
memcpy((char *)&sw, ptr, 2); ptr += 2;
memcpy((char *)&sh, ptr, 2); ptr += 2;
memcpy((char *)&se, ptr, 4); ptr += 4;
sx = rfbClientSwap16IfLE(sx);
sy = rfbClientSwap16IfLE(sy);
sw = rfbClientSwap16IfLE(sw);
sh = rfbClientSwap16IfLE(sh);
se = rfbClientSwap32IfLE(se);
if (se == rfbEncodingRaw)
{
client->GotBitmap(client, (unsigned char *)ptr, sx, sy, sw, sh);
ptr += ((sw * sh) * (BPP / 8));
}
}
return TRUE;
}
#undef CARDBPP

View File

@ -0,0 +1,191 @@
/*
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/*
* vncauth.c - Functions for VNC password management and authentication.
*/
#include "rfbproto.h"
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "crypto.h"
#include <string.h>
#include <math.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/time.h>
/* libvncclient does not need this */
#ifndef rfbEncryptBytes
/*
* We use a fixed key to store passwords, since we assume that our local
* file system is secure but nonetheless don't want to store passwords
* as plaintext.
*/
static unsigned char fixedkey[8] = {23,82,107,6,35,78,88,7};
/*
* Encrypt a password and store it in a file. Returns 0 if successful,
* 1 if the file could not be written.
*/
int
rfbEncryptAndStorePasswd(char *passwd, char *fname)
{
FILE *fp;
unsigned int i;
unsigned char encryptedPasswd[8];
int out_len;
if ((fp = fopen(fname,"w")) == NULL) return 1;
fchmod(fileno(fp), S_IRUSR|S_IWUSR);
/* pad password with nulls */
for (i = 0; i < 8; i++) {
if (i < strlen(passwd)) {
encryptedPasswd[i] = passwd[i];
} else {
encryptedPasswd[i] = 0;
}
}
/* Do encryption in-place - this way we overwrite our copy of the plaintext
password */
encrypt_rfbdes(encryptedPasswd, &out_len, fixedkey, encryptedPasswd, sizeof(encryptedPasswd));
for (i = 0; i < 8; i++) {
putc(encryptedPasswd[i], fp);
}
fclose(fp);
return 0;
}
/*
* Decrypt a password from a file. Returns a pointer to a newly allocated
* string containing the password or a null pointer if the password could
* not be retrieved for some reason.
*/
char *
rfbDecryptPasswdFromFile(char *fname)
{
FILE *fp;
int i, ch;
unsigned char *passwd = (unsigned char *)malloc(9);
int out_len;
if (!passwd || (fp = fopen(fname,"r")) == NULL) {
free(passwd);
return NULL;
}
for (i = 0; i < 8; i++) {
ch = getc(fp);
if (ch == EOF) {
fclose(fp);
free(passwd);
return NULL;
}
passwd[i] = ch;
}
fclose(fp);
if(!decrypt_rfbdes(passwd, &out_len, fixedkey, passwd, 8))
return NULL;
passwd[8] = 0;
return (char *)passwd;
}
/*
* Generate CHALLENGESIZE random bytes for use in challenge-response
* authentication.
*/
void
rfbRandomBytes(unsigned char *bytes)
{
int i;
static rfbBool s_srandom_called = FALSE;
if (!s_srandom_called) {
srandom((unsigned int)time(NULL) ^ (unsigned int)getpid());
s_srandom_called = TRUE;
}
for (i = 0; i < CHALLENGESIZE; i++) {
bytes[i] = (unsigned char)(random() & 255);
}
}
#endif
/*
* Encrypt CHALLENGESIZE bytes in memory using a password.
*/
void
rfbEncryptBytes(unsigned char *bytes, char *passwd)
{
unsigned char key[8];
unsigned int i;
int out_len;
/* key is simply password padded with nulls */
for (i = 0; i < 8; i++) {
if (i < strlen(passwd)) {
key[i] = passwd[i];
} else {
key[i] = 0;
}
}
encrypt_rfbdes(bytes, &out_len, key, bytes, CHALLENGESIZE);
}
void
rfbEncryptBytes2(unsigned char *where, const int length, unsigned char *key) {
int i, j, out_len;
for (i = 0; i< 8; i++)
where[i] ^= key[i];
encrypt_rfbdes(where, &out_len, key, where, 8);
for (i = 8; i < length; i += 8) {
for (j = 0; j < 8; j++) {
where[i + j] ^= where[i + j - 8];
}
encrypt_rfbdes(where + i, &out_len, key, where + i, 8);
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifdef LIBVNCSERVER_HAVE_LIBZ
/*
* zlib.c - handle zlib encoding.
*
* This file shouldn't be compiled directly. It is included multiple times by
* rfbproto.c, each time with a different definition of the macro BPP. For
* each value of BPP, this file defines a function which handles an zlib
* encoded rectangle with BPP bits per pixel.
*/
#define HandleZlibBPP CONCAT2E(HandleZlib,BPP)
#define CARDBPP CONCAT3E(uint,BPP,_t)
static rfbBool
HandleZlibBPP (rfbClient* client, int rx, int ry, int rw, int rh)
{
rfbZlibHeader hdr;
int remaining;
int inflateResult;
int toRead;
/* First make sure we have a large enough raw buffer to hold the
* decompressed data. In practice, with a fixed BPP, fixed frame
* buffer size and the first update containing the entire frame
* buffer, this buffer allocation should only happen once, on the
* first update.
*/
if ( client->raw_buffer_size < (( rw * rh ) * ( BPP / 8 ))) {
if ( client->raw_buffer != NULL ) {
free( client->raw_buffer );
}
client->raw_buffer_size = (( rw * rh ) * ( BPP / 8 ));
client->raw_buffer = (char*) malloc( client->raw_buffer_size );
}
if (!ReadFromRFBServer(client, (char *)&hdr, sz_rfbZlibHeader))
return FALSE;
remaining = rfbClientSwap32IfLE(hdr.nBytes);
/* Need to initialize the decompressor state. */
client->decompStream.next_in = ( Bytef * )client->buffer;
client->decompStream.avail_in = 0;
client->decompStream.next_out = ( Bytef * )client->raw_buffer;
client->decompStream.avail_out = client->raw_buffer_size;
client->decompStream.data_type = Z_BINARY;
/* Initialize the decompression stream structures on the first invocation. */
if ( client->decompStreamInited == FALSE ) {
inflateResult = inflateInit( &client->decompStream );
if ( inflateResult != Z_OK ) {
rfbClientLog(
"inflateInit returned error: %d, msg: %s\n",
inflateResult,
client->decompStream.msg);
return FALSE;
}
client->decompStreamInited = TRUE;
}
inflateResult = Z_OK;
/* Process buffer full of data until no more to process, or
* some type of inflater error, or Z_STREAM_END.
*/
while (( remaining > 0 ) &&
( inflateResult == Z_OK )) {
if ( remaining > RFB_BUFFER_SIZE ) {
toRead = RFB_BUFFER_SIZE;
}
else {
toRead = remaining;
}
/* Fill the buffer, obtaining data from the server. */
if (!ReadFromRFBServer(client, client->buffer,toRead))
return FALSE;
client->decompStream.next_in = ( Bytef * )client->buffer;
client->decompStream.avail_in = toRead;
/* Need to uncompress buffer full. */
inflateResult = inflate( &client->decompStream, Z_SYNC_FLUSH );
/* We never supply a dictionary for compression. */
if ( inflateResult == Z_NEED_DICT ) {
rfbClientLog("zlib inflate needs a dictionary!\n");
return FALSE;
}
if ( inflateResult < 0 ) {
rfbClientLog(
"zlib inflate returned error: %d, msg: %s\n",
inflateResult,
client->decompStream.msg);
return FALSE;
}
/* Result buffer allocated to be at least large enough. We should
* never run out of space!
*/
if (( client->decompStream.avail_in > 0 ) &&
( client->decompStream.avail_out <= 0 )) {
rfbClientLog("zlib inflate ran out of space!\n");
return FALSE;
}
remaining -= toRead;
} /* while ( remaining > 0 ) */
if ( inflateResult == Z_OK ) {
/* Put the uncompressed contents of the update on the screen. */
client->GotBitmap(client, (uint8_t *)client->raw_buffer, rx, ry, rw, rh);
}
else {
rfbClientLog(
"zlib inflate returned error: %d, msg: %s\n",
inflateResult,
client->decompStream.msg);
return FALSE;
}
return TRUE;
}
#undef CARDBPP
#endif

View File

@ -0,0 +1,427 @@
/*
* Copyright (C) 2005 Johannes E. Schindelin. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifdef LIBVNCSERVER_HAVE_LIBZ
/*
* zrle.c - handle zrle encoding.
*
* This file shouldn't be compiled directly. It is included multiple times by
* rfbproto.c, each time with a different definition of the macro BPP. For
* each value of BPP, this file defines a function which handles an zrle
* encoded rectangle with BPP bits per pixel.
*/
#ifndef REALBPP
#define REALBPP BPP
#endif
#if !defined(UNCOMP) || UNCOMP==0
#define HandleZRLE CONCAT2E(HandleZRLE,REALBPP)
#define HandleZRLETile CONCAT2E(HandleZRLETile,REALBPP)
#elif UNCOMP>0
#define HandleZRLE CONCAT3E(HandleZRLE,REALBPP,Down)
#define HandleZRLETile CONCAT3E(HandleZRLETile,REALBPP,Down)
#else
#define HandleZRLE CONCAT3E(HandleZRLE,REALBPP,Up)
#define HandleZRLETile CONCAT3E(HandleZRLETile,REALBPP,Up)
#endif
#define CARDBPP CONCAT3E(uint,BPP,_t)
#define CARDREALBPP CONCAT3E(uint,REALBPP,_t)
#define ENDIAN_LITTLE 0
#define ENDIAN_BIG 1
#define ENDIAN_NO 2
#define ZYWRLE_ENDIAN ENDIAN_LITTLE
#undef END_FIX
#if ZYWRLE_ENDIAN == ENDIAN_LITTLE
# define END_FIX LE
#elif ZYWRLE_ENDIAN == ENDIAN_BIG
# define END_FIX BE
#else
# define END_FIX NE
#endif
#define __RFB_CONCAT3E(a,b,c) CONCAT3E(a,b,c)
#define __RFB_CONCAT2E(a,b) CONCAT2E(a,b)
#undef CPIXEL
#if REALBPP != BPP
#if UNCOMP == 0
#define CPIXEL REALBPP
#elif UNCOMP>0
#define CPIXEL CONCAT2E(REALBPP,Down)
#else
#define CPIXEL CONCAT2E(REALBPP,Up)
#endif
#endif
#define PIXEL_T __RFB_CONCAT3E(uint,BPP,_t)
#if BPP!=8
#define ZYWRLE_DECODE 1
#include "zywrletemplate.c"
#endif
#undef CPIXEL
static int HandleZRLETile(rfbClient* client,
uint8_t* buffer,size_t buffer_length,
int x,int y,int w,int h);
static rfbBool
HandleZRLE (rfbClient* client, int rx, int ry, int rw, int rh)
{
rfbZRLEHeader header;
int remaining;
int inflateResult;
int toRead;
int min_buffer_size = rw * rh * (REALBPP / 8) * 2;
/* First make sure we have a large enough raw buffer to hold the
* decompressed data. In practice, with a fixed REALBPP, fixed frame
* buffer size and the first update containing the entire frame
* buffer, this buffer allocation should only happen once, on the
* first update.
*/
if ( client->raw_buffer_size < min_buffer_size) {
if ( client->raw_buffer != NULL ) {
free( client->raw_buffer );
}
client->raw_buffer_size = min_buffer_size;
client->raw_buffer = (char*) malloc( client->raw_buffer_size );
}
if (!ReadFromRFBServer(client, (char *)&header, sz_rfbZRLEHeader))
return FALSE;
remaining = rfbClientSwap32IfLE(header.length);
/* Need to initialize the decompressor state. */
client->decompStream.next_in = ( Bytef * )client->buffer;
client->decompStream.avail_in = 0;
client->decompStream.next_out = ( Bytef * )client->raw_buffer;
client->decompStream.avail_out = client->raw_buffer_size;
client->decompStream.data_type = Z_BINARY;
/* Initialize the decompression stream structures on the first invocation. */
if ( client->decompStreamInited == FALSE ) {
inflateResult = inflateInit( &client->decompStream );
if ( inflateResult != Z_OK ) {
rfbClientLog(
"inflateInit returned error: %d, msg: %s\n",
inflateResult,
client->decompStream.msg);
return FALSE;
}
client->decompStreamInited = TRUE;
}
inflateResult = Z_OK;
/* Process buffer full of data until no more to process, or
* some type of inflater error, or Z_STREAM_END.
*/
while (( remaining > 0 ) &&
( inflateResult == Z_OK )) {
if ( remaining > RFB_BUFFER_SIZE ) {
toRead = RFB_BUFFER_SIZE;
}
else {
toRead = remaining;
}
/* Fill the buffer, obtaining data from the server. */
if (!ReadFromRFBServer(client, client->buffer,toRead))
return FALSE;
client->decompStream.next_in = ( Bytef * )client->buffer;
client->decompStream.avail_in = toRead;
/* Need to uncompress buffer full. */
inflateResult = inflate( &client->decompStream, Z_SYNC_FLUSH );
/* We never supply a dictionary for compression. */
if ( inflateResult == Z_NEED_DICT ) {
rfbClientLog("zlib inflate needs a dictionary!\n");
return FALSE;
}
if ( inflateResult < 0 ) {
rfbClientLog(
"zlib inflate returned error: %d, msg: %s\n",
inflateResult,
client->decompStream.msg);
return FALSE;
}
/* Result buffer allocated to be at least large enough. We should
* never run out of space!
*/
if (( client->decompStream.avail_in > 0 ) &&
( client->decompStream.avail_out <= 0 )) {
rfbClientLog("zlib inflate ran out of space!\n");
return FALSE;
}
remaining -= toRead;
} /* while ( remaining > 0 ) */
if ( inflateResult == Z_OK ) {
char* buf=client->raw_buffer;
int i,j;
remaining = client->raw_buffer_size-client->decompStream.avail_out;
for(j=0; j<rh; j+=rfbZRLETileHeight)
for(i=0; i<rw; i+=rfbZRLETileWidth) {
int subWidth=(i+rfbZRLETileWidth>rw)?rw-i:rfbZRLETileWidth;
int subHeight=(j+rfbZRLETileHeight>rh)?rh-j:rfbZRLETileHeight;
int result=HandleZRLETile(client,(uint8_t *)buf,remaining,rx+i,ry+j,subWidth,subHeight);
if(result<0) {
rfbClientLog("ZRLE decoding failed (%d)\n",result);
return TRUE;
return FALSE;
}
buf+=result;
remaining-=result;
}
}
else {
rfbClientLog(
"zlib inflate returned error: %d, msg: %s\n",
inflateResult,
client->decompStream.msg);
return FALSE;
}
return TRUE;
}
#if REALBPP!=BPP && defined(UNCOMP) && UNCOMP!=0
#if UNCOMP>0
#define UncompressCPixel(pointer) ((*(CARDBPP*)pointer)>>UNCOMP)
#else
#define UncompressCPixel(pointer) ((*(CARDBPP*)pointer)<<(-(UNCOMP)))
#endif
#else
#define UncompressCPixel(pointer) (*(CARDBPP*)pointer)
#endif
static int HandleZRLETile(rfbClient* client,
uint8_t* buffer,size_t buffer_length,
int x,int y,int w,int h) {
uint8_t* buffer_copy = buffer;
uint8_t* buffer_end = buffer+buffer_length;
uint8_t type;
#if BPP!=8
uint8_t zywrle_level = (client->appData.qualityLevel & 0x80) ?
0 : (3 - client->appData.qualityLevel / 3);
#endif
if(buffer_length<1)
return -2;
type = *buffer;
buffer++;
{
if( type == 0 ) /* raw */
#if BPP!=8
if( zywrle_level > 0 ){
CARDBPP* pFrame = (CARDBPP*)client->frameBuffer + y*client->width+x;
int ret;
client->appData.qualityLevel |= 0x80;
ret = HandleZRLETile(client, buffer, buffer_end-buffer, x, y, w, h);
client->appData.qualityLevel &= 0x7F;
if( ret < 0 ){
return ret;
}
ZYWRLE_SYNTHESIZE( pFrame, pFrame, w, h, client->width, zywrle_level, (int*)client->zlib_buffer );
buffer += ret;
}else
#endif
{
#if REALBPP!=BPP
int i,j;
if(1+w*h*REALBPP/8>buffer_length) {
rfbClientLog("expected %d bytes, got only %d (%dx%d)\n",1+w*h*REALBPP/8,buffer_length,w,h);
return -3;
}
for(j=y*client->width; j<(y+h)*client->width; j+=client->width)
for(i=x; i<x+w; i++,buffer+=REALBPP/8)
((CARDBPP*)client->frameBuffer)[j+i] = UncompressCPixel(buffer);
#else
client->GotBitmap(client, buffer, x, y, w, h);
buffer+=w*h*REALBPP/8;
#endif
}
else if( type == 1 ) /* solid */
{
CARDBPP color = UncompressCPixel(buffer);
if(1+REALBPP/8>buffer_length)
return -4;
client->GotFillRect(client, x, y, w, h, color);
buffer+=REALBPP/8;
}
else if( type <= 127 ) /* packed Palette */
{
CARDBPP palette[128];
int i,j,shift,
bpp=(type>4?(type>16?8:4):(type>2?2:1)),
mask=(1<<bpp)-1,
divider=(8/bpp);
if(1+type*REALBPP/8+((w+divider-1)/divider)*h>buffer_length)
return -5;
/* read palette */
for(i=0; i<type; i++,buffer+=REALBPP/8)
palette[i] = UncompressCPixel(buffer);
/* read palettized pixels */
for(j=y*client->width; j<(y+h)*client->width; j+=client->width) {
for(i=x,shift=8-bpp; i<x+w; i++) {
((CARDBPP*)client->frameBuffer)[j+i] = palette[((*buffer)>>shift)&mask];
shift-=bpp;
if(shift<0) {
shift=8-bpp;
buffer++;
}
}
if(shift<8-bpp)
buffer++;
}
}
/* case 17 ... 127: not used, but valid */
else if( type == 128 ) /* plain RLE */
{
int i=0,j=0;
while(j<h) {
int color,length;
/* read color */
if(buffer+REALBPP/8+1>buffer_end)
return -7;
color = UncompressCPixel(buffer);
buffer+=REALBPP/8;
/* read run length */
length=1;
while(*buffer==0xff) {
if(buffer+1>=buffer_end)
return -8;
length+=*buffer;
buffer++;
}
length+=*buffer;
buffer++;
while(j<h && length>0) {
((CARDBPP*)client->frameBuffer)[(y+j)*client->width+x+i] = color;
length--;
i++;
if(i>=w) {
i=0;
j++;
}
}
if(length>0)
rfbClientLog("Warning: possible ZRLE corruption\n");
}
}
else if( type == 129 ) /* unused */
{
return -8;
}
else if( type >= 130 ) /* palette RLE */
{
CARDBPP palette[128];
int i,j;
if(2+(type-128)*REALBPP/8>buffer_length)
return -9;
/* read palette */
for(i=0; i<type-128; i++,buffer+=REALBPP/8)
palette[i] = UncompressCPixel(buffer);
/* read palettized pixels */
i=j=0;
while(j<h) {
int color,length;
/* read color */
if(buffer>=buffer_end)
return -10;
color = palette[(*buffer)&0x7f];
length=1;
if(*buffer&0x80) {
if(buffer+1>=buffer_end)
return -11;
buffer++;
/* read run length */
while(*buffer==0xff) {
if(buffer+1>=buffer_end)
return -8;
length+=*buffer;
buffer++;
}
length+=*buffer;
}
buffer++;
while(j<h && length>0) {
((CARDBPP*)client->frameBuffer)[(y+j)*client->width+x+i] = color;
length--;
i++;
if(i>=w) {
i=0;
j++;
}
}
if(length>0)
rfbClientLog("Warning: possible ZRLE corruption\n");
}
}
}
return buffer-buffer_copy;
}
#undef CARDBPP
#undef CARDREALBPP
#undef HandleZRLE
#undef HandleZRLETile
#undef UncompressCPixel
#endif
#undef UNCOMP
#undef REALBPP

View File

@ -0,0 +1,824 @@
/********************************************************************
* *
* THIS FILE IS PART OF THE 'ZYWRLE' VNC CODEC SOURCE CODE. *
* *
* USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
* GOVERNED BY A FOLLOWING BSD-STYLE SOURCE LICENSE. *
* PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
* *
* THE 'ZYWRLE' VNC CODEC SOURCE CODE IS (C) COPYRIGHT 2006 *
* BY Hitachi Systems & Services, Ltd. *
* (Noriaki Yamazaki, Research & Development Center) * *
* *
********************************************************************
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Hitachi Systems & Services, Ltd. nor
the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************/
/* Change Log:
V0.02 : 2008/02/04 : Fix mis encode/decode when width != scanline
(Thanks Johannes Schindelin, author of LibVNC
Server/Client)
V0.01 : 2007/02/06 : Initial release
*/
/* #define ZYWRLE_ENCODE */
/* #define ZYWRLE_DECODE */
#define ZYWRLE_QUANTIZE
/*
[References]
PLHarr:
Senecal, J. G., P. Lindstrom, M. A. Duchaineau, and K. I. Joy, "An Improved N-Bit to N-Bit Reversible Haar-Like Transform," Pacific Graphics 2004, October 2004, pp. 371-380.
EZW:
Shapiro, JM: Embedded Image Coding Using Zerotrees of Wavelet Coefficients, IEEE Trans. Signal. Process., Vol.41, pp.3445-3462 (1993).
*/
/* Template Macro stuffs. */
#undef ZYWRLE_ANALYZE
#undef ZYWRLE_SYNTHESIZE
#define ZYWRLE_ANALYZE __RFB_CONCAT3E(zywrleAnalyze,BPP,END_FIX)
#define ZYWRLE_SYNTHESIZE __RFB_CONCAT3E(zywrleSynthesize,BPP,END_FIX)
#define ZYWRLE_RGBYUV __RFB_CONCAT3E(zywrleRGBYUV,BPP,END_FIX)
#define ZYWRLE_YUVRGB __RFB_CONCAT3E(zywrleYUVRGB,BPP,END_FIX)
#define ZYWRLE_YMASK __RFB_CONCAT2E(ZYWRLE_YMASK,BPP)
#define ZYWRLE_UVMASK __RFB_CONCAT2E(ZYWRLE_UVMASK,BPP)
#define ZYWRLE_LOAD_PIXEL __RFB_CONCAT2E(ZYWRLE_LOAD_PIXEL,BPP)
#define ZYWRLE_SAVE_PIXEL __RFB_CONCAT2E(ZYWRLE_SAVE_PIXEL,BPP)
/* Packing/Unpacking pixel stuffs.
Endian conversion stuffs. */
#undef S_0
#undef S_1
#undef L_0
#undef L_1
#undef L_2
#if ZYWRLE_ENDIAN == ENDIAN_BIG
# define S_0 1
# define S_1 0
# define L_0 3
# define L_1 2
# define L_2 1
#else
# define S_0 0
# define S_1 1
# define L_0 0
# define L_1 1
# define L_2 2
#endif
/* Load/Save pixel stuffs. */
#define ZYWRLE_YMASK15 0xFFFFFFF8
#define ZYWRLE_UVMASK15 0xFFFFFFF8
#define ZYWRLE_LOAD_PIXEL15(pSrc,R,G,B) { \
R = (((unsigned char*)pSrc)[S_1]<< 1)& 0xF8; \
G = ((((unsigned char*)pSrc)[S_1]<< 6)|(((unsigned char*)pSrc)[S_0]>> 2))& 0xF8; \
B = (((unsigned char*)pSrc)[S_0]<< 3)& 0xF8; \
}
#define ZYWRLE_SAVE_PIXEL15(pDst,R,G,B) { \
R &= 0xF8; \
G &= 0xF8; \
B &= 0xF8; \
((unsigned char*)pDst)[S_1] = (unsigned char)( (R>>1)|(G>>6) ); \
((unsigned char*)pDst)[S_0] = (unsigned char)(((B>>3)|(G<<2))& 0xFF); \
}
#define ZYWRLE_YMASK16 0xFFFFFFFC
#define ZYWRLE_UVMASK16 0xFFFFFFF8
#define ZYWRLE_LOAD_PIXEL16(pSrc,R,G,B) { \
R = ((unsigned char*)pSrc)[S_1] & 0xF8; \
G = ((((unsigned char*)pSrc)[S_1]<< 5)|(((unsigned char*)pSrc)[S_0]>> 3))& 0xFC; \
B = (((unsigned char*)pSrc)[S_0]<< 3)& 0xF8; \
}
#define ZYWRLE_SAVE_PIXEL16(pDst,R,G,B) { \
R &= 0xF8; \
G &= 0xFC; \
B &= 0xF8; \
((unsigned char*)pDst)[S_1] = (unsigned char)( R |(G>>5) ); \
((unsigned char*)pDst)[S_0] = (unsigned char)(((B>>3)|(G<<3))& 0xFF); \
}
#define ZYWRLE_YMASK32 0xFFFFFFFF
#define ZYWRLE_UVMASK32 0xFFFFFFFF
#define ZYWRLE_LOAD_PIXEL32(pSrc,R,G,B) { \
R = ((unsigned char*)pSrc)[L_2]; \
G = ((unsigned char*)pSrc)[L_1]; \
B = ((unsigned char*)pSrc)[L_0]; \
}
#define ZYWRLE_SAVE_PIXEL32(pDst,R,G,B) { \
((unsigned char*)pDst)[L_2] = (unsigned char)R; \
((unsigned char*)pDst)[L_1] = (unsigned char)G; \
((unsigned char*)pDst)[L_0] = (unsigned char)B; \
}
#ifndef ZYWRLE_ONCE
#define ZYWRLE_ONCE
#ifndef __STRICT_ANSI__
# define InlineX inline
#else
# define InlineX
#endif
#ifdef ZYWRLE_ENCODE
/* Tables for Coefficients filtering. */
# ifndef ZYWRLE_QUANTIZE
/* Type A:lower bit omitting of EZW style. */
const static unsigned int zywrleParam[3][3]={
{0x0000F000,0x00000000,0x00000000},
{0x0000C000,0x00F0F0F0,0x00000000},
{0x0000C000,0x00C0C0C0,0x00F0F0F0},
/* {0x0000FF00,0x00000000,0x00000000},
{0x0000FF00,0x00FFFFFF,0x00000000},
{0x0000FF00,0x00FFFFFF,0x00FFFFFF}, */
};
# else
/* Type B:Non liner quantization filter. */
static const signed char zywrleConv[4][256]={
{ /* bi=5, bo=5 r=0.0:PSNR=24.849 */
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
},
{ /* bi=5, bo=5 r=2.0:PSNR=74.031 */
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 32,
32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32,
48, 48, 48, 48, 48, 48, 48, 48,
48, 48, 48, 56, 56, 56, 56, 56,
56, 56, 56, 56, 64, 64, 64, 64,
64, 64, 64, 64, 72, 72, 72, 72,
72, 72, 72, 72, 80, 80, 80, 80,
80, 80, 88, 88, 88, 88, 88, 88,
88, 88, 88, 88, 88, 88, 96, 96,
96, 96, 96, 104, 104, 104, 104, 104,
104, 104, 104, 104, 104, 112, 112, 112,
112, 112, 112, 112, 112, 112, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
0, -120, -120, -120, -120, -120, -120, -120,
-120, -120, -120, -112, -112, -112, -112, -112,
-112, -112, -112, -112, -104, -104, -104, -104,
-104, -104, -104, -104, -104, -104, -96, -96,
-96, -96, -96, -88, -88, -88, -88, -88,
-88, -88, -88, -88, -88, -88, -88, -80,
-80, -80, -80, -80, -80, -72, -72, -72,
-72, -72, -72, -72, -72, -64, -64, -64,
-64, -64, -64, -64, -64, -56, -56, -56,
-56, -56, -56, -56, -56, -56, -48, -48,
-48, -48, -48, -48, -48, -48, -48, -48,
-48, -32, -32, -32, -32, -32, -32, -32,
-32, -32, -32, -32, -32, -32, -32, -32,
-32, -32, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
},
{ /* bi=5, bo=4 r=2.0:PSNR=64.441 */
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
48, 48, 48, 48, 48, 48, 48, 48,
48, 48, 48, 48, 48, 48, 48, 48,
48, 48, 48, 48, 48, 48, 48, 48,
64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64,
80, 80, 80, 80, 80, 80, 80, 80,
80, 80, 80, 80, 80, 88, 88, 88,
88, 88, 88, 88, 88, 88, 88, 88,
104, 104, 104, 104, 104, 104, 104, 104,
104, 104, 104, 112, 112, 112, 112, 112,
112, 112, 112, 112, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
0, -120, -120, -120, -120, -120, -120, -120,
-120, -120, -120, -120, -120, -112, -112, -112,
-112, -112, -112, -112, -112, -112, -104, -104,
-104, -104, -104, -104, -104, -104, -104, -104,
-104, -88, -88, -88, -88, -88, -88, -88,
-88, -88, -88, -88, -80, -80, -80, -80,
-80, -80, -80, -80, -80, -80, -80, -80,
-80, -64, -64, -64, -64, -64, -64, -64,
-64, -64, -64, -64, -64, -64, -64, -64,
-64, -48, -48, -48, -48, -48, -48, -48,
-48, -48, -48, -48, -48, -48, -48, -48,
-48, -48, -48, -48, -48, -48, -48, -48,
-48, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
},
{ /* bi=5, bo=2 r=2.0:PSNR=43.175 */
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
88, 88, 88, 88, 88, 88, 88, 88,
88, 88, 88, 88, 88, 88, 88, 88,
88, 88, 88, 88, 88, 88, 88, 88,
88, 88, 88, 88, 88, 88, 88, 88,
88, 88, 88, 88, 88, 88, 88, 88,
88, 88, 88, 88, 88, 88, 88, 88,
88, 88, 88, 88, 88, 88, 88, 88,
88, 88, 88, 88, 88, 88, 88, 88,
0, -88, -88, -88, -88, -88, -88, -88,
-88, -88, -88, -88, -88, -88, -88, -88,
-88, -88, -88, -88, -88, -88, -88, -88,
-88, -88, -88, -88, -88, -88, -88, -88,
-88, -88, -88, -88, -88, -88, -88, -88,
-88, -88, -88, -88, -88, -88, -88, -88,
-88, -88, -88, -88, -88, -88, -88, -88,
-88, -88, -88, -88, -88, -88, -88, -88,
-88, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
}
};
const static signed char* zywrleParam[3][3][3]={
{{zywrleConv[0],zywrleConv[2],zywrleConv[0]},{zywrleConv[0],zywrleConv[0],zywrleConv[0]},{zywrleConv[0],zywrleConv[0],zywrleConv[0]}},
{{zywrleConv[0],zywrleConv[3],zywrleConv[0]},{zywrleConv[1],zywrleConv[1],zywrleConv[1]},{zywrleConv[0],zywrleConv[0],zywrleConv[0]}},
{{zywrleConv[0],zywrleConv[3],zywrleConv[0]},{zywrleConv[2],zywrleConv[2],zywrleConv[2]},{zywrleConv[1],zywrleConv[1],zywrleConv[1]}},
};
# endif
#endif
static InlineX void Harr(signed char* pX0, signed char* pX1)
{
/* Piecewise-Linear Harr(PLHarr) */
int X0 = (int)*pX0, X1 = (int)*pX1;
int orgX0 = X0, orgX1 = X1;
if ((X0 ^ X1) & 0x80) {
/* differ sign */
X1 += X0;
if (((X1^orgX1)&0x80)==0) {
/* |X1| > |X0| */
X0 -= X1; /* H = -B */
}
} else {
/* same sign */
X0 -= X1;
if (((X0 ^ orgX0) & 0x80) == 0) {
/* |X0| > |X1| */
X1 += X0; /* L = A */
}
}
*pX0 = (signed char)X1;
*pX1 = (signed char)X0;
}
/*
1D-Wavelet transform.
In coefficients array, the famous 'pyramid' decomposition is well used.
1D Model:
|L0L0L0L0|L0L0L0L0|H0H0H0H0|H0H0H0H0| : level 0
|L1L1L1L1|H1H1H1H1|H0H0H0H0|H0H0H0H0| : level 1
But this method needs line buffer because H/L is different position from X0/X1.
So, I used 'interleave' decomposition instead of it.
1D Model:
|L0H0L0H0|L0H0L0H0|L0H0L0H0|L0H0L0H0| : level 0
|L1H0H1H0|L1H0H1H0|L1H0H1H0|L1H0H1H0| : level 1
In this method, H/L and X0/X1 is always same position.
This lead us to more speed and less memory.
Of cause, the result of both method is quite same
because its only difference is that coefficient position.
*/
static InlineX void WaveletLevel(int* data, int size, int l, int SkipPixel)
{
int s, ofs;
signed char* pX0;
signed char* end;
pX0 = (signed char*)data;
s = (8<<l)*SkipPixel;
end = pX0+(size>>(l+1))*s;
s -= 2;
ofs = (4<<l)*SkipPixel;
while (pX0 < end) {
Harr(pX0, pX0+ofs);
pX0++;
Harr(pX0, pX0+ofs);
pX0++;
Harr(pX0, pX0+ofs);
pX0 += s;
}
}
#define InvWaveletLevel(d,s,l,pix) WaveletLevel(d,s,l,pix)
#ifdef ZYWRLE_ENCODE
# ifndef ZYWRLE_QUANTIZE
/* Type A:lower bit omitting of EZW style. */
static InlineX void FilterWaveletSquare(int* pBuf, int width, int height, int level, int l)
{
int r, s;
int x, y;
int* pH;
const unsigned int* pM;
pM = &(zywrleParam[level-1][l]);
s = 2<<l;
for (r = 1; r < 4; r++) {
pH = pBuf;
if (r & 0x01)
pH += s>>1;
if (r & 0x02)
pH += (s>>1)*width;
for (y = 0; y < height / s; y++) {
for (x = 0; x < width / s; x++) {
/*
these are same following code.
pH[x] = pH[x] / (~pM[x]+1) * (~pM[x]+1);
( round pH[x] with pM[x] bit )
'&' operator isn't 'round' but is 'floor'.
So, we must offset when pH[x] is negative.
*/
if (((signed char*)pH)[0] & 0x80)
((signed char*)pH)[0] += ~((signed char*)pM)[0];
if (((signed char*)pH)[1] & 0x80)
((signed char*)pH)[1] += ~((signed char*)pM)[1];
if (((signed char*)pH)[2] & 0x80)
((signed char*)pH)[2] += ~((signed char*)pM)[2];
*pH &= *pM;
pH += s;
}
pH += (s-1)*width;
}
}
}
# else
/*
Type B:Non liner quantization filter.
Coefficients have Gaussian curve and smaller value which is
large part of coefficients isn't more important than larger value.
So, I use filter of Non liner quantize/dequantize table.
In general, Non liner quantize formula is explained as following.
y=f(x) = sign(x)*round( ((abs(x)/(2^7))^ r )* 2^(bo-1) )*2^(8-bo)
x=f-1(y) = sign(y)*round( ((abs(y)/(2^7))^(1/r))* 2^(bi-1) )*2^(8-bi)
( r:power coefficient bi:effective MSB in input bo:effective MSB in output )
r < 1.0 : Smaller value is more important than larger value.
r > 1.0 : Larger value is more important than smaller value.
r = 1.0 : Liner quantization which is same with EZW style.
r = 0.75 is famous non liner quantization used in MP3 audio codec.
In contrast to audio data, larger value is important in wavelet coefficients.
So, I select r = 2.0 table( quantize is x^2, dequantize sqrt(x) ).
As compared with EZW style liner quantization, this filter tended to be
more sharp edge and be more compression rate but be more blocking noise and be less quality.
Especially, the surface of graphic objects has distinguishable noise in middle quality mode.
We need only quantized-dequantized(filtered) value rather than quantized value itself
because all values are packed or palette-lized in later ZRLE section.
This lead us not to need to modify client decoder when we change
the filtering procedure in future.
Client only decodes coefficients given by encoder.
*/
static InlineX void FilterWaveletSquare(int* pBuf, int width, int height, int level, int l)
{
int r, s;
int x, y;
int* pH;
const signed char** pM;
pM = zywrleParam[level-1][l];
s = 2<<l;
for (r = 1; r < 4; r++) {
pH = pBuf;
if (r & 0x01)
pH += s>>1;
if (r & 0x02)
pH += (s>>1)*width;
for (y = 0; y < height / s; y++) {
for (x = 0; x < width / s; x++) {
((signed char*)pH)[0] = pM[0][((unsigned char*)pH)[0]];
((signed char*)pH)[1] = pM[1][((unsigned char*)pH)[1]];
((signed char*)pH)[2] = pM[2][((unsigned char*)pH)[2]];
pH += s;
}
pH += (s-1)*width;
}
}
}
# endif
static InlineX void Wavelet(int* pBuf, int width, int height, int level)
{
int l, s;
int* pTop;
int* pEnd;
for (l = 0; l < level; l++) {
pTop = pBuf;
pEnd = pBuf+height*width;
s = width<<l;
while (pTop < pEnd) {
WaveletLevel(pTop, width, l, 1);
pTop += s;
}
pTop = pBuf;
pEnd = pBuf+width;
s = 1<<l;
while (pTop < pEnd) {
WaveletLevel(pTop, height,l, width);
pTop += s;
}
FilterWaveletSquare(pBuf, width, height, level, l);
}
}
#endif
#ifdef ZYWRLE_DECODE
static InlineX void InvWavelet(int* pBuf, int width, int height, int level)
{
int l, s;
int* pTop;
int* pEnd;
for (l = level - 1; l >= 0; l--) {
pTop = pBuf;
pEnd = pBuf+width;
s = 1<<l;
while (pTop < pEnd) {
InvWaveletLevel(pTop, height,l, width);
pTop += s;
}
pTop = pBuf;
pEnd = pBuf+height*width;
s = width<<l;
while (pTop < pEnd) {
InvWaveletLevel(pTop, width, l, 1);
pTop += s;
}
}
}
#endif
/* Load/Save coefficients stuffs.
Coefficients manages as 24 bits little-endian pixel. */
#define ZYWRLE_LOAD_COEFF(pSrc,R,G,B) { \
R = ((signed char*)pSrc)[2]; \
G = ((signed char*)pSrc)[1]; \
B = ((signed char*)pSrc)[0]; \
}
#define ZYWRLE_SAVE_COEFF(pDst,R,G,B) { \
((signed char*)pDst)[2] = (signed char)R; \
((signed char*)pDst)[1] = (signed char)G; \
((signed char*)pDst)[0] = (signed char)B; \
}
/*
RGB <=> YUV conversion stuffs.
YUV coversion is explained as following formula in strict meaning:
Y = 0.299R + 0.587G + 0.114B ( 0<=Y<=255)
U = -0.169R - 0.331G + 0.500B (-128<=U<=127)
V = 0.500R - 0.419G - 0.081B (-128<=V<=127)
I use simple conversion RCT(reversible color transform) which is described
in JPEG-2000 specification.
Y = (R + 2G + B)/4 ( 0<=Y<=255)
U = B-G (-256<=U<=255)
V = R-G (-256<=V<=255)
*/
#define ROUND(x) (((x)<0)?0:(((x)>255)?255:(x)))
/* RCT is N-bit RGB to N-bit Y and N+1-bit UV.
For make Same N-bit, UV is lossy.
More exact PLHarr, we reduce to odd range(-127<=x<=127). */
#define ZYWRLE_RGBYUV1(R,G,B,Y,U,V,ymask,uvmask) { \
Y = (R+(G<<1)+B)>>2; \
U = B-G; \
V = R-G; \
Y -= 128; \
U >>= 1; \
V >>= 1; \
Y &= ymask; \
U &= uvmask; \
V &= uvmask; \
if (Y == -128) \
Y += (0xFFFFFFFF-ymask+1); \
if (U == -128) \
U += (0xFFFFFFFF-uvmask+1); \
if (V == -128) \
V += (0xFFFFFFFF-uvmask+1); \
}
#define ZYWRLE_YUVRGB1(R,G,B,Y,U,V) { \
Y += 128; \
U <<= 1; \
V <<= 1; \
G = Y-((U+V)>>2); \
B = U+G; \
R = V+G; \
G = ROUND(G); \
B = ROUND(B); \
R = ROUND(R); \
}
/*
coefficient packing/unpacking stuffs.
Wavelet transform makes 4 sub coefficient image from 1 original image.
model with pyramid decomposition:
+------+------+
| | |
| L | Hx |
| | |
+------+------+
| | |
| H | Hxy |
| | |
+------+------+
So, we must transfer each sub images individually in strict meaning.
But at least ZRLE meaning, following one decompositon image is same as
avobe individual sub image. I use this format.
(Strictly saying, transfer order is reverse(Hxy->Hy->Hx->L)
for simplified procedure for any wavelet level.)
+------+------+
| L |
+------+------+
| Hx |
+------+------+
| Hy |
+------+------+
| Hxy |
+------+------+
*/
#define INC_PTR(data) \
data++; \
if( data-pData >= (w+uw) ){ \
data += scanline-(w+uw); \
pData = data; \
}
#define ZYWRLE_TRANSFER_COEFF(pBuf,data,r,w,h,scanline,level,TRANS) \
pH = pBuf; \
s = 2<<level; \
if (r & 0x01) \
pH += s>>1; \
if (r & 0x02) \
pH += (s>>1)*w; \
pEnd = pH+h*w; \
while (pH < pEnd) { \
pLine = pH+w; \
while (pH < pLine) { \
TRANS \
INC_PTR(data) \
pH += s; \
} \
pH += (s-1)*w; \
}
#define ZYWRLE_PACK_COEFF(pBuf,data,r,width,height,scanline,level) \
ZYWRLE_TRANSFER_COEFF(pBuf,data,r,width,height,scanline,level,ZYWRLE_LOAD_COEFF(pH,R,G,B);ZYWRLE_SAVE_PIXEL(data,R,G,B);)
#define ZYWRLE_UNPACK_COEFF(pBuf,data,r,width,height,scanline,level) \
ZYWRLE_TRANSFER_COEFF(pBuf,data,r,width,height,scanline,level,ZYWRLE_LOAD_PIXEL(data,R,G,B);ZYWRLE_SAVE_COEFF(pH,R,G,B);)
#define ZYWRLE_SAVE_UNALIGN(data,TRANS) \
pTop = pBuf+w*h; \
pEnd = pBuf + (w+uw)*(h+uh); \
while (pTop < pEnd) { \
TRANS \
INC_PTR(data) \
pTop++; \
}
#define ZYWRLE_LOAD_UNALIGN(data,TRANS) \
pTop = pBuf+w*h; \
if (uw) { \
pData= data + w; \
pEnd = (int*)(pData+ h*scanline); \
while (pData < (PIXEL_T*)pEnd) { \
pLine = (int*)(pData + uw); \
while (pData < (PIXEL_T*)pLine) { \
TRANS \
pData++; \
pTop++; \
} \
pData += scanline-uw; \
} \
} \
if (uh) { \
pData= data + h*scanline; \
pEnd = (int*)(pData+ uh*scanline); \
while (pData < (PIXEL_T*)pEnd) { \
pLine = (int*)(pData + w); \
while (pData < (PIXEL_T*)pLine) { \
TRANS \
pData++; \
pTop++; \
} \
pData += scanline-w; \
} \
} \
if (uw && uh) { \
pData= data + w+ h*scanline; \
pEnd = (int*)(pData+ uh*scanline); \
while (pData < (PIXEL_T*)pEnd) { \
pLine = (int*)(pData + uw); \
while (pData < (PIXEL_T*)pLine) { \
TRANS \
pData++; \
pTop++; \
} \
pData += scanline-uw; \
} \
}
static InlineX void zywrleCalcSize(int* pW, int* pH, int level)
{
*pW &= ~((1<<level)-1);
*pH &= ~((1<<level)-1);
}
#endif /* ZYWRLE_ONCE */
#ifndef CPIXEL
#ifdef ZYWRLE_ENCODE
static InlineX void ZYWRLE_RGBYUV(int* pBuf, PIXEL_T* data, int width, int height, int scanline)
{
int R, G, B;
int Y, U, V;
int* pLine;
int* pEnd;
pEnd = pBuf+height*width;
while (pBuf < pEnd) {
pLine = pBuf+width;
while (pBuf < pLine) {
ZYWRLE_LOAD_PIXEL(data,R,G,B);
ZYWRLE_RGBYUV1(R,G,B,Y,U,V,ZYWRLE_YMASK,ZYWRLE_UVMASK);
ZYWRLE_SAVE_COEFF(pBuf,V,Y,U);
pBuf++;
data++;
}
data += scanline-width;
}
}
#endif
#ifdef ZYWRLE_DECODE
static InlineX void ZYWRLE_YUVRGB(int* pBuf, PIXEL_T* data, int width, int height, int scanline) {
int R, G, B;
int Y, U, V;
int* pLine;
int* pEnd;
pEnd = pBuf+height*width;
while (pBuf < pEnd) {
pLine = pBuf+width;
while (pBuf < pLine) {
ZYWRLE_LOAD_COEFF(pBuf,V,Y,U);
ZYWRLE_YUVRGB1(R,G,B,Y,U,V);
ZYWRLE_SAVE_PIXEL(data,R,G,B);
pBuf++;
data++;
}
data += scanline-width;
}
}
#endif
#ifdef ZYWRLE_ENCODE
PIXEL_T* ZYWRLE_ANALYZE(PIXEL_T* dst, PIXEL_T* src, int w, int h, int scanline, int level, int* pBuf) {
int l;
int uw = w;
int uh = h;
int* pTop;
int* pEnd;
int* pLine;
PIXEL_T* pData;
int R, G, B;
int s;
int* pH;
zywrleCalcSize(&w, &h, level);
if (w == 0 || h == 0)
return NULL;
uw -= w;
uh -= h;
pData = dst;
ZYWRLE_LOAD_UNALIGN(src,*(PIXEL_T*)pTop=*pData;)
ZYWRLE_RGBYUV(pBuf, src, w, h, scanline);
Wavelet(pBuf, w, h, level);
for (l = 0; l < level; l++) {
ZYWRLE_PACK_COEFF(pBuf, dst, 3, w, h, scanline, l);
ZYWRLE_PACK_COEFF(pBuf, dst, 2, w, h, scanline, l);
ZYWRLE_PACK_COEFF(pBuf, dst, 1, w, h, scanline, l);
if (l == level - 1) {
ZYWRLE_PACK_COEFF(pBuf, dst, 0, w, h, scanline, l);
}
}
ZYWRLE_SAVE_UNALIGN(dst,*dst=*(PIXEL_T*)pTop;)
return dst;
}
#endif
#ifdef ZYWRLE_DECODE
PIXEL_T* ZYWRLE_SYNTHESIZE(PIXEL_T* dst, PIXEL_T* src, int w, int h, int scanline, int level, int* pBuf)
{
int l;
int uw = w;
int uh = h;
int* pTop;
int* pEnd;
int* pLine;
PIXEL_T* pData;
int R, G, B;
int s;
int* pH;
zywrleCalcSize(&w, &h, level);
if (w == 0 || h == 0)
return NULL;
uw -= w;
uh -= h;
pData = src;
for (l = 0; l < level; l++) {
ZYWRLE_UNPACK_COEFF(pBuf, src, 3, w, h, scanline, l);
ZYWRLE_UNPACK_COEFF(pBuf, src, 2, w, h, scanline, l);
ZYWRLE_UNPACK_COEFF(pBuf, src, 1, w, h, scanline, l);
if (l == level - 1) {
ZYWRLE_UNPACK_COEFF(pBuf, src, 0, w, h, scanline, l);
}
}
ZYWRLE_SAVE_UNALIGN(src,*(PIXEL_T*)pTop=*src;)
InvWavelet(pBuf, w, h, level);
ZYWRLE_YUVRGB(pBuf, dst, w, h, scanline);
ZYWRLE_LOAD_UNALIGN(dst,*pData=*(PIXEL_T*)pTop;)
return src;
}
#endif
#endif /* CPIXEL */
#undef ZYWRLE_RGBYUV
#undef ZYWRLE_YUVRGB
#undef ZYWRLE_LOAD_PIXEL
#undef ZYWRLE_SAVE_PIXEL

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Andri Yngvason
* Copyright (c) 2020 - 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
@ -24,10 +24,13 @@
#include <errno.h>
#include <signal.h>
#include <getopt.h>
#include <sys/mman.h>
#include <aml.h>
#include <wayland-client.h>
#include <xkbcommon/xkbcommon.h>
#include <drm_fourcc.h>
#include <gbm.h>
#include <xf86drm.h>
#include <fcntl.h>
#include "pixman.h"
#include "xdg-shell.h"
@ -36,13 +39,21 @@
#include "pointer.h"
#include "keyboard.h"
#include "vnc.h"
#include "pixels.h"
#include "region.h"
#include "buffer.h"
#include "renderer.h"
#include "renderer-egl.h"
#include "linux-dmabuf-unstable-v1.h"
#include "time-util.h"
#include "output.h"
#include "data-control.h"
struct buffer {
int width, height, stride;
size_t size;
enum wl_shm_format format;
struct wl_buffer* wl_buffer;
void* pixels;
#define CANARY_TICK_PERIOD INT64_C(100000) // us
#define CANARY_LETHALITY_LEVEL INT64_C(8000) // us
struct point {
double x, y;
};
struct window {
@ -50,20 +61,41 @@ struct window {
struct xdg_surface* xdg_surface;
struct xdg_toplevel* xdg_toplevel;
struct buffer* buffer;
struct buffer* buffers[3];
struct buffer* back_buffer;
int buffer_index;
struct pixman_region16 current_damage;
struct vnc_client* vnc;
void* vnc_fb;
bool is_frame_committed;
};
static void register_frame_callback(void);
static struct wl_display* wl_display;
static struct wl_registry* wl_registry;
struct wl_compositor* wl_compositor = NULL;
struct wl_shm* wl_shm = NULL;
struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1 = NULL;
struct gbm_device* gbm_device = NULL;
static struct xdg_wm_base* xdg_wm_base;
static struct wl_list seats;
static struct wl_list outputs;
struct pointer_collection* pointers;
struct keyboard_collection* keyboards;
static int drm_fd = -1;
static uint64_t last_canary_tick;
static struct data_control* data_control;
static struct zwlr_data_control_manager_v1 *manager;
static struct vnc_client* vnc;
static enum wl_shm_format wl_shm_format;
static bool have_format = false;
static bool have_egl = false;
static uint32_t shm_format = DRM_FORMAT_INVALID;
static uint32_t dmabuf_format = DRM_FORMAT_INVALID;
static bool do_run = true;
@ -86,6 +118,9 @@ static void on_seat_capability_change(struct seat* seat)
struct wl_keyboard* wl_keyboard =
wl_seat_get_keyboard(seat->wl_seat);
keyboard_collection_add_wl_keyboard(keyboards, wl_keyboard);
data_control = malloc(sizeof(data_control));
data_control_init(data_control, seat, manager);
} else {
// TODO Remove
}
@ -101,6 +136,9 @@ static void registry_add(void* data, struct wl_registry* registry, uint32_t id,
xdg_wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1);
} else if (strcmp(interface, "wl_shm") == 0) {
wl_shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
} else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) {
zwp_linux_dmabuf_v1 = wl_registry_bind(registry, id,
&zwp_linux_dmabuf_v1_interface, 2);
} else if (strcmp(interface, "wl_seat") == 0) {
struct wl_seat* wl_seat;
wl_seat = wl_registry_bind(registry, id, &wl_seat_interface, 5);
@ -113,6 +151,19 @@ static void registry_add(void* data, struct wl_registry* registry, uint32_t id,
wl_list_insert(&seats, &seat->link);
seat->on_capability_change = on_seat_capability_change;
} else if (strcmp(interface, "wl_output") == 0) {
struct wl_output* wl_output;
wl_output = wl_registry_bind(registry, id, &wl_output_interface, 2);
struct output* output = output_new(wl_output, id);
if (!output) {
wl_output_destroy(wl_output);
return;
}
wl_list_insert(&outputs, &output->link);
} else if (strcmp(interface, zwlr_data_control_manager_v1_interface.name) == 0) {
manager = wl_registry_bind(registry, id, &zwlr_data_control_manager_v1_interface, 2);
}
}
@ -130,77 +181,26 @@ static const struct wl_registry_listener registry_listener = {
.global_remove = registry_remove,
};
static struct buffer* buffer_create(int width, int height, int stride,
enum wl_shm_format format)
{
struct buffer* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->width = width;
self->height = height;
self->stride = stride;
self->format = format;
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, format);
wl_shm_pool_destroy(pool);
if (!self->wl_buffer)
goto shm_failure;
close(fd);
return self;
shm_failure:
pool_failure:
munmap(self->pixels, self->size);
mmap_failure:
close(fd);
failure:
free(self);
return NULL;
}
static void buffer_destroy(struct buffer* self)
{
wl_buffer_destroy(self->wl_buffer);
munmap(self->pixels, self->size);
free(self);
}
static void shm_format(void* data, struct wl_shm* shm, uint32_t format)
static void handle_shm_format(void* data, struct wl_shm* shm, uint32_t format)
{
(void)data;
(void)wl_shm;
if (have_format)
if (shm_format != DRM_FORMAT_INVALID)
return;
switch (format) {
case WL_SHM_FORMAT_XRGB8888:
wl_shm_format = format;
have_format = true;
uint32_t drm_format = drm_format_from_wl_shm(format);
switch (drm_format) {
case DRM_FORMAT_XRGB8888:
shm_format = drm_format;
}
// TODO: Support more formats
}
static const struct wl_shm_listener shm_listener = {
.format = shm_format,
.format = handle_shm_format,
};
static void xdg_wm_base_ping(void* data, struct xdg_wm_base* shell,
@ -213,6 +213,26 @@ static const struct xdg_wm_base_listener xdg_wm_base_listener = {
.ping = xdg_wm_base_ping,
};
static void handle_dmabuf_format(void *data,
struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format)
{
(void)data;
(void)zwp_linux_dmabuf;
if (dmabuf_format != DRM_FORMAT_INVALID)
return;
switch (format) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_XBGR8888:
dmabuf_format = format;
}
}
static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = {
.format = handle_dmabuf_format,
};
void on_wayland_event(void* obj)
{
int rc = wl_display_prepare_read(wl_display);
@ -263,7 +283,88 @@ static int init_signal_handler(void)
static void window_attach(struct window* w, int x, int y)
{
wl_surface_attach(w->wl_surface, w->buffer->wl_buffer, x, y);
w->back_buffer->is_attached = true;
wl_surface_attach(w->wl_surface, w->back_buffer->wl_buffer, x, y);
wl_surface_set_buffer_scale(window->wl_surface,
window->back_buffer->scale);
}
static struct point surface_coord_to_buffer_coord(double x, double y)
{
double scale = output_list_get_max_scale(&outputs);
struct point result = {
.x = round(x * scale),
.y = round(y * scale),
};
return result;
}
static struct point buffer_coord_to_surface_coord(double x, double y)
{
double scale = output_list_get_max_scale(&outputs);
struct point result = {
.x = x / scale,
.y = y / scale,
};
return result;
}
static void window_calculate_transform(struct window* w, double* scale,
int* x_pos, int* y_pos)
{
double src_width = vnc_client_get_width(w->vnc);
double src_height = vnc_client_get_height(w->vnc);
double dst_width = w->back_buffer->width;
double dst_height = w->back_buffer->height;
double hratio = (double)dst_width / (double)src_width;
double vratio = (double)dst_height / (double)src_height;
*scale = fmin(hratio, vratio);
if (hratio < vratio + 0.01 && hratio > vratio - 0.01) {
*x_pos = 0;
*y_pos = 0;
} else if (hratio < vratio) {
*x_pos = 0;
*y_pos = round(dst_height / 2.0 - *scale * src_height / 2.0);
} else {
*x_pos = round(dst_width / 2.0 - *scale * src_width / 2.0);
*y_pos = 0;
}
}
static void window_transfer_pixels(struct window* w)
{
double scale;
int x_pos, y_pos;
window_calculate_transform(w, &scale, &x_pos, &y_pos);
if (w->vnc->n_av_frames != 0) {
assert(have_egl);
render_av_frames_egl(w->back_buffer, w->vnc->av_frames,
w->vnc->n_av_frames, scale, x_pos, y_pos);
return;
}
struct image image = {
.pixels = w->vnc_fb,
.width = vnc_client_get_width(w->vnc),
.height = vnc_client_get_height(w->vnc),
.stride = vnc_client_get_stride(w->vnc),
// TODO: Get the format from the vnc module
.format = w->back_buffer->format,
.damage = &w->current_damage,
};
if (have_egl)
render_image_egl(w->back_buffer, &image, scale, x_pos, y_pos);
else
render_image(w->back_buffer, &image, scale, x_pos, y_pos);
}
static void window_commit(struct window* w)
@ -271,6 +372,12 @@ static void window_commit(struct window* w)
wl_surface_commit(w->wl_surface);
}
static void window_swap(struct window* w)
{
w->buffer_index = (w->buffer_index + 1) % 3;
w->back_buffer = w->buffers[w->buffer_index];
}
static void window_damage(struct window* w, int x, int y, int width, int height)
{
wl_surface_damage(w->wl_surface, x, y, width, height);
@ -293,10 +400,36 @@ static const struct xdg_surface_listener xdg_surface_listener = {
.configure = xdg_surface_configure,
};
static void window_resize(struct window* w, int width, int height, int scale)
{
if (width == 0 || height == 0 || scale == 0)
return;
if (w->back_buffer && w->back_buffer->width == width &&
w->back_buffer->height == height &&
w->back_buffer->scale == scale)
return;
for (int i = 0; i < 3; ++i)
buffer_destroy(w->buffers[i]);
for (int i = 0; i < 3; ++i) {
w->buffers[i] = have_egl
? buffer_create_dmabuf(scale * width, scale * height,
dmabuf_format)
: buffer_create_shm(scale * width, scale * height,
scale * 4 * width, shm_format);
w->buffers[i]->scale = scale;
}
w->back_buffer = w->buffers[0];
}
static void xdg_toplevel_configure(void* data, struct xdg_toplevel* toplevel,
int32_t width, int32_t height, struct wl_array* state)
{
// What to do here?
int32_t scale = output_list_get_max_scale(&outputs);
window_resize(data, width, height, scale);
}
static void xdg_toplevel_close(void* data, struct xdg_toplevel* toplevel)
@ -315,6 +448,8 @@ static struct window* window_create(const char* app_id, const char* title)
if (!w)
return NULL;
pixman_region_init(&w->current_damage);
w->wl_surface = wl_compositor_create_surface(wl_compositor);
if (!w->wl_surface)
goto wl_surface_failure;
@ -348,12 +483,14 @@ wl_surface_failure:
static void window_destroy(struct window* w)
{
if (w->buffer)
buffer_destroy(w->buffer);
for (int i = 0; i < 3; ++i)
buffer_destroy(w->buffers[i]);
free(w->vnc_fb);
xdg_toplevel_destroy(w->xdg_toplevel);
xdg_surface_destroy(w->xdg_surface);
wl_surface_destroy(w->wl_surface);
pixman_region_fini(&w->current_damage);
free(w);
}
@ -362,8 +499,16 @@ void on_pointer_event(struct pointer_collection* collection,
{
struct vnc_client* client = collection->userdata;
int x = wl_fixed_to_int(pointer->x);
int y = wl_fixed_to_int(pointer->y);
double scale;
int x_pos, y_pos;
window_calculate_transform(window, &scale, &x_pos, &y_pos);
struct point coord = surface_coord_to_buffer_coord(
wl_fixed_to_double(pointer->x),
wl_fixed_to_double(pointer->y));
int x = round((coord.x - (double)x_pos) / scale);
int y = round((coord.y - (double)y_pos) / scale);
enum pointer_button_mask pressed = pointer->pressed;
int vertical_steps = pointer->vertical_scroll_steps;
@ -378,14 +523,14 @@ void on_pointer_event(struct pointer_collection* collection,
if (vertical_steps < 0) {
vertical_steps *= -1;
scroll_mask |= POINTER_SCROLL_UP;
} else {
} else if (vertical_steps > 0) {
scroll_mask |= POINTER_SCROLL_DOWN;
}
if (horizontal_steps < 0) {
horizontal_steps *= -1;
scroll_mask |= POINTER_SCROLL_LEFT;
} else {
} else if (horizontal_steps > 0) {
scroll_mask |= POINTER_SCROLL_RIGHT;
}
@ -416,33 +561,51 @@ void on_keyboard_event(struct keyboard_collection* collection,
int on_vnc_client_alloc_fb(struct vnc_client* client)
{
assert(!window); // TODO: Support resizing
int width = vnc_client_get_width(client);
int height = vnc_client_get_height(client);
int stride = vnc_client_get_stride(client);
window = window_create(app_id, vnc_client_get_desktop_name(client));
if (!window)
return -1;
if (!window) {
window = window_create(app_id, vnc_client_get_desktop_name(client));
window->vnc = client;
window->buffer = buffer_create(width, height, stride, wl_shm_format);
vnc_client_set_fb(client, window->buffer->pixels);
int32_t scale = output_list_get_max_scale(&outputs);
window_resize(window, width, height, scale);
}
free(window->vnc_fb);
window->vnc_fb = malloc(height * stride);
assert(window->vnc_fb);
vnc_client_set_fb(client, window->vnc_fb);
return 0;
}
void on_vnc_client_update_fb(struct vnc_client* client)
static void get_frame_damage(struct vnc_client* client,
struct pixman_region16* damage)
{
if (!pixman_region_not_empty(&client->damage))
return;
pixman_region_union(damage, damage, &client->damage);
// TODO: Make sure that the buffer is released at this point, or make
// this a side-buffer and copy damaged regions into double buffers.
window_attach(window, 0, 0);
for (int i = 0; i < client->n_av_frames; ++i) {
const struct vnc_av_frame* frame = client->av_frames[i];
pixman_region_union_rect(damage, damage, frame->x, frame->y,
frame->width, frame->height);
}
}
static void apply_buffer_damage(struct pixman_region16* damage)
{
for (int i = 0; i < 3; ++i)
pixman_region_union(&window->buffers[i]->damage,
&window->buffers[i]->damage, damage);
}
static void window_damage_region(struct window* w,
struct pixman_region16* damage)
{
int n_rects = 0;
struct pixman_box16* box = pixman_region_rectangles(&client->damage,
&n_rects);
struct pixman_box16* box = pixman_region_rectangles(damage, &n_rects);
for (int i = 0; i < n_rects; ++i) {
int x = box[i].x1;
@ -450,10 +613,83 @@ void on_vnc_client_update_fb(struct vnc_client* client)
int width = box[i].x2 - x;
int height = box[i].y2 - y;
window_damage(window, x, y, width, height);
window_damage(w, x, y, width, height);
}
}
static void render_from_vnc(void)
{
if (!pixman_region_not_empty(&window->current_damage) &&
window->vnc->n_av_frames == 0)
return;
if (window->is_frame_committed)
return;
if (window->back_buffer->is_attached)
fprintf(stderr, "Oops, back-buffer is still attached.\n");
window_attach(window, 0, 0);
double scale;
int x_pos, y_pos;
window_calculate_transform(window, &scale, &x_pos, &y_pos);
struct pixman_region16 damage_scaled = { 0 }, buffer_damage = { 0 },
surface_damage = { 0 };
region_scale(&damage_scaled, &window->current_damage, scale);
region_translate(&buffer_damage, &damage_scaled, x_pos, y_pos);
pixman_region_clear(&damage_scaled);
double output_scale = output_list_get_max_scale(&outputs);
struct point scoord = buffer_coord_to_surface_coord(x_pos, y_pos);
region_scale(&damage_scaled, &window->current_damage,
scale / output_scale);
region_translate(&surface_damage, &damage_scaled, scoord.x, scoord.y);
pixman_region_fini(&damage_scaled);
apply_buffer_damage(&buffer_damage);
window_damage_region(window, &surface_damage);
pixman_region_fini(&surface_damage);
pixman_region_fini(&buffer_damage);
window_transfer_pixels(window);
window->is_frame_committed = true;
register_frame_callback();
window_commit(window);
window_swap(window);
pixman_region_clear(&window->current_damage);
vnc_client_clear_av_frames(window->vnc);
}
void on_vnc_client_update_fb(struct vnc_client* client)
{
get_frame_damage(window->vnc, &window->current_damage);
render_from_vnc();
}
static void handle_frame_callback(void* data, struct wl_callback* callback,
uint32_t time)
{
wl_callback_destroy(callback);
window->is_frame_committed = false;
if (!window->vnc->is_updating)
render_from_vnc();
}
static const struct wl_callback_listener frame_listener = {
.done = handle_frame_callback
};
static void register_frame_callback(void)
{
struct wl_callback* callback = wl_surface_frame(window->wl_surface);
wl_callback_add_listener(callback, &frame_listener, NULL);
}
void on_vnc_client_event(void* obj)
@ -477,6 +713,129 @@ int init_vnc_client_handler(struct vnc_client* client)
return rc;
}
static int find_render_node(char *node, size_t maxlen) {
bool r = -1;
drmDevice *devices[64];
int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0]));
for (int i = 0; i < n; ++i) {
drmDevice *dev = devices[i];
if (!(dev->available_nodes & (1 << DRM_NODE_RENDER)))
continue;
strncpy(node, dev->nodes[DRM_NODE_RENDER], maxlen);
node[maxlen - 1] = '\0';
r = 0;
break;
}
drmFreeDevices(devices, n);
return r;
}
static int init_gbm_device(void)
{
int rc;
char render_node[256];
rc = find_render_node(render_node, sizeof(render_node));
if (rc < 0)
return -1;
drm_fd = open(render_node, O_RDWR);
if (drm_fd < 0)
return 1;
gbm_device = gbm_create_device(drm_fd);
if (!gbm_device) {
close(drm_fd);
drm_fd = -1;
return -1;
}
return 0;
}
static int init_egl_renderer(void)
{
if (!zwp_linux_dmabuf_v1) {
printf("Missing linux-dmabuf-unstable-v1. Using software rendering.\n");
return -1;
}
zwp_linux_dmabuf_v1_add_listener(zwp_linux_dmabuf_v1,
&dmabuf_listener, NULL);
wl_display_roundtrip(wl_display);
if (dmabuf_format == DRM_FORMAT_INVALID) {
printf("No supported dmabuf pixel format found. Using software rendering.\n");
goto failure;
}
if (init_gbm_device() < 0) {
printf("Failed to find render node. Using software rendering.\n");
goto failure;
}
if (egl_init() < 0) {
printf("Failed initialise EGL. Using software rendering.\n");
goto failure;
}
printf("Using EGL for rendering...\n");
return 0;
failure:
if (zwp_linux_dmabuf_v1) {
zwp_linux_dmabuf_v1_destroy(zwp_linux_dmabuf_v1);
zwp_linux_dmabuf_v1 = NULL;
}
return -1;
}
static void on_canary_tick(void* obj)
{
(void)obj;
uint64_t t = gettime_us();
int64_t dt = t - last_canary_tick;
last_canary_tick = t;
int64_t delay = dt - CANARY_TICK_PERIOD;
// Early ticks are just a result of late ticks...
if (delay < CANARY_LETHALITY_LEVEL)
return;
fprintf(stderr, "WARNING: Long delays observed (%"PRIi64"). Something is blocking the main loop\n",
delay);
}
static void create_canary_ticker(void)
{
last_canary_tick = gettime_us();
struct aml* aml = aml_get_default();
struct aml_ticker* ticker = aml_ticker_new(CANARY_TICK_PERIOD,
on_canary_tick, NULL, NULL);
aml_start(aml, ticker);
aml_unref(ticker);
}
static void vnc_send_clipboard(char* text, size_t size) {
vnc_client_send_cut_text(vnc, text, size);
}
void run_main_loop_once(void)
{
struct aml* aml = aml_get_default();
wl_display_flush(wl_display);
aml_poll(aml, -1);
aml_dispatch(aml);
}
static int usage(int r)
{
fprintf(r ? stderr : stdout, "\
@ -486,10 +845,11 @@ Usage: wlvncc <address> [port]\n\
-c,--compression Compression level (0 - 9).\n\
-e,--encodings=<list> Set allowed encodings, comma separated list.\n\
Supported values: tight, zrle, ultra, copyrect,\n\
hextile, zlib, corre, rre, raw.\n\
hextile, zlib, corre, rre, raw, open-h264.\n\
-h,--help Get help.\n\
-n,--hide-cursor Hide the client-side cursor.\n\
-q,--quality Quality level (0 - 9).\n\
-s,--use-sw-renderer Use software rendering.\n\
\n\
");
return r;
@ -503,7 +863,8 @@ int main(int argc, char* argv[])
const char* encodings = NULL;
int quality = -1;
int compression = -1;
static const char* shortopts = "a:q:c:e:hn";
static const char* shortopts = "a:q:c:e:hns";
bool use_sw_renderer = false;
static const struct option longopts[] = {
{ "app-id", required_argument, NULL, 'a' },
@ -512,6 +873,7 @@ int main(int argc, char* argv[])
{ "help", no_argument, NULL, 'h' },
{ "quality", required_argument, NULL, 'q' },
{ "hide-cursor", no_argument, NULL, 'n' },
{ "use-sw-renderer", no_argument, NULL, 's' },
{ NULL, 0, NULL, 0 }
};
@ -536,6 +898,9 @@ int main(int argc, char* argv[])
case 'n':
cursor_type = POINTER_CURSOR_NONE;
break;
case 's':
use_sw_renderer = true;
break;
case 'h':
return usage(0);
default:
@ -553,6 +918,11 @@ int main(int argc, char* argv[])
if (n_args >= 2)
port = atoi(argv[optind + 1]);
if (aml_unstable_abi_version != AML_UNSTABLE_API) {
fprintf(stderr, "libaml is incompatible with current build of wlvncc!\n");
abort();
}
struct aml* aml = aml_new();
if (!aml)
return 1;
@ -563,8 +933,10 @@ int main(int argc, char* argv[])
goto signal_handler_failure;
wl_display = wl_display_connect(NULL);
if (!wl_display)
if (!wl_display) {
fprintf(stderr, "Failed to connect to local wayland display\n");
goto display_failure;
}
if (init_wayland_event_handler() < 0)
goto event_handler_failure;
@ -586,6 +958,7 @@ int main(int argc, char* argv[])
goto registry_failure;
wl_list_init(&seats);
wl_list_init(&outputs);
wl_registry_add_listener(wl_registry, &registry_listener, wl_display);
wl_display_roundtrip(wl_display);
@ -595,24 +968,40 @@ int main(int argc, char* argv[])
assert(xdg_wm_base);
wl_shm_add_listener(wl_shm, &shm_listener, NULL);
xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL);
if (!use_sw_renderer)
have_egl = init_egl_renderer() == 0;
wl_display_roundtrip(wl_display);
wl_display_roundtrip(wl_display);
struct vnc_client* vnc = vnc_client_create();
vnc = vnc_client_create(data_control);
if (!vnc)
goto vnc_failure;
vnc->alloc_fb = on_vnc_client_alloc_fb;
vnc->update_fb = on_vnc_client_update_fb;
data_control->vnc_write_clipboard = vnc_send_clipboard;
if (vnc_client_set_pixel_format(vnc, wl_shm_format) < 0) {
if (vnc_client_set_pixel_format(vnc, shm_format) < 0) {
fprintf(stderr, "Unsupported pixel format\n");
goto vnc_setup_failure;
}
if (encodings)
vnc_client_set_encodings(vnc, encodings);
if (encodings) {
if (!have_egl && strstr(encodings, "open-h264")) {
fprintf(stderr, "Open H.264 encoding won't work without EGL\n");
goto vnc_setup_failure;
}
} else if (have_egl) {
encodings = "open-h264,tight,zrle,ultra,copyrect,hextile,zlib"
",corre,rre,raw";
} else {
encodings = "tight,zrle,ultra,copyrect,hextile,zlib,corre,rre,raw";
}
vnc_client_set_encodings(vnc, encodings);
if (quality >= 0)
vnc_client_set_quality_level(vnc, quality);
@ -628,16 +1017,20 @@ int main(int argc, char* argv[])
if (init_vnc_client_handler(vnc) < 0)
goto vnc_setup_failure;
if (vnc_client_init(vnc) < 0) {
fprintf(stderr, "Failed to connect to server\n");
goto vnc_setup_failure;
}
pointers->userdata = vnc;
keyboards->userdata = vnc;
wl_display_dispatch(wl_display);
while (do_run) {
wl_display_flush(wl_display);
aml_poll(aml, -1);
aml_dispatch(aml);
}
create_canary_ticker();
while (do_run)
run_main_loop_once();
rc = 0;
if (window)
@ -645,10 +1038,18 @@ int main(int argc, char* argv[])
vnc_setup_failure:
vnc_client_destroy(vnc);
vnc_failure:
output_list_destroy(&outputs);
seat_list_destroy(&seats);
wl_compositor_destroy(wl_compositor);
wl_shm_destroy(wl_shm);
xdg_wm_base_destroy(xdg_wm_base);
egl_finish();
if (zwp_linux_dmabuf_v1)
zwp_linux_dmabuf_v1_destroy(zwp_linux_dmabuf_v1);
if (gbm_device)
gbm_device_destroy(gbm_device);
if (drm_fd >= 0)
close(drm_fd);
wl_registry_destroy(wl_registry);
registry_failure:
@ -662,5 +1063,9 @@ display_failure:
signal_handler_failure:
aml_unref(aml);
printf("Exiting...\n");
// @TODO this will throw an segfault (can't determine proxy version, but why?)
if (data_control)
data_control_destroy(data_control);
return rc;
}

286
src/open-h264.c 100644
View File

@ -0,0 +1,286 @@
/*
* 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 "open-h264.h"
#include "rfbclient.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <arpa/inet.h>
#include <libavcodec/avcodec.h>
enum open_h264_flags {
OPEN_H264_RESET_CONTEXT = 1 << 0,
OPEN_H264_RESET_ALL_CONTEXTS = 1 << 1,
};
struct open_h264_msg_head {
uint32_t length;
uint32_t flags;
} __attribute__((packed));
struct open_h264_context {
rfbRectangle rect;
AVCodecParserContext* parser;
AVCodecContext* codec_ctx;
AVBufferRef* hwctx_ref;
};
struct open_h264 {
rfbClient* client;
struct open_h264_context* contexts[OPEN_H264_MAX_CONTEXTS];
int n_contexts;
};
static bool are_rects_equal(const rfbRectangle* a, const rfbRectangle* b)
{
return memcmp(a, b, sizeof(*a)) == 0;
}
static int find_context_index(const struct open_h264* self,
const rfbRectangle* rect)
{
for (int i = 0; i < self->n_contexts; ++i)
if (are_rects_equal(&self->contexts[i]->rect, rect))
return i;
return -1;
}
static struct open_h264_context* find_context(
const struct open_h264* self, const rfbRectangle* rect)
{
int i = find_context_index(self, rect);
return i >= 0 ? self->contexts[i] : NULL;
}
static struct open_h264_context* open_h264_context_create(
struct open_h264* self, const rfbRectangle* rect)
{
if (self->n_contexts >= OPEN_H264_MAX_CONTEXTS)
return NULL;
struct open_h264_context* context = calloc(1, sizeof(*context));
if (!context)
return NULL;
memcpy(&context->rect, rect, sizeof(context->rect));
const AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec)
goto failure;
context->parser = av_parser_init(codec->id);
if (!context->parser)
goto failure;
context->codec_ctx = avcodec_alloc_context3(codec);
if (!context->codec_ctx)
goto failure;
if (av_hwdevice_ctx_create(&context->hwctx_ref, AV_HWDEVICE_TYPE_VAAPI,
NULL, NULL, 0) != 0)
goto failure;
context->codec_ctx->hw_device_ctx = av_buffer_ref(context->hwctx_ref);
if (avcodec_open2(context->codec_ctx, codec, NULL) != 0)
goto failure;
self->contexts[self->n_contexts++] = context;
return context;
failure:
av_buffer_unref(&context->hwctx_ref);
avcodec_free_context(&context->codec_ctx);
av_parser_close(context->parser);
free(context);
return NULL;
}
static void open_h264_context_destroy(struct open_h264_context* context)
{
av_buffer_unref(&context->hwctx_ref);
avcodec_free_context(&context->codec_ctx);
av_parser_close(context->parser);
free(context);
}
static struct open_h264_context* get_context(struct open_h264* self,
const rfbRectangle* rect)
{
struct open_h264_context* context = find_context(self, rect);
return context ? context : open_h264_context_create(self, rect);
}
static void reset_context(struct open_h264* self,
const rfbRectangle* rect)
{
int i = find_context_index(self, rect);
if (i < 0)
return;
open_h264_context_destroy(self->contexts[i]);
--self->n_contexts;
memmove(&self->contexts[i], &self->contexts[i + 1],
self->n_contexts - i);
}
static void reset_all_contexts(struct open_h264* self)
{
for (int i = 0; i < self->n_contexts; ++i) {
open_h264_context_destroy(self->contexts[i]);
self->contexts[i] = NULL;
}
}
struct open_h264* open_h264_create(rfbClient* client)
{
// Use this to enable debug logs
// av_log_set_level(AV_LOG_DEBUG);
struct open_h264* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->client = client;
return self;
}
void open_h264_destroy(struct open_h264* self)
{
if (!self)
return;
reset_all_contexts(self);
free(self);
}
static bool decode_frame(struct open_h264_context* context, AVFrame* frame,
AVPacket* packet)
{
av_frame_unref(frame);
int rc;
rc = avcodec_send_packet(context->codec_ctx, packet);
if (rc < 0)
return false;
struct AVFrame* vaapi_frame = av_frame_alloc();
if (!vaapi_frame)
return false;
rc = avcodec_receive_frame(context->codec_ctx, vaapi_frame);
if (rc < 0)
return false;
frame->format = AV_PIX_FMT_DRM_PRIME;
rc = av_hwframe_map(frame, vaapi_frame, AV_HWFRAME_MAP_DIRECT);
if (rc < 0) {
av_frame_free(&vaapi_frame);
return false;
}
av_frame_copy_props(frame, vaapi_frame);
av_frame_free(&vaapi_frame);
return true;
}
static int parse_elementary_stream(struct open_h264_context* context,
AVPacket* packet, const uint8_t* src, uint32_t length)
{
return av_parser_parse2(context->parser, context->codec_ctx,
&packet->data, &packet->size, src, length,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
}
struct AVFrame* open_h264_decode_rect(struct open_h264* self,
rfbFramebufferUpdateRectHeader* msg)
{
bool have_frame = false;
struct open_h264_msg_head head = { 0 };
if (!ReadFromRFBServer(self->client, (char*)&head, sizeof(head)))
return NULL;
uint32_t length = ntohl(head.length);
enum open_h264_flags flags = ntohl(head.flags);
if (flags & OPEN_H264_RESET_ALL_CONTEXTS) {
reset_all_contexts(self);
} else if (flags & OPEN_H264_RESET_CONTEXT) {
reset_context(self, &msg->r);
}
struct open_h264_context* context = get_context(self, &msg->r);
if (!context)
return NULL;
char* data = calloc(1, length + AV_INPUT_BUFFER_PADDING_SIZE);
if (!data)
return NULL;
AVFrame* frame = av_frame_alloc();
if (!frame)
goto failure;
AVPacket* packet = av_packet_alloc();
if (!packet)
goto failure;
if (!ReadFromRFBServer(self->client, data, length))
goto failure;
uint8_t* dp = (uint8_t*)data;
while (length > 0) {
int rc = parse_elementary_stream(context, packet, dp, length);
if (rc < 0)
goto failure;
dp += rc;
length -= rc;
// The h264 elementary stream doesn't have end-markers, so the
// parser doesn't know where the frame ends. This flushes it:
if (packet->size == 0 && length == 0) {
int rc = parse_elementary_stream(context, packet, dp,
length);
if (rc < 0)
goto failure;
}
// If we get multiple frames per rect, there's no point in
// rendering them all, so we just return the last one.
if (packet->size != 0)
have_frame = decode_frame(context, frame, packet);
}
failure:
av_packet_free(&packet);
if (!have_frame)
av_frame_unref(frame);
free(data);
return have_frame ? frame : NULL;
}

108
src/output.c 100644
View File

@ -0,0 +1,108 @@
/*
* Copyright (c) 2019 - 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 <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <wayland-client.h>
#include "output.h"
static void output_handle_geometry(void* data, struct wl_output* wl_output,
int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
int32_t subpixel, const char* make, const char* model,
int32_t transform)
{
}
static void output_handle_mode(void* data, struct wl_output* wl_output,
uint32_t flags, int32_t width, int32_t height, int32_t refresh)
{
}
static void output_handle_done(void* data, struct wl_output* wl_output)
{
}
static void output_handle_scale(void* data, struct wl_output* wl_output,
int32_t factor)
{
struct output* output = data;
output->scale = factor;
}
static const struct wl_output_listener output_listener = {
.geometry = output_handle_geometry,
.mode = output_handle_mode,
.done = output_handle_done,
.scale = output_handle_scale,
};
void output_destroy(struct output* output)
{
wl_output_destroy(output->wl_output);
free(output);
}
void output_list_destroy(struct wl_list* list)
{
struct output* output;
struct output* tmp;
wl_list_for_each_safe(output, tmp, list, link) {
wl_list_remove(&output->link);
output_destroy(output);
}
}
int32_t output_list_get_max_scale(const struct wl_list* list)
{
struct output* output;
int32_t scale = 1;
wl_list_for_each(output, list, link)
if (output->scale > scale)
scale = output->scale;
return scale;
}
struct output* output_new(struct wl_output* wl_output, uint32_t id)
{
struct output* output = calloc(1, sizeof(*output));
if (!output)
return NULL;
output->wl_output = wl_output;
output->id = id;
wl_output_add_listener(output->wl_output, &output_listener,
output);
return output;
}
struct output* output_find_by_id(struct wl_list* list, uint32_t id)
{
struct output* output;
wl_list_for_each(output, list, link)
if (output->id == id)
return output;
return NULL;
}

115
src/pixels.c 100644
View File

@ -0,0 +1,115 @@
/*
* 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 <libdrm/drm_fourcc.h>
#include <wayland-client.h>
#include <pixman.h>
#include <stdbool.h>
#include <assert.h>
enum wl_shm_format drm_format_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 drm_format_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;
}
bool drm_format_to_pixman_fmt(pixman_format_code_t* dst, uint32_t src)
{
#define LOWER_R r
#define LOWER_G g
#define LOWER_B b
#define LOWER_A a
#define LOWER_X x
#define LOWER_
#define LOWER(x) LOWER_##x
#define CONCAT_(a, b) a ## b
#define CONCAT(a, b) CONCAT_(a, b)
#define FMT_DRM(x, y, z, v, a, b, c, d) DRM_FORMAT_##x##y##z##v##a##b##c##d
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define FMT_PIXMAN(x, y, z, v, a, b, c, d) \
CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(\
PIXMAN_, LOWER(x)), a), LOWER(y)), b), LOWER(z)), c), LOWER(v)), d)
#else
#define FMT_PIXMAN(x, y, z, v, a, b, c, d) \
CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(\
PIXMAN_, LOWER(v)), d), LOWER(z)), c), LOWER(y)), b), LOWER(x)), a)
#endif
switch (src) {
#define X(...) \
case FMT_DRM(__VA_ARGS__): *dst = FMT_PIXMAN(__VA_ARGS__); break
/* 32 bits */
X(A,R,G,B,8,8,8,8);
X(A,B,G,R,8,8,8,8);
X(X,R,G,B,8,8,8,8);
X(X,B,G,R,8,8,8,8);
X(R,G,B,A,8,8,8,8);
X(B,G,R,A,8,8,8,8);
X(R,G,B,X,8,8,8,8);
X(B,G,R,X,8,8,8,8);
/* 24 bits */
X(R,G,B,,8,8,8,);
X(B,G,R,,8,8,8,);
/* 16 bits */
X(R,G,B,,5,6,5,);
X(B,G,R,,5,6,5,);
/* These are incompatible on big endian */
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
X(A,R,G,B,2,10,10,10);
X(X,R,G,B,2,10,10,10);
X(A,B,G,R,2,10,10,10);
X(X,B,G,R,2,10,10,10);
X(A,R,G,B,1,5,5,5);
X(A,B,G,R,1,5,5,5);
X(X,R,G,B,1,5,5,5);
X(X,B,G,R,1,5,5,5);
X(A,R,G,B,4,4,4,4);
X(A,B,G,R,4,4,4,4);
X(X,R,G,B,4,4,4,4);
X(X,B,G,R,4,4,4,4);
#endif
#undef X
default: return false;
}
return true;
}

70
src/region.c 100644
View File

@ -0,0 +1,70 @@
/*
* 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 <math.h>
#include <pixman.h>
void region_scale(struct pixman_region16* dst, struct pixman_region16* src,
double scale)
{
if (scale == 1.0) {
pixman_region_copy(dst, src);
return;
}
pixman_region_fini(dst);
pixman_region_init(dst);
int n_rects = 0;
pixman_box16_t* rects = pixman_region_rectangles(src, &n_rects);
for (int i = 0; i < n_rects; ++i) {
pixman_box16_t* r = &rects[i];
int x1 = floor((double)r->x1 * scale);
int x2 = ceil((double)r->x2 * scale);
int y1 = floor((double)r->y1 * scale);
int y2 = ceil((double)r->y2 * scale);
pixman_region_union_rect(dst, dst, x1, y1, x2 - x1, y2 - y1);
}
}
void region_translate(struct pixman_region16* dst, struct pixman_region16* src,
int x, int y)
{
if (x == 0 && y == 0) {
pixman_region_copy(dst, src);
return;
}
pixman_region_fini(dst);
pixman_region_init(dst);
int n_rects = 0;
pixman_box16_t* rects = pixman_region_rectangles(src, &n_rects);
for (int i = 0; i < n_rects; ++i) {
pixman_box16_t* r = &rects[i];
int x1 = r->x1 + x;
int x2 = r->x2 + x;
int y1 = r->y1 + y;
int y2 = r->y2 + y;
pixman_region_union_rect(dst, dst, x1, y1, x2 - x1, y2 - y1);
}
}

544
src/renderer-egl.c 100644
View File

@ -0,0 +1,544 @@
/*
* 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 "buffer.h"
#include "renderer.h"
#include "renderer-egl.h"
#include "vnc.h"
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <gbm.h>
#include <drm_fourcc.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <libavutil/frame.h>
#include <libavutil/hwcontext_drm.h>
#define XSTR(s) STR(s)
#define STR(s) #s
#define EGL_EXTENSION_LIST \
X(PFNEGLGETPLATFORMDISPLAYEXTPROC, eglGetPlatformDisplayEXT) \
X(PFNEGLCREATEIMAGEKHRPROC, eglCreateImageKHR) \
X(PFNEGLDESTROYIMAGEKHRPROC, eglDestroyImageKHR) \
#define GL_EXTENSION_LIST \
X(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC, glEGLImageTargetTexture2DOES) \
X(PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC, glEGLImageTargetRenderbufferStorageOES) \
#define X(t, n) static t n;
EGL_EXTENSION_LIST
GL_EXTENSION_LIST
#undef X
enum {
ATTR_INDEX_POS = 0,
ATTR_INDEX_TEXTURE,
};
struct fbo_info {
GLuint fbo;
GLuint rbo;
int width, height;
};
static EGLDisplay egl_display = EGL_NO_DISPLAY;
static EGLContext egl_context = EGL_NO_CONTEXT;
static GLuint shader_program = 0;
static GLuint shader_program_ext = 0;
static GLuint texture = 0;
static const char *vertex_shader_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.0, 1.0);\n"
"}\n";
static const char *fragment_shader_src =
"precision mediump float;\n"
"uniform sampler2D u_tex;\n"
"varying vec2 v_texture;\n"
"void main() {\n"
" vec4 colour = texture2D(u_tex, v_texture);\n"
" gl_FragColor = vec4(colour.rgb, 1.0);\n"
"}\n";
static const char *fragment_shader_ext_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";
struct {
GLuint u_tex;
} uniforms;
static int egl_load_egl_ext(void)
{
#define X(t, n) \
n = (t)eglGetProcAddress(XSTR(n)); \
if (!n) \
return -1;
EGL_EXTENSION_LIST
#undef X
return 0;
}
static int egl_load_gl_ext(void)
{
#define X(t, n) \
n = (t)eglGetProcAddress(XSTR(n)); \
if (!n) \
return -1;
GL_EXTENSION_LIST
#undef X
return 0;
}
static int compile_shaders(const char* vert_src, const char* frag_src)
{
GLuint vert = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert, 1, &vert_src, NULL);
glCompileShader(vert);
GLint is_compiled = 0;
glGetShaderiv(vert, GL_COMPILE_STATUS, &is_compiled);
assert(is_compiled);
GLuint frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag, 1, &frag_src, NULL);
glCompileShader(frag);
glGetShaderiv(frag, GL_COMPILE_STATUS, &is_compiled);
assert(is_compiled);
int prog = glCreateProgram();
glAttachShader(prog, vert);
glAttachShader(prog, frag);
glBindAttribLocation(prog, ATTR_INDEX_POS, "pos");
glBindAttribLocation(prog, ATTR_INDEX_TEXTURE, "texture");
glLinkProgram(prog);
glDeleteShader(vert);
glDeleteShader(frag);
GLint is_linked = 0;
glGetProgramiv(prog, GL_LINK_STATUS, &is_linked);
assert(is_linked);
uniforms.u_tex = glGetUniformLocation(prog, "u_tex");
return prog;
}
int egl_init(void)
{
int rc;
rc = eglBindAPI(EGL_OPENGL_ES_API);
if (!rc)
return -1;
if (egl_load_egl_ext() < 0)
return -1;
egl_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
EGL_DEFAULT_DISPLAY, NULL);
if (egl_display == EGL_NO_DISPLAY)
return -1;
rc = eglInitialize(egl_display, NULL, NULL);
if (!rc)
goto failure;
static const EGLint attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
egl_context = eglCreateContext(egl_display, EGL_NO_CONFIG_KHR,
EGL_NO_CONTEXT, attribs);
if (egl_context == EGL_NO_CONTEXT)
goto failure;
if (!eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
egl_context))
goto failure;
if (egl_load_gl_ext() < 0)
goto failure;
shader_program = compile_shaders(vertex_shader_src,
fragment_shader_src);
shader_program_ext = compile_shaders(vertex_shader_src,
fragment_shader_ext_src);
return 0;
failure:
eglDestroyContext(egl_display, egl_context);
return -1;
}
void egl_finish(void)
{
if (texture)
glDeleteTextures(1, &texture);
if (shader_program_ext)
glDeleteProgram(shader_program_ext);
if (shader_program)
glDeleteProgram(shader_program);
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
eglDestroyContext(egl_display, egl_context);
eglTerminate(egl_display);
}
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 fbo_from_gbm_bo(struct fbo_info* dst, struct gbm_bo* bo)
{
memset(dst, 0, sizeof(*dst));
int index = 0;
EGLint attr[128];
// Won't do multi-planar...
int n_planes = gbm_bo_get_plane_count(bo);
assert(n_planes == 1);
int width = gbm_bo_get_width(bo);
int height = gbm_bo_get_height(bo);
append_attr(attr, &index, EGL_WIDTH, width);
append_attr(attr, &index, EGL_HEIGHT, height);
append_attr(attr, &index, EGL_LINUX_DRM_FOURCC_EXT,
gbm_bo_get_format(bo));
int fd = gbm_bo_get_fd(bo);
// Plane 0:
uint64_t mod = gbm_bo_get_modifier(bo);
uint32_t mod_hi = mod >> 32;
uint32_t mod_lo = mod & 0xffffffff;
append_attr(attr, &index, EGL_DMA_BUF_PLANE0_FD_EXT, fd);
append_attr(attr, &index, EGL_DMA_BUF_PLANE0_OFFSET_EXT,
gbm_bo_get_offset(bo, 0));
append_attr(attr, &index, EGL_DMA_BUF_PLANE0_PITCH_EXT,
gbm_bo_get_stride(bo));
append_attr(attr, &index, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, mod_lo);
append_attr(attr, &index, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, mod_hi);
attr[index++] = EGL_NONE;
EGLImageKHR image = eglCreateImageKHR(egl_display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, attr);
assert(image != EGL_NO_IMAGE_KHR);
GLuint rbo = 0;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, rbo);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
assert(status == GL_FRAMEBUFFER_COMPLETE);
dst->fbo = fbo;
dst->rbo = rbo;
dst->width = width;
dst->height = height;
eglDestroyImageKHR(egl_display, image);
close(fd);
}
#define X(lc, uc) \
static EGLint plane_ ## lc ## _key(int plane) \
{ \
switch (plane) { \
case 0: return EGL_DMA_BUF_PLANE0_ ## uc ## _EXT; \
case 1: return EGL_DMA_BUF_PLANE1_ ## uc ## _EXT; \
case 2: return EGL_DMA_BUF_PLANE2_ ## uc ## _EXT; \
case 3: return EGL_DMA_BUF_PLANE3_ ## uc ## _EXT; \
} \
return EGL_NONE; \
}
X(fd, FD);
X(offset, OFFSET);
X(pitch, PITCH);
X(modifier_lo, MODIFIER_LO);
X(modifier_hi, MODIFIER_HI);
#undef X
static void dmabuf_attr_append_planes(EGLint* dst, int* index,
struct AVDRMFrameDescriptor* desc)
{
struct AVDRMPlaneDescriptor *plane;
struct AVDRMObjectDescriptor *obj;
for (int i = 0; i < desc->nb_layers; ++i) {
assert(desc->layers[i].nb_planes == 1);
plane = &desc->layers[i].planes[0];
obj = &desc->objects[plane->object_index];
append_attr(dst, index, plane_fd_key(i), obj->fd);
append_attr(dst, index, plane_offset_key(i), plane->offset);
append_attr(dst, index, plane_pitch_key(i), plane->pitch);
append_attr(dst, index, plane_modifier_lo_key(i),
obj->format_modifier);
append_attr(dst, index, plane_modifier_hi_key(i),
obj->format_modifier >> 32);
}
}
static GLuint texture_from_av_frame(const struct AVFrame* frame)
{
int index = 0;
EGLint attr[128];
AVDRMFrameDescriptor *desc = (void*)frame->data[0];
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, DRM_FORMAT_NV12);
append_attr(attr, &index, EGL_IMAGE_PRESERVED_KHR, EGL_TRUE);
dmabuf_attr_append_planes(attr, &index, desc);
attr[index++] = EGL_NONE;
EGLImageKHR image = eglCreateImageKHR(egl_display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, attr);
assert(image != EGL_NO_IMAGE_KHR);
GLuint tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
eglDestroyImageKHR(egl_display, image);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
return tex;
}
void gl_draw(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 },
};
glVertexAttribPointer(ATTR_INDEX_POS, 2, GL_FLOAT, GL_FALSE, 0,
s_vertices);
glVertexAttribPointer(ATTR_INDEX_TEXTURE, 2, GL_FLOAT, GL_FALSE, 0,
s_positions);
glEnableVertexAttribArray(ATTR_INDEX_POS);
glEnableVertexAttribArray(ATTR_INDEX_TEXTURE);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(ATTR_INDEX_TEXTURE);
glDisableVertexAttribArray(ATTR_INDEX_POS);
}
GLenum gl_format_from_drm(uint32_t format)
{
switch (format) {
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
return GL_BGRA_EXT;
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
return GL_RGBA;
}
return 0;
}
void import_image_with_damage(const struct image* src,
struct pixman_region16* damage)
{
GLenum fmt = gl_format_from_drm(src->format);
int n_rects = 0;
struct pixman_box16* rects =
pixman_region_rectangles(damage, &n_rects);
for (int i = 0; i < n_rects; ++i) {
int x = rects[i].x1;
int y = rects[i].y1;
int width = rects[i].x2 - x;
int height = rects[i].y2 - y;
glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, x);
glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, y);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, fmt,
GL_UNSIGNED_BYTE, src->pixels);
}
glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0);
}
void render_image_egl(struct buffer* dst, const struct image* src,
double scale, int x_pos, int y_pos)
{
struct fbo_info fbo;
fbo_from_gbm_bo(&fbo, dst->bo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo.fbo);
bool is_new_texture = !texture;
if (!texture)
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, src->stride / 4);
if (is_new_texture) {
GLenum fmt = gl_format_from_drm(src->format);
glTexImage2D(GL_TEXTURE_2D, 0, fmt, src->width, src->height, 0,
fmt, GL_UNSIGNED_BYTE, src->pixels);
} else {
import_image_with_damage(src,
(struct pixman_region16*)src->damage);
}
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
int width = round((double)src->width * scale);
int height = round((double)src->height * scale);
glViewport(x_pos, y_pos, width, height);
glUseProgram(shader_program);
struct pixman_box16* ext = pixman_region_extents(&dst->damage);
glScissor(ext->x1, ext->y1, ext->x2 - ext->x1, ext->y2 - ext->y1);
glEnable(GL_SCISSOR_TEST);
gl_draw();
glDisable(GL_SCISSOR_TEST);
glFlush();
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo.fbo);
glDeleteRenderbuffers(1, &fbo.rbo);
pixman_region_clear(&dst->damage);
}
void render_av_frames_egl(struct buffer* dst, struct vnc_av_frame** src,
int n_av_frames, double scale, int x_pos, int y_pos)
{
struct fbo_info fbo;
fbo_from_gbm_bo(&fbo, dst->bo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo.fbo);
struct pixman_box16* ext = pixman_region_extents(&dst->damage);
glScissor(ext->x1, ext->y1, ext->x2 - ext->x1, ext->y2 - ext->y1);
glEnable(GL_SCISSOR_TEST);
glUseProgram(shader_program_ext);
for (int i = 0; i < n_av_frames; ++i) {
const struct vnc_av_frame* frame = src[i];
int width = round((double)frame->width * scale);
int height = round((double)frame->height * scale);
glViewport(x_pos + frame->x, y_pos + frame->y, width, height);
GLuint tex = texture_from_av_frame(frame->frame);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
gl_draw();
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
}
glDisable(GL_SCISSOR_TEST);
glFlush();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo.fbo);
glDeleteRenderbuffers(1, &fbo.rbo);
pixman_region_clear(&dst->damage);
}

65
src/renderer.c 100644
View File

@ -0,0 +1,65 @@
/*
* 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 "renderer.h"
#include "buffer.h"
#include "pixels.h"
#include <stdbool.h>
#include <unistd.h>
#include <stdint.h>
#include <pixman.h>
#include <assert.h>
void render_image(struct buffer* dst, const struct image* src, double scale,
int x_pos, int y_pos)
{
bool ok __attribute__((unused));
pixman_format_code_t dst_fmt = 0;
ok = drm_format_to_pixman_fmt(&dst_fmt, dst->format);
assert(ok);
pixman_format_code_t src_fmt = 0;
ok = drm_format_to_pixman_fmt(&src_fmt, src->format);
assert(ok);
pixman_image_t* dstimg = pixman_image_create_bits_no_clear(dst_fmt,
dst->width, dst->height, dst->pixels, dst->stride);
pixman_image_t* srcimg = pixman_image_create_bits_no_clear(src_fmt,
src->width, src->height, src->pixels, src->stride);
pixman_fixed_t src_scale = pixman_double_to_fixed(1.0 / scale);
pixman_transform_t xform;
pixman_transform_init_scale(&xform, src_scale, src_scale);
pixman_image_set_transform(srcimg, &xform);
pixman_image_set_clip_region(dstimg, &dst->damage);
pixman_image_composite(PIXMAN_OP_OVER, srcimg, NULL, dstimg,
0, 0,
0, 0,
x_pos, y_pos,
dst->width, dst->height);
pixman_image_unref(srcimg);
pixman_image_unref(dstimg);
pixman_region_clear(&dst->damage);
}

2683
src/rfbproto.c 100644

File diff suppressed because it is too large Load Diff

557
src/sasl.c 100644
View File

@ -0,0 +1,557 @@
/*
* The software in this file is derived from the vncconnection.c source file
* from the GTK VNC Widget with modifications by S. Waterman <simon.waterman@zynstra.com>
* for compatibility with libvncserver. The copyright and license
* statements below apply only to this source file and to no other parts of the
* libvncserver library.
*
* GTK VNC Widget
*
* Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
* Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* sasl.c - functions to deal with client side of the SASL protocol.
*/
#include <errno.h>
#include "rfbclient.h"
#include "sockets.h"
#include "sasl.h"
#include "tls.h"
/*
* NB, keep in sync with similar method in qemud/remote.c
*/
static char *vnc_connection_addr_to_string(char *host, int port)
{
char * buf = (char *)malloc(strlen(host) + 7);
if (buf) {
sprintf(buf, "%s;%hu", host, (unsigned short)port);
}
return buf;
}
static int log_func(void *context,
int level,
const char *message)
{
rfbClientLog("SASL: %s\n", message);
return SASL_OK;
}
static int user_callback_adapt(void *context,
int id,
const char **result,
unsigned *len)
{
rfbClient* client = (rfbClient *)context;
if (id != SASL_CB_AUTHNAME) {
rfbClientLog("Unrecognized SASL callback ID %d\n", id);
return SASL_FAIL;
}
if (!client->GetUser) {
rfbClientLog("Client user callback not found\n");
return SASL_FAIL;
}
*result = client->GetUser(client);
if (! *result) return SASL_FAIL;
/**len = strlen(*result);*/
return SASL_OK;
}
static int password_callback_adapt(sasl_conn_t *conn,
void * context,
int id,
sasl_secret_t **secret)
{
rfbClient* client = (rfbClient *)context;
char * password;
if (id != SASL_CB_PASS) {
rfbClientLog("Unrecognized SASL callback ID %d\n", id);
return SASL_FAIL;
}
if (client->saslSecret) { /* If we've already got it just return it. */
*secret = client->saslSecret;
return SASL_OK;
}
if (!client->GetPassword) {
rfbClientLog("Client password callback not found\n");
return SASL_FAIL;
}
password = client->GetPassword(client);
if (! password) return SASL_FAIL;
sasl_secret_t *lsec = (sasl_secret_t *)malloc(sizeof(sasl_secret_t) + strlen(password));
if (!lsec) {
rfbClientLog("Could not allocate sasl_secret_t\n");
return SASL_FAIL;
}
strcpy((char *)lsec->data, password);
lsec->len = strlen(password);
client->saslSecret = lsec;
*secret = lsec;
/* Clear client password */
size_t i;
for (i = 0; i < lsec->len; i++) {
password[i] = '\0';
}
free(password);
return SASL_OK;
}
#define SASL_MAX_MECHLIST_LEN 300
#define SASL_MAX_DATA_LEN (1024 * 1024)
/* Perform the SASL authentication process
*/
rfbBool
HandleSASLAuth(rfbClient *client)
{
sasl_conn_t *saslconn = NULL;
sasl_security_properties_t secprops;
const char *clientout;
char *serverin = NULL;
unsigned int clientoutlen, serverinlen;
int err, complete = 0;
char *localAddr = NULL, *remoteAddr = NULL;
const void *val;
sasl_ssf_t ssf;
sasl_callback_t saslcb[] = {
{SASL_CB_LOG, (void *)log_func, NULL},
{SASL_CB_AUTHNAME, client->GetUser ? (void *)user_callback_adapt : NULL, client},
{SASL_CB_PASS, client->GetPassword ? (void *)password_callback_adapt : NULL, client},
{ .id = 0 },
};
sasl_interact_t *interact = NULL;
uint32_t mechlistlen;
char *mechlist;
char *wantmech;
const char *mechname;
client->saslconn = NULL;
/* Sets up the SASL library as a whole */
err = sasl_client_init(NULL);
rfbClientLog("Client initialize SASL authentication %d\n", err);
if (err != SASL_OK) {
rfbClientLog("failed to initialize SASL library: %d (%s)\n",
err, sasl_errstring(err, NULL, NULL));
goto error;
}
/* Get local address in form IPADDR:PORT */
struct sockaddr_storage localAddress;
socklen_t addressLength = sizeof(localAddress);
char buf[INET6_ADDRSTRLEN];
int port;
if (getsockname(client->sock, (struct sockaddr*)&localAddress, &addressLength)) {
rfbClientLog("failed to get local address\n");
goto error;
}
if (localAddress.ss_family == AF_INET) {
struct sockaddr_in *sa_in = (struct sockaddr_in*)&localAddress;
inet_ntop(AF_INET, &(sa_in->sin_addr), buf, INET_ADDRSTRLEN);
port = ntohs(sa_in->sin_port);
localAddr = vnc_connection_addr_to_string(buf, port);
} else if (localAddress.ss_family == AF_INET6) {
struct sockaddr_in6 *sa_in = (struct sockaddr_in6*)&localAddress;
inet_ntop(AF_INET6, &(sa_in->sin6_addr), buf, INET6_ADDRSTRLEN);
port = ntohs(sa_in->sin6_port);
localAddr = vnc_connection_addr_to_string(buf, port);
} else {
rfbClientLog("failed to get local address\n");
goto error;
}
/* Get remote address in form IPADDR:PORT */
remoteAddr = vnc_connection_addr_to_string(client->serverHost, client->serverPort);
rfbClientLog("Client SASL new host:'%s' local:'%s' remote:'%s'\n", client->serverHost, localAddr, remoteAddr);
/* Setup a handle for being a client */
err = sasl_client_new("vnc",
client->serverHost,
localAddr,
remoteAddr,
saslcb,
SASL_SUCCESS_DATA,
&saslconn);
free(localAddr);
free(remoteAddr);
if (err != SASL_OK) {
rfbClientLog("Failed to create SASL client context: %d (%s)\n",
err, sasl_errstring(err, NULL, NULL));
goto error;
}
/* Initialize some connection props we care about */
if (client->tlsSession) {
if (!(ssf = (sasl_ssf_t)GetTLSCipherBits(client))) {
rfbClientLog("%s", "invalid cipher size for TLS session\n");
goto error;
}
rfbClientLog("Setting external SSF %d\n", ssf);
err = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf);
if (err != SASL_OK) {
rfbClientLog("cannot set external SSF %d (%s)\n",
err, sasl_errstring(err, NULL, NULL));
goto error;
}
}
memset (&secprops, 0, sizeof secprops);
/* If we've got TLS, we don't care about SSF */
secprops.min_ssf = client->tlsSession ? 0 : 56; /* Equiv to DES supported by all Kerberos */
secprops.max_ssf = client->tlsSession ? 0 : 100000; /* Very strong ! AES == 256 */
secprops.maxbufsize = 100000;
/* If we're not TLS, then forbid any anonymous or trivially crackable auth */
secprops.security_flags = client->tlsSession ? 0 :
SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
err = sasl_setprop(saslconn, SASL_SEC_PROPS, &secprops);
if (err != SASL_OK) {
rfbClientLog("cannot set security props %d (%s)\n",
err, sasl_errstring(err, NULL, NULL));
goto error;
}
/* Get the supported mechanisms from the server */
if (!ReadFromRFBServer(client, (char *)&mechlistlen, 4)) {
rfbClientLog("failed to read mechlistlen\n");
goto error;
}
mechlistlen = rfbClientSwap32IfLE(mechlistlen);
rfbClientLog("mechlistlen is %d\n", mechlistlen);
if (mechlistlen > SASL_MAX_MECHLIST_LEN) {
rfbClientLog("mechlistlen %d too long\n", mechlistlen);
goto error;
}
mechlist = malloc(mechlistlen+1);
if (!mechlist || !ReadFromRFBServer(client, mechlist, mechlistlen)) {
free(mechlist);
goto error;
}
mechlist[mechlistlen] = '\0';
/* Allow the client to influence the mechanism selected. */
if (client->GetSASLMechanism) {
wantmech = client->GetSASLMechanism(client, mechlist);
if (wantmech && *wantmech != 0) {
if (strstr(mechlist, wantmech) == NULL) {
rfbClientLog("Client requested SASL mechanism %s not supported by server\n",
wantmech);
free(mechlist);
free(wantmech);
goto error;
} else {
free(mechlist);
mechlist = wantmech;
}
}
}
rfbClientLog("Client start negotiation mechlist '%s'\n", mechlist);
/* Start the auth negotiation on the client end first */
err = sasl_client_start(saslconn,
mechlist,
&interact,
&clientout,
&clientoutlen,
&mechname);
if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
rfbClientLog("Failed to start SASL negotiation: %d (%s)\n",
err, sasl_errdetail(saslconn));
free(mechlist);
mechlist = NULL;
goto error;
}
/* Need to gather some credentials from the client */
if (err == SASL_INTERACT) {
rfbClientLog("User interaction required but not currently supported\n");
goto error;
}
rfbClientLog("Server start negotiation with mech %s. Data %d bytes %p '%s'\n",
mechname, clientoutlen, clientout, clientout);
if (clientoutlen > SASL_MAX_DATA_LEN) {
rfbClientLog("SASL negotiation data too long: %d bytes\n",
clientoutlen);
goto error;
}
/* Send back the chosen mechname */
uint32_t mechnamelen = rfbClientSwap32IfLE(strlen(mechname));
if (!WriteToRFBServer(client, (char *)&mechnamelen, 4)) goto error;
if (!WriteToRFBServer(client, (char *)mechname, strlen(mechname))) goto error;
/* NB, distinction of NULL vs "" is *critical* in SASL */
if (clientout) {
uint32_t colsw = rfbClientSwap32IfLE(clientoutlen + 1);
if (!WriteToRFBServer(client, (char *)&colsw, 4)) goto error;
if (!WriteToRFBServer(client, (char *)clientout, clientoutlen + 1)) goto error;
} else {
uint32_t temp = 0;
if (!WriteToRFBServer(client, (char *)&temp, 4)) goto error;
}
rfbClientLog("%s", "Getting sever start negotiation reply\n");
/* Read the 'START' message reply from server */
if (!ReadFromRFBServer(client, (char *)&serverinlen, 4)) goto error;
serverinlen = rfbClientSwap32IfLE(serverinlen);
if (serverinlen > SASL_MAX_DATA_LEN) {
rfbClientLog("SASL negotiation data too long: %d bytes\n",
serverinlen);
goto error;
}
/* NB, distinction of NULL vs "" is *critical* in SASL */
if (serverinlen) {
serverin = malloc(serverinlen);
if (!serverin || !ReadFromRFBServer(client, serverin, serverinlen)) goto error;
serverin[serverinlen-1] = '\0';
serverinlen--;
} else {
serverin = NULL;
}
if (!ReadFromRFBServer(client, (char *)&complete, 1)) goto error;
rfbClientLog("Client start result complete: %d. Data %d bytes %p '%s'\n",
complete, serverinlen, serverin, serverin);
/* Loop-the-loop...
* Even if the server has completed, the client must *always* do at least one step
* in this loop to verify the server isn't lying about something. Mutual auth */
for (;;) {
err = sasl_client_step(saslconn,
serverin,
serverinlen,
&interact,
&clientout,
&clientoutlen);
if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
rfbClientLog("Failed SASL step: %d (%s)\n",
err, sasl_errdetail(saslconn));
goto error;
}
/* Need to gather some credentials from the client */
if (err == SASL_INTERACT) {
rfbClientLog("User interaction required but not currently supported\n");
goto error;
}
if (serverin) {
free(serverin);
serverin = NULL;
}
rfbClientLog("Client step result %d. Data %d bytes %p '%s'\n", err, clientoutlen, clientout, clientout);
/* Previous server call showed completion & we're now locally complete too */
if (complete && err == SASL_OK)
break;
/* Not done, prepare to talk with the server for another iteration */
/* NB, distinction of NULL vs "" is *critical* in SASL */
if (clientout) {
uint32_t colsw = rfbClientSwap32IfLE(clientoutlen + 1);
if (!WriteToRFBServer(client, (char *)&colsw, 4)) goto error;
if (!WriteToRFBServer(client, (char *)clientout, clientoutlen + 1)) goto error;
} else {
uint32_t temp = 0;
if (!WriteToRFBServer(client, (char *)&temp, 4)) goto error;
}
rfbClientLog("Server step with %d bytes %p\n", clientoutlen, clientout);
if (!ReadFromRFBServer(client, (char *)&serverinlen, 4)) goto error;
serverinlen = rfbClientSwap32IfLE(serverinlen);
if (serverinlen > SASL_MAX_DATA_LEN) {
rfbClientLog("SASL negotiation data too long: %d bytes\n",
serverinlen);
goto error;
}
/* NB, distinction of NULL vs "" is *critical* in SASL */
if (serverinlen) {
serverin = malloc(serverinlen);
if (!serverin || !ReadFromRFBServer(client, serverin, serverinlen)) goto error;
serverin[serverinlen-1] = '\0';
serverinlen--;
} else {
serverin = NULL;
}
if (!ReadFromRFBServer(client, (char *)&complete, 1)) goto error;
rfbClientLog("Client step result complete: %d. Data %d bytes %p '%s'\n",
complete, serverinlen, serverin, serverin);
/* This server call shows complete, and earlier client step was OK */
if (complete && err == SASL_OK) {
free(serverin);
serverin = NULL;
break;
}
}
/* Check for suitable SSF if non-TLS */
if (!client->tlsSession) {
err = sasl_getprop(saslconn, SASL_SSF, &val);
if (err != SASL_OK) {
rfbClientLog("cannot query SASL ssf on connection %d (%s)\n",
err, sasl_errstring(err, NULL, NULL));
goto error;
}
ssf = *(const int *)val;
rfbClientLog("SASL SSF value %d\n", ssf);
if (ssf < 56) { /* 56 == DES level, good for Kerberos */
rfbClientLog("negotiation SSF %d was not strong enough\n", ssf);
goto error;
}
}
rfbClientLog("%s", "SASL authentication complete\n");
uint32_t result;
if (!ReadFromRFBServer(client, (char *)&result, 4)) {
rfbClientLog("Failed to read authentication result\n");
goto error;
}
result = rfbClientSwap32IfLE(result);
if (result != 0) {
rfbClientLog("Authentication failure\n");
goto error;
}
rfbClientLog("Authentication successful - switching to SSF\n");
/* This must come *after* check-auth-result, because the former
* is defined to be sent unencrypted, and setting saslconn turns
* on the SSF layer encryption processing */
client->saslconn = saslconn;
/* Clear SASL secret from memory if set - it'll be free'd on dispose */
if (client->saslSecret) {
size_t i;
for (i = 0; i < client->saslSecret->len; i++)
client->saslSecret->data[i] = '\0';
client->saslSecret->len = 0;
}
return TRUE;
error:
if (client->saslSecret) {
size_t i;
for (i = 0; i < client->saslSecret->len; i++)
client->saslSecret->data[i] = '\0';
client->saslSecret->len = 0;
}
if (saslconn)
sasl_dispose(&saslconn);
return FALSE;
}
int
ReadFromSASL(rfbClient* client, char *out, unsigned int n)
{
size_t want;
if (client->saslDecoded == NULL) {
char *encoded;
int encodedLen;
int err, ret;
encodedLen = 8192;
encoded = (char *)malloc(encodedLen);
if (!encoded) {
errno = EIO;
return -EIO;
}
ret = read(client->sock, encoded, encodedLen);
if (ret < 0) {
free(encoded);
return ret;
}
if (ret == 0) {
free(encoded);
errno = EIO;
return -EIO;
}
err = sasl_decode(client->saslconn, encoded, ret,
&client->saslDecoded, &client->saslDecodedLength);
free(encoded);
if (err != SASL_OK) {
rfbClientLog("Failed to decode SASL data %s\n",
sasl_errstring(err, NULL, NULL));
return -EINVAL;
}
client->saslDecodedOffset = 0;
}
want = client->saslDecodedLength - client->saslDecodedOffset;
if (want > n)
want = n;
memcpy(out,
client->saslDecoded + client->saslDecodedOffset,
want);
client->saslDecodedOffset += want;
if (client->saslDecodedOffset == client->saslDecodedLength) {
client->saslDecodedLength = client->saslDecodedOffset = 0;
client->saslDecoded = NULL;
}
if (!want) {
errno = EAGAIN;
return -EAGAIN;
}
return want;
}

378
src/sockets.c 100644
View File

@ -0,0 +1,378 @@
/*
* Copyright (C) 2022 Andri Yngvasin <andri@yngvason.is>
* Copyright (C) 2011-2012 Christian Beier <dontmind@freeshell.org>
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/*
* sockets.c - functions to deal with sockets.
*/
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/param.h>
#include <poll.h>
#include "rfbclient.h"
#include "sockets.h"
#include "tls.h"
#include "sasl.h"
void run_main_loop_once(void);
rfbBool errorMessageOnReadFailure = TRUE;
rfbBool ReadToBuffer(rfbClient* client) {
if (client->buffered == RFB_BUF_SIZE)
return FALSE;
ssize_t size;
#if defined(LIBVNCSERVER_HAVE_GNUTLS) || defined(LIBVNCSERVER_HAVE_LIBSSL)
if (client->tlsSession) {
size = ReadFromTLS(client, client->buf + client->buffered,
RFB_BUF_SIZE - client->buffered);
} else
#endif
#ifdef LIBVNCSERVER_HAVE_SASL
if (client->saslconn) {
size = ReadFromSASL(client, client->buf + client->buffered,
RFB_BUF_SIZE - client->buffered);
} else
#endif
{
size = recv(client->sock, client->buf + client->buffered,
RFB_BUF_SIZE - client->buffered, MSG_DONTWAIT);
}
if (size == 0)
return FALSE;
if (size > 0)
client->buffered += size;
return TRUE;
}
rfbBool ReadFromRFBServer(rfbClient* client, char *out, unsigned int n)
{
if (!out)
return FALSE;
while (n != 0) {
while (n != 0 && client->buffered == 0) {
run_main_loop_once();
if (!ReadToBuffer(client))
return FALSE;
}
unsigned int size = MIN(client->buffered, n);
memcpy(out, client->buf, size);
client->buffered -= size;
memmove(client->buf, client->buf + size, client->buffered);
out += size;
n -= size;
}
return TRUE;
}
/*
* Write an exact number of bytes, and don't return until you've sent them.
*/
rfbBool
WriteToRFBServer(rfbClient* client, const char *buf, unsigned int n)
{
struct pollfd fds;
int i = 0;
int j;
const char *obuf = buf;
#ifdef LIBVNCSERVER_HAVE_SASL
const char *output;
unsigned int outputlen;
int err;
#endif /* LIBVNCSERVER_HAVE_SASL */
if (client->tlsSession) {
/* WriteToTLS() will guarantee either everything is written, or error/eof returns */
i = WriteToTLS(client, buf, n);
if (i <= 0) return FALSE;
return TRUE;
}
#ifdef LIBVNCSERVER_HAVE_SASL
if (client->saslconn) {
err = sasl_encode(client->saslconn,
buf, n,
&output, &outputlen);
if (err != SASL_OK) {
rfbClientLog("Failed to encode SASL data %s",
sasl_errstring(err, NULL, NULL));
return FALSE;
}
obuf = output;
n = outputlen;
}
#endif /* LIBVNCSERVER_HAVE_SASL */
// TODO: Dispatch events while waiting
while (i < n) {
j = write(client->sock, obuf + i, (n - i));
if (j > 0) {
i += j;
continue;
}
if (j == 0) {
rfbClientLog("write failed\n");
return FALSE;
}
if (errno != EWOULDBLOCK && errno != EAGAIN) {
rfbClientErr("write\n");
return FALSE;
}
fds.fd = client->sock;
fds.events = POLLOUT;
if (poll(&fds, 1, -1) <= 0) {
rfbClientErr("poll\n");
return FALSE;
}
}
return TRUE;
}
static rfbBool WaitForConnected(int socket, unsigned int secs)
{
struct pollfd fds = {
.fd = socket,
.events = POLLIN | POLLOUT | POLLERR | POLLHUP,
};
if (poll(&fds, 1, secs * 1000) != 1)
return FALSE;
int so_error = 0;
socklen_t len = sizeof(so_error);
getsockopt(socket, SOL_SOCKET, SO_ERROR, &so_error, &len);
return so_error == 0 ? TRUE : FALSE;
}
rfbSocket
ConnectClientToTcpAddr6(const char *hostname, int port)
{
rfbSocket sock = ConnectClientToTcpAddr6WithTimeout(hostname, port, DEFAULT_CONNECT_TIMEOUT);
/* put socket back into blocking mode for compatibility reasons */
if (sock != RFB_INVALID_SOCKET) {
SetBlocking(sock);
}
return sock;
}
rfbSocket
ConnectClientToTcpAddr6WithTimeout(const char *hostname, int port, unsigned int timeout)
{
rfbSocket sock;
int n;
struct addrinfo hints, *res, *ressave;
char port_s[10];
int one = 1;
snprintf(port_s, 10, "%d", port);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((n = getaddrinfo(strcmp(hostname,"") == 0 ? "localhost": hostname, port_s, &hints, &res)))
{
rfbClientErr("ConnectClientToTcpAddr6: getaddrinfo (%s)\n", gai_strerror(n));
return RFB_INVALID_SOCKET;
}
ressave = res;
sock = RFB_INVALID_SOCKET;
while (res)
{
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sock != RFB_INVALID_SOCKET)
{
if (SetNonBlocking(sock)) {
if (connect(sock, res->ai_addr, res->ai_addrlen) == 0) {
break;
} else {
if ((errno == EWOULDBLOCK || errno == EINPROGRESS) && WaitForConnected(sock, timeout))
break;
rfbCloseSocket(sock);
sock = RFB_INVALID_SOCKET;
}
} else {
rfbCloseSocket(sock);
sock = RFB_INVALID_SOCKET;
}
}
res = res->ai_next;
}
freeaddrinfo(ressave);
if (sock == RFB_INVALID_SOCKET)
{
rfbClientErr("ConnectClientToTcpAddr6: connect\n");
return RFB_INVALID_SOCKET;
}
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
(char *)&one, sizeof(one)) < 0) {
rfbClientErr("ConnectToTcpAddr: setsockopt\n");
rfbCloseSocket(sock);
return RFB_INVALID_SOCKET;
}
return sock;
}
rfbSocket
ConnectClientToUnixSock(const char *sockFile)
{
rfbSocket sock = ConnectClientToUnixSockWithTimeout(sockFile, DEFAULT_CONNECT_TIMEOUT);
/* put socket back into blocking mode for compatibility reasons */
if (sock != RFB_INVALID_SOCKET) {
SetBlocking(sock);
}
return sock;
}
rfbSocket
ConnectClientToUnixSockWithTimeout(const char *sockFile, unsigned int timeout)
{
rfbSocket sock;
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
if(strlen(sockFile) + 1 > sizeof(addr.sun_path)) {
rfbClientErr("ConnectToUnixSock: socket file name too long\n");
return RFB_INVALID_SOCKET;
}
strcpy(addr.sun_path, sockFile);
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == RFB_INVALID_SOCKET) {
rfbClientErr("ConnectToUnixSock: socket (%s)\n",strerror(errno));
return RFB_INVALID_SOCKET;
}
if (!SetNonBlocking(sock))
return RFB_INVALID_SOCKET;
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr.sun_family) + strlen(addr.sun_path)) < 0 &&
!(errno == EINPROGRESS && WaitForConnected(sock, timeout))) {
rfbClientErr("ConnectToUnixSock: connect\n");
rfbCloseSocket(sock);
return RFB_INVALID_SOCKET;
}
return sock;
}
/*
* SetNonBlocking sets a socket into non-blocking mode.
*/
rfbBool
SetNonBlocking(rfbSocket sock)
{
int flags = fcntl(sock, F_GETFL);
if(flags < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
rfbClientErr("Setting socket to non-blocking failed: %s\n",strerror(errno));
return FALSE;
}
return TRUE;
}
rfbBool SetBlocking(rfbSocket sock)
{
int flags = fcntl(sock, F_GETFL);
if(flags < 0 || fcntl(sock, F_SETFL, flags & ~O_NONBLOCK) < 0) {
rfbClientErr("Setting socket to blocking failed: %s\n",strerror(errno));
return FALSE;
}
return TRUE;
}
/*
* SetDSCP sets a socket's IP QoS parameters aka Differentiated Services Code Point field
*/
rfbBool
SetDSCP(rfbSocket sock, int dscp)
{
int level, cmd;
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);
if(getsockname(sock, &addr, &addrlen) != 0) {
rfbClientErr("Setting socket QoS failed while getting socket address: %s\n",strerror(errno));
return FALSE;
}
switch(addr.sa_family)
{
case AF_INET6:
level = IPPROTO_IPV6;
cmd = IPV6_TCLASS;
break;
case AF_INET:
level = IPPROTO_IP;
cmd = IP_TOS;
break;
default:
rfbClientErr("Setting socket QoS failed: Not bound to IP address");
return FALSE;
}
if(setsockopt(sock, level, cmd, (void*)&dscp, sizeof(dscp)) != 0) {
rfbClientErr("Setting socket QoS failed: %s\n", strerror(errno));
return FALSE;
}
return TRUE;
}
/*
* Test if the other end of a socket is on the same machine.
*/
rfbBool
SameMachine(rfbSocket sock)
{
struct sockaddr_in peeraddr, myaddr;
socklen_t addrlen = sizeof(struct sockaddr_in);
getpeername(sock, (struct sockaddr *)&peeraddr, &addrlen);
getsockname(sock, (struct sockaddr *)&myaddr, &addrlen);
return (peeraddr.sin_addr.s_addr == myaddr.sin_addr.s_addr);
}

587
src/tls_gnutls.c 100644
View File

@ -0,0 +1,587 @@
/*
* Copyright (C) 2009 Vic Lee.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include "rfbclient.h"
#include "tls.h"
#include <stdio.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <errno.h>
static const char *rfbTLSPriority = "NORMAL:+DHE-DSS:+RSA:+DHE-RSA";
static const char *rfbAnonTLSPriority= "NORMAL:+ANON-DH";
#define DH_BITS 1024
static gnutls_dh_params_t rfbDHParams;
static rfbBool rfbTLSInitialized = FALSE;
static int
verify_certificate_callback (gnutls_session_t session)
{
unsigned int status;
const gnutls_datum_t *cert_list;
unsigned int cert_list_size;
int ret;
gnutls_x509_crt_t cert;
rfbClient *sptr;
char *hostname;
sptr = (rfbClient *)gnutls_session_get_ptr(session);
if (!sptr) {
rfbClientLog("Failed to validate certificate - missing client data\n");
return GNUTLS_E_CERTIFICATE_ERROR;
}
hostname = sptr->serverHost;
if (!hostname) {
rfbClientLog("No server hostname found for client\n");
return GNUTLS_E_CERTIFICATE_ERROR;
}
/* This verification function uses the trusted CAs in the credentials
* structure. So you must have installed one or more CA certificates.
*/
ret = gnutls_certificate_verify_peers2 (session, &status);
if (ret < 0)
{
rfbClientLog ("Certificate validation call failed\n");
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (status & GNUTLS_CERT_INVALID)
rfbClientLog("The certificate is not trusted.\n");
if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
rfbClientLog("The certificate hasn't got a known issuer.\n");
if (status & GNUTLS_CERT_REVOKED)
rfbClientLog("The certificate has been revoked.\n");
if (status & GNUTLS_CERT_EXPIRED)
rfbClientLog("The certificate has expired\n");
if (status & GNUTLS_CERT_NOT_ACTIVATED)
rfbClientLog("The certificate is not yet activated\n");
if (status)
return GNUTLS_E_CERTIFICATE_ERROR;
/* Up to here the process is the same for X.509 certificates and
* OpenPGP keys. From now on X.509 certificates are assumed. This can
* be easily extended to work with openpgp keys as well.
*/
if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) {
rfbClientLog("The certificate was not X509\n");
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (gnutls_x509_crt_init (&cert) < 0)
{
rfbClientLog("Error initialising certificate structure\n");
return GNUTLS_E_CERTIFICATE_ERROR;
}
cert_list = gnutls_certificate_get_peers (session, &cert_list_size);
if (cert_list == NULL)
{
rfbClientLog("No certificate was found!\n");
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0)
{
rfbClientLog("Error parsing certificate\n");
return GNUTLS_E_CERTIFICATE_ERROR;
}
// Hostname verification does NOT work
//if (!gnutls_x509_crt_check_hostname (cert, hostname))
// {
// rfbClientLog("The certificate's owner does not match hostname '%s'\n",
// hostname);
// return GNUTLS_E_CERTIFICATE_ERROR;
// }
gnutls_x509_crt_deinit (cert);
/* notify gnutls to continue handshake normally */
return 0;
}
static rfbBool
InitializeTLS(void)
{
int ret;
if (rfbTLSInitialized) return TRUE;
if ((ret = gnutls_global_init()) < 0 ||
(ret = gnutls_dh_params_init(&rfbDHParams)) < 0 ||
(ret = gnutls_dh_params_generate2(rfbDHParams, DH_BITS)) < 0)
{
rfbClientLog("Failed to initialized GnuTLS: %s.\n", gnutls_strerror(ret));
return FALSE;
}
rfbClientLog("GnuTLS version %s initialized.\n", gnutls_check_version(NULL));
rfbTLSInitialized = TRUE;
return TRUE;
}
static ssize_t
PushTLS(gnutls_transport_ptr_t transport, const void *data, size_t len)
{
rfbClient *client = (rfbClient*)transport;
int ret;
while (1)
{
ret = write(client->sock, data, len);
if (ret < 0)
{
if (errno == EINTR) continue;
return -1;
}
return ret;
}
}
static ssize_t
PullTLS(gnutls_transport_ptr_t transport, void *data, size_t len)
{
rfbClient *client = (rfbClient*)transport;
int ret;
while (1)
{
ret = read(client->sock, data, len);
if (ret < 0)
{
if (errno == EINTR) continue;
return -1;
}
return ret;
}
}
static rfbBool
InitializeTLSSession(rfbClient* client, rfbBool anonTLS)
{
int ret;
const char *p;
if (client->tlsSession) return TRUE;
if ((ret = gnutls_init((gnutls_session_t*)&client->tlsSession, GNUTLS_CLIENT)) < 0)
{
rfbClientLog("Failed to initialized TLS session: %s.\n", gnutls_strerror(ret));
return FALSE;
}
if ((ret = gnutls_priority_set_direct((gnutls_session_t)client->tlsSession,
anonTLS ? rfbAnonTLSPriority : rfbTLSPriority, &p)) < 0)
{
rfbClientLog("Warning: Failed to set TLS priority: %s (%s).\n", gnutls_strerror(ret), p);
}
gnutls_transport_set_ptr((gnutls_session_t)client->tlsSession, (gnutls_transport_ptr_t)client);
gnutls_transport_set_push_function((gnutls_session_t)client->tlsSession, PushTLS);
gnutls_transport_set_pull_function((gnutls_session_t)client->tlsSession, PullTLS);
INIT_MUTEX(client->tlsRwMutex);
rfbClientLog("TLS session initialized.\n");
return TRUE;
}
static rfbBool
SetTLSAnonCredential(rfbClient* client)
{
gnutls_anon_client_credentials_t anonCred;
int ret;
if ((ret = gnutls_anon_allocate_client_credentials(&anonCred)) < 0 ||
(ret = gnutls_credentials_set((gnutls_session_t)client->tlsSession, GNUTLS_CRD_ANON, anonCred)) < 0)
{
FreeTLS(client);
rfbClientLog("Failed to create anonymous credentials: %s", gnutls_strerror(ret));
return FALSE;
}
rfbClientLog("TLS anonymous credential created.\n");
return TRUE;
}
static rfbBool
HandshakeTLS(rfbClient* client)
{
int timeout = 15;
int ret;
while (timeout > 0 && (ret = gnutls_handshake((gnutls_session_t)client->tlsSession)) < 0)
{
if (!gnutls_error_is_fatal(ret))
{
rfbClientLog("TLS handshake blocking.\n");
sleep(1);
timeout--;
continue;
}
rfbClientLog("TLS handshake failed: %s.\n", gnutls_strerror(ret));
FreeTLS(client);
return FALSE;
}
if (timeout <= 0)
{
rfbClientLog("TLS handshake timeout.\n");
FreeTLS(client);
return FALSE;
}
rfbClientLog("TLS handshake done.\n");
return TRUE;
}
/* VeNCrypt sub auth. 1 byte auth count, followed by count * 4 byte integers */
static rfbBool
ReadVeNCryptSecurityType(rfbClient* client, uint32_t *result)
{
uint8_t count=0;
uint8_t loop=0;
uint8_t flag=0;
uint32_t tAuth[256], t;
char buf1[500],buf2[10];
uint32_t authScheme;
if (!ReadFromRFBServer(client, (char *)&count, 1)) return FALSE;
if (count==0)
{
rfbClientLog("List of security types is ZERO. Giving up.\n");
return FALSE;
}
rfbClientLog("We have %d security types to read\n", count);
authScheme=0;
/* now, we have a list of available security types to read ( uint8_t[] ) */
for (loop=0;loop<count;loop++)
{
if (!ReadFromRFBServer(client, (char *)&tAuth[loop], 4)) return FALSE;
t=rfbClientSwap32IfLE(tAuth[loop]);
rfbClientLog("%d) Received security type %d\n", loop, t);
if (flag) continue;
if (t==rfbVeNCryptTLSNone ||
t==rfbVeNCryptTLSVNC ||
t==rfbVeNCryptTLSPlain ||
#ifdef LIBVNCSERVER_HAVE_SASL
t==rfbVeNCryptTLSSASL ||
t==rfbVeNCryptX509SASL ||
#endif /*LIBVNCSERVER_HAVE_SASL */
t==rfbVeNCryptX509None ||
t==rfbVeNCryptX509VNC ||
t==rfbVeNCryptX509Plain)
{
flag++;
authScheme=t;
rfbClientLog("Selecting security type %d (%d/%d in the list)\n", authScheme, loop, count);
/* send back 4 bytes (in original byte order!) indicating which security type to use */
if (!WriteToRFBServer(client, (char *)&tAuth[loop], 4)) return FALSE;
}
tAuth[loop]=t;
}
if (authScheme==0)
{
memset(buf1, 0, sizeof(buf1));
for (loop=0;loop<count;loop++)
{
if (strlen(buf1)>=sizeof(buf1)-1) break;
snprintf(buf2, sizeof(buf2), (loop>0 ? ", %d" : "%d"), (int)tAuth[loop]);
strncat(buf1, buf2, sizeof(buf1)-strlen(buf1)-1);
}
rfbClientLog("Unknown VeNCrypt authentication scheme from VNC server: %s\n",
buf1);
return FALSE;
}
*result = authScheme;
return TRUE;
}
static void
FreeX509Credential(rfbCredential *cred)
{
if (cred->x509Credential.x509CACertFile) free(cred->x509Credential.x509CACertFile);
if (cred->x509Credential.x509CACrlFile) free(cred->x509Credential.x509CACrlFile);
if (cred->x509Credential.x509ClientCertFile) free(cred->x509Credential.x509ClientCertFile);
if (cred->x509Credential.x509ClientKeyFile) free(cred->x509Credential.x509ClientKeyFile);
free(cred);
}
static gnutls_certificate_credentials_t
CreateX509CertCredential(rfbCredential *cred)
{
// Use this to enable debug logs
//gnutls_global_set_log_level(GNUTLS_DEBUG_LEVEL);
gnutls_certificate_credentials_t x509_cred;
int ret;
if (!cred->x509Credential.x509CACertFile)
{
rfbClientLog("No CA certificate provided.\n");
return NULL;
}
if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0)
{
rfbClientLog("Cannot allocate credentials: %s.\n", gnutls_strerror(ret));
return NULL;
}
if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred,
cred->x509Credential.x509CACertFile, GNUTLS_X509_FMT_PEM)) < 0)
{
rfbClientLog("Cannot load CA credentials: %s.\n", gnutls_strerror(ret));
gnutls_certificate_free_credentials (x509_cred);
return NULL;
}
if (cred->x509Credential.x509ClientCertFile && cred->x509Credential.x509ClientKeyFile)
{
if ((ret = gnutls_certificate_set_x509_key_file(x509_cred,
cred->x509Credential.x509ClientCertFile, cred->x509Credential.x509ClientKeyFile,
GNUTLS_X509_FMT_PEM)) < 0)
{
rfbClientLog("Cannot load client certificate or key: %s.\n", gnutls_strerror(ret));
gnutls_certificate_free_credentials (x509_cred);
return NULL;
}
} else
{
rfbClientLog("No client certificate or key provided.\n");
}
if (cred->x509Credential.x509CACrlFile)
{
if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred,
cred->x509Credential.x509CACrlFile, GNUTLS_X509_FMT_PEM)) < 0)
{
rfbClientLog("Cannot load CRL: %s.\n", gnutls_strerror(ret));
gnutls_certificate_free_credentials (x509_cred);
return NULL;
}
} else
{
rfbClientLog("No CRL provided.\n");
}
gnutls_certificate_set_dh_params (x509_cred, rfbDHParams);
return x509_cred;
}
rfbBool
HandleAnonTLSAuth(rfbClient* client)
{
if (!InitializeTLS() || !InitializeTLSSession(client, TRUE)) return FALSE;
if (!SetTLSAnonCredential(client)) return FALSE;
if (!HandshakeTLS(client)) return FALSE;
return TRUE;
}
rfbBool
HandleVeNCryptAuth(rfbClient* client)
{
uint8_t major, minor, status;
uint32_t authScheme;
rfbBool anonTLS;
gnutls_certificate_credentials_t x509_cred = NULL;
int ret;
if (!InitializeTLS()) return FALSE;
/* Read VeNCrypt version */
if (!ReadFromRFBServer(client, (char *)&major, 1) ||
!ReadFromRFBServer(client, (char *)&minor, 1))
{
return FALSE;
}
rfbClientLog("Got VeNCrypt version %d.%d from server.\n", (int)major, (int)minor);
if (major != 0 && minor != 2)
{
rfbClientLog("Unsupported VeNCrypt version.\n");
return FALSE;
}
if (!WriteToRFBServer(client, (char *)&major, 1) ||
!WriteToRFBServer(client, (char *)&minor, 1) ||
!ReadFromRFBServer(client, (char *)&status, 1))
{
return FALSE;
}
if (status != 0)
{
rfbClientLog("Server refused VeNCrypt version %d.%d.\n", (int)major, (int)minor);
return FALSE;
}
if (!ReadVeNCryptSecurityType(client, &authScheme)) return FALSE;
if (!ReadFromRFBServer(client, (char *)&status, 1) || status != 1)
{
rfbClientLog("Server refused VeNCrypt authentication %d (%d).\n", authScheme, (int)status);
return FALSE;
}
client->subAuthScheme = authScheme;
/* Some VeNCrypt security types are anonymous TLS, others are X509 */
switch (authScheme)
{
case rfbVeNCryptTLSNone:
case rfbVeNCryptTLSVNC:
case rfbVeNCryptTLSPlain:
#ifdef LIBVNCSERVER_HAVE_SASL
case rfbVeNCryptTLSSASL:
#endif /* LIBVNCSERVER_HAVE_SASL */
anonTLS = TRUE;
break;
default:
anonTLS = FALSE;
break;
}
/* Get X509 Credentials if it's not anonymous */
if (!anonTLS)
{
rfbCredential *cred;
if (!client->GetCredential)
{
rfbClientLog("GetCredential callback is not set.\n");
return FALSE;
}
cred = client->GetCredential(client, rfbCredentialTypeX509);
if (!cred)
{
rfbClientLog("Reading credential failed\n");
return FALSE;
}
x509_cred = CreateX509CertCredential(cred);
FreeX509Credential(cred);
if (!x509_cred) return FALSE;
}
/* Start up the TLS session */
if (!InitializeTLSSession(client, anonTLS)) return FALSE;
if (anonTLS)
{
if (!SetTLSAnonCredential(client)) return FALSE;
}
else
{
/* Set the certificate verification callback. */
gnutls_certificate_set_verify_function (x509_cred, verify_certificate_callback);
gnutls_session_set_ptr ((gnutls_session_t)client->tlsSession, (void *)client);
if ((ret = gnutls_credentials_set((gnutls_session_t)client->tlsSession, GNUTLS_CRD_CERTIFICATE, x509_cred)) < 0)
{
rfbClientLog("Cannot set x509 credential: %s.\n", gnutls_strerror(ret));
FreeTLS(client);
return FALSE;
}
}
if (!HandshakeTLS(client)) return FALSE;
/* We are done here. The caller should continue with client->subAuthScheme
* to do actual sub authentication.
*/
return TRUE;
}
int
ReadFromTLS(rfbClient* client, char *out, unsigned int n)
{
ssize_t ret;
LOCK(client->tlsRwMutex);
ret = gnutls_record_recv((gnutls_session_t)client->tlsSession, out, n);
UNLOCK(client->tlsRwMutex);
if (ret >= 0) return ret;
if (ret == GNUTLS_E_REHANDSHAKE || ret == GNUTLS_E_AGAIN)
{
errno = EAGAIN;
} else
{
rfbClientLog("Error reading from TLS: %s.\n", gnutls_strerror(ret));
errno = EINTR;
}
return -1;
}
int
WriteToTLS(rfbClient* client, const char *buf, unsigned int n)
{
unsigned int offset = 0;
ssize_t ret;
while (offset < n)
{
LOCK(client->tlsRwMutex);
ret = gnutls_record_send((gnutls_session_t)client->tlsSession, buf+offset, (size_t)(n-offset));
UNLOCK(client->tlsRwMutex);
if (ret == 0) continue;
if (ret < 0)
{
if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) continue;
rfbClientLog("Error writing to TLS: %s.\n", gnutls_strerror(ret));
return -1;
}
offset += (unsigned int)ret;
}
return offset;
}
void FreeTLS(rfbClient* client)
{
if (client->tlsSession)
{
gnutls_deinit((gnutls_session_t)client->tlsSession);
client->tlsSession = NULL;
TINI_MUTEX(client->tlsRwMutex);
}
}
#ifdef LIBVNCSERVER_HAVE_SASL
int
GetTLSCipherBits(rfbClient* client)
{
gnutls_cipher_algorithm_t cipher = gnutls_cipher_get((gnutls_session_t)client->tlsSession);
return gnutls_cipher_get_key_size(cipher) * 8;
}
#endif /* LIBVNCSERVER_HAVE_SASL */

68
src/tls_none.c 100644
View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2012 Christian Beier.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include "rfbclient.h"
#include "tls.h"
#include <errno.h>
rfbBool HandleAnonTLSAuth(rfbClient* client)
{
rfbClientLog("TLS is not supported.\n");
return FALSE;
}
rfbBool HandleVeNCryptAuth(rfbClient* client)
{
rfbClientLog("TLS is not supported.\n");
return FALSE;
}
int ReadFromTLS(rfbClient* client, char *out, unsigned int n)
{
rfbClientLog("TLS is not supported.\n");
errno = EINTR;
return -1;
}
int WriteToTLS(rfbClient* client, const char *buf, unsigned int n)
{
rfbClientLog("TLS is not supported.\n");
errno = EINTR;
return -1;
}
void FreeTLS(rfbClient* client)
{
}
#ifdef LIBVNCSERVER_HAVE_SASL
int
GetTLSCipherBits(rfbClient* client)
{
rfbClientLog("TLS is not supported.\n");
return 0;
}
#endif /* LIBVNCSERVER_HAVE_SASL */

686
src/tls_openssl.c 100644
View File

@ -0,0 +1,686 @@
/*
* Copyright (C) 2012 Philip Van Hoof <philip@codeminded.be>
* Copyright (C) 2009 Vic Lee.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include "rfbclient.h"
#include <stdio.h>
#include <errno.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/rand.h>
#include "tls.h"
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
static rfbBool rfbTLSInitialized = FALSE;
// Locking callbacks are only initialized if we have mutex support.
#if defined(LIBVNCSERVER_HAVE_LIBPTHREAD)
static MUTEX(*mutex_buf) = NULL;
struct CRYPTO_dynlock_value {
MUTEX(mutex);
};
static void locking_function(int mode, int n, const char *file, int line)
{
if (mode & CRYPTO_LOCK)
LOCK(mutex_buf[n]);
else
UNLOCK(mutex_buf[n]);
}
static unsigned long id_function(void)
{
return ((unsigned long) CURRENT_THREAD_ID);
}
static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line)
{
struct CRYPTO_dynlock_value *value;
value = (struct CRYPTO_dynlock_value *)
malloc(sizeof(struct CRYPTO_dynlock_value));
if (!value)
goto err;
INIT_MUTEX(value->mutex);
return value;
err:
return (NULL);
}
static void dyn_lock_function (int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
{
if (mode & CRYPTO_LOCK)
LOCK(l->mutex);
else
UNLOCK(l->mutex);
}
static void
dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line)
{
TINI_MUTEX(l->mutex);
free(l);
}
static rfbBool InitLockingCb()
{
mutex_buf = malloc(CRYPTO_num_locks() * MUTEX_SIZE);
if (mutex_buf == NULL) {
rfbClientLog("Failed to initialized OpenSSL: memory.\n");
return FALSE;
}
int i;
for (i = 0; i < CRYPTO_num_locks(); i++)
INIT_MUTEX(mutex_buf[i]);
CRYPTO_set_locking_callback(locking_function);
CRYPTO_set_id_callback(id_function);
CRYPTO_set_dynlock_create_callback(dyn_create_function);
CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
return TRUE;
}
#else
//If mutex support is not available, locking initialization is a no-op.
static rfbBool InitLockingCb() { return TRUE; }
#endif
static int
ssl_error_to_errno (int ssl_error)
{
switch (ssl_error) {
case SSL_ERROR_NONE:
return 0;
case SSL_ERROR_ZERO_RETURN:
/* this one does not map well at all */
//d(printf ("ssl_errno: SSL_ERROR_ZERO_RETURN\n"));
return EINVAL;
case SSL_ERROR_WANT_READ: /* non-fatal; retry */
case SSL_ERROR_WANT_WRITE: /* non-fatal; retry */
//d(printf ("ssl_errno: SSL_ERROR_WANT_[READ,WRITE]\n"));
return EAGAIN;
case SSL_ERROR_SYSCALL:
//d(printf ("ssl_errno: SSL_ERROR_SYSCALL\n"));
return EINTR;
case SSL_ERROR_SSL:
//d(printf ("ssl_errno: SSL_ERROR_SSL <-- very useful error...riiiiight\n"));
return EINTR;
default:
//d(printf ("ssl_errno: default error\n"));
return EINTR;
}
}
static rfbBool
InitializeTLS(void)
{
if (rfbTLSInitialized)
return TRUE;
if (!InitLockingCb())
return FALSE;
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
RAND_load_file("/dev/urandom", 1024);
rfbClientLog("OpenSSL version %s initialized.\n", SSLeay_version(SSLEAY_VERSION));
rfbTLSInitialized = TRUE;
return TRUE;
}
static int sock_read_ready(SSL *ssl, uint32_t ms)
{
int r = 0;
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(SSL_get_fd(ssl), &fds);
tv.tv_sec = ms / 1000;
tv.tv_usec = (ms % 1000) * 1000;
r = select (SSL_get_fd(ssl) + 1, &fds, NULL, NULL, &tv);
return r;
}
static int wait_for_data(SSL *ssl, int ret, int timeout)
{
int err;
int retval = 1;
err = SSL_get_error(ssl, ret);
switch(err)
{
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
ret = sock_read_ready(ssl, timeout*1000);
if (ret == -1) {
retval = 2;
}
break;
default:
retval = 3;
long verify_res = SSL_get_verify_result(ssl);
if (verify_res != X509_V_OK)
rfbClientLog("Could not verify server certificate: %s.\n",
X509_verify_cert_error_string(verify_res));
break;
}
ERR_clear_error();
return retval;
}
static rfbBool
load_crls_from_file(char *file, SSL_CTX *ssl_ctx)
{
X509_STORE *st;
int i;
int count = 0;
BIO *bio;
STACK_OF(X509_INFO) *xis = NULL;
X509_INFO *xi;
st = SSL_CTX_get_cert_store(ssl_ctx);
bio = BIO_new_file(file, "r");
if (bio == NULL)
return FALSE;
xis = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
BIO_free(bio);
for (i = 0; i < sk_X509_INFO_num(xis); i++)
{
xi = sk_X509_INFO_value(xis, i);
if (xi->crl)
{
X509_STORE_add_crl(st, xi->crl);
xi->crl = NULL;
count++;
}
}
sk_X509_INFO_pop_free(xis, X509_INFO_free);
if (count > 0)
return TRUE;
else
return FALSE;
}
static SSL *
open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS, rfbCredential *cred)
{
SSL_CTX *ssl_ctx = NULL;
SSL *ssl = NULL;
int n, finished = 0;
X509_VERIFY_PARAM *param;
uint8_t verify_crls;
if (!(ssl_ctx = SSL_CTX_new(SSLv23_client_method())))
{
rfbClientLog("Could not create new SSL context.\n");
return NULL;
}
param = X509_VERIFY_PARAM_new();
/* Setup verification if not anonymous */
if (!anonTLS)
{
verify_crls = cred->x509Credential.x509CrlVerifyMode;
if (cred->x509Credential.x509CACertFile)
{
if (!SSL_CTX_load_verify_locations(ssl_ctx, cred->x509Credential.x509CACertFile, NULL))
{
rfbClientLog("Failed to load CA certificate from %s.\n",
cred->x509Credential.x509CACertFile);
goto error_free_ctx;
}
} else {
rfbClientLog("Using default paths for certificate verification.\n");
SSL_CTX_set_default_verify_paths (ssl_ctx);
}
if (cred->x509Credential.x509CACrlFile)
{
if (!load_crls_from_file(cred->x509Credential.x509CACrlFile, ssl_ctx))
{
rfbClientLog("CRLs could not be loaded.\n");
goto error_free_ctx;
}
if (verify_crls == rfbX509CrlVerifyNone) verify_crls = rfbX509CrlVerifyAll;
}
if (cred->x509Credential.x509ClientCertFile && cred->x509Credential.x509ClientKeyFile)
{
if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cred->x509Credential.x509ClientCertFile) != 1)
{
rfbClientLog("Client certificate could not be loaded.\n");
goto error_free_ctx;
}
if (SSL_CTX_use_PrivateKey_file(ssl_ctx, cred->x509Credential.x509ClientKeyFile,
SSL_FILETYPE_PEM) != 1)
{
rfbClientLog("Client private key could not be loaded.\n");
goto error_free_ctx;
}
if (SSL_CTX_check_private_key(ssl_ctx) == 0) {
rfbClientLog("Client certificate and private key do not match.\n");
goto error_free_ctx;
}
}
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
if (verify_crls == rfbX509CrlVerifyClient)
X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
else if (verify_crls == rfbX509CrlVerifyAll)
X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
if(!X509_VERIFY_PARAM_set1_host(param, client->serverHost, strlen(client->serverHost)))
{
rfbClientLog("Could not set server name for verification.\n");
goto error_free_ctx;
}
SSL_CTX_set1_param(ssl_ctx, param);
SSL_CTX_set_cipher_list(ssl_ctx, "ALL");
} else { /* anonTLS here */
/* Need anonymous ciphers for anonTLS, see https://github.com/LibVNC/libvncserver/issues/347#issuecomment-597477103 */
SSL_CTX_set_cipher_list(ssl_ctx, "aNULL");
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER
/*
See https://www.openssl.org/docs/man1.1.0/man3/SSL_set_security_level.html
Not specifying 0 here makes LibVNCClient fail connecting to some servers.
*/
SSL_CTX_set_security_level(ssl_ctx, 0);
/*
Specifying a maximum protocol version of 1.2 gets us ADH cipher on OpenSSL 1.1.x,
see https://github.com/LibVNC/libvncserver/issues/347#issuecomment-597974313
*/
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_2_VERSION);
#endif
}
if (!(ssl = SSL_new (ssl_ctx)))
{
rfbClientLog("Could not create a new SSL session.\n");
goto error_free_ctx;
}
SSL_set_fd (ssl, sockfd);
SSL_CTX_set_app_data (ssl_ctx, client);
do
{
n = SSL_connect(ssl);
if (n != 1)
{
if (wait_for_data(ssl, n, 1) != 1)
{
finished = 1;
SSL_shutdown(ssl);
goto error_free_ssl;
}
}
} while( n != 1 && finished != 1 );
X509_VERIFY_PARAM_free(param);
return ssl;
error_free_ssl:
SSL_free(ssl);
error_free_ctx:
X509_VERIFY_PARAM_free(param);
SSL_CTX_free(ssl_ctx);
return NULL;
}
static rfbBool
InitializeTLSSession(rfbClient* client, rfbBool anonTLS, rfbCredential *cred)
{
if (client->tlsSession) return TRUE;
client->tlsSession = open_ssl_connection (client, client->sock, anonTLS, cred);
if (!client->tlsSession)
return FALSE;
INIT_MUTEX(client->tlsRwMutex);
rfbClientLog("TLS session initialized.\n");
return TRUE;
}
static rfbBool
HandshakeTLS(rfbClient* client)
{
int timeout = 15;
int ret;
return TRUE;
while (timeout > 0 && (ret = SSL_do_handshake(client->tlsSession)) < 0)
{
if (ret != -1)
{
rfbClientLog("TLS handshake blocking.\n");
sleep(1);
timeout--;
continue;
}
rfbClientLog("TLS handshake failed.\n");
FreeTLS(client);
return FALSE;
}
if (timeout <= 0)
{
rfbClientLog("TLS handshake timeout.\n");
FreeTLS(client);
return FALSE;
}
rfbClientLog("TLS handshake done.\n");
return TRUE;
}
/* VeNCrypt sub auth. 1 byte auth count, followed by count * 4 byte integers */
static rfbBool
ReadVeNCryptSecurityType(rfbClient* client, uint32_t *result)
{
uint8_t count=0;
uint8_t loop=0;
uint8_t flag=0;
uint32_t tAuth[256], t;
char buf1[500],buf2[10];
uint32_t authScheme;
if (!ReadFromRFBServer(client, (char *)&count, 1)) return FALSE;
if (count==0)
{
rfbClientLog("List of security types is ZERO. Giving up.\n");
return FALSE;
}
rfbClientLog("We have %d security types to read\n", count);
authScheme=0;
/* now, we have a list of available security types to read ( uint8_t[] ) */
for (loop=0;loop<count;loop++)
{
if (!ReadFromRFBServer(client, (char *)&tAuth[loop], 4)) return FALSE;
t=rfbClientSwap32IfLE(tAuth[loop]);
rfbClientLog("%d) Received security type %d\n", loop, t);
if (flag) continue;
if (t==rfbVeNCryptTLSNone ||
t==rfbVeNCryptTLSVNC ||
t==rfbVeNCryptTLSPlain ||
#ifdef LIBVNCSERVER_HAVE_SASL
t==rfbVeNCryptTLSSASL ||
t==rfbVeNCryptX509SASL ||
#endif /*LIBVNCSERVER_HAVE_SASL */
t==rfbVeNCryptX509None ||
t==rfbVeNCryptX509VNC ||
t==rfbVeNCryptX509Plain)
{
flag++;
authScheme=t;
rfbClientLog("Selecting security type %d (%d/%d in the list)\n", authScheme, loop, count);
/* send back 4 bytes (in original byte order!) indicating which security type to use */
if (!WriteToRFBServer(client, (char *)&tAuth[loop], 4)) return FALSE;
}
tAuth[loop]=t;
}
if (authScheme==0)
{
memset(buf1, 0, sizeof(buf1));
for (loop=0;loop<count;loop++)
{
if (strlen(buf1)>=sizeof(buf1)-1) break;
snprintf(buf2, sizeof(buf2), (loop>0 ? ", %d" : "%d"), (int)tAuth[loop]);
strncat(buf1, buf2, sizeof(buf1)-strlen(buf1)-1);
}
rfbClientLog("Unknown VeNCrypt authentication scheme from VNC server: %s\n",
buf1);
return FALSE;
}
*result = authScheme;
return TRUE;
}
rfbBool
HandleAnonTLSAuth(rfbClient* client)
{
if (!InitializeTLS() || !InitializeTLSSession(client, TRUE, NULL)) return FALSE;
if (!HandshakeTLS(client)) return FALSE;
return TRUE;
}
static void
FreeX509Credential(rfbCredential *cred)
{
if (cred->x509Credential.x509CACertFile) free(cred->x509Credential.x509CACertFile);
if (cred->x509Credential.x509CACrlFile) free(cred->x509Credential.x509CACrlFile);
if (cred->x509Credential.x509ClientCertFile) free(cred->x509Credential.x509ClientCertFile);
if (cred->x509Credential.x509ClientKeyFile) free(cred->x509Credential.x509ClientKeyFile);
free(cred);
}
rfbBool
HandleVeNCryptAuth(rfbClient* client)
{
uint8_t major, minor, status;
uint32_t authScheme;
rfbBool anonTLS;
rfbCredential *cred = NULL;
rfbBool result = TRUE;
if (!InitializeTLS()) return FALSE;
/* Read VeNCrypt version */
if (!ReadFromRFBServer(client, (char *)&major, 1) ||
!ReadFromRFBServer(client, (char *)&minor, 1))
{
return FALSE;
}
rfbClientLog("Got VeNCrypt version %d.%d from server.\n", (int)major, (int)minor);
if (major != 0 && minor != 2)
{
rfbClientLog("Unsupported VeNCrypt version.\n");
return FALSE;
}
if (!WriteToRFBServer(client, (char *)&major, 1) ||
!WriteToRFBServer(client, (char *)&minor, 1) ||
!ReadFromRFBServer(client, (char *)&status, 1))
{
return FALSE;
}
if (status != 0)
{
rfbClientLog("Server refused VeNCrypt version %d.%d.\n", (int)major, (int)minor);
return FALSE;
}
if (!ReadVeNCryptSecurityType(client, &authScheme)) return FALSE;
if (!ReadFromRFBServer(client, (char *)&status, 1) || status != 1)
{
rfbClientLog("Server refused VeNCrypt authentication %d (%d).\n", authScheme, (int)status);
return FALSE;
}
client->subAuthScheme = authScheme;
/* Some VeNCrypt security types are anonymous TLS, others are X509 */
switch (authScheme)
{
case rfbVeNCryptTLSNone:
case rfbVeNCryptTLSVNC:
case rfbVeNCryptTLSPlain:
#ifdef LIBVNCSERVER_HAVE_SASL
case rfbVeNCryptTLSSASL:
#endif /* LIBVNCSERVER_HAVE_SASL */
anonTLS = TRUE;
break;
default:
anonTLS = FALSE;
break;
}
/* Get X509 Credentials if it's not anonymous */
if (!anonTLS)
{
if (!client->GetCredential)
{
rfbClientLog("GetCredential callback is not set.\n");
return FALSE;
}
cred = client->GetCredential(client, rfbCredentialTypeX509);
if (!cred)
{
rfbClientLog("Reading credential failed\n");
return FALSE;
}
}
/* Start up the TLS session */
if (!InitializeTLSSession(client, anonTLS, cred)) result = FALSE;
if (!HandshakeTLS(client)) result = FALSE;
/* We are done here. The caller should continue with client->subAuthScheme
* to do actual sub authentication.
*/
if (cred) FreeX509Credential(cred);
return result;
}
int
ReadFromTLS(rfbClient* client, char *out, unsigned int n)
{
int ret = 0;
int ssl_error = SSL_ERROR_NONE;
LOCK(client->tlsRwMutex);
ret = SSL_read (client->tlsSession, out, n);
if (ret < 0)
ssl_error = SSL_get_error(client->tlsSession, ret);
UNLOCK(client->tlsRwMutex);
if (ret >= 0)
return ret;
else {
errno = ssl_error_to_errno(ssl_error);
if (errno != EAGAIN) {
rfbClientLog("Error reading from TLS: -.\n");
}
}
return -1;
}
int
WriteToTLS(rfbClient* client, const char *buf, unsigned int n)
{
unsigned int offset = 0;
int ret = 0;
int ssl_error = SSL_ERROR_NONE;
while (offset < n)
{
LOCK(client->tlsRwMutex);
ret = SSL_write (client->tlsSession, buf + offset, (size_t)(n-offset));
if (ret < 0)
ssl_error = SSL_get_error (client->tlsSession, ret);
UNLOCK(client->tlsRwMutex);
if (ret == 0) continue;
if (ret < 0)
{
errno = ssl_error_to_errno(ssl_error);
if (errno == EAGAIN || errno == EWOULDBLOCK) continue;
rfbClientLog("Error writing to TLS: -\n");
return -1;
}
offset += (unsigned int)ret;
}
return offset;
}
void FreeTLS(rfbClient* client)
{
if (client->tlsSession)
{
SSL_free(client->tlsSession);
client->tlsSession = NULL;
TINI_MUTEX(client->tlsRwMutex);
}
}
#ifdef LIBVNCSERVER_HAVE_SASL
int GetTLSCipherBits(rfbClient* client)
{
SSL *ssl = (SSL *)(client->tlsSession);
const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
return SSL_CIPHER_get_bits(cipher, NULL);
}
#endif /* LIBVNCSERVER_HAVE_SASL */

858
src/turbojpeg.c 100644
View File

@ -0,0 +1,858 @@
/*
* Copyright (C)2009-2012 D. R. Commander. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the libjpeg-turbo Project nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/* TurboJPEG/OSS: this implements the TurboJPEG API using libjpeg-turbo */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef JCS_EXTENSIONS
#define JPEG_INTERNAL_OPTIONS
#endif
#include <jpeglib.h>
#include <jerror.h>
#include <setjmp.h>
#include "./turbojpeg.h"
#define PAD(v, p) ((v+(p)-1)&(~((p)-1)))
#define CSTATE_START 100
#define DSTATE_START 200
#define MEMZERO(ptr, size) memset(ptr, 0, size)
#ifndef min
#define min(a,b) ((a)<(b)?(a):(b))
#endif
#ifndef max
#define max(a,b) ((a)>(b)?(a):(b))
#endif
/* Error handling (based on example in example.c) */
static char errStr[JMSG_LENGTH_MAX]="No error";
struct my_error_mgr
{
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};
typedef struct my_error_mgr *my_error_ptr;
static void my_error_exit(j_common_ptr cinfo)
{
my_error_ptr myerr=(my_error_ptr)cinfo->err;
(*cinfo->err->output_message)(cinfo);
longjmp(myerr->setjmp_buffer, 1);
}
/* Based on output_message() in jerror.c */
static void my_output_message(j_common_ptr cinfo)
{
(*cinfo->err->format_message)(cinfo, errStr);
}
/* Global structures, macros, etc. */
enum {COMPRESS=1, DECOMPRESS=2};
typedef struct _tjinstance
{
struct jpeg_compress_struct cinfo;
struct jpeg_decompress_struct dinfo;
struct jpeg_destination_mgr jdst;
struct jpeg_source_mgr jsrc;
struct my_error_mgr jerr;
int init;
} tjinstance;
static const int pixelsize[TJ_NUMSAMP]={3, 3, 3, 1, 3};
#define NUMSF 4
static const tjscalingfactor sf[NUMSF]={
{1, 1},
{1, 2},
{1, 4},
{1, 8}
};
#define _throw(m) {snprintf(errStr, JMSG_LENGTH_MAX, "%s", m); \
retval=-1; goto bailout;}
#define getinstance(handle) tjinstance *this=(tjinstance *)handle; \
j_compress_ptr cinfo=NULL; j_decompress_ptr dinfo=NULL; \
(void) cinfo; (void) dinfo; /* silence warnings */ \
if(!this) {snprintf(errStr, JMSG_LENGTH_MAX, "Invalid handle"); \
return -1;} \
cinfo=&this->cinfo; dinfo=&this->dinfo;
static int getPixelFormat(int pixelSize, int flags)
{
if(pixelSize==1) return TJPF_GRAY;
if(pixelSize==3)
{
if(flags&TJ_BGR) return TJPF_BGR;
else return TJPF_RGB;
}
if(pixelSize==4)
{
if(flags&TJ_ALPHAFIRST)
{
if(flags&TJ_BGR) return TJPF_XBGR;
else return TJPF_XRGB;
}
else
{
if(flags&TJ_BGR) return TJPF_BGRX;
else return TJPF_RGBX;
}
}
return -1;
}
static int setCompDefaults(struct jpeg_compress_struct *cinfo,
int pixelFormat, int subsamp, int jpegQual)
{
int retval=0;
switch(pixelFormat)
{
case TJPF_GRAY:
cinfo->in_color_space=JCS_GRAYSCALE; break;
#if JCS_EXTENSIONS==1
case TJPF_RGB:
cinfo->in_color_space=JCS_EXT_RGB; break;
case TJPF_BGR:
cinfo->in_color_space=JCS_EXT_BGR; break;
case TJPF_RGBX:
case TJPF_RGBA:
cinfo->in_color_space=JCS_EXT_RGBX; break;
case TJPF_BGRX:
case TJPF_BGRA:
cinfo->in_color_space=JCS_EXT_BGRX; break;
case TJPF_XRGB:
case TJPF_ARGB:
cinfo->in_color_space=JCS_EXT_XRGB; break;
case TJPF_XBGR:
case TJPF_ABGR:
cinfo->in_color_space=JCS_EXT_XBGR; break;
#else
case TJPF_RGB:
case TJPF_BGR:
case TJPF_RGBX:
case TJPF_BGRX:
case TJPF_XRGB:
case TJPF_XBGR:
case TJPF_RGBA:
case TJPF_BGRA:
case TJPF_ARGB:
case TJPF_ABGR:
cinfo->in_color_space=JCS_RGB; pixelFormat=TJPF_RGB;
break;
#endif
}
cinfo->input_components=tjPixelSize[pixelFormat];
jpeg_set_defaults(cinfo);
if(jpegQual>=0)
{
jpeg_set_quality(cinfo, jpegQual, TRUE);
if(jpegQual>=96) cinfo->dct_method=JDCT_ISLOW;
else cinfo->dct_method=JDCT_FASTEST;
}
if(subsamp==TJSAMP_GRAY)
jpeg_set_colorspace(cinfo, JCS_GRAYSCALE);
else
jpeg_set_colorspace(cinfo, JCS_YCbCr);
cinfo->comp_info[0].h_samp_factor=tjMCUWidth[subsamp]/8;
cinfo->comp_info[1].h_samp_factor=1;
cinfo->comp_info[2].h_samp_factor=1;
cinfo->comp_info[0].v_samp_factor=tjMCUHeight[subsamp]/8;
cinfo->comp_info[1].v_samp_factor=1;
cinfo->comp_info[2].v_samp_factor=1;
return retval;
}
static int setDecompDefaults(struct jpeg_decompress_struct *dinfo,
int pixelFormat)
{
int retval=0;
switch(pixelFormat)
{
case TJPF_GRAY:
dinfo->out_color_space=JCS_GRAYSCALE; break;
#if JCS_EXTENSIONS==1
case TJPF_RGB:
dinfo->out_color_space=JCS_EXT_RGB; break;
case TJPF_BGR:
dinfo->out_color_space=JCS_EXT_BGR; break;
case TJPF_RGBX:
dinfo->out_color_space=JCS_EXT_RGBX; break;
case TJPF_BGRX:
dinfo->out_color_space=JCS_EXT_BGRX; break;
case TJPF_XRGB:
dinfo->out_color_space=JCS_EXT_XRGB; break;
case TJPF_XBGR:
dinfo->out_color_space=JCS_EXT_XBGR; break;
#if JCS_ALPHA_EXTENSIONS==1
case TJPF_RGBA:
dinfo->out_color_space=JCS_EXT_RGBA; break;
case TJPF_BGRA:
dinfo->out_color_space=JCS_EXT_BGRA; break;
case TJPF_ARGB:
dinfo->out_color_space=JCS_EXT_ARGB; break;
case TJPF_ABGR:
dinfo->out_color_space=JCS_EXT_ABGR; break;
#endif
#else
case TJPF_RGB:
case TJPF_BGR:
case TJPF_RGBX:
case TJPF_BGRX:
case TJPF_XRGB:
case TJPF_XBGR:
case TJPF_RGBA:
case TJPF_BGRA:
case TJPF_ARGB:
case TJPF_ABGR:
dinfo->out_color_space=JCS_RGB; break;
#endif
default:
_throw("Unsupported pixel format");
}
bailout:
return retval;
}
static int getSubsamp(j_decompress_ptr dinfo)
{
int retval=-1, i, k;
for(i=0; i<NUMSUBOPT; i++)
{
if(dinfo->num_components==pixelsize[i])
{
if(dinfo->comp_info[0].h_samp_factor==tjMCUWidth[i]/8
&& dinfo->comp_info[0].v_samp_factor==tjMCUHeight[i]/8)
{
int match=0;
for(k=1; k<dinfo->num_components; k++)
{
if(dinfo->comp_info[k].h_samp_factor==1
&& dinfo->comp_info[k].v_samp_factor==1)
match++;
}
if(match==dinfo->num_components-1)
{
retval=i; break;
}
}
}
}
return retval;
}
#ifndef JCS_EXTENSIONS
/* Conversion functions to emulate the colorspace extensions. This allows the
TurboJPEG wrapper to be used with libjpeg */
#define TORGB(PS, ROFFSET, GOFFSET, BOFFSET) { \
int rowPad=pitch-width*PS; \
while(height--) \
{ \
unsigned char *endOfRow=src+width*PS; \
while(src<endOfRow) \
{ \
dst[RGB_RED]=src[ROFFSET]; \
dst[RGB_GREEN]=src[GOFFSET]; \
dst[RGB_BLUE]=src[BOFFSET]; \
dst+=RGB_PIXELSIZE; src+=PS; \
} \
src+=rowPad; \
} \
}
static unsigned char *toRGB(unsigned char *src, int width, int pitch,
int height, int pixelFormat, unsigned char *dst)
{
unsigned char *retval=src;
switch(pixelFormat)
{
case TJPF_RGB:
#if RGB_RED!=0 || RGB_GREEN!=1 || RGB_BLUE!=2 || RGB_PIXELSIZE!=3
retval=dst; TORGB(3, 0, 1, 2);
#endif
break;
case TJPF_BGR:
#if RGB_RED!=2 || RGB_GREEN!=1 || RGB_BLUE!=0 || RGB_PIXELSIZE!=3
retval=dst; TORGB(3, 2, 1, 0);
#endif
break;
case TJPF_RGBX:
case TJPF_RGBA:
#if RGB_RED!=0 || RGB_GREEN!=1 || RGB_BLUE!=2 || RGB_PIXELSIZE!=4
retval=dst; TORGB(4, 0, 1, 2);
#endif
break;
case TJPF_BGRX:
case TJPF_BGRA:
#if RGB_RED!=2 || RGB_GREEN!=1 || RGB_BLUE!=0 || RGB_PIXELSIZE!=4
retval=dst; TORGB(4, 2, 1, 0);
#endif
break;
case TJPF_XRGB:
case TJPF_ARGB:
#if RGB_RED!=1 || RGB_GREEN!=2 || RGB_BLUE!=3 || RGB_PIXELSIZE!=4
retval=dst; TORGB(4, 1, 2, 3);
#endif
break;
case TJPF_XBGR:
case TJPF_ABGR:
#if RGB_RED!=3 || RGB_GREEN!=2 || RGB_BLUE!=1 || RGB_PIXELSIZE!=4
retval=dst; TORGB(4, 3, 2, 1);
#endif
break;
}
return retval;
}
#define FROMRGB(PS, ROFFSET, GOFFSET, BOFFSET, SETALPHA) { \
int rowPad=pitch-width*PS; \
while(height--) \
{ \
unsigned char *endOfRow=dst+width*PS; \
while(dst<endOfRow) \
{ \
dst[ROFFSET]=src[RGB_RED]; \
dst[GOFFSET]=src[RGB_GREEN]; \
dst[BOFFSET]=src[RGB_BLUE]; \
SETALPHA \
dst+=PS; src+=RGB_PIXELSIZE; \
} \
dst+=rowPad; \
} \
}
static void fromRGB(unsigned char *src, unsigned char *dst, int width,
int pitch, int height, int pixelFormat)
{
switch(pixelFormat)
{
case TJPF_RGB:
#if RGB_RED!=0 || RGB_GREEN!=1 || RGB_BLUE!=2 || RGB_PIXELSIZE!=3
FROMRGB(3, 0, 1, 2,);
#endif
break;
case TJPF_BGR:
#if RGB_RED!=2 || RGB_GREEN!=1 || RGB_BLUE!=0 || RGB_PIXELSIZE!=3
FROMRGB(3, 2, 1, 0,);
#endif
break;
case TJPF_RGBX:
#if RGB_RED!=0 || RGB_GREEN!=1 || RGB_BLUE!=2 || RGB_PIXELSIZE!=4
FROMRGB(4, 0, 1, 2,);
#endif
break;
case TJPF_RGBA:
#if RGB_RED!=0 || RGB_GREEN!=1 || RGB_BLUE!=2 || RGB_PIXELSIZE!=4
FROMRGB(4, 0, 1, 2, dst[3]=0xFF;);
#endif
break;
case TJPF_BGRX:
#if RGB_RED!=2 || RGB_GREEN!=1 || RGB_BLUE!=0 || RGB_PIXELSIZE!=4
FROMRGB(4, 2, 1, 0,);
#endif
break;
case TJPF_BGRA:
#if RGB_RED!=2 || RGB_GREEN!=1 || RGB_BLUE!=0 || RGB_PIXELSIZE!=4
FROMRGB(4, 2, 1, 0, dst[3]=0xFF;); return;
#endif
break;
case TJPF_XRGB:
#if RGB_RED!=1 || RGB_GREEN!=2 || RGB_BLUE!=3 || RGB_PIXELSIZE!=4
FROMRGB(4, 1, 2, 3,); return;
#endif
break;
case TJPF_ARGB:
#if RGB_RED!=1 || RGB_GREEN!=2 || RGB_BLUE!=3 || RGB_PIXELSIZE!=4
FROMRGB(4, 1, 2, 3, dst[0]=0xFF;); return;
#endif
break;
case TJPF_XBGR:
#if RGB_RED!=3 || RGB_GREEN!=2 || RGB_BLUE!=1 || RGB_PIXELSIZE!=4
FROMRGB(4, 3, 2, 1,); return;
#endif
break;
case TJPF_ABGR:
#if RGB_RED!=3 || RGB_GREEN!=2 || RGB_BLUE!=1 || RGB_PIXELSIZE!=4
FROMRGB(4, 3, 2, 1, dst[0]=0xFF;); return;
#endif
break;
}
}
#endif
/* General API functions */
DLLEXPORT char* DLLCALL tjGetErrorStr(void)
{
return errStr;
}
DLLEXPORT int DLLCALL tjDestroy(tjhandle handle)
{
getinstance(handle);
if(setjmp(this->jerr.setjmp_buffer)) return -1;
if(this->init&COMPRESS) jpeg_destroy_compress(cinfo);
if(this->init&DECOMPRESS) jpeg_destroy_decompress(dinfo);
free(this);
return 0;
}
/* Compressor */
static boolean empty_output_buffer(j_compress_ptr cinfo)
{
ERREXIT(cinfo, JERR_BUFFER_SIZE);
return TRUE;
}
static void dst_noop(j_compress_ptr cinfo)
{
}
static tjhandle _tjInitCompress(tjinstance *this)
{
/* This is also straight out of example.c */
this->cinfo.err=jpeg_std_error(&this->jerr.pub);
this->jerr.pub.error_exit=my_error_exit;
this->jerr.pub.output_message=my_output_message;
if(setjmp(this->jerr.setjmp_buffer))
{
/* If we get here, the JPEG code has signaled an error. */
if(this) free(this);
return NULL;
}
jpeg_create_compress(&this->cinfo);
this->cinfo.dest=&this->jdst;
this->jdst.init_destination=dst_noop;
this->jdst.empty_output_buffer=empty_output_buffer;
this->jdst.term_destination=dst_noop;
this->init|=COMPRESS;
return (tjhandle)this;
}
DLLEXPORT tjhandle DLLCALL tjInitCompress(void)
{
tjinstance *this=NULL;
if((this=(tjinstance *)malloc(sizeof(tjinstance)))==NULL)
{
snprintf(errStr, JMSG_LENGTH_MAX,
"tjInitCompress(): Memory allocation failure");
return NULL;
}
MEMZERO(this, sizeof(tjinstance));
return _tjInitCompress(this);
}
DLLEXPORT unsigned long DLLCALL tjBufSize(int width, int height,
int jpegSubsamp)
{
unsigned long retval=0; int mcuw, mcuh, chromasf;
if(width<1 || height<1 || jpegSubsamp<0 || jpegSubsamp>=NUMSUBOPT)
_throw("tjBufSize(): Invalid argument");
/*
* This allows for rare corner cases in which a JPEG image can actually be
* larger than the uncompressed input (we wouldn't mention it if it hadn't
* happened before.)
*/
mcuw=tjMCUWidth[jpegSubsamp];
mcuh=tjMCUHeight[jpegSubsamp];
chromasf=jpegSubsamp==TJSAMP_GRAY? 0: 4*64/(mcuw*mcuh);
retval=PAD(width, mcuw) * PAD(height, mcuh) * (2 + chromasf) + 2048;
bailout:
return retval;
}
DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height)
{
unsigned long retval=0;
if(width<1 || height<1)
_throw("TJBUFSIZE(): Invalid argument");
/*
* This allows for rare corner cases in which a JPEG image can actually be
* larger than the uncompressed input (we wouldn't mention it if it hadn't
* happened before.)
*/
retval=PAD(width, 16) * PAD(height, 16) * 6 + 2048;
bailout:
return retval;
}
DLLEXPORT int DLLCALL tjCompress2(tjhandle handle, unsigned char *srcBuf,
int width, int pitch, int height, int pixelFormat, unsigned char **jpegBuf,
unsigned long *jpegSize, int jpegSubsamp, int jpegQual, int flags)
{
int i, retval=0; JSAMPROW *row_pointer=NULL;
#ifndef JCS_EXTENSIONS
unsigned char *rgbBuf=NULL;
#endif
getinstance(handle)
if((this->init&COMPRESS)==0)
_throw("tjCompress2(): Instance has not been initialized for compression");
if(srcBuf==NULL || width<=0 || pitch<0 || height<=0 || pixelFormat<0
|| pixelFormat>=TJ_NUMPF || jpegBuf==NULL || jpegSize==NULL
|| jpegSubsamp<0 || jpegSubsamp>=NUMSUBOPT || jpegQual<0 || jpegQual>100)
_throw("tjCompress2(): Invalid argument");
if(setjmp(this->jerr.setjmp_buffer))
{
/* If we get here, the JPEG code has signaled an error. */
retval=-1;
goto bailout;
}
if(pitch==0) pitch=width*tjPixelSize[pixelFormat];
#ifndef JCS_EXTENSIONS
if(pixelFormat!=TJPF_GRAY)
{
rgbBuf=(unsigned char *)malloc(width*height*RGB_PIXELSIZE);
if(!rgbBuf) _throw("tjCompress2(): Memory allocation failure");
srcBuf=toRGB(srcBuf, width, pitch, height, pixelFormat, rgbBuf);
pitch=width*RGB_PIXELSIZE;
}
#endif
cinfo->image_width=width;
cinfo->image_height=height;
if(flags&TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1");
else if(flags&TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1");
else if(flags&TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1");
if(setCompDefaults(cinfo, pixelFormat, jpegSubsamp, jpegQual)==-1)
return -1;
this->jdst.next_output_byte=*jpegBuf;
this->jdst.free_in_buffer=tjBufSize(width, height, jpegSubsamp);
jpeg_start_compress(cinfo, TRUE);
if((row_pointer=(JSAMPROW *)malloc(sizeof(JSAMPROW)*height))==NULL)
_throw("tjCompress2(): Memory allocation failure");
for(i=0; i<height; i++)
{
if(flags&TJFLAG_BOTTOMUP) row_pointer[i]=&srcBuf[(height-i-1)*pitch];
else row_pointer[i]=&srcBuf[i*pitch];
}
while(cinfo->next_scanline<cinfo->image_height)
{
jpeg_write_scanlines(cinfo, &row_pointer[cinfo->next_scanline],
cinfo->image_height-cinfo->next_scanline);
}
jpeg_finish_compress(cinfo);
*jpegSize=tjBufSize(width, height, jpegSubsamp)
-(unsigned long)(this->jdst.free_in_buffer);
bailout:
if(cinfo->global_state>CSTATE_START) jpeg_abort_compress(cinfo);
#ifndef JCS_EXTENSIONS
if(rgbBuf) free(rgbBuf);
#endif
if(row_pointer) free(row_pointer);
return retval;
}
DLLEXPORT int DLLCALL tjCompress(tjhandle handle, unsigned char *srcBuf,
int width, int pitch, int height, int pixelSize, unsigned char *jpegBuf,
unsigned long *jpegSize, int jpegSubsamp, int jpegQual, int flags)
{
int retval=0; unsigned long size = 0;
retval=tjCompress2(handle, srcBuf, width, pitch, height,
getPixelFormat(pixelSize, flags), &jpegBuf, &size, jpegSubsamp, jpegQual,
flags);
*jpegSize=size;
return retval;
}
/* Decompressor */
static boolean fill_input_buffer(j_decompress_ptr dinfo)
{
ERREXIT(dinfo, JERR_BUFFER_SIZE);
return TRUE;
}
static void skip_input_data(j_decompress_ptr dinfo, long num_bytes)
{
dinfo->src->next_input_byte += (size_t) num_bytes;
dinfo->src->bytes_in_buffer -= (size_t) num_bytes;
}
static void src_noop(j_decompress_ptr dinfo)
{
}
static tjhandle _tjInitDecompress(tjinstance *this)
{
/* This is also straight out of example.c */
this->dinfo.err=jpeg_std_error(&this->jerr.pub);
this->jerr.pub.error_exit=my_error_exit;
this->jerr.pub.output_message=my_output_message;
if(setjmp(this->jerr.setjmp_buffer))
{
/* If we get here, the JPEG code has signaled an error. */
if(this) free(this);
return NULL;
}
jpeg_create_decompress(&this->dinfo);
this->dinfo.src=&this->jsrc;
this->jsrc.init_source=src_noop;
this->jsrc.fill_input_buffer=fill_input_buffer;
this->jsrc.skip_input_data=skip_input_data;
this->jsrc.resync_to_restart=jpeg_resync_to_restart;
this->jsrc.term_source=src_noop;
this->init|=DECOMPRESS;
return (tjhandle)this;
}
DLLEXPORT tjhandle DLLCALL tjInitDecompress(void)
{
tjinstance *this;
if((this=(tjinstance *)malloc(sizeof(tjinstance)))==NULL)
{
snprintf(errStr, JMSG_LENGTH_MAX,
"tjInitDecompress(): Memory allocation failure");
return NULL;
}
MEMZERO(this, sizeof(tjinstance));
return _tjInitDecompress(this);
}
DLLEXPORT int DLLCALL tjDecompressHeader2(tjhandle handle,
unsigned char *jpegBuf, unsigned long jpegSize, int *width, int *height,
int *jpegSubsamp)
{
int retval=0;
getinstance(handle);
if((this->init&DECOMPRESS)==0)
_throw("tjDecompressHeader2(): Instance has not been initialized for decompression");
if(jpegBuf==NULL || jpegSize<=0 || width==NULL || height==NULL
|| jpegSubsamp==NULL)
_throw("tjDecompressHeader2(): Invalid argument");
if(setjmp(this->jerr.setjmp_buffer))
{
/* If we get here, the JPEG code has signaled an error. */
return -1;
}
this->jsrc.bytes_in_buffer=jpegSize;
this->jsrc.next_input_byte=jpegBuf;
jpeg_read_header(dinfo, TRUE);
*width=dinfo->image_width;
*height=dinfo->image_height;
*jpegSubsamp=getSubsamp(dinfo);
jpeg_abort_decompress(dinfo);
if(*jpegSubsamp<0)
_throw("tjDecompressHeader2(): Could not determine subsampling type for JPEG image");
if(*width<1 || *height<1)
_throw("tjDecompressHeader2(): Invalid data returned in header");
bailout:
return retval;
}
DLLEXPORT int DLLCALL tjDecompressHeader(tjhandle handle,
unsigned char *jpegBuf, unsigned long jpegSize, int *width, int *height)
{
int jpegSubsamp;
return tjDecompressHeader2(handle, jpegBuf, jpegSize, width, height,
&jpegSubsamp);
}
DLLEXPORT tjscalingfactor* DLLCALL tjGetScalingFactors(int *numscalingfactors)
{
if(numscalingfactors==NULL)
{
snprintf(errStr, JMSG_LENGTH_MAX,
"tjGetScalingFactors(): Invalid argument");
return NULL;
}
*numscalingfactors=NUMSF;
return (tjscalingfactor *)sf;
}
DLLEXPORT int DLLCALL tjDecompress2(tjhandle handle, unsigned char *jpegBuf,
unsigned long jpegSize, unsigned char *dstBuf, int width, int pitch,
int height, int pixelFormat, int flags)
{
int i, retval=0; JSAMPROW *row_pointer=NULL;
int jpegwidth, jpegheight, scaledw, scaledh;
#ifndef JCS_EXTENSIONS
unsigned char *rgbBuf=NULL;
unsigned char *_dstBuf=NULL; int _pitch=0;
#endif
getinstance(handle);
if((this->init&DECOMPRESS)==0)
_throw("tjDecompress2(): Instance has not been initialized for decompression");
if(jpegBuf==NULL || jpegSize<=0 || dstBuf==NULL || width<0 || pitch<0
|| height<0 || pixelFormat<0 || pixelFormat>=TJ_NUMPF)
_throw("tjDecompress2(): Invalid argument");
if(flags&TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1");
else if(flags&TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1");
else if(flags&TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1");
if(setjmp(this->jerr.setjmp_buffer))
{
/* If we get here, the JPEG code has signaled an error. */
retval=-1;
goto bailout;
}
this->jsrc.bytes_in_buffer=jpegSize;
this->jsrc.next_input_byte=jpegBuf;
jpeg_read_header(dinfo, TRUE);
if(setDecompDefaults(dinfo, pixelFormat)==-1)
{
retval=-1; goto bailout;
}
if(flags&TJFLAG_FASTUPSAMPLE) dinfo->do_fancy_upsampling=FALSE;
jpegwidth=dinfo->image_width; jpegheight=dinfo->image_height;
if(width==0) width=jpegwidth;
if(height==0) height=jpegheight;
for(i=0; i<NUMSF; i++)
{
scaledw=TJSCALED(jpegwidth, sf[i]);
scaledh=TJSCALED(jpegheight, sf[i]);
if(scaledw<=width && scaledh<=height)
break;
}
if(scaledw>width || scaledh>height)
_throw("tjDecompress2(): Could not scale down to desired image dimensions");
#ifndef JCS_EXTENSIONS
width=scaledw; height=scaledh;
#endif
dinfo->scale_num=sf[i].num;
dinfo->scale_denom=sf[i].denom;
jpeg_start_decompress(dinfo);
if(pitch==0) pitch=dinfo->output_width*tjPixelSize[pixelFormat];
#ifndef JCS_EXTENSIONS
if(pixelFormat!=TJPF_GRAY &&
(RGB_RED!=tjRedOffset[pixelFormat] ||
RGB_GREEN!=tjGreenOffset[pixelFormat] ||
RGB_BLUE!=tjBlueOffset[pixelFormat] ||
RGB_PIXELSIZE!=tjPixelSize[pixelFormat]))
{
rgbBuf=(unsigned char *)malloc(width*height*3);
if(!rgbBuf) _throw("tjDecompress2(): Memory allocation failure");
_pitch=pitch; pitch=width*3;
_dstBuf=dstBuf; dstBuf=rgbBuf;
}
#endif
if((row_pointer=(JSAMPROW *)malloc(sizeof(JSAMPROW)
*dinfo->output_height))==NULL)
_throw("tjDecompress2(): Memory allocation failure");
for(i=0; i<(int)dinfo->output_height; i++)
{
if(flags&TJFLAG_BOTTOMUP)
row_pointer[i]=&dstBuf[(dinfo->output_height-i-1)*pitch];
else row_pointer[i]=&dstBuf[i*pitch];
}
while(dinfo->output_scanline<dinfo->output_height)
{
jpeg_read_scanlines(dinfo, &row_pointer[dinfo->output_scanline],
dinfo->output_height-dinfo->output_scanline);
}
jpeg_finish_decompress(dinfo);
#ifndef JCS_EXTENSIONS
fromRGB(rgbBuf, _dstBuf, width, _pitch, height, pixelFormat);
#endif
bailout:
if(dinfo->global_state>DSTATE_START) jpeg_abort_decompress(dinfo);
#ifndef JCS_EXTENSIONS
if(rgbBuf) free(rgbBuf);
#endif
if(row_pointer) free(row_pointer);
return retval;
}
DLLEXPORT int DLLCALL tjDecompress(tjhandle handle, unsigned char *jpegBuf,
unsigned long jpegSize, unsigned char *dstBuf, int width, int pitch,
int height, int pixelSize, int flags)
{
return tjDecompress2(handle, jpegBuf, jpegSize, dstBuf, width, pitch,
height, getPixelFormat(pixelSize, flags), flags);
}

282
src/vnc.c
View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Andri Yngvason
* Copyright (c) 2020 - 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
@ -19,15 +19,50 @@
#include <assert.h>
#include <string.h>
#include <stdbool.h>
#include <limits.h>
#include <pixman.h>
#include <rfb/rfbclient.h>
#include <wayland-client.h>
#include <libdrm/drm_fourcc.h>
#include <libavutil/frame.h>
#include <stdio.h>
#include <data-control.h>
#include "rfbclient.h"
#include "vnc.h"
#include "open-h264.h"
#include "usdt.h"
#define RFB_ENCODING_OPEN_H264 50
#define RFB_ENCODING_PTS -1000
#define NO_PTS UINT64_MAX
extern const unsigned short code_map_linux_to_qnum[];
extern const unsigned int code_map_linux_to_qnum_len;
static uint64_t vnc_client_htonll(uint64_t x)
{
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return __builtin_bswap64(x);
#else
return x;
#endif
}
static bool vnc_client_lock_handler(struct vnc_client* self)
{
if (self->handler_lock)
return false;
self->handler_lock = true;
return true;
}
static void vnc_client_unlock_handler(struct vnc_client* self)
{
assert(self->handler_lock);
self->handler_lock = false;
}
static rfbBool vnc_client_alloc_fb(rfbClient* client)
{
struct vnc_client* self = rfbClientGetClientData(client, NULL);
@ -42,22 +77,155 @@ static void vnc_client_update_box(rfbClient* client, int x, int y, int width,
struct vnc_client* self = rfbClientGetClientData(client, NULL);
assert(self);
if (self->current_rect_is_av_frame) {
self->current_rect_is_av_frame = false;
return;
}
pixman_region_union_rect(&self->damage, &self->damage, x, y, width,
height);
}
void vnc_client_clear_av_frames(struct vnc_client* self)
{
for (int i = 0; i < self->n_av_frames; ++i) {
av_frame_unref(self->av_frames[i]->frame);
av_frame_free(&self->av_frames[i]->frame);
free(self->av_frames[i]);
}
self->n_av_frames = 0;
}
static void vnc_client_start_update(rfbClient* client)
{
struct vnc_client* self = rfbClientGetClientData(client, NULL);
assert(self);
self->pts = NO_PTS;
pixman_region_clear(&self->damage);
vnc_client_clear_av_frames(self);
self->is_updating = true;
}
static void vnc_client_cancel_update(rfbClient* client)
{
struct vnc_client* self = rfbClientGetClientData(client, NULL);
assert(self);
self->is_updating = false;
}
static void vnc_client_finish_update(rfbClient* client)
{
struct vnc_client* self = rfbClientGetClientData(client, NULL);
assert(self);
self->update_fb(self);
DTRACE_PROBE2(wlvncc, vnc_client_finish_update, client, self->pts);
pixman_region_clear(&self->damage);
self->is_updating = false;
self->update_fb(self);
}
struct vnc_client* vnc_client_create(void)
static void vnc_client_got_cut_text(rfbClient* client, const char* text,
int len)
{
struct vnc_client* self = rfbClientGetClientData(client, NULL);
assert(self);
if (self->cut_text)
self->cut_text(self, text, len);
else {
printf("Cut text is not defined!\n");
}
}
static rfbBool vnc_client_handle_open_h264_rect(rfbClient* client,
rfbFramebufferUpdateRectHeader* rect_header)
{
if ((int)rect_header->encoding != RFB_ENCODING_OPEN_H264)
return FALSE;
struct vnc_client* self = rfbClientGetClientData(client, NULL);
assert(self);
if (!self->open_h264)
self->open_h264 = open_h264_create(client);
if (!self->open_h264)
return false;
AVFrame* frame = open_h264_decode_rect(self->open_h264, rect_header);
if (!frame)
return false;
assert(self->n_av_frames < VNC_CLIENT_MAX_AV_FRAMES);
struct vnc_av_frame* f = calloc(1, sizeof(*f));
if (!f) {
av_frame_unref(frame);
av_frame_free(&frame);
return false;
}
f->frame = frame;
f->x = rect_header->r.x;
f->y = rect_header->r.y;
f->width = rect_header->r.w;
f->height = rect_header->r.h;
self->av_frames[self->n_av_frames++] = f;
self->current_rect_is_av_frame = true;
return true;
}
static void vnc_client_init_open_h264(void)
{
static int encodings[] = { RFB_ENCODING_OPEN_H264, 0 };
static rfbClientProtocolExtension ext = {
.encodings = encodings,
.handleEncoding = vnc_client_handle_open_h264_rect,
};
rfbClientRegisterExtension(&ext);
}
static rfbBool vnc_client_handle_pts_rect(rfbClient* client,
rfbFramebufferUpdateRectHeader* rect_header)
{
if ((int)rect_header->encoding != RFB_ENCODING_PTS)
return FALSE;
struct vnc_client* self = rfbClientGetClientData(client, NULL);
assert(self);
uint64_t pts_msg = 0;
if (!ReadFromRFBServer(self->client, (char*)&pts_msg, sizeof(pts_msg)))
return FALSE;
self->pts = vnc_client_htonll(pts_msg);
DTRACE_PROBE1(wlvncc, vnc_client_handle_pts_rect, self->pts);
return TRUE;
}
static void vnc_client_init_pts_ext(void)
{
static int encodings[] = { RFB_ENCODING_PTS, 0 };
static rfbClientProtocolExtension ext = {
.encodings = encodings,
.handleEncoding = vnc_client_handle_pts_rect,
};
rfbClientRegisterExtension(&ext);
}
struct vnc_client* vnc_client_create(struct data_control* data_control)
{
vnc_client_init_open_h264();
vnc_client_init_pts_ext();
struct vnc_client* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
@ -75,11 +243,21 @@ struct vnc_client* vnc_client_create(void)
goto failure;
self->client = client;
self->data_control = data_control;
rfbClientSetClientData(client, NULL, self);
client->MallocFrameBuffer = vnc_client_alloc_fb;
client->GotFrameBufferUpdate = vnc_client_update_box;
client->FinishedFrameBufferUpdate = vnc_client_finish_update;
client->StartingFrameBufferUpdate = vnc_client_start_update;
client->CancelledFrameBufferUpdate = vnc_client_cancel_update;
client->GotXCutText = vnc_client_got_cut_text;
self->cut_text = cut_text;
self->pts = NO_PTS;
// Handle authentication
client->GetCredential = handle_vnc_authentication;
return self;
@ -88,8 +266,40 @@ failure:
return NULL;
}
rfbCredential* handle_vnc_authentication(struct _rfbClient *client, int credentialType) {
rfbCredential* creds = (rfbCredential*) malloc(sizeof(rfbCredential));
if (client->authScheme == rfbVeNCrypt && credentialType == rfbCredentialTypeX509) {
char* path = getenv("TLS_CA");
rfbClientLog("Using TLS CA certificate from env 'TLS_CA': %s\n", path);
creds->x509Credential.x509CACertFile = malloc(strlen(path) + 1);
strcpy(creds->x509Credential.x509CACertFile, path);
creds->x509Credential.x509CrlVerifyMode = rfbX509CrlVerifyAll;
} else if (client->authScheme == rfbVeNCrypt && credentialType == rfbCredentialTypeUser) {
const* username = getenv("VNC_USERNAME");
const* password = getenv("VNC_PASSWORD");
rfbClientLog("Using username and password for VNC authentication 'VNC_USERNAME', 'VNC_PASSWORD'\n");
creds->userCredential.password = malloc(strlen(password) + 1);
creds->userCredential.username = malloc(strlen(username) + 1);
strcpy(creds->userCredential.password, password);
strcpy(creds->userCredential.username, username);
} else {
}
return creds;
}
void cut_text (struct vnc_client* self, const char* text, size_t size) {
data_control_to_clipboard(self->data_control, text, size);
//printf("Received string FROM vnc_server: %s\n", text);
}
void vnc_client_destroy(struct vnc_client* self)
{
vnc_client_clear_av_frames(self);
open_h264_destroy(self->open_h264);
rfbClientCleanup(self->client);
free(self);
}
@ -98,20 +308,27 @@ int vnc_client_connect(struct vnc_client* self, const char* address, int port)
{
rfbClient* client = self->client;
if (!ConnectToRFBServer(client, address, port))
return -1;
return ConnectToRFBServer(client, address, port) ? 0 : -1;
}
int vnc_client_init(struct vnc_client* self)
{
int rc = -1;
rfbClient* client = self->client;
vnc_client_lock_handler(self);
if (!InitialiseRFBConnection(client))
return -1;
goto failure;
client->width = client->si.framebufferWidth;
client->height = client->si.framebufferHeight;
if (!client->MallocFrameBuffer(client))
return -1;
goto failure;
if (!SetFormatAndEncodings(client))
return -1;
goto failure;
if (client->updateRect.x < 0) {
client->updateRect.x = client->updateRect.y = 0;
@ -123,20 +340,25 @@ int vnc_client_connect(struct vnc_client* self, const char* address, int port)
client->updateRect.x, client->updateRect.y,
client->updateRect.w, client->updateRect.h,
FALSE))
return -1;
goto failure;
return 0;
SendIncrementalFramebufferUpdateRequest(client);
SendIncrementalFramebufferUpdateRequest(client);
rc = 0;
failure:
vnc_client_unlock_handler(self);
return rc;
}
int vnc_client_set_pixel_format(struct vnc_client* self,
enum wl_shm_format format)
int vnc_client_set_pixel_format(struct vnc_client* self, uint32_t format)
{
rfbPixelFormat* dst = &self->client->format;
int bpp = -1;
switch (format) {
case WL_SHM_FORMAT_ARGB8888:
case WL_SHM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
dst->redShift = 16;
dst->greenShift = 8;
dst->blueShift = 0;
@ -203,7 +425,21 @@ const char* vnc_client_get_desktop_name(const struct vnc_client* self)
int vnc_client_process(struct vnc_client* self)
{
return HandleRFBServerMessage(self->client) ? 0 : -1;
if (!ReadToBuffer(self->client))
return -1;
if (!vnc_client_lock_handler(self))
return 0;
int rc;
while (self->client->buffered > 0) {
rc = HandleRFBServerMessage(self->client) ? 0 : -1;
if (rc < 0)
break;
}
vnc_client_unlock_handler(self);
return rc;
}
void vnc_client_send_pointer_event(struct vnc_client* self, int x, int y,
@ -222,7 +458,8 @@ void vnc_client_send_keyboard_event(struct vnc_client* self, uint32_t symbol,
if (!qnum)
qnum = code;
SendExtendedKeyEvent(self->client, symbol, qnum, is_pressed);
if (!SendExtendedKeyEvent(self->client, symbol, qnum, is_pressed))
SendKeyEvent(self->client, symbol, is_pressed);
}
void vnc_client_set_encodings(struct vnc_client* self, const char* encodings)
@ -239,3 +476,10 @@ void vnc_client_set_compression_level(struct vnc_client* self, int value)
{
self->client->appData.compressLevel = value;
}
void vnc_client_send_cut_text(struct vnc_client* self, const char* text,
size_t len)
{
// libvncclient doesn't modify text, so typecast is OK.
SendClientCutText(self->client, (char*)text, len);
}

393
src/vncviewer.c 100644
View File

@ -0,0 +1,393 @@
/*
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
/*
* vncviewer.c - the Xt-based VNC viewer.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "rfbclient.h"
#include "tls.h"
static void Dummy(rfbClient* client) {
}
static rfbBool DummyPoint(rfbClient* client, int x, int y) {
return TRUE;
}
static void DummyRect(rfbClient* client, int x, int y, int w, int h) {
}
#include <termios.h>
static char* ReadPassword(rfbClient* client) {
int i;
char* p=calloc(1,9);
if (!p) return p;
struct termios save,noecho;
if(tcgetattr(fileno(stdin),&save)!=0) return p;
noecho=save; noecho.c_lflag &= ~ECHO;
if(tcsetattr(fileno(stdin),TCSAFLUSH,&noecho)!=0) return p;
fprintf(stderr,"Password: ");
fflush(stderr);
i=0;
while(1) {
int c=fgetc(stdin);
if(c=='\n')
break;
if(i<8) {
p[i]=c;
i++;
p[i]=0;
}
}
tcsetattr(fileno(stdin),TCSAFLUSH,&save);
return p;
}
static rfbBool MallocFrameBuffer(rfbClient* client) {
uint64_t allocSize;
if(client->frameBuffer) {
free(client->frameBuffer);
client->frameBuffer = NULL;
}
/* SECURITY: promote 'width' into uint64_t so that the multiplication does not overflow
'width' and 'height' are 16-bit integers per RFB protocol design
SIZE_MAX is the maximum value that can fit into size_t
*/
allocSize = (uint64_t)client->width * client->height * client->format.bitsPerPixel/8;
if (allocSize >= SIZE_MAX) {
rfbClientErr("CRITICAL: cannot allocate frameBuffer, requested size is too large\n");
return FALSE;
}
client->frameBuffer=malloc( (size_t)allocSize );
if (client->frameBuffer == NULL)
rfbClientErr("CRITICAL: frameBuffer allocation failed, requested size too large or not enough memory?\n");
return client->frameBuffer?TRUE:FALSE;
}
/* messages */
static rfbBool CheckRect(rfbClient* client, int x, int y, int w, int h) {
return x + w <= client->width && y + h <= client->height;
}
static void FillRectangle(rfbClient* client, int x, int y, int w, int h, uint32_t colour) {
int i,j;
if (client->frameBuffer == NULL) {
return;
}
if (!CheckRect(client, x, y, w, h)) {
rfbClientLog("Rect out of bounds: %dx%d at (%d, %d)\n", x, y, w, h);
return;
}
#define FILL_RECT(BPP) \
for(j=y*client->width;j<(y+h)*client->width;j+=client->width) \
for(i=x;i<x+w;i++) \
((uint##BPP##_t*)client->frameBuffer)[j+i]=colour;
switch(client->format.bitsPerPixel) {
case 8: FILL_RECT(8); break;
case 16: FILL_RECT(16); break;
case 32: FILL_RECT(32); break;
default:
rfbClientLog("Unsupported bitsPerPixel: %d\n",client->format.bitsPerPixel);
}
}
static void CopyRectangle(rfbClient* client, const uint8_t* buffer, int x, int y, int w, int h) {
int j;
if (client->frameBuffer == NULL) {
return;
}
if (!CheckRect(client, x, y, w, h)) {
rfbClientLog("Rect out of bounds: %dx%d at (%d, %d)\n", x, y, w, h);
return;
}
#define COPY_RECT(BPP) \
{ \
int rs = w * BPP / 8, rs2 = client->width * BPP / 8; \
for (j = ((x * (BPP / 8)) + (y * rs2)); j < (y + h) * rs2; j += rs2) { \
memcpy(client->frameBuffer + j, buffer, rs); \
buffer += rs; \
} \
}
switch(client->format.bitsPerPixel) {
case 8: COPY_RECT(8); break;
case 16: COPY_RECT(16); break;
case 32: COPY_RECT(32); break;
default:
rfbClientLog("Unsupported bitsPerPixel: %d\n",client->format.bitsPerPixel);
}
}
/* TODO: test */
static void CopyRectangleFromRectangle(rfbClient* client, int src_x, int src_y, int w, int h, int dest_x, int dest_y) {
int i,j;
if (client->frameBuffer == NULL) {
return;
}
if (!CheckRect(client, src_x, src_y, w, h)) {
rfbClientLog("Source rect out of bounds: %dx%d at (%d, %d)\n", src_x, src_y, w, h);
return;
}
if (!CheckRect(client, dest_x, dest_y, w, h)) {
rfbClientLog("Dest rect out of bounds: %dx%d at (%d, %d)\n", dest_x, dest_y, w, h);
return;
}
#define COPY_RECT_FROM_RECT(BPP) \
{ \
uint##BPP##_t* _buffer=((uint##BPP##_t*)client->frameBuffer)+(src_y-dest_y)*client->width+src_x-dest_x; \
if (dest_y < src_y) { \
for(j = dest_y*client->width; j < (dest_y+h)*client->width; j += client->width) { \
if (dest_x < src_x) { \
for(i = dest_x; i < dest_x+w; i++) { \
((uint##BPP##_t*)client->frameBuffer)[j+i]=_buffer[j+i]; \
} \
} else { \
for(i = dest_x+w-1; i >= dest_x; i--) { \
((uint##BPP##_t*)client->frameBuffer)[j+i]=_buffer[j+i]; \
} \
} \
} \
} else { \
for(j = (dest_y+h-1)*client->width; j >= dest_y*client->width; j-=client->width) { \
if (dest_x < src_x) { \
for(i = dest_x; i < dest_x+w; i++) { \
((uint##BPP##_t*)client->frameBuffer)[j+i]=_buffer[j+i]; \
} \
} else { \
for(i = dest_x+w-1; i >= dest_x; i--) { \
((uint##BPP##_t*)client->frameBuffer)[j+i]=_buffer[j+i]; \
} \
} \
} \
} \
}
switch(client->format.bitsPerPixel) {
case 8: COPY_RECT_FROM_RECT(8); break;
case 16: COPY_RECT_FROM_RECT(16); break;
case 32: COPY_RECT_FROM_RECT(32); break;
default:
rfbClientLog("Unsupported bitsPerPixel: %d\n",client->format.bitsPerPixel);
}
}
static void initAppData(AppData* data) {
data->shareDesktop=TRUE;
data->viewOnly=FALSE;
data->encodingsString="tight zrle ultra copyrect hextile zlib corre rre raw";
data->useBGR233=FALSE;
data->nColours=0;
data->forceOwnCmap=FALSE;
data->forceTrueColour=FALSE;
data->requestedDepth=0;
data->compressLevel=3;
data->qualityLevel=5;
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
data->enableJPEG=TRUE;
#else
data->enableJPEG=FALSE;
#endif
data->useRemoteCursor=FALSE;
}
rfbClient* rfbGetClient(int bitsPerSample,int samplesPerPixel,
int bytesPerPixel) {
rfbClient* client=(rfbClient*)calloc(sizeof(rfbClient),1);
if(!client) {
rfbClientErr("Couldn't allocate client structure!\n");
return NULL;
}
initAppData(&client->appData);
client->endianTest = 1;
client->programName="";
client->serverHost=strdup("");
client->serverPort=5900;
client->destHost = NULL;
client->destPort = 5900;
client->connectTimeout = DEFAULT_CONNECT_TIMEOUT;
client->readTimeout = DEFAULT_READ_TIMEOUT;
/* default: use complete frame buffer */
client->updateRect.x = -1;
client->frameBuffer = NULL;
client->outputWindow = 0;
client->format.bitsPerPixel = bytesPerPixel*8;
client->format.depth = bitsPerSample*samplesPerPixel;
client->appData.requestedDepth=client->format.depth;
client->format.bigEndian = *(char *)&client->endianTest?FALSE:TRUE;
client->format.trueColour = 1;
if (client->format.bitsPerPixel == 8) {
client->format.redMax = 7;
client->format.greenMax = 7;
client->format.blueMax = 3;
client->format.redShift = 0;
client->format.greenShift = 3;
client->format.blueShift = 6;
} else {
client->format.redMax = (1 << bitsPerSample) - 1;
client->format.greenMax = (1 << bitsPerSample) - 1;
client->format.blueMax = (1 << bitsPerSample) - 1;
if(!client->format.bigEndian) {
client->format.redShift = 0;
client->format.greenShift = bitsPerSample;
client->format.blueShift = bitsPerSample * 2;
} else {
if(client->format.bitsPerPixel==8*3) {
client->format.redShift = bitsPerSample*2;
client->format.greenShift = bitsPerSample*1;
client->format.blueShift = 0;
} else {
client->format.redShift = bitsPerSample*3;
client->format.greenShift = bitsPerSample*2;
client->format.blueShift = bitsPerSample;
}
}
}
client->bufoutptr=client->buf;
client->buffered=0;
#ifdef LIBVNCSERVER_HAVE_LIBZ
client->raw_buffer_size = -1;
client->decompStreamInited = FALSE;
#ifdef LIBVNCSERVER_HAVE_LIBJPEG
memset(client->zlibStreamActive,0,sizeof(rfbBool)*4);
#endif
#endif
client->HandleCursorPos = DummyPoint;
client->SoftCursorLockArea = DummyRect;
client->SoftCursorUnlockScreen = Dummy;
client->GotFrameBufferUpdate = DummyRect;
client->GotCopyRect = CopyRectangleFromRectangle;
client->GotFillRect = FillRectangle;
client->GotBitmap = CopyRectangle;
client->FinishedFrameBufferUpdate = NULL;
client->GetPassword = ReadPassword;
client->MallocFrameBuffer = MallocFrameBuffer;
client->Bell = Dummy;
client->CurrentKeyboardLedState = 0;
client->HandleKeyboardLedState = (HandleKeyboardLedStateProc)DummyPoint;
client->QoS_DSCP = 0;
client->authScheme = 0;
client->subAuthScheme = 0;
client->GetCredential = NULL;
client->tlsSession = NULL;
client->LockWriteToTLS = NULL;
client->UnlockWriteToTLS = NULL;
client->sock = RFB_INVALID_SOCKET;
client->listenSock = RFB_INVALID_SOCKET;
client->listenAddress = NULL;
client->listen6Sock = RFB_INVALID_SOCKET;
client->listen6Address = NULL;
client->clientAuthSchemes = NULL;
#ifdef LIBVNCSERVER_HAVE_SASL
client->GetSASLMechanism = NULL;
client->GetUser = NULL;
client->saslSecret = NULL;
#endif /* LIBVNCSERVER_HAVE_SASL */
client->requestedResize = FALSE;
client->screen.width = 0;
client->screen.height = 0;
return client;
}
void rfbClientCleanup(rfbClient* client) {
#ifdef LIBVNCSERVER_HAVE_LIBZ
int i;
for ( i = 0; i < 4; i++ ) {
if (client->zlibStreamActive[i] == TRUE ) {
if (inflateEnd (&client->zlibStream[i]) != Z_OK &&
client->zlibStream[i].msg != NULL)
rfbClientLog("inflateEnd: %s\n", client->zlibStream[i].msg);
}
}
if ( client->decompStreamInited == TRUE ) {
if (inflateEnd (&client->decompStream) != Z_OK &&
client->decompStream.msg != NULL)
rfbClientLog("inflateEnd: %s\n", client->decompStream.msg );
}
#endif
if (client->ultra_buffer)
free(client->ultra_buffer);
if (client->raw_buffer)
free(client->raw_buffer);
FreeTLS(client);
while (client->clientData) {
rfbClientData* next = client->clientData->next;
free(client->clientData);
client->clientData = next;
}
free(client->vncRec);
if (client->sock != RFB_INVALID_SOCKET)
rfbCloseSocket(client->sock);
if (client->listenSock != RFB_INVALID_SOCKET)
rfbCloseSocket(client->listenSock);
free(client->desktopName);
free(client->serverHost);
if (client->destHost)
free(client->destHost);
if (client->clientAuthSchemes)
free(client->clientAuthSchemes);
#ifdef LIBVNCSERVER_HAVE_SASL
if (client->saslSecret)
free(client->saslSecret);
#endif /* LIBVNCSERVER_HAVE_SASL */
free(client);
}