On Zorin OS, GNOME is the default desktop interface and GDM is the default greeter (login screen). It seems that GDM will always attach to :0 which is display 0, thus when I had my x11vnc service running, it will always work.

But in my new installation, Debian 13 with LightDM, each screen (desktop and login) is attached to different display. If a user is logged in and then he performs a “Lock Screen”, there will be two instances, which causes the current connection to black screen as it’s attached to the desktop screen.

I had to use AI to help me write a script and then perfected by myself which will loop and check for displays. It will always attach to :1 (i.e. login screen) if it’s found first and attach to :0 if there is no :1.

Below is the snippet used:

#!/bin/bash

# File to store the previous Xorg process state
STATE_FILE="/tmp/x11vnc_xorg_state"
VNC_PASS=/home/jerry/.vnc/passwd

# Function to find the active display and its Xauthority file
find_active_display() {
    # Check for lock screen (:1)
    if [ -f /var/run/lightdm/root/:1 ]; then
        echo ":1"
        echo "/var/run/lightdm/root/:1"
    # Check for greeter (:0, no user logged in)
    elif [ -f /var/run/lightdm/root/:0 ]; then
        echo ":0"
        echo "/var/run/lightdm/root/:0"
    # Check for user session (:0)
    elif [ -f /run/user/*/xauth* ]; then
        echo ":0"
        echo "$(find /run/user -name 'xauth*' -type f | head -n 1)"
    else
        echo "No Xauthority file found." >&2
        return 1
    fi
}

# Function to get current Xorg processes
get_xorg_state() {
    #ps aux | grep '[X]org' | awk '{print $12 " " $16}' | sort | sha1sum | awk '{print $1}'
    ls /var/run/lightdm/root | sha1sum | awk '{print $1}'
}

has_x11vnc_running() {
    if ps aux | grep -P "\bx11vnc -display\b" &>/dev/null; then
      true
    else
      false
    fi
}

stop_x11vnc() {
    pkill -f "x11vnc -display" 2>/dev/null
    # Wait for all x11vnc processes to terminate
    while pgrep -f "x11vnc -display" >/dev/null; do
        sleep 0.1
    done
}

start_x11vnc() {
    x11vnc -display "$DISPLAY_NUM" -auth guess -rfbauth $VNC_PASS -noxdamage -forever -shared -rfbport 5900 -bg -o /var/log/x11vnc.log &
    #x11vnc -display "$DISPLAY_NUM" -auth guess -rfbauth /etc/x11vnc.passwd -noxdamage -forever -shared -rfbport 5900 -bg &
    sleep 2
}

# Store initial Xorg state
get_xorg_state > "$STATE_FILE"

# Main loop to monitor Xorg processes
while :; do
    # Get current display and Xauthority
    read -r DISPLAY_NUM XAUTH < <(find_active_display)
    if [ $? -ne 0 ]; then
        echo "No display found, retrying in 5 seconds..." >&2
        sleep 5
        continue
    fi

    ## Start x11vnc in background
    if ! has_x11vnc_running; then
        start_x11vnc
    fi


    # Monitor Xorg processes for changes
    while :; do
        sleep 2
        CURRENT_STATE=$(get_xorg_state)
        PREV_STATE=$(cat "$STATE_FILE")
        
        if [ "$CURRENT_STATE" != "$PREV_STATE" ]; then
            echo "Xorg process change detected, restarting x11vnc..." >&2
            stop_x11vnc
            echo "$CURRENT_STATE" > "$STATE_FILE"
            break
        else
            break
        fi
    done
done

UPDATE: This version uses inotifywait (I really dislike polling methods).

#!/bin/bash

VNC_PASS=/home/jerry/.vnc/passwd

# Function to find the active display and its Xauthority file
find_active_display() {
    # Check for lock screen (:1)
    if [ -f /var/run/lightdm/root/:1 ]; then
        echo ":1"
        echo "/var/run/lightdm/root/:1"
    # Check for greeter (:0, no user logged in)
    elif [ -f /var/run/lightdm/root/:0 ]; then
        echo ":0"
        echo "/var/run/lightdm/root/:0"
    # Check for user session (:0)
    elif [ -f /run/user/*/xauth* ]; then
        echo ":0"
        echo "$(find /run/user -name 'xauth*' -type f | head -n 1)"
    else
        echo "No Xauthority file found." >&2
        return 1
    fi
}

has_x11vnc_running() {
    if ps aux | grep -P "\bx11vnc -display\b" &>/dev/null; then
      true
    else
      false
    fi
}

stop_x11vnc() {
    pkill -9 -f "x11vnc -display" 2>/dev/null
    # Wait for all x11vnc processes to terminate
    while pgrep -f "x11vnc -display" >/dev/null; do
        sleep 1
    done
}

start_x11vnc() {
    # Get current display and Xauthority
    read -r DISPLAY_NUM XAUTH < <(find_active_display)
    if [ $? -ne 0 ]; then
        echo "No display found, retrying in 5 seconds..." >&2
        sleep 5
    fi

    x11vnc -display "$DISPLAY_NUM" -auth guess -rfbauth $VNC_PASS -noxdamage -forever -shared -rfbport 5900 -bg -o /var/log/x11vnc.log &
}

start_x11vnc

inotifywait -m /var/run/lightdm/root -e create -e delete -e modify |
    while read -r directory events filename; do
        echo "Detected change in $directory: $filename ($events)" >&2
        if ! has_x11vnc_running; then
            start_x11vnc
        else
            stop_x11vnc
            start_x11vnc
        fi
    done