What is the best practice for a daemon application running as root that needs access to the GUI? I'm porting over a C++/Qt application from Windows. On Windows, the program runs as a service. It's discouraged from programs running as a service to also interact with the user and display GUI elements.

Therefore, my program has two parts. The first part runs as a service daemon and plays audio in the background. The program starts up when the computer starts up. The second program, which starts up when any user logs in (Via Start Menu->All Users->Startup), communicates via local socket with the service daemon to display a GUI pop-up when the audio is playing.

Moving over to MacOS, what is the best practice to utilize the same program?

Should I merge both programs into one and run it as root via launchd and have the one daemon display GUI as well as play audio?

Or should I rather keep it as two programs like it is now - root daemon playing audio and find a way that the second program can automatically execute when each user logs in?
On macOS, you should avoid running any process as root. Apple would likely suggest keeping this type of application in two parts. Each part should be sandboxed with appropriate entitlements.

Your background computer wide process will not be able to access the graphical interface. You will need a second process running within the user's session to provide an interface to the user.

Use launchd to launch your computer wide process on-demand; see XPC services below. This way, while nothing is playing, you avoid consuming any resources.

XPC Services

For macOS, Apple want you to communicate using the XPC services architecture. This approach uses Mach ports at the low level, but your processes do not need to be too concerned about that detail.