Colored lobby card for the 1931 Universal horror movie Frankenstein, featuring Boris Karloff, but with 'Starring Xorg Server as the monster' written instead

I was reading about C++ recently, and that made me remember about an attempt I made at putting C++ into Xorg Server. I remember somewhat succeeding a couple years ago, then shelving the project and doing nothing else with it. So I went searching through my private Github repositories, and found the project I called AxeOrg. Then I downloaded the project and started retracing my steps from the beginning.

It's a common misconception that you can just take C code and compile it with a C++ compiler. Given the name you'd think that C++ is a superset of C, but it's not. C and C++ diverged from an earlier non-standardized version of C, known as Classic C. So standard C is not compatible with C++, and you cannot just take C code and expect it to be able to compile with a C++ compiler.

So let me take you through the steps I went through in order to shove C++ into this thing. To start out, the first thing to do was to add C++ to meson. It was easy enough. I just needed to add 'cpp' and 'cpp_std=c++23' to the project function in the meson.build file.

project('xserver', 'cpp', 'c',
        default_options: [
            'buildtype=debugoptimized',
            'cpp_std=c++23',
        ],
        version: '21.1.8',
        meson_version: '>= 0.47.0',
)

Then I ran:

meson setup builddir \
    --prefix=/usr \
    --localstatedir=/var \
    -Dsuid_wrapper=true \
    -Dxkb_output_dir=/var/lib/xkb \
    -Dmodule_dir=/usr/lib/xorg/modules

As I remember when I did this a couple years ago, all I needed to do was change 'c' to 'cpp', and then running meson setup just worked. But this second time around it didn't. I'm suspecting what I did was compile it the first time with C files, then maybe it somehow used the already built object files? I have no idea. I know for certain it compiled because otherwise I wouldn't have committed it years ago in a state that didn't compile, at least on my machine at the time. Strange.

But then once Meson configured everything, I ran this ninja command to try to compile the project:

ninja -C builddir -j4

So everything compiles just fine, but what's the point of the C++ compiler, if I don't actually compile any C++ code? So I had to change something from C to C++, and I changed the main entrypoint dix/main.c to dix/main.cpp. Then I went into dix/meson.build and changed main.c there to main.cpp. Then I reran the meson setup, and then ran the ninja command to compile it, and obviously it didn't compile. It threw a bunch of errors. So I did what I always do when I'm messing around with C++, go through the errors one at a time and eventually I'll run out of errors. So the first error in the list was:

In file included from ../dix/main.cpp:101:
../include/servermd.h:51:2: error: #error Drivers must include xorg-server.h before any other xserver headers
   51 | #error Drivers must include xorg-server.h before any other xserver headers

That's something to do with servermd.h. I don't know what that's for so, why just comment it out? :^)

-#include "servermd.h"
+// #include "servermd.h"

There we go, good enough. Onto the next error.

So the next series of errors I get go like this:

In file included from ../include/misc.h:119,
                 from ../include/screenint.h:50,
                 from ../include/scrnintstr.h:50,
                 from ../dix/main.cpp:85:
../include/os.h:570:22: error: conflicting declaration of 'void* xreallocarray(void*, size_t, size_t)' with 'C' linkage
  570 | #define reallocarray xreallocarray

That has something to do with these lines in include/os.h.

#ifndef HAVE_REALLOCARRAY
#define reallocarray xreallocarray
extern _X_EXPORT void *
reallocarray(void *optr, size_t nmemb, size_t size);
#endif

Hmm, HAVE_REALLOCARRAY? More like, I really don't care. You're getting commented out.

// #ifndef HAVE_REALLOCARRAY
// #define reallocarray xreallocarray
// extern _X_EXPORT void *
// reallocarray(void *optr, size_t nmemb, size_t size);
// #endif

Boom, problem solved.

Alright, there are a bunch more errors with the included files. I'm going to get to those in a second, but there's one last error that's an easy fix in dix/main.cpp.

../dix/main.cpp: In function 'int dix_main(int, char**, char**)':
../dix/main.cpp:156:34: error: invalid conversion from 'void*' to 'ClientPtr' {aka '_Client*'} [-fpermissive]
  156 |             serverClient = calloc(sizeof(ClientRec), 1);

This is C++ complaining about the return value of calloc. C++ is stricter than C with respect to implicit conversions. So all I need to do is add a typecast onto that to explicitly convert the return value.

-serverClient = calloc(sizeof(ClientRec), 1);
+serverClient = (ClientPtr)calloc(sizeof(ClientRec), 1);

Alright, so on to the big elephant in the room. 

In file included from ../dix/main.cpp:87:
../include/scrnintstr.h:68:16: error: expected identifier before ';' token
   68 |     short class;
      |                ^
In file included from ../include/windowstr.h:51,
                 from ../dix/main.cpp:90:
../include/pixmapstr.h:59:24: error: expected identifier before ';' token
   59 |     unsigned char class;        /* specific to type */
      |                        ^
../include/pixmapstr.h:59:14: error: multiple types in one declaration
   59 |     unsigned char class;        /* specific to type */
      |              ^~~~
../include/pixmapstr.h:59:24: error: declaration does not declare anything [-fpermissive]
   59 |     unsigned char class;        /* specific to type */
      |                        ^
In file included from ../dix/main.cpp:96:
../include/colormapst.h:91:16: error: expected identifier before ';' token
   91 |     short class;                /* PseudoColor or DirectColor */
      |                ^
