LibVNC / libvncserver

LibVNCServer/LibVNCClient are cross-platform C libraries that allow you to easily implement VNC server or client functionality in your program.
GNU General Public License v2.0
1.14k stars 494 forks source link

MCU support #632

Open kdschlosser opened 1 month ago

kdschlosser commented 1 month ago

Is your feature request related to a problem? Please describe. No

Describe the solution you'd like I have been working with micro controllers that have attached touch screen displays and writing a binding for a GUI framework to MicroPython. One of the things I am striving to provide to the users is fast prototyping. Currently a user is able to write Python code and it will run on a micro controller for creating a GUI to be shown on an attached display. The binding is also able to be compiled to run on macOS and also Linux and this allows a user to be able to develop without needing to use an MCU with an attached display. While the ability to test a GUI on a PC does make things easier it has a downside, no hardware related bits are available. So NO GPIO access and things like that. This would make sense seeing as how the binding is running on a PC and not on the actual MCU.

What I am wanting to do is allow the user to be able to develop without the need to have a display attached to the MCU. Using the RFB protocol would be the best solution because just about every OS available has some form of VNC client software available. Ideally I would like to make thing easier by using an already existing library. The only hangup I am having is all the libraries I have come across handle to actual receiving and transmitting of the data. This is something that I would not use because the MCU I am using doesn't have the same API for networking as Linux et al. has. The library would be used to handle the reading of the incoming data and formatting the outgoing data but the actual sending and receiving would be done using code that I would write. This could be done easily if there are functions that would handle the reading of the data and creating the packets needed for sending.

The MCU I am working with uses an Xtensa 32-bit LX7 processor with 2 cores \@ 240mhz. Depending on how it's configured it can have 2mb-8mb of RAM and 4mb-32mb of flash based program storage.

The first thing to hurdle is getting the library to compile using a gcc like compiler that is made for these specific MCU's. I don't know enough about CMAKE to know if adding it to my projects CMAKE build will carry over using a specific compiler and also how to set option flags that would normally be done by passing them in the build command.

The processor that I am using is dual core and I have the ability to set specific code to run on specific cores. I see that this library supports pthread but unfortunately this is not available. FreeRTOS is what is used to handle "threads". while the functionality of how the threading works is pretty close to pthread the API is completely different. I did come across a library that is a wrapper around FreeRTOS to provide a pthread API. I have not tested this to see if it will work. I am pretty sure there is going to be things that I will need to modify to get it to run on the MCU I am using. If there were macros that could be changed by the user to point to a different function for thing like creating a mutex and creating a thread this would make you library very flexible.

Describe alternatives you've considered I have looked at quite a few different VNC server libraries as a possible solution. Your library is the most complete in terms of much of the RFB protocol API is supported and the way it is written also would make it easier to make alterations that would allow it to be run on a micro controller.

Additional context Now that I am thinking about it providing macros to handle using encryption libraries other than the ones directly supported by the library would be of added benefit as a lot of MCUs have hardware accelerators for doing the encrypting and decrypting.

bk138 commented 1 month ago

You can build without threading support, the rest is probably reseaching cmake and FreeRTOS. If your efforts are successful, let me know; would be happy to merge in some documentation w.r.t. embedded systems!

kdschlosser commented 1 month ago

That would really make the library truly cross platform. What I am wanting to do is bare metal compiling so no OS. Not really an embedded system.

question tho. are there functions that are public that can encode and decode the VNC packets without having to use any of the built in connection code. The network related API for the ESP32 doesn't follow POSIX standards, not 100% anyway. so I would only use the library to encode and decode the packets. the library would handle reading the data that I would feed into it via a function call and it would work like it would normally would when doing that. for sending messages I would only need the library to build the packets when transmitting.

bk138 commented 1 month ago

I guess your best bet is to write/get-from-a-third-party shims for BSD socket functions; there might be other issues as well. But at least documenting those here in public would be nice.

kdschlosser commented 1 month ago

I am still crossing the bridge to get it to compile. It appears the only way to set the config options is I have to add the project as a sub directory. That is the only way the environment will carry over to it.

There is no way to just use the packet encoding/decoding portions of the library? I could write wrapper code for the network functions to make it work.

kdschlosser commented 4 weeks ago

I am actually pretty damned close to getting it to compile. There are a few issues however.

You have some macro definitions that are redefinition's. you don't have any guards in place to see if they are already defined. I am thinking you may want to do this...

you have INADDR_NONE being defined in both the server and the client. Not sure why but they are bumping heads. They also bump heads with it being defined in lwip (light weight IP stack) which is what is used by the esp32. Nice thing about lwip is it has posix compat headers YAY!! and between that and also the posix compat header that can be gotten for FreeRTOS that is what is allowing me to compile your library on the MCU. I did have to write a new cmake script to handle ti but that wasn't too big a deal to do.

I am going to manually add the guard for that macro and see if there is anything else that causes problems. at the moment that is all there appears to be.

bk138 commented 4 weeks ago

If you got a simple PR with additional header guards, I'll happily merge this.

kdschlosser commented 4 weeks ago

I got sidetracked on something else and I have not put a guard in for the one error I am getting. Once I have it compiling I will submit a PR for any changes to the code I have made,

kdschlosser commented 4 weeks ago

OK so I am making headway. so far a simple include solves the issue. In include/rfb/rfbproto.h there is this code...

#ifndef INADDR_NONE
#define                INADDR_NONE     ((in_addr_t) 0xffffffff)
#endif

and it's awesome that there is a guard in place to check to see if it is already defined. Issue is this macro gets set before sys/socket.h is included anywhere in libvncserver. sys/socket.h gets included after the macro gets set and the issue is there is no guard in inet.h which is where the macro gets set a second time.

There are a couple of ways to handle this.

  1. include sys/socket.h in include/rfb/rfbproto.h prior to the macro getting set.
  2. tell the user that they must include sys/socket.h in their project prior to including any part of libvncserver

Personally I believe that option 1 is the best way to go about it because it is pretty much a gurantee never have anyone open an issue should they not read the docs or they missed the line where it informs them of option 2 above. It's not going to cause any kind of an issue to include sys/socket.h before that macro gets set as it is something that ends up being used in libvncserver anyhow.

I changed the lines seen below which are located in include/rfb/rfbproto.h

#if defined(WIN32)
typedef int8_t rfbBool;
#include <sys/timeb.h>
#include <winsock2.h>
#endif

to read

#if defined(WIN32)
typedef int8_t rfbBool;
#include <sys/timeb.h>
#include <winsock2.h>
#else
#include <sys/socket.h>
#endif

and that solved that issue.

Next issue is a bit more complex and it is one that is going to take me a bit to figure out how to solve. Perhaps you might be able to help me out with the best solution.

This is the issue.

In a multitude of places errno.h is included in the esp32 build. The esp32 SDK (ESP-IDF) has created replacement headers for some things so it aligns with the posix API. Not all things have been created which is why there is a need to use the compat headers from lwip and also from another library that is a wrapper around FreeRTOS to provide a posix API for creating threads. Now here is the issue.

Includes are handled so when an include is wrapped in double quotes the way the compiler does the search is local path from where the include is taking place and then it searches the paths that are provided using the -I compiler argument. When an include is wrapped in gt and le the compiler searches the paths provided by the -I compiler argument first and then it looks for includes at the system level.

Se here is the issue.... lwip provides an errno.h as does the FreeRTOS wrapper and the ESP32 SDK provides one as well. since the file exists in 3 locations the first one that the compiler comes across when searching the paths is going to be the one that it is going to use. The issue is not all of the needed definitions exist in a single one of those files. The only way I can think to handle this is to create my own errno.h file and include each of the other 3 files using the abolsute path to each file and wrapping each one in double quotes. Now I am able to collect the paths programmatically using cmake, no issue there. what I do not know how to do is to create the file using cmake. This is something that you might be able to assist me in doing. In order to do this I would be using part of the ESP-IDF build system to collect the paths to the files. I think it would be better if code was written that wouldn't be tied specifically to the ESP-IDF build system. If there is a way to search the include paths supplied to cmake to locate header files that are the same name and create a new header file if multiples are found and include the multiples in the new header file and set the path to the generated file so it will be what is used when included.. I think that is the proper way to go about it.

It would take me days to figure out how to do that with cmake so it would do a proper recursive search so the paths and filename match properly and create the same path and filename.

bk138 commented 3 weeks ago

My 2 cents here: For the time being, don't bother to automate this with cmake, let humans do it. I think a section in the README describing the very special steps for the errno.h situation on esp32 would be enough.

kdschlosser commented 3 weeks ago

OK so I am getting down to the wire with this...

couple of errors I am not sure how to fix.,...

