diff --git a/tablet-vnc.sh b/tablet-vnc.sh new file mode 100755 index 0000000..f6c783b --- /dev/null +++ b/tablet-vnc.sh @@ -0,0 +1,254 @@ +#!/bin/bash + +# --- +# A script to create a virtual second screen on an unused display output. +# Ideal for use with an Android tablet and a VNC client. +# Based on the tutorial from: https://github.com/santiagofdezg/linux-extend-screen +# --- + +# --- Configuration --- +# You can change the default values here or pass them as arguments. +DEFAULT_WIDTH=1920 +DEFAULT_HEIGHT=1200 +DEFAULT_REFRESH=60 +DEFAULT_UNUSED_DISPLAY="DisplayPort-0" +DEFAULT_POSITION="left-of" # Options: left-of, right-of, above, below + +# Temporary file to store the state of the created mode +STATE_FILE="/tmp/extended_screen_state" +PASS_FILE="/tmp/vnc_pass_$$" + +# --- Functions --- + +# Function to clean up temporary files and screen configuration on exit/interruption +cleanup() { + echo "" # Add a newline for cleaner output after ^C + echo "--- Interrupted: Cleaning up virtual screen and temporary files ---" + + # Clean up the password file + rm -f "$PASS_FILE" + + # Clean up the screen configuration if the state file exists + if [ -f "$STATE_FILE" ]; then + read -r UNUSED_DISPLAY MODE_NAME <"$STATE_FILE" + + if [ -n "$UNUSED_DISPLAY" ] && [ -n "$MODE_NAME" ]; then + echo "Disabling display '$UNUSED_DISPLAY' and removing mode '$MODE_NAME'..." + # Turn off the display and remove the mode, redirecting output to prevent error spam + xrandr --output "$UNUSED_DISPLAY" --off >/dev/null 2>&1 + xrandr --delmode "$UNUSED_DISPLAY" "$MODE_NAME" >/dev/null 2>&1 + # Check if mode is still in use by another monitor before removing globally + if ! xrandr | grep -B1 "$MODE_NAME" | grep -q " connected"; then + xrandr --rmmode "$MODE_NAME" >/dev/null 2>&1 + fi + fi + rm -f "$STATE_FILE" + fi + echo "Cleanup complete." + # Exit the script to prevent it from continuing after cleanup + exit 0 +} + +# Trap interruption signals (like Ctrl+C) to run the cleanup function +trap cleanup HUP INT QUIT TERM + +# Function to display how to use the script +usage() { + echo "Usage: $0 {start|stop} [options]" + echo "" + echo "Actions:" + echo " start : Sets up and enables the virtual screen." + echo " stop : Disables and removes the virtual screen configuration." + echo "" + echo "Options for 'start':" + echo " --width : Horizontal resolution (Default: $DEFAULT_WIDTH)" + echo " --height : Vertical resolution (Default: $DEFAULT_HEIGHT)" + echo " --refresh : Refresh rate (Default: $DEFAULT_REFRESH)" + echo " --display : Unused display output (e.g., HDMI-1) (Default: $DEFAULT_UNUSED_DISPLAY)" + echo " --position : Position relative to primary screen (Default: $DEFAULT_POSITION)" + echo " --password : (Optional) Set a password for the VNC connection." + echo " -h, --help : Show this help message." + echo "" + echo "Example: $0 start --width 1280 --height 800 --display HDMI-1 --password mysecret" + exit 1 +} + +# Function to check for required commands +check_dependencies() { + for cmd in xrandr gtf x11vnc ip hostname; do + if ! command -v "$cmd" &> /dev/null; then + echo "Error: Required command '$cmd' not found." + echo "Please install it to continue. (e.g., 'sudo apt install x11vnc iproute2')" + exit 1 + fi + done +} + +# --- Main Script Logic --- + +# Check for dependencies first +check_dependencies + +ACTION="$1" +shift # Consume the action (start/stop) + +# Handle the action +case "$ACTION" in + start) + # Set default values + WIDTH=$DEFAULT_WIDTH + HEIGHT=$DEFAULT_HEIGHT + REFRESH=$DEFAULT_REFRESH + UNUSED_DISPLAY=$DEFAULT_UNUSED_DISPLAY + POSITION=$DEFAULT_POSITION + PASSWORD="" + + # Parse named arguments + while [ "$#" -gt 0 ]; do + case "$1" in + --width) WIDTH="$2"; shift 2 ;; + --height) HEIGHT="$2"; shift 2 ;; + --refresh) REFRESH="$2"; shift 2 ;; + --display) UNUSED_DISPLAY="$2"; shift 2 ;; + --position) POSITION="$2"; shift 2 ;; + --password) PASSWORD="$2"; shift 2 ;; + -h|--help) usage ;; + *) echo "Unknown option: $1"; usage ;; + esac + done + + # Auto-detect the primary display + PRIMARY_DISPLAY=$(xrandr | grep ' primary ' | awk '{print $1}') + if [ -z "$PRIMARY_DISPLAY" ]; + then + echo "Error: Could not automatically detect the primary display." + exit 1 + fi + + echo "--- Starting Virtual Screen Setup ---" + echo "Primary Display: $PRIMARY_DISPLAY" + echo "Virtual Display: $UNUSED_DISPLAY at ${WIDTH}x${HEIGHT}@${REFRESH}Hz" + echo "Position: $POSITION $PRIMARY_DISPLAY" + + # 1. Generate the modeline + REFRESH_FORMATTED=$(printf "%.2f" "$REFRESH") + MODE_NAME=$(printf "%sx%s_%s" "$WIDTH" "$HEIGHT" "${REFRESH_FORMATTED//./_}") + + # 2. Check if the mode already exists. If not, create it. + if ! xrandr | grep -q "$MODE_NAME"; then + echo "Mode '$MODE_NAME' not found. Creating it..." + FULL_MODELINE=$(gtf "$WIDTH" "$HEIGHT" "$REFRESH" | grep 'Modeline') + if [ -z "$FULL_MODELINE" ]; then + echo "Error: Could not generate a modeline for the given resolution." + exit 1 + fi + MODE_DETAILS=$(echo "$FULL_MODELINE" | awk '{$1=$2=""; print $0}') + + echo "Creating new mode..." + xrandr --newmode "$MODE_NAME" $MODE_DETAILS || { + echo "Failed to create new mode." + exit 1 + } + else + echo "Mode '$MODE_NAME' already exists. Using existing mode." + fi + + # 3. Add the mode to the unused display + echo "Adding mode to display '$UNUSED_DISPLAY'..." + xrandr --addmode "$UNUSED_DISPLAY" "$MODE_NAME" || { + echo "Failed to add mode to display." + exit 1 + } + + # 4. Enable the output and position it + echo "Enabling and positioning display..." + xrandr --output "$UNUSED_DISPLAY" --mode "$MODE_NAME" --"$POSITION" "$PRIMARY_DISPLAY" || { + echo "Failed to enable and position display." + exit 1 + } + + sleep 2 + + # 5. Calculate the VNC clip region dynamically + echo "Calculating VNC clip region..." + VNC_X=0 + VNC_Y=0 + SCREEN_INFO=$(xrandr | grep "$UNUSED_DISPLAY" | grep -o '[0-9]*x[0-9]*+[0-9]*+[0-9]*') + if [[ $SCREEN_INFO =~ \+([0-9]+)\+([0-9]+)$ ]]; then + VNC_X=${BASH_REMATCH[1]} + VNC_Y=${BASH_REMATCH[2]} + echo "Detected screen position at ${VNC_X}, ${VNC_Y}" + else + echo "Warning: Could not automatically determine screen position for VNC. Defaulting to 0,0." + fi + VNC_CLIP="${WIDTH}x${HEIGHT}+${VNC_X}+${VNC_Y}" + + # Save the state for the 'stop' command and the cleanup trap + echo "$UNUSED_DISPLAY $MODE_NAME" >"$STATE_FILE" + + # 6. Start the VNC server + echo "" + echo "--- Starting VNC Server ---" + echo "Clipping region: $VNC_CLIP" + echo "Hostname: $(hostname)" + echo "Your IP addresses are:" + ip -4 addr show | grep -oP 'inet \K[\d.]+' + echo "" + echo "Connect your VNC client to one of the IPs above on port 5900." + echo "Press Ctrl+C in this terminal to stop the VNC server and clean up." + echo "Or run '$0 stop' in another terminal." + + VNC_CMD="x11vnc -clip \"$VNC_CLIP\" -repeat -noxdamage -forever" + if [ -n "$PASSWORD" ]; then + echo "Using password protection." + # Use x11vnc's utility to create a properly formatted password file. + x11vnc -storepasswd "$PASSWORD" "$PASS_FILE" + VNC_CMD+=" -rfbauth $PASS_FILE" + fi + + eval $VNC_CMD + + echo "VNC server stopped." + # If VNC server stops for any reason other than Ctrl+C, run cleanup + # The trap handles Ctrl+C and exits, so this code won't be reached in that case. + cleanup + ;; + + stop) + echo "--- Stopping Virtual Screen ---" + if [ ! -f "$STATE_FILE" ]; then + echo "Warning: State file not found. Try running 'start' first." + exit 1 + fi + + read -r UNUSED_DISPLAY MODE_NAME <"$STATE_FILE" + + if [ -z "$UNUSED_DISPLAY" ] || [ -z "$MODE_NAME" ]; then + echo "Error: Could not read valid settings from state file." + exit 1 + fi + + echo "Disabling display '$UNUSED_DISPLAY'..." + xrandr --output "$UNUSED_DISPLAY" --off + + echo "Removing mode '$MODE_NAME' from display..." + xrandr --delmode "$UNUSED_DISPLAY" "$MODE_NAME" || echo "Warning: Could not delete mode from display. It might not have been added or is stuck." + + if ! xrandr | grep -B1 "$MODE_NAME" | grep -q " connected"; then + echo "Deleting global mode '$MODE_NAME'..." + xrandr --rmmode "$MODE_NAME" || echo "Warning: Could not remove global mode. It may be stuck or was already removed." + else + echo "Mode '$MODE_NAME' is still in use by another monitor; not removing it." + fi + + rm "$STATE_FILE" + echo "Virtual screen has been disabled and cleaned up." + ;; + + *) + usage + ;; +esac + +exit 0 +