In file included from ../dix/main.cpp:101:
/usr/include/X11/fonts/fontstruct.h:156:18: error: expected unqualified-id before 'private'
  156 |     void        *private;
      |                  ^~~~~~~
/usr/include/X11/fonts/fontstruct.h:195:36: error: expected ',' or '...' before 'private'
  195 |                              void *private);
      |                                    ^~~~~~~
/usr/include/X11/fonts/fontstruct.h:223:34: error: expected ',' or '...' before 'private'
  223 |                            void *private);
      |                                  ^~~~~~~
In file included from ../include/exevents.h:34,
                 from ../dix/main.cpp:111:
../include/inputstr.h:562:15: error: expected unqualified-id before 'public'
  562 |     DeviceRec public;

That's just the tip of the iceberg. You see all those complaints about class, private, and public? These are keywords in C++ but not C, and they are scattered all over the place in the Xorg Server codebase. C can go about just fine because it doesn't care about public or private, that's just C++ stuff. As far as I know, there's no easy way to incorporate C code that uses C++ keywords into a C++ project. Maybe I could have tried macros, but I do recall that when I was attempting this years ago, that I tried to do something like s/public/XPublic/g, but it didn't work. 

So I painstakingly went through each and every file that had C++ keywords, and renamed them so the compiler would not complain. public became XPublic, private became XPrivate, and class became XClass. I spent several hours going through each and every occurrence of a C++ keyword and replacing it with a non keyword.

In total, there were 346 occurrences of C++ keywords around the code base, and I replaced every single one of them by hand. I also had to copy an include called fontstruct.h to the project, and edit the private keywords there to be XPrivate.

Keep in mind, that this was before chatbots were wide spread. This seems like the type of task you could give to Codex on YOLO mode, come back an hour later, and it would probably have successfully ironed out all the C++ errors for me. But I didn't have that, so I did it myself.

With that out of the way though, the final thing to do is to wrap all the includes in dix/main.cpp with a giant extern "C" block so the linker can properly link things together. The C++ compiler does name mangling in order to help the linker distinguish between functions with the same name in different parts of the codebase. But this doesn't work with C code, so I have to wrap it.

extern "C" {

#ifdef HAVE_DIX_CONFIG_H
#include 
#include 
#endif

#include 
#include 
#include "client.h"
/*
And so on...
*/
}

For the final touch, I quickly went into the `xf86PrintBanner` function and changed the name. And I also put some C++ code in main.cpp just to demonstrate some actual C++. I just imported iostream and did a quick cout with this:

#include 

...

std::cout << "AxeOrg Server starting..." << std::endl;

And after all that, it compiles and runs.

# ./builddir/hw/xfree86/Xorg -version
AxeOrg Server starting...

AxeOrg Server 1.21.1.8
X Protocol Version 11, Revision 0
Current Operating System: Linux d8f793098e00 6.5.0-28-generic #29~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Apr  4 14:39:20 UTC 2 x86_64
Kernel command line: BOOT_IMAGE=/boot/vmlinuz-6.5.0-28-generic root=UUID=0d60feb7-80fa-4458-87ee-5b4e0a9d7dd2 ro quiet splash vt.handoff=7
 
Current version of pixman: 0.42.2
        Before reporting problems, check http://wiki.x.org
        to make sure that you have the latest version.

And you're probably wondering, does it work for actually displaying stuff too? Of course not. It just segfaults. But hey, it's a pretty cool banner printer. I tried wrapping every C file with extern "C" (with ifdef __cplusplus macros of course) in the hopes that that might help but no.

I started to spend another few hours trying to debug the segfaults when I thought to myself, what the fuck am I doing? The X window system is over 40 years old, with it first being announced in 1984. The current X implementation that is Xorg Server, is the accumulation of 40 years of technical debt. It is almost certainly dead at this point. Killed by the people who were working on it 20 years ago, in order to replace it with their less capable display protocol called Wayland. That's not just me being mean, that's what they intentionally did in their design by pushing all the functionality out to be implemented by the desktop environments.

And also on using C++ on this half dead display server. C++ is just a year younger than the X window system, and it has just as much legacy baggage as X. I mean what is there for me to say about C++ that hasn't been said by a million other people before? It's awful really. That typecast I did up there? Apparently that's not the recommended way to do typecasting. That's an implicit, C style typecast. But today it's recommended to do explicit typecasts with static_cast most of the time, otherwise I might do some unsafe type conversion which could lead to undefined behavior. C++ is chock full of footguns like these that you just have to know about, and the C++ committee can't get rid of them because they need to keep backwards compatibility.

Today I wouldn't choose to use C++ unless there's really no other good choice, like for example if I was making a game in Unreal, using Qt, or doing something else where C++ is deeply embedded. I really hope Carbon Lang succeeds. It's supposed to be a successor language to C++, with interoperability and memory safety. But until that's ready, and also per their recommendations, I'll use other languages that are better suited for whatever task I'm trying to do. With all the hassle it is to put C++ into this C project, it might be more worth while to just rewrite it in Rust. That is if Xorg Server is even worth rewriting.

So I've been defiling the zombie corpse of Xorg Server, with this bastard language that is C++. Then I realized what I was doing, and shoved it into my morgue of private Github repositories where it lie unseen and forgotten about… Until today. I don't know if I'll release the full source to the public — it's probably best to just burn it in a fire and forget it ever happened. But regardless, may God have mercy on my soul.