/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/sockets.c: In function 'rfbProcessNewConnection':
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/sockets.c:480:19: error: storage size of 'rlim' isn't known
480 |     struct rlimit rlim;
|                   ^~~~
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/sockets.c:509:8: error: implicit declaration of function 'getrlimit' [-Werror=implicit-function-declaration]
509 |     if(getrlimit(RLIMIT_NOFILE, &rlim) < 0)
|        ^~~~~~~~~
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/sockets.c:509:18: error: 'RLIMIT_NOFILE' undeclared (first use in this function)
509 |     if(getrlimit(RLIMIT_NOFILE, &rlim) < 0)
|                  ^~~~~~~~~~~~~
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/sockets.c:509:18: note: each undeclared identifier is reported only once for each function it appears in
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/sockets.c:480:19: warning: unused variable 'rlim' [-Wunused-variable]
480 |     struct rlimit rlim;
|                   ^~~~
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncclient/listen.c: In function 'listenForIncomingConnections':
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncclient/listen.c:91:18: error: implicit declaration of function 'wait4'; did you mean 'wait'? [-Werror=implicit-function-declaration]
91 |     while ((pid= wait4(-1, &status, WNOHANG, (struct rusage *)0))>0);
|                  ^~~~~
|                  wait
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/stats.c: In function 'messageNameClient2Server':
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/stats.c:83:42: error: format '%X' expects argument of type 'unsigned int', but argument 4 has type 'uint32_t' {aka 'long unsigned int'} [-Werror=format=]
83 |         snprintf(buf, len, "cli2svr-0x%08X", type);
|                                       ~~~^   ~~~~
|                                          |   |
|                                          |   uint32_t {aka long unsigned int}
|                                          unsigned int
|                                       %08lX
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/stats.c: In function 'encodingName':
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/stats.c:157:38: error: format '%X' expects argument of type 'unsigned int', but argument 4 has type 'uint32_t' {aka 'long unsigned int'} [-Werror=format=]
157 |         snprintf(buf, len, "Enc(0x%08X)", type);
|                                   ~~~^    ~~~~
|                                      |    |
|                                      |    uint32_t {aka long unsigned int}
|                                      unsigned int
|                                   %08lX
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/httpd.c: In function 'validateString':
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncserver/httpd.c:661:22: error: array subscript has type 'char' [-Werror=char-subscripts]
661 |         if (!isalnum(*ptr) && *ptr != '_' && *ptr != '.'
|                      ^~~~
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncclient/vncviewer.c: In function 'rfbGetClient':
/vnc_compile_test/lvgl_micropython/lib/libvncserver/src/libvncclient/vncviewer.c:343:36: warning: cast between incompatible function types from 'rfbBool (*)(rfbClient *, int,  int)' {aka 'signed char (*)(struct _rfbClient *, int,  int)'} to 'void (*)(struct _rfbClient *, int,  int)' [-Wcast-function-type]
343 |   client->HandleKeyboardLedState = (HandleKeyboardLedStateProc)DummyPoint;
|                                    ^
bk138 commented 3 weeks ago

This is probably all stuff not in FreeRTOS. I recommend commenting out vigorously and getting stuff to build, take notes, then clean up later.

PS: Also, you probably don't need libvncclient, so errors there can be ignored.

kdschlosser commented 3 weeks ago

There is a use case for having the client which is providing a client to the user. LVGL is a light weight graphics library so providing a client would be easy to do because all of the graphics bits exist.

There also seems to be no way to compile only the server without it compiling the client too. Can I omit the client source files from being compiled? There doesn't appear to be a macro available to turn off the client being compiled.

kdschlosser commented 3 weeks ago

I don't know what wait4, rlimit and getrlimit are apart of. perhaps it is something I would be able to implement. Just need to know what they are. Is there an alternative way. Perhaps there is an alternate way of providing what those things are providing to lbvncserver???

kdschlosser commented 3 weeks ago

can the http server be turned off? does it need to compile when it is not being used?... We only plan on using sockets to send and receive data so there really is no need to have the http code compiled. I don't know if there is a dependence on it being compiled.

bk138 commented 3 weeks ago

There is a use case for having the client which is providing a client to the user. LVGL is a light weight graphics library so providing a client would be easy to do because all of the graphics bits exist.

There also seems to be no way to compile only the server without it compiling the client too. Can I omit the client source files from being compiled? There doesn't appear to be a macro available to turn off the client being compiled.

No, both always built as of now.

bk138 commented 3 weeks ago

I don't know what wait4, rlimit and getrlimit are apart of. perhaps it is something I would be able to implement. Just need to know what they are. Is there an alternative way. Perhaps there is an alternate way of providing what those things are providing to lbvncserver???

No idea, this code is very likely from a time before I entered the project.

bk138 commented 3 weeks ago

can the http server be turned off? does it need to compile when it is not being used?... We only plan on using sockets to send and receive data so there really is no need to have the http code compiled. I don't know if there is a dependence on it being compiled.

Not as of now, in principle of course yes. It's not essential at all.

kdschlosser commented 3 weeks ago

Not as of now, in principle of course yes. It's not essential at all.

How do I go about turning it off? There is no macro that controls this. would I need to remove the source files from the list to be compiled?

I am thinking there should be some config macros added to the source files and header files that control what does and doesn't get compiled. This is easier than having to modify the build script to control turning on and off parts of the library that could be considered as optional.

kdschlosser commented 3 weeks ago

IDK if the server code has any dependency on the code in the client if it doesn't then the client would be an optional thing. The same with the client being dependent on any of the code in the server side of things. I see there is a common folder and I would imagine anything that both the client and server both use would be located in there. That kind of indicates that the server code should be able to compile without the client code and vice versa.

kdschlosser commented 3 weeks ago

ok so here is some information in wait4 It is in sys/wait.h and this is what the man pages say for it.

These functions are nonstandard; in new programs, the use of waitpid(2) or waitid(2) is preferable.

https://man7.org/linux/man-pages/man2/wait4.2.html

so a code update to use waitpid or waitid is going to be the best solution to correct that error.

kdschlosser commented 3 weeks ago

fixing the issue with 'getrlimit` should be pretty easy to do as well. need to add a check to the build system to see if that function is available. Right now a check is done to see if the resource.h file exists instead of check to see if the specific function that is needed exists. pretty simple thing to correct.

flyqie commented 3 weeks ago

Thank you for your contribution, your work has realized what I have always wanted to do but have not been able to do!

What encodings do you currently want to support? All or just some encodings (due to the performance of the MCU)?

kdschlosser commented 3 weeks ago

what do you mean by encodings? are you referring to the color format? if that is the case then RGB565, RGB888, xRGB888 and ARGB888. we will use no encryption due to the additional overhead because of using software encryption. Possibly in the future add encryption but one what is supported at the hardware level on the MCU.

I already wrote the code that converts the keysyms that are not control/function keys to UTF-8 and also single byte ASCII is those codes are received.

being able to do file transfers will be a handy thing to have working. IDK if your library handles writing a pointer to the frame buffer data or not, if it does that could be used but I think that the GUI frameworks handling of rendering a cursor might be better.

Functionally how it is going to work is the RAW RGB framebuffer data is going to get handed off to the VCN server if there is a client connection. The user will be able to choose whether or not to blank the display when a client is connected to VNC or to leave it displaying what is seen on the VNC connection. Could even use it to go one step further and set up a kind of VM design where more than one user is able to access the thing and have 2 completely different UI's showing over the connection.

MicroControllers these days are pretty robust, some of them being 4 cores @ 1.4ghz and they have hardware graphics accelerators built in. The GUI framework is able to render to a 1280 x 1024 display having a framerate of 60fps on some MCU's. That's pretty quick. That's a very large amount of data to send to a display to able to maintain 60fps refresh rate on the display. it comes out to 176947200 bits a second. or 176mbit transfer speed to the display. The limitation is not going to be the MCU as far as processing ability so much as it is going to be a limit of the network connection like WiFI which is only going to be able to send data at 30mbit a second. I will hammer out some code that will decide what color format to use based on how fast the data is transferring.

kdschlosser commented 3 weeks ago

I do have a question about how exactly the data is handled on the client.

I know that there are 2 ways that frame buffer data can be transmitted. It can be transmitted when the client requests it or the server can send it when it is told to send it. Now my question is does the entire frame buffer get sent when a request is made or when the server needs to send an update? or does only the updated parts get sent?

I am hoping that only the parts that have been updated are what gets sent. I mam thinking the client should be writing data to a frame buffer that it has made for the entire display and when an update is received that new data gets written to the frame buffer and then the frame buffer gets rendered on the client side. The protocol specification doesn't go into much detail about the handling of this and if partial frame buffer data is able to be sent.

kdschlosser commented 3 weeks ago

Also, I am going to submit another PR and this PR is for enabling/disabling the server and client code. It stops the code from being compiled. program storage is rather skinny on an MCU so the smaller we can make the compiled binary the better. don't need to have the client code on the MCU if it is not going to be used.

I know that the changes I made are not going to work because the test suite is not divided by client and server. It would be easier if someone more familiar with the test suite could separate the client and server tests so we could have the CI build only the client, then only the server and then both... this way it would ensure that everything is working correctly.

flyqie commented 3 weeks ago

what do you mean by encodings? are you referring to the color format? if that is the case then RGB565, RGB888, xRGB888 and ARGB888. we will use no encryption due to the additional overhead because of using software encryption. Possibly in the future add encryption but one what is supported at the hardware level on the MCU.

I already wrote the code that converts the keysyms that are not control/function keys to UTF-8 and also single byte ASCII is those codes are received.

being able to do file transfers will be a handy thing to have working. IDK if your library handles writing a pointer to the frame buffer data or not, if it does that could be used but I think that the GUI frameworks handling of rendering a cursor might be better.

Functionally how it is going to work is the RAW RGB framebuffer data is going to get handed off to the VCN server if there is a client connection. The user will be able to choose whether or not to blank the display when a client is connected to VNC or to leave it displaying what is seen on the VNC connection. Could even use it to go one step further and set up a kind of VM design where more than one user is able to access the thing and have 2 completely different UI's showing over the connection.

MicroControllers these days are pretty robust, some of them being 4 cores @ 1.4ghz and they have hardware graphics accelerators built in. The GUI framework is able to render to a 1280 x 1024 display having a framerate of 60fps on some MCU's. That's pretty quick. That's a very large amount of data to send to a display to able to maintain 60fps refresh rate on the display. it comes out to 176947200 bits a second. or 176mbit transfer speed to the display. The limitation is not going to be the MCU as far as processing ability so much as it is going to be a limit of the network connection like WiFI which is only going to be able to send data at 30mbit a second. I will hammer out some code that will decide what color format to use based on how fast the data is transferring.

Sorry for the confusion, I am referring to

https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#76encodings

kdschlosser commented 3 weeks ago

Ahhhh, compression. I would have to run tests to see if the time it takes to do the compression would give better performance than transferring the raw data. It all depends on how small the data that gets sent is. we also have to deal with compatibility of the external compression libraries and the MCU. That will be something that I can mess with after getting uncompressed (raw) to work.

kdschlosser commented 3 weeks ago

It compiles!!! WOOOOO... Both the server and the client compile. dunno if they actually work just yet but to cross compilation using the xtensa GCC compiler was a success.

bk138 commented 3 weeks ago

Not as of now, in principle of course yes. It's not essential at all.

How do I go about turning it off? There is no macro that controls this. would I need to remove the source files from the list to be compiled?

As of now, yes. A CMake switch could be added, but is not there (yet).

I am thinking there should be some config macros added to the source files and header files that control what does and doesn't get compiled. This is easier than having to modify the build script to control turning on and off parts of the library that could be considered as optional.

Yes, am open for it.

bk138 commented 3 weeks ago

IDK if the server code has any dependency on the code in the client if it doesn't then the client would be an optional thing. The same with the client being dependent on any of the code in the server side of things. I see there is a common folder and I would imagine anything that both the client and server both use would be located in there. That kind of indicates that the server code should be able to compile without the client code and vice versa.

You're correct. server does only depend on common, client depends on common. no client<->server dependency. The configure logic is simply not (yet) in CMake :-)

bk138 commented 3 weeks ago

I do have a question about how exactly the data is handled on the client.

I know that there are 2 ways that frame buffer data can be transmitted. It can be transmitted when the client requests it or the server can send it when it is told to send it.

That's basically the same ;-) The RFB protocol is pull-based, so you described it correctly.

Now my question is does the entire frame buffer get sent when a request is made or when the server needs to send an update? or does only the updated parts get sent?

Depends on the request. A client can request a full update or tell the server "gimme just the changed parts". See https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#743framebufferupdaterequest

bk138 commented 3 weeks ago

It compiles!!! WOOOOO... Both the server and the client compile. dunno if they actually work just yet but to cross compilation using the xtensa GCC compiler was a success.

Congrats! Looking forward to your PRs.

kdschlosser commented 3 weeks ago

You're correct. server does only depend on common, client depends on common. no client<->server dependency. The configure logic is simply not (yet) in CMake :-)

I added the logic tho I have not yet tested it. You are more then welcome to give it a go if you want or you can look at the code to see what you think before I submit a PR for it.

Here are the branches I have for the things I have changed, some I have submitted a PR for and others I have not.

https://github.com/kdschlosser/libvncserver/tree/adds-build-options

https://github.com/kdschlosser/libvncserver/tree/fixes-invalid-pointer-cast

https://github.com/kdschlosser/libvncserver/tree/fixes-string-formatting

https://github.com/kdschlosser/libvncserver/tree/fixes-string-formatting

https://github.com/kdschlosser/libvncserver/tree/replaces-wait4

That addresses all of the issues in libvncserver that I was having when compiling. I managed to get all other issues sorted out externally by overriding the libc/glibc header files. There were issues with how things were implimented in the xtensa compiler and in the ESP-IDF for posix compatability that needed to be corrected.

For example in errno.h there is an inclusion for sys/errno.h and in sys/errno.h there is a macro errno that collects the current error number. For some reason that include to sys/errno.h was not added to the extensa compiler or to the compat headers in the ESP-IDF. so I needed to override this behavior so including error.h would also pull the macro. Took a bit of poking about to figure out how to go about doing this and what I did was in the cmake script I have it create a new errno.h file in a subdirectory located in the binary directory and in that file are 2 includes. one that points to the errno.h file included with the compiler by using the absolute path and the other is an include to <sys/errno.h>. I then added the absolute path to the folder where the new errno.h is located to the implicit includes using this code..

set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES
        ${CMAKE_BINARY_DIR}/gen_includes
        ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}
    )

