314 lines
10 KiB
Bash
314 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
# Shared setup flow for Zenbook Duo desktop backends.
|
|
# Desktop-specific wrappers declare only their backend dependency deltas and then
|
|
# call run_duo_setup.
|
|
|
|
DUO_COMMON_DNF_PACKAGES=(usbutils iio-sensor-proxy systemd)
|
|
DUO_COMMON_APT_PACKAGES=(usbutils iio-sensor-proxy systemd)
|
|
DUO_COMMON_PACMAN_PACKAGES=(usbutils iio-sensor-proxy systemd)
|
|
DUO_COMMON_MANUAL_DEPENDENCIES_HINT="usbutils, iio-sensor-proxy, systemd"
|
|
|
|
# Rust runtime and UI share one Tauri crate; build these before install-rust-runtime.sh.
|
|
DUO_COMMON_DNF_BUILD_PACKAGES=(
|
|
gcc gcc-c++ make pkgconf-pkg-config openssl-devel
|
|
gtk3-devel webkit2gtk4.1-devel librsvg2-devel
|
|
)
|
|
DUO_COMMON_APT_BUILD_PACKAGES=(
|
|
build-essential pkg-config libssl-dev
|
|
libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev librsvg2-dev
|
|
)
|
|
DUO_COMMON_PACMAN_BUILD_PACKAGES=(
|
|
base-devel pkgconf openssl gtk3 webkit2gtk-4.1 librsvg
|
|
)
|
|
|
|
# Keep these defaults aligned with DuoSettings::default in the Rust runtime and
|
|
# DEFAULT_DUO_SETTINGS in the frontend.
|
|
DUO_DEFAULT_BACKLIGHT=0
|
|
DUO_DEFAULT_SCALE=1.66
|
|
DUO_DEFAULT_USB_MEDIA_REMAP_ENABLED=true
|
|
DUO_DEFAULT_START_ON_BOOT_MINIMIZED=false
|
|
DUO_INSTALLER_MARKS_SETUP_COMPLETED=true
|
|
DUO_SUDOERS_DROPIN_PATH="/etc/sudoers.d/zenbook-duo"
|
|
|
|
build_duo_setup_package_lists() {
|
|
DNF_DESKTOP_PACKAGES=("${DNF_DESKTOP_PACKAGES[@]:-}")
|
|
APT_DESKTOP_PACKAGES=("${APT_DESKTOP_PACKAGES[@]:-}")
|
|
PACMAN_DESKTOP_PACKAGES=("${PACMAN_DESKTOP_PACKAGES[@]:-}")
|
|
|
|
DNF_PACKAGES=("${DUO_COMMON_DNF_PACKAGES[@]}" "${DNF_DESKTOP_PACKAGES[@]}")
|
|
APT_PACKAGES=("${DUO_COMMON_APT_PACKAGES[@]}" "${APT_DESKTOP_PACKAGES[@]}")
|
|
PACMAN_PACKAGES=("${DUO_COMMON_PACMAN_PACKAGES[@]}" "${PACMAN_DESKTOP_PACKAGES[@]}")
|
|
|
|
if [ -n "${MANUAL_DESKTOP_DEPENDENCIES_HINT:-}" ]; then
|
|
MANUAL_DEPENDENCIES_HINT="${DUO_COMMON_MANUAL_DEPENDENCIES_HINT}, ${MANUAL_DESKTOP_DEPENDENCIES_HINT}"
|
|
else
|
|
MANUAL_DEPENDENCIES_HINT="${DUO_COMMON_MANUAL_DEPENDENCIES_HINT}"
|
|
fi
|
|
}
|
|
|
|
run_duo_setup() {
|
|
: "${SETUP_SCRIPT_NAME:?SETUP_SCRIPT_NAME is required}"
|
|
|
|
DEFAULT_BACKLIGHT="${DEFAULT_BACKLIGHT:-${DUO_DEFAULT_BACKLIGHT}}"
|
|
DEFAULT_SCALE="${DEFAULT_SCALE:-${DUO_DEFAULT_SCALE}}"
|
|
USB_MEDIA_REMAP_ENABLED="${USB_MEDIA_REMAP_ENABLED:-${DUO_DEFAULT_USB_MEDIA_REMAP_ENABLED}}"
|
|
START_ON_BOOT_MINIMIZED="${START_ON_BOOT_MINIMIZED:-${DUO_DEFAULT_START_ON_BOOT_MINIMIZED}}"
|
|
|
|
build_duo_setup_package_lists
|
|
|
|
parse_duo_setup_args "$@"
|
|
resolve_duo_target_user
|
|
migrate_duo_sudoers
|
|
prompt_duo_defaults
|
|
install_duo_dependencies
|
|
install_duo_build_dependencies
|
|
configure_duo_sudoers
|
|
configure_duo_input_access
|
|
write_duo_settings_defaults
|
|
install_duo_rust_runtime
|
|
|
|
echo "Install complete."
|
|
}
|
|
|
|
parse_duo_setup_args() {
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
--usb-media-remap)
|
|
USB_MEDIA_REMAP_ENABLED=true
|
|
shift
|
|
;;
|
|
--no-usb-media-remap)
|
|
USB_MEDIA_REMAP_ENABLED=false
|
|
shift
|
|
;;
|
|
*)
|
|
# Unknown flag/arg - ignore to keep backwards compatibility.
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
resolve_duo_target_user() {
|
|
TARGET_USER="${USER:-}"
|
|
if [ -z "${TARGET_USER}" ]; then
|
|
TARGET_USER="$(id -un 2>/dev/null || true)"
|
|
fi
|
|
|
|
if [ "${EUID}" = "0" ]; then
|
|
if [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then
|
|
TARGET_USER="${SUDO_USER}"
|
|
else
|
|
echo "ERROR: ${SETUP_SCRIPT_NAME} must be run from a real user session."
|
|
echo "Run: ./${SETUP_SCRIPT_NAME}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
TARGET_UID="$(id -u "${TARGET_USER}" 2>/dev/null || true)"
|
|
TARGET_HOME="$(getent passwd "${TARGET_USER}" 2>/dev/null | cut -d: -f6)"
|
|
if [ -z "${TARGET_UID}" ] || [ -z "${TARGET_HOME}" ]; then
|
|
echo "ERROR: failed to resolve TARGET_USER=${TARGET_USER}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
duo_prompt() {
|
|
local prompt="$1"
|
|
local reply_var="$2"
|
|
local value=""
|
|
if [ -r /dev/tty ]; then
|
|
read -r -p "${prompt}" value </dev/tty
|
|
else
|
|
read -r -p "${prompt}" value
|
|
fi
|
|
printf -v "${reply_var}" '%s' "${value}"
|
|
}
|
|
|
|
prompt_duo_defaults() {
|
|
local input=""
|
|
duo_prompt "What would you like to use for the default keyboard backlight brightness [0-3] (default: ${DEFAULT_BACKLIGHT})? " input
|
|
DEFAULT_BACKLIGHT="${input:-${DEFAULT_BACKLIGHT}}"
|
|
|
|
duo_prompt "What would you like to use for monitor scale (1 = 100%, 1.5 = 150%, 1.66 = 166%, 2=200%) (default: ${DEFAULT_SCALE})? " input
|
|
DEFAULT_SCALE="${input:-${DEFAULT_SCALE}}"
|
|
|
|
local remap_answer=""
|
|
local remap_prompt="Enable USB Media Remap by default? [Y/n] "
|
|
if [ "${USB_MEDIA_REMAP_ENABLED}" != true ]; then
|
|
remap_prompt="Enable USB Media Remap by default? [y/N] "
|
|
fi
|
|
duo_prompt "${remap_prompt}" remap_answer
|
|
case "${remap_answer}" in
|
|
[yY]|[yY][eE][sS])
|
|
USB_MEDIA_REMAP_ENABLED=true
|
|
;;
|
|
[nN]|[nN][oO])
|
|
USB_MEDIA_REMAP_ENABLED=false
|
|
;;
|
|
esac
|
|
}
|
|
|
|
install_duo_dependencies() {
|
|
if command -v dnf >/dev/null 2>&1; then
|
|
sudo dnf install -y "${DNF_PACKAGES[@]}"
|
|
elif command -v apt >/dev/null 2>&1; then
|
|
sudo apt install -y "${APT_PACKAGES[@]}"
|
|
elif command -v pacman >/dev/null 2>&1; then
|
|
sudo pacman -S --needed --noconfirm "${PACMAN_PACKAGES[@]}"
|
|
else
|
|
echo "Unsupported package manager. Please install dependencies manually:"
|
|
echo " ${MANUAL_DEPENDENCIES_HINT}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
install_duo_build_dependencies() {
|
|
echo "Installing Rust/Tauri build dependencies..."
|
|
if command -v dnf >/dev/null 2>&1; then
|
|
sudo dnf install -y "${DUO_COMMON_DNF_BUILD_PACKAGES[@]}"
|
|
sudo dnf install -y libappindicator-gtk3-devel \
|
|
|| sudo dnf install -y libappindicator3-devel \
|
|
|| sudo dnf install -y ayatana-appindicator3-devel \
|
|
|| echo "WARN: Could not install an AppIndicator *-devel package." >&2
|
|
elif command -v apt >/dev/null 2>&1; then
|
|
sudo apt install -y "${DUO_COMMON_APT_BUILD_PACKAGES[@]}"
|
|
sudo apt install -y libayatana-appindicator3-dev \
|
|
|| sudo apt install -y libappindicator3-dev \
|
|
|| echo "WARN: Could not install an AppIndicator *-dev package." >&2
|
|
elif command -v pacman >/dev/null 2>&1; then
|
|
sudo pacman -S --needed --noconfirm "${DUO_COMMON_PACMAN_BUILD_PACKAGES[@]}"
|
|
sudo pacman -S --needed --noconfirm libayatana-appindicator \
|
|
|| echo "WARN: Could not install libayatana-appindicator." >&2
|
|
else
|
|
echo "WARN: Could not install build dependencies automatically." >&2
|
|
echo "Install GTK/WebKit dev packages manually before the Rust runtime build." >&2
|
|
fi
|
|
}
|
|
|
|
remove_legacy_duo_sudoers_entry() {
|
|
if sudo grep -q "/usr/bin/tee /sys/class/backlight/.*/brightness" /etc/sudoers 2>/dev/null; then
|
|
sudo sed -i '\|/usr/bin/tee /sys/class/backlight/.*/brightness|d' /etc/sudoers
|
|
fi
|
|
}
|
|
|
|
write_duo_sudoers_dropin() {
|
|
if [ -z "${TARGET_USER}" ]; then
|
|
return 0
|
|
fi
|
|
|
|
local backlight_entries=()
|
|
local backlight_dir
|
|
for backlight_dir in /sys/class/backlight/*; do
|
|
[ -e "${backlight_dir}/brightness" ] || continue
|
|
backlight_entries+=("${TARGET_USER} ALL=NOPASSWD:/usr/bin/tee ${backlight_dir}/brightness")
|
|
done
|
|
|
|
if [ "${#backlight_entries[@]}" -eq 0 ]; then
|
|
echo "WARN: No backlight brightness paths found; skipping sudoers drop-in." >&2
|
|
return 0
|
|
fi
|
|
|
|
{
|
|
echo "# Managed by Zenbook Duo installer."
|
|
printf '%s\n' "${backlight_entries[@]}"
|
|
} | sudo tee "${DUO_SUDOERS_DROPIN_PATH}" >/dev/null
|
|
sudo chmod 0440 "${DUO_SUDOERS_DROPIN_PATH}"
|
|
}
|
|
|
|
migrate_duo_sudoers() {
|
|
remove_legacy_duo_sudoers_entry
|
|
}
|
|
|
|
configure_duo_sudoers() {
|
|
write_duo_sudoers_dropin
|
|
}
|
|
|
|
configure_duo_input_access() {
|
|
if [ -n "${TARGET_USER}" ] && ! groups "${TARGET_USER}" | grep -q '\binput\b'; then
|
|
sudo usermod -aG input "${TARGET_USER}"
|
|
echo "Added ${TARGET_USER} to input group (logout/login required for full effect)"
|
|
fi
|
|
|
|
echo 'SUBSYSTEM=="input", ATTRS{name}=="*ASUS Zenbook Duo Keyboard", TAG+="uaccess", MODE="0660", GROUP="input"' | sudo tee /etc/udev/rules.d/90-zenbook-duo-keyboard.rules >/dev/null
|
|
|
|
# Do not install a hwdb remap for KEYBOARD_KEY_7003*: it overrides the USB Fn layer.
|
|
sudo rm -f /etc/udev/hwdb.d/90-zenbook-duo-keyboard.hwdb
|
|
sudo systemd-hwdb update
|
|
sudo udevadm trigger
|
|
}
|
|
|
|
write_duo_settings_defaults() {
|
|
local python3=""
|
|
python3="$(command -v python3 || true)"
|
|
[ -n "${python3}" ] || return 0
|
|
[ -n "${TARGET_HOME}" ] || return 0
|
|
|
|
local config_dir="${TARGET_HOME}/.config/zenbook-duo"
|
|
local settings_file="${config_dir}/settings.json"
|
|
local start_on_boot_minimized="${START_ON_BOOT_MINIMIZED:-${DUO_DEFAULT_START_ON_BOOT_MINIMIZED}}"
|
|
|
|
local python_cmd=("${python3}")
|
|
if [ "${TARGET_USER}" != "${USER:-}" ] || [ "${EUID}" = "0" ]; then
|
|
sudo -u "${TARGET_USER}" mkdir -p "${config_dir}" 2>/dev/null || true
|
|
python_cmd=(sudo -u "${TARGET_USER}" "${python3}")
|
|
else
|
|
mkdir -p "${config_dir}" 2>/dev/null || true
|
|
fi
|
|
|
|
"${python_cmd[@]}" - "${settings_file}" "${DEFAULT_BACKLIGHT}" "${DEFAULT_SCALE}" "${USB_MEDIA_REMAP_ENABLED}" "${start_on_boot_minimized}" "${DUO_INSTALLER_MARKS_SETUP_COMPLETED}" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
settings_file = Path(sys.argv[1])
|
|
default_backlight = int(sys.argv[2])
|
|
default_scale = float(sys.argv[3])
|
|
usb_media_remap_enabled = sys.argv[4].strip().lower() in ("1", "true", "yes", "y", "on")
|
|
start_on_boot_minimized = sys.argv[5].strip().lower() in ("1", "true", "yes", "y", "on")
|
|
setup_completed = sys.argv[6].strip().lower() in ("1", "true", "yes", "y", "on")
|
|
|
|
settings_file.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
data = {}
|
|
if settings_file.exists():
|
|
try:
|
|
loaded = json.loads(settings_file.read_text())
|
|
if isinstance(loaded, dict):
|
|
data = loaded
|
|
except Exception:
|
|
data = {}
|
|
|
|
data.setdefault("autoDualScreen", True)
|
|
data.setdefault("syncBrightness", True)
|
|
data.setdefault("theme", "system")
|
|
data.setdefault("invertSensorRotation", False)
|
|
|
|
data["defaultBacklight"] = default_backlight
|
|
data["defaultScale"] = default_scale
|
|
data["usbMediaRemapEnabled"] = usb_media_remap_enabled
|
|
data["startOnBootMinimized"] = start_on_boot_minimized
|
|
data["setupCompleted"] = setup_completed
|
|
|
|
settings_file.write_text(json.dumps(data, indent=2) + "\n")
|
|
PY
|
|
}
|
|
|
|
install_duo_rust_runtime() {
|
|
local args=()
|
|
case "${DUO_RUNTIME_MODE:-}" in
|
|
build-only)
|
|
args+=(--build-only)
|
|
echo "Building Rust runtime..."
|
|
;;
|
|
install-only)
|
|
args+=(--install-only)
|
|
echo "Installing Rust runtime..."
|
|
;;
|
|
*)
|
|
echo "Building and installing Rust runtime..."
|
|
;;
|
|
esac
|
|
"${DUO_SETUP_DIR}/install-rust-runtime.sh" "${args[@]}"
|
|
}
|