325 lines
12 KiB
Bash
325 lines
12 KiB
Bash
#!/bin/bash
|
||
#
|
||
# wechat-universal.sh - wrapper for WeChat Universal
|
||
# Copyright (C) 2024 Guoxin "7Ji" Pu <pugokushin@gmail.com>
|
||
#
|
||
# This program is free software; you can redistribute it and/or modify
|
||
# it under the terms of the GNU General Public License as published by
|
||
# the Free Software Foundation; either version 2 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with this program; if not, write to the Free Software
|
||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
#
|
||
|
||
dbus_call() { # $1: dest, $2: object path, $3: method
|
||
local args=(dbus-send --session --dest="$1" --print-reply "${@:2}")
|
||
echo "D-Bus calling with: ${args[@]}" >&2
|
||
"${args[@]}"
|
||
}
|
||
|
||
# Based on: https://github.com/web1n/wechat-universal-flatpak/blob/91f5d0d246881077812881e800a22f5a02cea438/wechat.sh#L3
|
||
notifier_get() {
|
||
local dbus_item
|
||
for dbus_item in $(
|
||
dbus_call org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames |
|
||
sed -n 's/ string "\(org\.kde\.StatusNotifierItem.\+\)"/\1/p'
|
||
)
|
||
do
|
||
if dbus_call "${dbus_item}" \
|
||
/StatusNotifierItem \
|
||
org.freedesktop.DBus.Properties.Get \
|
||
string:org.kde.StatusNotifierItem \
|
||
string:Id |
|
||
grep -q 'string "wechat"'
|
||
then
|
||
echo "${dbus_item}"
|
||
return
|
||
fi
|
||
done
|
||
}
|
||
|
||
try_move_foreground() {
|
||
# Try to find existing WeChat window first and display it, before actually opening a new one
|
||
notifier_item=$(notifier_get)
|
||
if [[ "${notifier_item}" ]]; then
|
||
echo 'WeChat is already running, moving it to foreground'
|
||
# Based on: https://github.com/web1n/wechat-universal-flatpak/blob/91f5d0d246881077812881e800a22f5a02cea438/wechat.sh#L25
|
||
dbus_call "${notifier_item}" \
|
||
/StatusNotifierItem \
|
||
org.kde.StatusNotifierItem.Activate \
|
||
int32:0 \
|
||
int32:0
|
||
return $?
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
try_start() {
|
||
|
||
# Data folder setup
|
||
# If user has declared a custom data dir, no need to query xdg for documents dir, but always resolve that to absolute path
|
||
if [[ "${WECHAT_DATA_DIR}" ]]; then
|
||
WECHAT_DATA_DIR=$(readlink -f -- "${WECHAT_DATA_DIR}")
|
||
else
|
||
XDG_DOCUMENTS_DIR="${XDG_DOCUMENTS_DIR:-$(xdg-user-dir DOCUMENTS)}"
|
||
if [[ -z "${XDG_DOCUMENTS_DIR}" ]]; then
|
||
echo 'Error: Failed to get XDG_DOCUMENTS_DIR, refuse to continue'
|
||
exit 1
|
||
fi
|
||
WECHAT_DATA_DIR="${XDG_DOCUMENTS_DIR}/WeChat_Data"
|
||
fi
|
||
echo "Using '${WECHAT_DATA_DIR}' as Wechat Data folder"
|
||
WECHAT_FILES_DIR="${WECHAT_DATA_DIR}/xwechat_files"
|
||
WECHAT_HOME_DIR="${WECHAT_DATA_DIR}/home"
|
||
|
||
# Runtime folder setup
|
||
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-$(xdg-user-dir RUNTIME)}"
|
||
if [[ -z "${XDG_RUNTIME_DIR}" ]]; then
|
||
echo 'Error: Failed to get XDG_RUNTIME_DIR, refuse to continue'
|
||
exit 1
|
||
fi
|
||
if [[ -z "${XAUTHORITY}" ]]; then
|
||
echo 'Warning: No XAUTHORITY set, runnning in no-X environment? Generating it'
|
||
export XAUTHORITY=$(mktemp "${XDG_RUNTIME_DIR}"/xauth_XXXXXX)
|
||
echo "Info: Generated XAUTHORITY at '${XAUTHORITY}'"
|
||
fi
|
||
|
||
# wechat-universal only supports xcb
|
||
export QT_QPA_PLATFORM=xcb \
|
||
QT_AUTO_SCREEN_SCALE_FACTOR=1 \
|
||
PATH="/sandbox:${PATH}"
|
||
|
||
if [[ -z "${IME_WORKAROUND}" || "${IME_WORKAROUND}" == 'auto' ]]; then
|
||
case "${XMODIFIERS}" in
|
||
*@im=fcitx*)
|
||
IME_WORKAROUND='fcitx'
|
||
;;
|
||
*@im=ibus*)
|
||
IME_WORKAROUND='ibus'
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
case "${IME_WORKAROUND}" in
|
||
fcitx)
|
||
echo "IME workaround for fcitx applied"
|
||
export QT_IM_MODULE=fcitx \
|
||
GTK_IM_MODULE=fcitx
|
||
;;
|
||
ibus)
|
||
echo "IME workaround for ibus applied"
|
||
export QT_IM_MODULE=ibus \
|
||
GTK_IM_MODULE=ibus \
|
||
IBUS_USE_PORTAL=1
|
||
;;
|
||
esac
|
||
|
||
BWRAP_DEV_BINDS=()
|
||
for DEV_NODE in /dev/{nvidia{-uvm,ctl,*[0-9]},video*[0-9]}; do
|
||
[[ -e "${DEV_NODE}" ]] && BWRAP_DEV_BINDS+=(--dev-bind "${DEV_NODE}"{,})
|
||
done
|
||
|
||
# Custom exposed folders
|
||
# echo "Hint: Custom binds could either be declared in '~/.config/wechat-universal/binds.list', each line a path, absolute or relative to your HOME; or as argument \`--bind [custom bind]\`"
|
||
BWRAP_CUSTOM_BINDS=()
|
||
if [[ -f "${CUSTOM_BINDS_CONFIG}" ]]; then
|
||
mapfile -t CUSTOM_BINDS_PRESISTENT < "${CUSTOM_BINDS_CONFIG}"
|
||
fi
|
||
cd ~ # 7Ji: two chdir.3 should be cheaper than a lot of per-dir calculation
|
||
for CUSTOM_BIND in "${CUSTOM_BINDS_RUNTIME[@]}" "${CUSTOM_BINDS_PRESISTENT[@]}"; do
|
||
CUSTOM_BIND=$(readlink -f -- "${CUSTOM_BIND}")
|
||
echo "Custom bind: '${CUSTOM_BIND}'"
|
||
BWRAP_CUSTOM_BINDS+=(--bind "${CUSTOM_BIND}"{,})
|
||
done
|
||
cd - > /dev/null
|
||
|
||
DBUS_SESSION_BUS_PATH=''
|
||
if [[ "${DBUS_SESSION_BUS_ADDRESS}" ]]; then
|
||
DBUS_SESSION_BUS_PATH=$(echo "${DBUS_SESSION_BUS_ADDRESS}" | sed 's/^unix:\(.\+=.\+\)\?path=\(.\+\)\(,.\+=.\+\|$\)/\2/')
|
||
fi
|
||
if [[ -z "${DBUS_SESSION_BUS_PATH}" ]]; then
|
||
DBUS_SESSION_BUS_PATH="${XDG_RUNTIME_DIR}/bus"
|
||
echo "Failed to extract \$DBUS_SESSION_BUS_ADDRESS from \$DBUS_SESSION_BUS_ADDRESS (${DBUS_SESSION_BUS_ADDRESS}), using fallback ${DBUS_SESSION_BUS_PATH}"
|
||
fi
|
||
|
||
mkdir -p "${WECHAT_FILES_DIR}" "${WECHAT_HOME_DIR}"
|
||
ln -snf "${WECHAT_FILES_DIR}" "${WECHAT_HOME_DIR}/xwechat_files"
|
||
|
||
BWRAP_ARGS=(
|
||
# Drop privileges
|
||
--unshare-all
|
||
--share-net
|
||
--cap-drop ALL
|
||
# --die-with-parent
|
||
|
||
# /
|
||
--ro-bind /bin{,}
|
||
--ro-bind /sbin{,}
|
||
--ro-bind /lib{,}
|
||
--ro-bind /lib64{,}
|
||
# /usr
|
||
--ro-bind /usr{,}
|
||
|
||
--bind /bin/true /usr/bin/lsblk
|
||
|
||
# /sandbox
|
||
--ro-bind /usr/lib64/flatpak-xdg-utils/xdg-open /sandbox/xdg-open
|
||
--ro-bind /usr/lib64/wechat-universal/bin/dde-file-manager /sandbox/dde-file-manager
|
||
--ro-bind /sbin/ip /sandbox/ip
|
||
# /dev
|
||
--dev /dev
|
||
--dev-bind /dev/dri{,}
|
||
--tmpfs /dev/shm
|
||
|
||
# /proc
|
||
--proc /proc
|
||
|
||
# /etc
|
||
--ro-bind /etc/machine-id{,}
|
||
--ro-bind /etc/passwd{,}
|
||
--ro-bind /etc/nsswitch.conf{,}
|
||
--ro-bind /etc/resolv.conf{,}
|
||
--ro-bind /etc/localtime{,}
|
||
--ro-bind-try /etc/fonts{,}
|
||
|
||
# /sys
|
||
--dir /sys/dev # hack for Intel / AMD graphics, mesa calling virtual nodes needs /sys/dev being 0755
|
||
--ro-bind /sys/dev/char{,}
|
||
--ro-bind /sys/devices{,}
|
||
|
||
# /tmp
|
||
--tmpfs /tmp
|
||
|
||
# /opt
|
||
--ro-bind /opt/wechat-universal{,}
|
||
|
||
# license fixups in various places
|
||
--ro-bind /usr/lib64/wechat-universal/license/ /usr/lib/license
|
||
--ro-bind /usr/lib64/wechat-universal/etc/lsb-release /etc/lsb-release
|
||
|
||
# /home
|
||
--bind "${WECHAT_HOME_DIR}" "${HOME}"
|
||
--bind "${WECHAT_FILES_DIR}"{,}
|
||
--bind-try "${HOME}/.pki"{,}
|
||
--ro-bind-try "${HOME}/.fontconfig"{,}
|
||
--ro-bind-try "${HOME}/.fonts"{,}
|
||
--ro-bind-try "${HOME}/.config/fontconfig"{,}
|
||
--ro-bind-try "${HOME}/.local/share/fonts"{,}
|
||
--ro-bind-try "${HOME}/.icons"{,}
|
||
--ro-bind-try "${HOME}/.local/share/icons"{,}
|
||
|
||
# /run
|
||
--dev-bind /run/dbus{,}
|
||
--ro-bind-try /run/systemd/userdb{,}
|
||
--ro-bind-try "${XAUTHORITY}"{,}
|
||
# --ro-bind "${DBUS_SESSION_BUS_PATH}"{,}
|
||
--ro-bind "${XDG_RUNTIME_DIR}/pulse"{,}
|
||
--setenv DBUS_SESSION_BUS_PATH "${DBUS_SESSION_BUS_PATH}"
|
||
--setenv LD_PRELOAD "/usr/lib64/wechat-universal/keyBlocker.so:${LD_PRELOAD}"
|
||
)
|
||
|
||
exec bwrap "${BWRAP_ARGS[@]}" "${BWRAP_CUSTOM_BINDS[@]}" "${BWRAP_DEV_BINDS[@]}" /opt/wechat-universal/wechat "$@"
|
||
echo "Error: Failed to exec bwrap, rerun this script with 'bash -x $0' to show the full command history"
|
||
return 1
|
||
}
|
||
|
||
bwrap_start() {
|
||
IFS=':' read -r -a CUSTOM_BINDS_RUNTIME <<< "${CUSTOM_BINDS}"
|
||
if [[ -z "${CUSTOM_BINDS_CONFIG}" && ! -v CUSTOM_BINDS_CONFIG ]]; then
|
||
CUSTOM_BINDS_CONFIG=~/.config/wechat-universal/binds.list
|
||
fi
|
||
# Parsing arguments, for any option, argument > environment
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
'--data')
|
||
WECHAT_DATA_DIR="$2"
|
||
shift
|
||
;;
|
||
'--bind')
|
||
CUSTOM_BINDS_RUNTIME+=("$2")
|
||
shift
|
||
;;
|
||
'--binds-config')
|
||
CUSTOM_BINDS_CONFIG="$2"
|
||
shift
|
||
;;
|
||
'--ime')
|
||
IME_WORKAROUND="$2"
|
||
shift
|
||
;;
|
||
'--help')
|
||
if [[ "${LANG}" == zh_CN* ]]; then
|
||
echo "$0 (--data [微信数据文件夹]) (--bind [自定义绑定挂载] (--bind ...))) (--ime [输入法]) (--help)"
|
||
echo
|
||
printf ' --%s\t%s\n' \
|
||
'data [微信数据文件夹]' '微信数据文件夹的路径,绝对路径,或相对于用户HOME的相对路径。 默认:~/文档/Wechat_Data;环境变量: WECHAT_DATA_DIR' \
|
||
'bind [自定义绑定挂载]' '自定义的绑定挂载,可被声明多次,绝对路径,或相对于用户HOME的相对路径。环境变量: CUSTOM_BINDS (用冒号:分隔,与PATH相似)' \
|
||
'binds-config [文件]' '以每行一个的方式列明应被绑定挂载的路径的纯文本配置文件,每行定义与--bind一致。默认:~/.config/wechat-universal/binds.list;环境变量:CUSTOM_BINDS_CONFIG' \
|
||
'ime [输入法名称或特殊值]' '应用输入法对应环境变量修改,可支持:fcitx (不论是否为5), ibus,特殊值:none不应用,auto自动判断。默认: auto;环境变量: IME_WORKAROUND'\
|
||
'help' ''
|
||
echo
|
||
echo "命令行参数比环境变量优先级更高,如果命令行参数与环境变量皆为空,则使用默认值"
|
||
else
|
||
echo "$0 (--data [wechat data]) (--bind [custom bind] (--bind ...))) (--ime [ime]) (--help)"
|
||
echo
|
||
printf ' --%s\t%s\n' \
|
||
'data [wechat data]' 'Path to Wechat_Data folder, absolute or relative to user home, default: ~/Documents/Wechat_Data, as environment: WECHAT_DATA_DIR' \
|
||
'bind [custom bind]' 'Custom bindings, could be specified multiple times, absolute or relative to user home, as environment: CUSTOM_BINDS (colon ":" seperated like PATH)' \
|
||
'binds-config [file]' 'Path to text file that contains one --bind value per line, default: ~/.config/wechat-universal/binds.list, as environment: CUSTOM_BINDS_CONFIG'\
|
||
'ime [input method]' 'Apply IME-specific workaround, support: fcitx (also for 5), ibus, default: auto, as environment: IME_WORKAROUND'\
|
||
'help' ''
|
||
echo
|
||
echo "Arguments take priority over environment, if both argument and environment are empty, the default value would be used"
|
||
fi
|
||
return 0
|
||
;;
|
||
esac
|
||
shift
|
||
done
|
||
if try_move_foreground; then
|
||
return 0
|
||
else
|
||
try_start "$@"
|
||
return $?
|
||
fi
|
||
}
|
||
|
||
bwrap_stop() {
|
||
# Try to find existing WeChat window first and display it, before actually opening a new one
|
||
notifier_item=$(notifier_get)
|
||
[[ -z "${notifier_item}" ]] && return
|
||
echo 'Stopping running WeChat instance'
|
||
# Based on: https://github.com/web1n/wechat-universal-flatpak/blob/91f5d0d246881077812881e800a22f5a02cea438/wechat.sh#L35
|
||
dbus_call "${notifier_item}" \
|
||
/MenuBar \
|
||
com.canonical.dbusmenu.Event \
|
||
int32:1 \
|
||
string:clicked \
|
||
variant:string:'Quit WeChat' \
|
||
uint32:0
|
||
}
|
||
|
||
command="$1"
|
||
case "$command" in
|
||
'stop')
|
||
shift
|
||
bwrap_stop "$@"
|
||
;;
|
||
'start')
|
||
shift
|
||
bwrap_start "$@"
|
||
;;
|
||
*)
|
||
echo "Unknown command '${command}', allowed: start, stop"
|
||
exit 1
|
||
;;
|
||
esac
|
||
exit $?
|