this way the errno.h file that is generated is what gets found first and is what gets included when #include <errno,h> gets used in the library. It was a bunch of little tweaky stuff like that that needed to be done.

Now... That being said this could be handled a little bit differently to make sure this doesn't happen if wanting to compile using other MCU's the code I wrote to handle this in the cmake file is specific to the ESP32 series of MCU's using the ESP-IDF version 5.2.x. If wanting to make it universal so the problem would not come up using any compiler that provides posix compat headers you would need to create a errno.h file in the common directory and in that file you would place the following code.

#ifndef WIN32
    #include <sys/errno.h>
#endif

#include <errno.h>

Then you would change all of the includes for errno.h in the library to point to that header file using a relative path

#include "../common/errno.h"

That is a more graceful approach that covers pretty much all the bases for most of the C compilers that use the libc or glibc libraries or some form of a posix compat set of libraries.

kdschlosser commented 3 weeks ago

This is an example cmake file that can be used to compile the library as an ESP32 component

Click me for code ```cmake cmake_minimum_required(VERSION 3.4) set(PROJECT_LANGUAGES C) if(DEFINED CMAKE_CXX_COMPILER) set(CMAKE_CXX_STANDARD 17) list(APPEND PROJECT_LANGUAGES CXX) endif(DEFINED CMAKE_CXX_COMPILER) include(CheckFunctionExists) include(CheckSymbolExists) include(CheckIncludeFile) include(CheckTypeSize) include(TestBigEndian) include(CheckCSourceCompiles) include(CheckCSourceRuns) include(GNUInstallDirs) enable_testing() set(LIBVNCSERVER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/libvncserver) set(COMMON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/common) set(LIBVNCCLIENT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/libvncclient) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules/") include(CheckFunctionExists) include(CheckSymbolExists) include(CheckIncludeFile) include(CheckTypeSize) include(TestBigEndian) include(CheckCSourceCompiles) include(CheckCSourceRuns) include(GNUInstallDirs) check_include_file("dirent.h" LIBVNCSERVER_HAVE_DIRENT_H) check_include_file("endian.h" LIBVNCSERVER_HAVE_ENDIAN_H) check_include_file("fcntl.h" LIBVNCSERVER_HAVE_FCNTL_H) check_include_file("netinet/in.h" LIBVNCSERVER_HAVE_NETINET_IN_H) check_include_file("sys/endian.h" LIBVNCSERVER_HAVE_SYS_ENDIAN_H) check_include_file("sys/socket.h" LIBVNCSERVER_HAVE_SYS_SOCKET_H) check_include_file("sys/stat.h" LIBVNCSERVER_HAVE_SYS_STAT_H) check_include_file("sys/time.h" LIBVNCSERVER_HAVE_SYS_TIME_H) check_include_file("sys/types.h" LIBVNCSERVER_HAVE_SYS_TYPES_H) check_include_file("sys/wait.h" LIBVNCSERVER_HAVE_SYS_WAIT_H) check_include_file("unistd.h" LIBVNCSERVER_HAVE_UNISTD_H) check_include_file("sys/resource.h" LIBVNCSERVER_HAVE_SYS_RESOURCE_H) # headers needed for check_type_size() check_include_file("vfork.h" LIBVNCSERVER_HAVE_VFORK_H) check_include_file("ws2tcpip.h" LIBVNCSERVER_HAVE_WS2TCPIP_H) check_include_file("arpa/inet.h" HAVE_ARPA_INET_H) check_include_file("stdint.h" HAVE_STDINT_H) check_include_file("stddef.h" HAVE_STDDEF_H) check_include_file("sys/types.h" HAVE_SYS_TYPES_H) # error out if required headers not found if(NOT HAVE_STDINT_H) message(FATAL_ERROR "Could NOT find required header stdint.h") endif() check_function_exists(getrlimit LIBVNCSERVER_HAVE_GETRLIMIT) check_function_exists(gettimeofday LIBVNCSERVER_HAVE_GETTIMEOFDAY) check_function_exists(vfork LIBVNCSERVER_HAVE_VFORK) check_function_exists(vprintf LIBVNCSERVER_HAVE_VPRINTF) check_function_exists(mmap LIBVNCSERVER_HAVE_MMAP) check_function_exists(fork LIBVNCSERVER_HAVE_FORK) check_function_exists(ftime LIBVNCSERVER_HAVE_FTIME) check_function_exists(gethostbyname LIBVNCSERVER_HAVE_GETHOSTBYNAME) check_function_exists(gethostname LIBVNCSERVER_HAVE_GETHOSTNAME) check_function_exists(inet_ntoa LIBVNCSERVER_HAVE_INET_NTOA) check_function_exists(memmove LIBVNCSERVER_HAVE_MEMMOVE) check_function_exists(memset LIBVNCSERVER_HAVE_MEMSET) check_function_exists(mkfifo LIBVNCSERVER_HAVE_MKFIFO) check_function_exists(select LIBVNCSERVER_HAVE_SELECT) check_function_exists(socket LIBVNCSERVER_HAVE_SOCKET) check_function_exists(strchr LIBVNCSERVER_HAVE_STRCHR) check_function_exists(strcspn LIBVNCSERVER_HAVE_STRCSPN) check_function_exists(strdup LIBVNCSERVER_HAVE_STRDUP) check_function_exists(strerror LIBVNCSERVER_HAVE_STRERROR) check_function_exists(strstr LIBVNCSERVER_HAVE_STRSTR) check_symbol_exists(htobe64 "endian.h" LIBVNCSERVER_HAVE_HTOBE64) check_symbol_exists(htobe64 "sys/endian.h" LIBVNCSERVER_HAVE_HTOBE64) check_symbol_exists(OSSwapHostToBigInt64 "libkern/OSByteOrder.h" LIBVNCSERVER_HAVE_OSSWAPHOSTTOBIGINT64) set(LIBVNCSERVER_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}/include ${LIBVNCSERVER_DIR} ${LIBVNCCLIENT_DIR} ${COMMON_DIR} ) set(LIBVNCSERVER_SOURCES ${LIBVNCSERVER_DIR}/main.c ${LIBVNCSERVER_DIR}/rfbserver.c ${LIBVNCSERVER_DIR}/rfbregion.c ${LIBVNCSERVER_DIR}/auth.c ${LIBVNCSERVER_DIR}/sockets.c ${LIBVNCSERVER_DIR}/stats.c ${LIBVNCSERVER_DIR}/corre.c ${LIBVNCSERVER_DIR}/hextile.c ${LIBVNCSERVER_DIR}/rre.c ${LIBVNCSERVER_DIR}/translate.c ${LIBVNCSERVER_DIR}/cutpaste.c ${LIBVNCSERVER_DIR}/httpd.c ${LIBVNCSERVER_DIR}/cursor.c ${LIBVNCSERVER_DIR}/font.c ${LIBVNCSERVER_DIR}/draw.c ${LIBVNCSERVER_DIR}/selbox.c ${COMMON_DIR}/vncauth.c ${COMMON_DIR}/sockets.c ${LIBVNCSERVER_DIR}/cargs.c ${LIBVNCSERVER_DIR}/ultra.c ${LIBVNCSERVER_DIR}/scale.c ${COMMON_DIR}/ghpringbuf.c ) set(LIBVNCCLIENT_SOURCES ${LIBVNCCLIENT_DIR}/cursor.c ${LIBVNCCLIENT_DIR}/listen.c ${LIBVNCCLIENT_DIR}/rfbclient.c ${LIBVNCCLIENT_DIR}/sockets.c ${LIBVNCCLIENT_DIR}/vncviewer.c ) if(ESP_PLATFORM) # check to see if we are compiling for ESP32 using the ESP-IDF check_symbol_exists(htobe64 "machine/endian.h" LIBVNCSERVER_HAVE_HTOBE64) # getting esp-idf component paths idf_component_get_property(NEWLIB_DIR newlib COMPONENT_DIR) idf_component_get_property(LWIP_DIR lwip COMPONENT_DIR) file(WRITE ${CMAKE_BINARY_DIR}/gen_includes/errno.h "#include \"${LWIP_DIR}/lwip/src/include/lwip/errno.h\"\n" "#include \"${NEWLIB_DIR}/platform_include/errno.h\"\n" "#include \n" ) set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES ${CMAKE_BINARY_DIR}/gen_includes ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES} ) set(LIBVNCSERVER_INCLUDES ${CMAKE_BINARY_DIR}/gen_includes ${LIBVNCSERVER_INCLUDES} ) set(LIBVNCSERVER_HAVE_SYS_SOCKET_H 1) set(LIBVNCSERVER_HAVE_NETINET_IN_H 1) set(LIBVNCSERVER_HAVE_DIRENT_H, 1) set(HAVE_ARPA_INET_H 1) set(LIBVNCSERVER_HAVE_GETHOSTBYNAME 1) set(LIBVNCSERVER_HAVE_GETHOSTNAME 1) set(LIBVNCSERVER_HAVE_INET_NTOA 1) set(LIBVNCSERVER_HAVE_SELECT 1) set(LIBVNCSERVER_HAVE_SOCKET 1) set(LIBVNCSERVER_HAVE_STRCHR 1) set(LIBVNCSERVER_HAVE_STRDUP 1) set(LIBVNCSERVER_HAVE_STRSTR 1) set(HAVE_LIBVNCSERVER_PID_T 1) set(HAVE_LIBVNCSERVER_SIZE_T 1) set(HAVE_LIBVNCSERVER_SOCKLEN_T 1) set(HAVE_LIBVNCSERVER_IN_ADDR_T 1) set(LIBVNCSERVER_HAVE_LIBPTHREAD 1) set(LIBVNCSERVER_ALLOW24BPP 1) set(LibVNCServer_VERSION "0.9.15") set(LibVNCServer_VERSION_MAJOR "0") set(LibVNCServer_VERSION_MINOR "9") set(LibVNCServer_VERSION_PATCH "15") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/rfb/rfbconfig.h.cmakein ${CMAKE_CURRENT_BINARY_DIR}/include/rfb/rfbconfig.h) idf_component_register( SRCS ${LIBVNCSERVER_SOURCES} ${LIBVNCCLIENT_SOURCES} INCLUDE_DIRS ${LIBVNCSERVER_INCLUDES} REQUIRES lwip newlib ) else() project(LibVNCServer VERSION 0.9.15 LANGUAGES ${PROJECT_LANGUAGES}) set(PACKAGE_NAME "LibVNCServer") set(FULL_PACKAGE_NAME "LibVNCServer") set(VERSION_SO "1") set(PROJECT_BUGREPORT_PATH "https://github.com/LibVNC/libvncserver/issues") set(LIBVNCSRVEXAMPLE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples/server) set(LIBVNCCLIEXAMPLE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples/client) set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(CMAKE_GENERATOR MATCHES "Unix Makefiles|Ninja") # some LSP servers expect compile_commands.json in the project root add_custom_target( copy-compile-commands ALL ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json ${CMAKE_CURRENT_SOURCE_DIR} ) endif(CMAKE_GENERATOR MATCHES "Unix Makefiles|Ninja") include_directories(${LIBVNCSERVER_INCLUDES}) # all the build configuration switches option(LIBVNCSERVER_INSTALL "Generate installation target" ON) option(BUILD_SHARED_LIBS "Build shared libraries" ${UNIX}) option(WITH_ZLIB "Search for the zlib compression library to support additional encodings" ON) option(WITH_LZO "Search for the LZO compression library to omit internal miniLZO implementation" ON) option(WITH_JPEG "Search for the libjpeg compression library to support additional encodings" ON) option(WITH_PNG "Search for the PNG compression library to support additional encodings" ON) option(WITH_SDL "Search for the Simple Direct Media Layer library to build an example SDL vnc client" ON) option(WITH_GTK "Search for the GTK library to build an example GTK vnc client" ON) option(WITH_LIBSSHTUNNEL "Search for libsshtunnel to build an example ssh-tunneled client" ON) option(WITH_THREADS "Search for a threading library to build with multithreading support" ON) option(PREFER_WIN32THREADS "When searching for a threading library, prefer win32 threads if they are found" OFF) option(WITH_GNUTLS "Search for the GnuTLS secure communications library to support TLS" ON) option(WITH_OPENSSL "Search for the OpenSSL cryptography library to support TLS and use as crypto backend" ON) option(WITH_SYSTEMD "Search for libsystemd to build with systemd socket activation support" ON) option(WITH_GCRYPT "Search for Libgcrypt to use as crypto backend" ON) option(WITH_FFMPEG "Search for FFMPEG to build an example VNC to MPEG encoder" ON) option(WITH_TIGHTVNC_FILETRANSFER "Enable filetransfer if there is pthreads support" ON) option(WITH_24BPP "Allow 24 bpp" ON) option(WITH_IPv6 "Enable IPv6 Support" ON) option(WITH_WEBSOCKETS "Build with websockets support" ON) option(WITH_SASL "Build with SASL support" ON) option(WITH_XCB "Build with XCB support" ON) option(WITH_EXAMPLES "Build examples" ON) option(WITH_TESTS "Build tests" ON) option(WITH_QT "Build the Qt client example" ON) if(WITH_ZLIB) find_package(ZLIB) endif(WITH_ZLIB) if(WITH_LZO) find_package(LZO) endif() if(WITH_XCB) find_package(X11) # Need CMake 3.24.0 to find XCB libraries. see https://cmake.org/cmake/help/v3.24/module/FindX11.html endif() if(WITH_JPEG) find_package(JPEG) if(JPEG_FOUND) # Check whether the version of libjpeg we found was libjpeg-turbo and print a # warning if not. set(CMAKE_REQUIRED_LIBRARIES ${JPEG_LIBRARIES}) if(JPEG_INCLUDE_DIRS) # this was not present in 3.4 set(CMAKE_REQUIRED_INCLUDES ${JPEG_INCLUDE_DIRS}) else() set(CMAKE_REQUIRED_INCLUDES ${JPEG_INCLUDE_DIR}) endif() set(JPEG_TEST_SOURCE "\n #include \n #include \n int main(void) {\n struct jpeg_compress_struct cinfo;\n struct jpeg_error_mgr jerr;\n cinfo.err=jpeg_std_error(&jerr);\n jpeg_create_compress(&cinfo);\n cinfo.input_components = 3;\n jpeg_set_defaults(&cinfo);\n cinfo.in_color_space = JCS_EXT_RGB;\n jpeg_default_colorspace(&cinfo);\n return 0;\n }" ) if(CMAKE_CROSSCOMPILING) check_c_source_compiles("${JPEG_TEST_SOURCE}" FOUND_LIBJPEG_TURBO) else() check_c_source_runs("${JPEG_TEST_SOURCE}" FOUND_LIBJPEG_TURBO) endif() set(CMAKE_REQUIRED_LIBRARIES) set(CMAKE_REQUIRED_INCLUDES) set(CMAKE_REQUIRED_DEFINITIONS) if(NOT FOUND_LIBJPEG_TURBO) # last-resort grep check (when using LibVNCServer as a CMake subdir together with turbojpeg CMake subdir, the build check above fails since turbojpeg is not yet built when LibVNCServer is configured) get_filename_component(JPEGLIB_H_PATH "${JPEG_INCLUDE_DIR}/jpeglib.h" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") message(STATUS "Runtime check for libjpeg-turbo failed, inspecting ${JPEGLIB_H_PATH}") file(STRINGS ${JPEGLIB_H_PATH} FOUND_LIBJPEG_TURBO REGEX "JCS_EXT_RGB") if(NOT FOUND_LIBJPEG_TURBO) message(WARNING "*** The libjpeg library you are building against is not libjpeg-turbo. Performance will be reduced. You can obtain libjpeg-turbo from: https://sourceforge.net/projects/libjpeg-turbo/files/ ***") else() message(STATUS "Detected libjpeg-turbo via ${JPEGLIB_H_PATH}") endif() endif() endif(JPEG_FOUND) endif(WITH_JPEG) if(WITH_PNG) find_package(PNG) endif(WITH_PNG) if(WITH_SDL) find_package(SDL2) endif(WITH_SDL) if(WITH_GTK) find_package(GTK2) endif(WITH_GTK) if(WITH_QT) find_package(Qt5 COMPONENTS Core Widgets QUIET) endif(WITH_QT) if(WITH_LIBSSHTUNNEL) find_path(LIBSSHTUNNEL_INCLUDE_DIR libsshtunnel.h) find_library(LIBSSHTUNNEL_LIBRARY sshtunnel) if("${LIBSSHTUNNEL_LIBRARY}" MATCHES ".*NOTFOUND.*") # would otherwise contain -NOTFOUND, confusing target_link_libraries() set(LIBSSHTUNNEL_LIBRARY "") endif() endif(WITH_LIBSSHTUNNEL) if(WITH_THREADS) find_package(Threads) endif(WITH_THREADS) if(WITH_GNUTLS) find_package(GnuTLS 3.4.0) endif(WITH_GNUTLS) if(WITH_OPENSSL) find_package(OpenSSL) endif(WITH_OPENSSL) if(WITH_SYSTEMD AND NOT ANDROID AND NOT WIN32) find_package(PkgConfig) pkg_check_modules(SYSTEMD "libsystemd") endif(WITH_SYSTEMD AND NOT ANDROID AND NOT WIN32) if(WITH_GCRYPT) find_library(LIBGCRYPT_LIBRARIES gcrypt) endif(WITH_GCRYPT) if(WITH_FFMPEG) find_package( FFMPEG 3.1.0 COMPONENTS avformat avcodec avutil swscale ) endif(WITH_FFMPEG) if(WITH_THREADS AND Threads_FOUND) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${CMAKE_THREAD_LIBS_INIT} ) endif(WITH_THREADS AND Threads_FOUND) if(ZLIB_FOUND) set(LIBVNCSERVER_HAVE_LIBZ 1) else() unset(ZLIB_LIBRARIES) # would otherwise contain -NOTFOUND, confusing target_link_libraries() endif(ZLIB_FOUND) if(LZO_FOUND) set(LIBVNCSERVER_HAVE_LZO 1) else() unset(LZO_LIBRARIES CACHE) # would otherwise contain -NOTFOUND, confusing target_link_libraries() endif() if(JPEG_FOUND) set(LIBVNCSERVER_HAVE_LIBJPEG 1) else() unset(JPEG_LIBRARIES) # would otherwise confuse target_link_libraries() endif(JPEG_FOUND) if(PNG_FOUND) set(LIBVNCSERVER_HAVE_LIBPNG 1) else() unset(PNG_LIBRARIES) # would otherwise contain -NOTFOUND, confusing target_link_libraries() endif(PNG_FOUND) if(NOT OPENSSL_FOUND) unset(OPENSSL_LIBRARIES) # would otherwise contain -NOTFOUND, confusing target_link_libraries() endif() if(SYSTEMD_FOUND) add_definitions(-DLIBVNCSERVER_WITH_SYSTEMD) include_directories(${SYSTEMD_INCLUDE_DIRS}) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${SYSTEMD_LIBRARIES} ) endif(SYSTEMD_FOUND) # common crypto used by both libvncserver and libvncclient if(WITH_GCRYPT AND LIBGCRYPT_LIBRARIES) message(STATUS "Building crypto with Libgcrypt") set(CRYPTO_LIBRARIES ${LIBGCRYPT_LIBRARIES}) set(CRYPTO_SOURCES ${COMMON_DIR}/crypto_libgcrypt.c) elseif(OPENSSL_FOUND) message(STATUS "Building crypto with OpenSSL") set(CRYPTO_LIBRARIES ${OPENSSL_LIBRARIES}) set(CRYPTO_SOURCES ${COMMON_DIR}/crypto_openssl.c) else() message(STATUS "Building crypto with builtin functions, only including SHA1 and D3DES") set(CRYPTO_SOURCES ${COMMON_DIR}/crypto_included.c ${COMMON_DIR}/sha1.c ${COMMON_DIR}/d3des.c ) endif() if(WITH_WEBSOCKETS AND (LIBVNCSERVER_HAVE_HTOBE64 OR LIBVNCSERVER_HAVE_OSSWAPHOSTTOBIGINT64)) set(LIBVNCSERVER_WITH_WEBSOCKETS 1) endif() if(WITH_GCRYPT AND LIBGCRYPT_LIBRARIES) set(LIBVNCSERVER_HAVE_LIBGCRYPT 1) endif(WITH_GCRYPT AND LIBGCRYPT_LIBRARIES) if(GNUTLS_FOUND) set(LIBVNCSERVER_HAVE_GNUTLS 1) endif(GNUTLS_FOUND) if(OPENSSL_FOUND) include_directories(${OPENSSL_INCLUDE_DIR}) set(LIBVNCSERVER_HAVE_LIBSSL 1) endif(OPENSSL_FOUND) if(WITH_IPv6) if(WIN32 AND LIBVNCSERVER_HAVE_WS2TCPIP_H AND LIBVNCSERVER_HAVE_VPRINTF) set(LIBVNCSERVER_IPv6 1) endif() if(NOT WIN32) set(LIBVNCSERVER_IPv6 1) endif() endif(WITH_IPv6) if(WITH_24BPP) set(LIBVNCSERVER_ALLOW24BPP 1) endif() # Make sure that only one threading system is used. This happens on MinGW. if(WITH_THREADS) if(CMAKE_USE_PTHREADS_INIT AND CMAKE_USE_WIN32_THREADS_INIT) if(PREFER_WIN32THREADS) unset(CMAKE_USE_PTHREADS_INIT) else() unset(CMAKE_USE_WIN32_THREADS_INIT) endif(PREFER_WIN32THREADS) endif(CMAKE_USE_PTHREADS_INIT AND CMAKE_USE_WIN32_THREADS_INIT) if(CMAKE_USE_PTHREADS_INIT) message(STATUS "Threads support is using pthreads") set(LIBVNCSERVER_HAVE_LIBPTHREAD 1) endif(CMAKE_USE_PTHREADS_INIT) if(CMAKE_USE_WIN32_THREADS_INIT) message(STATUS "Threads support is using win32 threads") set(LIBVNCSERVER_HAVE_WIN32THREADS 1) endif(CMAKE_USE_WIN32_THREADS_INIT) endif(WITH_THREADS) if(LIBVNCSERVER_HAVE_SYS_SOCKET_H) # socklen_t list(APPEND CMAKE_EXTRA_INCLUDE_FILES "sys/socket.h") endif(LIBVNCSERVER_HAVE_SYS_SOCKET_H) if(HAVE_ARPA_INET_H) # in_addr_t list(APPEND CMAKE_EXTRA_INCLUDE_FILES "arpa/inet.h") endif(HAVE_ARPA_INET_H) if(NOT HAVE_LIBVNCSERVER_IN_ADDR_T) set(LIBVNCSERVER_NEED_INADDR_T 1) endif(NOT HAVE_LIBVNCSERVER_IN_ADDR_T) TEST_BIG_ENDIAN(LIBVNCSERVER_WORDS_BIGENDIAN) if(WITH_SASL) find_path(SASL2_INCLUDE_DIR sasl/sasl.h) find_library(LIBSASL2_LIBRARIES sasl2 libsasl.lib) endif(WITH_SASL) if(WITH_SASL AND LIBSASL2_LIBRARIES AND SASL2_INCLUDE_DIR) message(STATUS "Building with SASL: ${LIBSASL2_LIBRARIES} and ${SASL2_INCLUDE_DIR}") set(LIBVNCSERVER_HAVE_SASL 1) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${LIBSASL2_LIBRARIES}) include_directories(${SASL2_INCLUDE_DIR}) endif(WITH_SASL AND LIBSASL2_LIBRARIES AND SASL2_INCLUDE_DIR) list(APPEND LIBVNCSERVER_SOURCES ${CRYPTO_SOURCES}) list(APPEND LIBVNCCLIENT_SOURCES ${COMMON_DIR}/ghpringbuf.c ${COMMON_DIR}/sockets.c ${CRYPTO_SOURCES} ) if(JPEG_FOUND) list(APPEND LIBVNCCLIENT_SOURCES ${COMMON_DIR}/turbojpeg.c) endif() TEST_BIG_ENDIAN(LIBVNCSERVER_WORDS_BIGENDIAN) if(GNUTLS_FOUND) message(STATUS "Building TLS with GnuTLS") list(APPEND LIBVNCCLIENT_SOURCES ${LIBVNCCLIENT_DIR}/tls_gnutls.c) list(APPEND LIBVNCSERVER_SOURCES ${LIBVNCSERVER_DIR}/rfbssl_gnutls.c) include_directories(${GNUTLS_INCLUDE_DIR}) elseif(OPENSSL_FOUND) message(STATUS "Building TLS with OpenSSL") list(APPEND LIBVNCCLIENT_SOURCES ${LIBVNCCLIENT_DIR}/tls_openssl.c) list(APPEND LIBVNCSERVER_SOURCES ${LIBVNCSERVER_DIR}/rfbssl_openssl.c) include_directories(${OPENSSL_INCLUDE_DIR}) else() message(STATUS "Building without TLS") list(APPEND LIBVNCCLIENT_SOURCES ${LIBVNCCLIENT_DIR}/tls_none.c) list(APPEND LIBVNCSERVER_SOURCES ${LIBVNCSERVER_DIR}/rfbssl_none.c) endif() if(LIBVNCSERVER_HAVE_SASL) list(APPEND LIBVNCCLIENT_SOURCES ${LIBVNCCLIENT_DIR}/sasl.c) endif() if(ZLIB_FOUND) add_definitions(-DLIBVNCSERVER_HAVE_LIBZ) include_directories(${ZLIB_INCLUDE_DIR}) list(APPEND LIBVNCSERVER_SOURCES ${LIBVNCSERVER_DIR}/zlib.c ${LIBVNCSERVER_DIR}/zrle.c ${LIBVNCSERVER_DIR}/zrleoutstream.c ${LIBVNCSERVER_DIR}/zrlepalettehelper.c ) endif(ZLIB_FOUND) if(LZO_FOUND) add_definitions(-DLIBVNCSERVER_HAVE_LZO) include_directories(${LZO_INCLUDE_DIR}) else() list(APPEND LIBVNCSERVER_SOURCES ${COMMON_DIR}/minilzo.c) list(APPEND LIBVNCCLIENT_SOURCES ${COMMON_DIR}/minilzo.c) endif() if(JPEG_FOUND) add_definitions(-DLIBVNCSERVER_HAVE_LIBJPEG) include_directories(${JPEG_INCLUDE_DIR}) if(PNG_FOUND OR ZLIB_FOUND) set(TIGHT_C ${LIBVNCSERVER_DIR}/tight.c ${COMMON_DIR}/turbojpeg.c) endif(PNG_FOUND OR ZLIB_FOUND) endif(JPEG_FOUND) if(PNG_FOUND) add_definitions(-DLIBVNCSERVER_HAVE_LIBPNG) include_directories(${PNG_INCLUDE_DIR}) endif(PNG_FOUND) list(APPEND LIBVNCSERVER_SOURCES ${TIGHT_C}) if(WITH_THREADS AND WITH_TIGHTVNC_FILETRANSFER AND CMAKE_USE_PTHREADS_INIT) list(APPEND LIBVNCSERVER_SOURCES ${LIBVNCSERVER_DIR}/tightvnc-filetransfer/rfbtightserver.c ${LIBVNCSERVER_DIR}/tightvnc-filetransfer/handlefiletransferrequest.c ${LIBVNCSERVER_DIR}/tightvnc-filetransfer/filetransfermsg.c ${LIBVNCSERVER_DIR}/tightvnc-filetransfer/filelistinfo.c ) endif(WITH_THREADS AND WITH_TIGHTVNC_FILETRANSFER AND CMAKE_USE_PTHREADS_INIT) if(LIBVNCSERVER_WITH_WEBSOCKETS) add_definitions(-DLIBVNCSERVER_WITH_WEBSOCKETS) list(APPEND LIBVNCSERVER_SOURCES ${LIBVNCSERVER_DIR}/websockets.c ${LIBVNCSERVER_DIR}/ws_decode.c ${COMMON_DIR}/base64.c ) endif(LIBVNCSERVER_WITH_WEBSOCKETS) add_library(vncclient ${LIBVNCCLIENT_SOURCES}) add_library(vncserver ${LIBVNCSERVER_SOURCES}) if(WIN32) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ws2_32) if(WITH_TIGHTVNC_FILETRANSFER) add_definitions(-D_WIN32_WINNT=0x0600) endif(WITH_TIGHTVNC_FILETRANSFER) endif(WIN32) target_link_libraries(vncclient ${ADDITIONAL_LIBS} ${ZLIB_LIBRARIES} ${LZO_LIBRARIES} ${JPEG_LIBRARIES} ${CRYPTO_LIBRARIES} ${GNUTLS_LIBRARIES} ${OPENSSL_LIBRARIES} ) target_link_libraries(vncserver ${ADDITIONAL_LIBS} ${ZLIB_LIBRARIES} ${LZO_LIBRARIES} ${JPEG_LIBRARIES} ${PNG_LIBRARIES} ${CRYPTO_LIBRARIES} ${GNUTLS_LIBRARIES} ${OPENSSL_LIBRARIES} ) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/rfb/rfbconfig.h.cmakein ${CMAKE_CURRENT_BINARY_DIR}/include/rfb/rfbconfig.h) SET_TARGET_PROPERTIES(vncclient vncserver PROPERTIES SOVERSION "${VERSION_SO}" VERSION "${LibVNCServer_VERSION}" C_STANDARD 90 ) # EXAMPLES set(LIBVNCSERVER_EXAMPLES backchannel camera cursors colourmaptest example fontsel pnmshow pnmshow24 regiontest repeater rotate simple simple15 storepasswd vncev multicast ) if(WITH_THREADS AND (CMAKE_USE_PTHREADS_INIT OR CMAKE_USE_WIN32_THREADS_INIT)) list(APPEND LIBVNCSERVER_EXAMPLES blooptest) endif(WITH_THREADS AND (CMAKE_USE_PTHREADS_INIT OR CMAKE_USE_WIN32_THREADS_INIT)) if(WITH_THREADS AND WITH_TIGHTVNC_FILETRANSFER AND CMAKE_USE_PTHREADS_INIT) list(APPEND LIBVNCSERVER_EXAMPLES filetransfer) endif(WITH_THREADS AND WITH_TIGHTVNC_FILETRANSFER AND CMAKE_USE_PTHREADS_INIT) if(ANDROID) list(APPEND LIBVNCSERVER_EXAMPLES androidvncserver) endif(ANDROID) if(X11_xcb_FOUND AND X11_xcb_xtest_FOUND AND X11_xcb_keysyms_FOUND) list(APPEND LIBVNCSERVER_EXAMPLES x11) else() # clear NOTFOUND unset(X11_xcb_LIB CACHE) unset(X11_xcb_xtest_LIB CACHE) unset(X11_xcb_keysyms_LIB CACHE) endif(X11_xcb_FOUND AND X11_xcb_xtest_FOUND AND X11_xcb_keysyms_FOUND) set(LIBVNCCLIENT_EXAMPLES backchannel ppmtest ) if(SDL2_FOUND) include_directories(${SDL2_INCLUDE_DIR}) list(APPEND LIBVNCCLIENT_EXAMPLES SDLvncviewer) endif(SDL2_FOUND) if(GTK2_FOUND) include_directories(${GTK2_INCLUDE_DIRS}) list(APPEND LIBVNCCLIENT_EXAMPLES gtkvncviewer) endif(GTK2_FOUND) if(WITH_LIBSSHTUNNEL AND LIBSSHTUNNEL_LIBRARY AND LIBSSHTUNNEL_INCLUDE_DIR) message(STATUS "Building with libsshtunnel: ${LIBSSHTUNNEL_LIBRARY} and ${LIBSSHTUNNEL_INCLUDE_DIR}") include_directories(${LIBSSHTUNNEL_INCLUDE_DIR}) list(APPEND LIBVNCCLIENT_EXAMPLES sshtunnel) endif() if(FFMPEG_FOUND) include_directories(${FFMPEG_INCLUDE_DIRS}) list(APPEND LIBVNCCLIENT_EXAMPLES vnc2mpg) endif(FFMPEG_FOUND) if(WITH_EXAMPLES) foreach(e ${LIBVNCSERVER_EXAMPLES}) add_executable(examples_${e} ${LIBVNCSRVEXAMPLE_DIR}/${e}.c) set_target_properties(examples_${e} PROPERTIES OUTPUT_NAME ${e}) set_target_properties(examples_${e} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/server) target_link_libraries(examples_${e} vncserver ${CMAKE_THREAD_LIBS_INIT} ${X11_xcb_LIB} ${X11_xcb_xtest_LIB} ${X11_xcb_keysyms_LIB}) endforeach(e ${LIBVNCSERVER_EXAMPLES}) foreach(e ${LIBVNCCLIENT_EXAMPLES}) add_executable(client_examples_${e} ${LIBVNCCLIEXAMPLE_DIR}/${e}.c ${LIBVNCCLIEXAMPLE_DIR}/${${e}_EXTRA_SOURCES} ) set_target_properties(client_examples_${e} PROPERTIES OUTPUT_NAME ${e}) set_target_properties(client_examples_${e} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/client) target_link_libraries(client_examples_${e} vncclient ${CMAKE_THREAD_LIBS_INIT} ${SDL2_LIBRARY} ${GTK2_LIBRARIES} ${FFMPEG_LIBRARIES} ${LIBSSHTUNNEL_LIBRARY}) endforeach(e ${LIBVNCCLIENT_EXAMPLES}) #This example must have its own building instructions, #apart from the other examples because it is written in #C++, so it has a distinct file extension and depends on #a C++ compiler if(Qt5Widgets_FOUND AND WITH_QT AND DEFINED CMAKE_CXX_COMPILER) add_executable(client_examples_qt5client ${LIBVNCCLIEXAMPLE_DIR}/qt5client.cpp ${LIBVNCCLIEXAMPLE_DIR}/${qt5client_EXTRA_SOURCES}) set_target_properties(client_examples_qt5client PROPERTIES OUTPUT_NAME qt5client) set_target_properties(client_examples_qt5client PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/client) target_link_libraries(client_examples_qt5client vncclient ${CMAKE_THREAD_LIBS_INIT} ${Qt5Widgets_LIBRARIES}) endif(Qt5Widgets_FOUND AND WITH_QT AND DEFINED CMAKE_CXX_COMPILER) endif(WITH_EXAMPLES) # # them tests # if(WITH_TESTS) # First fuzzing if(DEFINED ENV{LIB_FUZZING_ENGINE}) add_executable(fuzz_server ${TESTS_DIR}/fuzz_server.c) target_link_libraries(fuzz_server vncserver ${CMAKE_THREAD_LIBS_INIT} $ENV{LIB_FUZZING_ENGINE}) endif() if(UNIX) set(ADDITIONAL_TEST_LIBS m) endif(UNIX) set(SIMPLETESTS cargstest copyrecttest ) if(WITH_THREADS AND (CMAKE_USE_PTHREADS_INIT OR CMAKE_USE_WIN32_THREADS_INIT)) set(SIMPLETESTS ${SIMPLETESTS} encodingstest ) endif(WITH_THREADS AND (CMAKE_USE_PTHREADS_INIT OR CMAKE_USE_WIN32_THREADS_INIT)) foreach(t ${SIMPLETESTS}) add_executable(test_${t} ${TESTS_DIR}/${t}.c) set_target_properties(test_${t} PROPERTIES OUTPUT_NAME ${t}) set_target_properties(test_${t} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test) target_link_libraries(test_${t} vncserver vncclient ${ADDITIONAL_TEST_LIBS}) endforeach(t ${SIMPLETESTS}) if(WITH_JPEG AND FOUND_LIBJPEG_TURBO) add_executable(test_tjunittest ${TESTS_DIR}/tjunittest.c ${TESTS_DIR}/tjutil.c ${TESTS_DIR}/tjutil.h ${COMMON_DIR}/turbojpeg.c ${COMMON_DIR}/turbojpeg.h ) set_target_properties(test_tjunittest PROPERTIES OUTPUT_NAME tjunittest) set_target_properties(test_tjunittest PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test) target_link_libraries(test_tjunittest vncserver vncclient ${ADDITIONAL_TEST_LIBS}) add_executable(test_tjbench ${TESTS_DIR}/tjbench.c ${TESTS_DIR}/tjutil.c ${TESTS_DIR}/tjutil.h ${TESTS_DIR}/bmp.c ${TESTS_DIR}/bmp.h ${COMMON_DIR}/turbojpeg.c ${COMMON_DIR}/turbojpeg.h ) set_target_properties(test_tjbench PROPERTIES OUTPUT_NAME tjbench) set_target_properties(test_tjbench PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test) target_link_libraries(test_tjbench vncserver vncclient ${ADDITIONAL_TEST_LIBS}) endif(WITH_JPEG AND FOUND_LIBJPEG_TURBO) if(LIBVNCSERVER_WITH_WEBSOCKETS) add_executable(test_wstest ${TESTS_DIR}/wstest.c ${TESTS_DIR}/wstestdata.inc ) set_target_properties(test_wstest PROPERTIES OUTPUT_NAME wstest) set_target_properties(test_wstest PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test) target_link_libraries(test_wstest vncserver vncclient ${ADDITIONAL_TEST_LIBS}) endif(LIBVNCSERVER_WITH_WEBSOCKETS) add_test(NAME cargs COMMAND test_cargstest) if(UNIX) add_test(NAME includetest COMMAND ${TESTS_DIR}/includetest.sh ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_MAKE_PROGRAM}) endif(UNIX) if(WITH_JPEG AND FOUND_LIBJPEG_TURBO) add_test(NAME turbojpeg COMMAND test_tjunittest) endif(WITH_JPEG AND FOUND_LIBJPEG_TURBO) if(LIBVNCSERVER_WITH_WEBSOCKETS) add_test(NAME wstest COMMAND test_wstest) endif(LIBVNCSERVER_WITH_WEBSOCKETS) endif(WITH_TESTS) # # this gets the libraries needed by TARGET in "-libx -liby ..." form # function(get_link_libraries OUT TARGET) set(RESULT "") get_target_property(LIBRARIES ${TARGET} INTERFACE_LINK_LIBRARIES) foreach(LIB ${LIBRARIES}) if("${LIB}" MATCHES ".*NOTFOUND.*") continue() endif() string(REGEX REPLACE "^.*/lib" "" LIB ${LIB}) # remove leading path and "lib" name prefix string(REGEX REPLACE "-l" "" LIB ${LIB}) # remove leading -l string(REGEX REPLACE "\\.so$" "" LIB ${LIB}) # remove trailing .so list(APPEND RESULT "-l${LIB}") endforeach() list(REMOVE_DUPLICATES RESULT) string(CONCAT RESULT ${RESULT}) # back to string if(RESULT) string(REPLACE "-l" " -l" RESULT ${RESULT}) # re-add separators endif(RESULT) set(${OUT} ${RESULT} PARENT_SCOPE) endfunction() get_link_libraries(PRIVATE_LIBS vncserver) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/libvncserver/libvncserver.pc.cmakein ${CMAKE_CURRENT_BINARY_DIR}/libvncserver.pc @ONLY) get_link_libraries(PRIVATE_LIBS vncclient) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/libvncclient/libvncclient.pc.cmakein ${CMAKE_CURRENT_BINARY_DIR}/libvncclient.pc @ONLY) if(LIBVNCSERVER_INSTALL) set(INSTALL_HEADER_FILES include/rfb/keysym.h include/rfb/threading.h include/rfb/rfb.h include/rfb/rfbclient.h ${CMAKE_CURRENT_BINARY_DIR}/include/rfb/rfbconfig.h include/rfb/rfbproto.h include/rfb/rfbregion.h ) set_property(TARGET vncclient PROPERTY PUBLIC_HEADER ${INSTALL_HEADER_FILES}) set_property(TARGET vncserver PROPERTY PUBLIC_HEADER ${INSTALL_HEADER_FILES}) if(WIN32) INSTALL(TARGETS vncclient vncserver EXPORT LibVNCServerTargets RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_BINDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rfb INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) else() INSTALL(TARGETS vncclient vncserver EXPORT LibVNCServerTargets RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rfb INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) endif() # Install cmake configure files install(EXPORT LibVNCServerTargets NAMESPACE "LibVNCServer::" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/LibVNCServer" ) include(CMakePackageConfigHelpers) configure_package_config_file( ${PROJECT_SOURCE_DIR}/cmake/Modules/LibVNCServerConfig.cmake.in ${CMAKE_BINARY_DIR}/LibVNCServerConfig.cmake INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/LibVNCServer" ) # Install cmake version configure file write_basic_package_version_file( "${CMAKE_BINARY_DIR}/LibVNCServerConfigVersion.cmake" VERSION ${PARA_VERSION} COMPATIBILITY AnyNewerVersion ) install(FILES "${CMAKE_BINARY_DIR}/LibVNCServerConfigVersion.cmake" "${CMAKE_BINARY_DIR}/LibVNCServerConfig.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/LibVNCServer" ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libvncserver.pc ${CMAKE_CURRENT_BINARY_DIR}/libvncclient.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) endif() ```

