initial commit

This commit is contained in:
2026-09-26 17:37:13 +02:00
commit 839bebd8d4
226 changed files with 58701 additions and 0 deletions
+313
View File
@@ -0,0 +1,313 @@
#!/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[@]}"
}