That would allow it to compile as it did originally and also hanmdle the ESP32 ends of things.

kdschlosser commented 3 weeks ago

That's basically the same ;-) The RFB protocol is pull-based, so you described it correctly.

This is not correct. In later version of the RFB protocol it can be set so it's push based. the client would not send any requests for a frame buffer update. The server would push the changes when the changes occur. This is an optional feature to use. When working with a GUI framework like I am it is optimal to keep the amount of data that is being sent to as minimal as possible so having the server only push data when changes have occurred is the most ideal thing to do.

Depends on the request. A client can request a full update or tell the server "gimme just the changed parts". so the client keeps an active copy of the full size of the frame buffer?

Because we are dealing with memory constrained devices the need to create a full sized display buffer on the MCU in a lot of cases is not possible. This is where the server pushing the sections that have updated comes into play. There is no actual display sized frame buffer in these cases. There is only a partial and that partial is typically 1/10th the size of a display sized frame buffer. the graphics framework is able to track where objects would reside on a full sized frame buffer so it known the bounding rect for areas that need updating and to stay within the memory constraimnts of the MCU only the areas that need updating are what actually get rendered to the partial frame buffer. If there are multiple areas that need to be rendered then it renders one area and calls a flush callback so that area is able to be sent to the display or in this case it would be passed to the RFB server to be transferred over the network connection to the client. When the flush function returns the buffer gets filled with the next area to be sent.

This process gets a wee bit more complex because of some MCU's having direct memory access for it's peripherals. What that means is a buffer can be sent without blocking.. It gets sent without used the processor at all. To make use of this feature you would have 2 partial sized frame buffers so while one is sending the other can be rendered to. Now there are mechanics in place that keep things in sync and this involves a callback function that gets passed to the DMA controller so a notification can be passed when the DMA transfer has finished. in that callback is where the GUI framework is told that it can pass the just rendered buffer to the flush function. if it is not told it can do that the whole program stalls.

This is where things get a bit hard to deal with because on order to attach that callback for the buffer transfer being finished it has to be done at the same time the buffer gets passed to be written. This is something that is handled internally in libvncserver. The encoding of the packet gets done and then it makes the call to send the data. If the encoding portions of the code were publicly exposed then we could simply encode the packet and then I could take care of calling the function to send the data and passing the callback at the same time. Right now the encoding portions of the server are not publicly exposed and that is something that would improve the performance by a very large amount if it was. For the time being I just want to see if I can get it up and running even if it's done at a lower speed. If I can then we can discuss making the packet encoding public.

It also appears that by default the library wants to create it's own frame buffer that is the size of the display.

kdschlosser commented 3 weeks ago

Question for ya.

Would you entertain the idea of separating the socket code from the server code? I ask this because I have some people that are interested in running RFB over a serial blueteeth connection. The RFB protocol specification doesn't lock the use of the protocol to any one kind of connection type. so long as data is able to be sent and received then the protocol should work over that connection. Right now the library is locked to using a socket connection and I believe that separating the socket code from both the client and server code would align more with the specification. I am not saying to remove the socket code, just to move it so it is not a requirement to use it. function pointers to callback functions for sending and receiving can be used by the server and client code this way all that has to be done is assigning functions to those pointers so the server and client code would be able to call them. Probably would need some kind of an initialization function pointer as well.

kdschlosser commented 3 weeks ago

Hey I wanted to also let you know that if you want I can write a CPython binding to your library. It would automatically generate the Python code form the header files and it would also compile the library for Linux, macOS and Windows. Then people could use it with their favorite Python GUI framework. This would allow them to provide a way to remotely connect to a specific application that they have written... by using the HTTPd code in your library it could be used as a way for the user to make their application available on the web without the need to port their application code. That could be a really useful thing to be able to do. I know that porting an application for the sole purpose of it being accessible on the web is a royal pain in the ass to do.

bk138 commented 3 weeks ago

You're correct. server does only depend on common, client depends on common. no client<->server dependency. The configure logic is simply not (yet) in CMake :-)

I added the logic tho I have not yet tested it. You are more then welcome to give it a go if you want or you can look at the code to see what you think before I submit a PR for it.

Here are the branches I have for the things I have changed, some I have submitted a PR for and others I have not.

Please do a PR for each change you want to bring in - that makes collaboration and review way easier :-)

bk138 commented 3 weeks ago

Question for ya.

Would you entertain the idea of separating the socket code from the server code?

Would be open for it. This was raised before #432 and handled partially #477 #234. I have to be honest in that I lack the time-for-review and testing capacity for big changes touching the fundamentals - a focused PR that only changes one thing is way easier to review and more likely to get in.

bk138 commented 3 weeks ago

Hey I wanted to also let you know that if you want I can write a CPython binding to your library.

That sounds like a good fit for an external project that I'd happily link from the main README.

kdschlosser commented 3 weeks ago

Please do a PR for each change you want to bring in - that makes collaboration and review way easier :-)

This is why you see a branch made for each fix that is being made. They will all be made as separate PR's