|
#!/bin/bash |
|
set -euo pipefail |
|
|
|
# virtual-slack: Manage Slack variants with separate config dirs and deep-link routing |
|
# |
|
# Usage: |
|
# virtual-slack create <number> |
|
# virtual-slack remove <number> |
|
# virtual-slack make-default <number> # make this variant handle slack:// links |
|
# virtual-slack restore-default # restore original Slack as handler |
|
|
|
if command -v slack >/dev/null 2>&1; then |
|
_SLACK_BIN="$(command -v slack)" |
|
SRC_BIN="$(readlink -f "$_SLACK_BIN")" |
|
else |
|
SRC_BIN="/usr/lib/slack/slack" |
|
fi |
|
APP_DIR="$HOME/.local/share/applications" |
|
LOCAL_SLACK_DESKTOP="$APP_DIR/slack.desktop" |
|
|
|
require_src_bin() { |
|
if [ ! -x "$SRC_BIN" ]; then |
|
echo "Slack binary not found. Expected at: $SRC_BIN" |
|
echo "Adjust SRC_BIN in this script to your system path." |
|
exit 1 |
|
fi |
|
} |
|
|
|
require_variant_number() { |
|
local num="$1" |
|
if [[ ! "$num" =~ ^[0-9]+$ ]]; then |
|
echo "Error: variant number must be numeric." |
|
exit 1 |
|
fi |
|
} |
|
|
|
write_local_slack_desktop_variant() { |
|
local num="$1" |
|
local variant_root="$HOME/.local/share/virtual-slack-$num" |
|
local url_launcher="$variant_root/open-slack-$num-url.sh" |
|
|
|
cat > "$LOCAL_SLACK_DESKTOP" <<EOF |
|
[Desktop Entry] |
|
Name=Slack |
|
StartupWMClass=browser-windox |
|
Comment=Slack Desktop |
|
GenericName=Slack Client for Linux |
|
Exec=$url_launcher %U |
|
Icon=slack |
|
Type=Application |
|
StartupNotify=true |
|
Categories=GNOME;GTK;Network;InstantMessaging; |
|
MimeType=x-scheme-handler/slack; |
|
EOF |
|
} |
|
|
|
write_local_slack_desktop_original() { |
|
cat > "$LOCAL_SLACK_DESKTOP" <<EOF |
|
[Desktop Entry] |
|
Name=Slack |
|
StartupWMClass=Slack |
|
Comment=Slack Desktop |
|
GenericName=Slack Client for Linux |
|
Exec=/usr/bin/slack --gtk-version=3 -s %U |
|
Icon=slack |
|
Type=Application |
|
StartupNotify=true |
|
Categories=GNOME;GTK;Network;InstantMessaging; |
|
MimeType=x-scheme-handler/slack; |
|
EOF |
|
} |
|
|
|
set_mime_default() { |
|
local desktop_file="$1" |
|
local mime_file="$2" |
|
local all_desktops |
|
local ordered |
|
|
|
mkdir -p "$(dirname "$mime_file")" |
|
touch "$mime_file" |
|
|
|
all_desktops="${desktop_file};slack.desktop;slack-2.desktop;slack-3.desktop;" |
|
ordered="$(printf '%s' "$all_desktops" | awk -F';' ' |
|
BEGIN { out="" } |
|
{ |
|
for (i = 1; i <= NF; i++) { |
|
if ($i != "" && !seen[$i]++) { |
|
out = out $i ";" |
|
} |
|
} |
|
} |
|
END { printf "%s", out } |
|
')" |
|
|
|
if ! grep -q '^\[Default Applications\]' "$mime_file"; then |
|
printf ' |
|
[Default Applications] |
|
' >> "$mime_file" |
|
fi |
|
|
|
if grep -q '^x-scheme-handler/slack=' "$mime_file"; then |
|
sed -i "s|^x-scheme-handler/slack=.*|x-scheme-handler/slack=${desktop_file};|" "$mime_file" |
|
else |
|
awk -v d="$desktop_file" ' |
|
BEGIN { done=0 } |
|
{ print } |
|
/^\[Default Applications\]$/ && !done { |
|
print "x-scheme-handler/slack=" d ";" |
|
done=1 |
|
} |
|
END { |
|
if (!done) { |
|
print "[Default Applications]" |
|
print "x-scheme-handler/slack=" d ";" |
|
} |
|
} |
|
' "$mime_file" > "${mime_file}.tmp" && mv "${mime_file}.tmp" "$mime_file" |
|
fi |
|
|
|
if ! grep -q '^\[Added Associations\]' "$mime_file"; then |
|
printf '\n[Added Associations]\n' >> "$mime_file" |
|
fi |
|
|
|
if grep -q '^x-scheme-handler/slack=' "$mime_file"; then |
|
awk -v d="$desktop_file" -v o="$ordered" ' |
|
BEGIN { in_default=0; in_added=0 } |
|
/^\[Default Applications\]$/ { in_default=1; in_added=0; print; next } |
|
/^\[Added Associations\]$/ { in_default=0; in_added=1; print; next } |
|
/^\[/ { in_default=0; in_added=0; print; next } |
|
in_default && /^x-scheme-handler\/slack=/ { print "x-scheme-handler/slack=" d ";"; next } |
|
in_added && /^x-scheme-handler\/slack=/ { print "x-scheme-handler/slack=" o; next } |
|
{ print } |
|
' "$mime_file" > "${mime_file}.tmp" && mv "${mime_file}.tmp" "$mime_file" |
|
else |
|
printf '\n[Default Applications]\nx-scheme-handler/slack=%s;\n' "$desktop_file" >> "$mime_file" |
|
printf '\n[Added Associations]\nx-scheme-handler/slack=%s\n' "$ordered" >> "$mime_file" |
|
fi |
|
} |
|
|
|
create_slack() { |
|
NUM="$1" |
|
require_variant_number "$NUM" |
|
require_src_bin |
|
DST_BIN_DIR="$(dirname "$SRC_BIN")" |
|
DST_BIN="$DST_BIN_DIR/slack-$NUM" |
|
VAR_ROOT="$HOME/.local/share/virtual-slack-$NUM" |
|
VAR_BIN="$VAR_ROOT/bin" |
|
LAUNCHER="$VAR_ROOT/launch-slack-$NUM.sh" |
|
URL_LAUNCHER="$VAR_ROOT/open-slack-$NUM-url.sh" |
|
CONFIG_DIR="$HOME/.config-$NUM" |
|
DESKTOP_FILE="$APP_DIR/slack-$NUM.desktop" |
|
|
|
echo "[1/6] Copying Slack binary to $DST_BIN ..." |
|
sudo cp -rp "$SRC_BIN" "$DST_BIN" |
|
|
|
echo "[2/6] Patching window class string ..." |
|
if sudo grep -aq $'\x00browser-window\x00' "$DST_BIN"; then |
|
sudo sed -i 's/\x00browser-window\x00/\x00browser-windox\x00/g' "$DST_BIN" |
|
else |
|
echo "Note: 'browser-window' marker not found; skipping patch." |
|
fi |
|
|
|
echo "[3/6] Creating per-variant shims ..." |
|
mkdir -p "$VAR_BIN" |
|
|
|
SYS_XDG_OPEN="$(command -v xdg-open || true)" |
|
SYS_GIO="$(command -v gio || true)" |
|
[ -z "$SYS_XDG_OPEN" ] && SYS_XDG_OPEN="/usr/bin/xdg-open" |
|
[ -z "$SYS_GIO" ] && SYS_GIO="/usr/bin/gio" |
|
|
|
cat > "$VAR_BIN/xdg-open" <<'EOF' |
|
#!/bin/sh |
|
# Ensure child apps (browsers etc.) use normal user config, not the virtual Slack's config. |
|
unset XDG_CONFIG_HOME |
|
exec /usr/bin/xdg-open "$@" |
|
EOF |
|
chmod +x "$VAR_BIN/xdg-open" |
|
|
|
cat > "$VAR_BIN/gio" <<'EOF' |
|
#!/bin/sh |
|
unset XDG_CONFIG_HOME |
|
exec /usr/bin/gio "$@" |
|
EOF |
|
chmod +x "$VAR_BIN/gio" |
|
|
|
[ "$SYS_XDG_OPEN" != "/usr/bin/xdg-open" ] && sed -i "s|/usr/bin/xdg-open|$SYS_XDG_OPEN|g" "$VAR_BIN/xdg-open" |
|
[ "$SYS_GIO" != "/usr/bin/gio" ] && sed -i "s|/usr/bin/gio|$SYS_GIO|g" "$VAR_BIN/gio" |
|
|
|
echo "[4/6] Creating launcher ..." |
|
mkdir -p "$VAR_ROOT" |
|
cat > "$LAUNCHER" <<EOF |
|
#!/bin/sh |
|
export PATH="$VAR_BIN:\$PATH" |
|
export XDG_CONFIG_HOME="$CONFIG_DIR" |
|
exec "$DST_BIN" --gtk-version=3 -s "\$@" |
|
EOF |
|
chmod +x "$LAUNCHER" |
|
|
|
cat > "$URL_LAUNCHER" <<EOF |
|
#!/bin/sh |
|
URL="\${1:-}" |
|
|
|
# Avoid the primary Slack instance hijacking slack:// deep links via single-instance IPC. |
|
# Kill only the original Slack launcher path, not this variant. |
|
for slack_cmd in "$SRC_BIN" /usr/bin/slack; do |
|
[ -n "\$slack_cmd" ] || continue |
|
pkill -f "^\$slack_cmd( |$)" >/dev/null 2>&1 || true |
|
done |
|
|
|
for _ in 1 2 3 4 5 6 7 8 9 10; do |
|
still_running=0 |
|
for slack_cmd in "$SRC_BIN" /usr/bin/slack; do |
|
[ -n "\$slack_cmd" ] || continue |
|
if pgrep -f "^\$slack_cmd( |$)" >/dev/null 2>&1; then |
|
still_running=1 |
|
break |
|
fi |
|
done |
|
[ "\$still_running" -eq 0 ] && break |
|
sleep 0.2 |
|
done |
|
|
|
exec "$LAUNCHER" "\$URL" |
|
EOF |
|
chmod +x "$URL_LAUNCHER" |
|
|
|
echo "[5/6] Writing desktop entry ..." |
|
mkdir -p "$APP_DIR" |
|
cat > "$DESKTOP_FILE" <<EOF |
|
[Desktop Entry] |
|
Name=Slack $NUM |
|
Comment=Slack using alternate config directory $NUM |
|
Exec=$URL_LAUNCHER %u |
|
Icon=slack |
|
Type=Application |
|
Terminal=false |
|
Categories=Network;Chat; |
|
StartupWMClass=browser-windox |
|
MimeType=x-scheme-handler/slack; |
|
EOF |
|
|
|
echo "[6/6] Refreshing menus ..." |
|
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$APP_DIR" || true |
|
command -v kbuildsycoca5 >/dev/null 2>&1 && kbuildsycoca5 || true |
|
|
|
echo "✅ Slack $NUM installed (virtual-slack-$NUM)." |
|
echo "" |
|
echo "Tip: To login into workspaces using this slack instance," |
|
echo " you need to make this instance to be able to capture" |
|
echo " slack:// links by runnning:" |
|
echo "" |
|
echo " virtual-slack make-default $NUM" |
|
echo "" |
|
echo " Once you have logged in successfully, you need to restore the" |
|
echo " original slack handler by running:" |
|
echo "" |
|
echo " virtual-slack restore-default" |
|
echo "" |
|
} |
|
|
|
remove_slack() { |
|
NUM="$1" |
|
require_variant_number "$NUM" |
|
require_src_bin |
|
DST_BIN_DIR="$(dirname "$SRC_BIN")" |
|
DST_BIN="$DST_BIN_DIR/slack-$NUM" |
|
VAR_ROOT="$HOME/.local/share/virtual-slack-$NUM" |
|
DESKTOP_FILE="$APP_DIR/slack-$NUM.desktop" |
|
CONFIG_DIR="$HOME/.config-$NUM" |
|
|
|
echo "[1/4] Removing cloned binary..." |
|
[ -f "$DST_BIN" ] && sudo rm -f "$DST_BIN" && echo "Deleted $DST_BIN" || echo "Not found: $DST_BIN" |
|
|
|
echo "[2/4] Removing virtual-slack runtime dir..." |
|
[ -d "$VAR_ROOT" ] && rm -rf "$VAR_ROOT" && echo "Deleted $VAR_ROOT" || echo "Not found: $VAR_ROOT" |
|
|
|
echo "[3/4] Removing desktop entry..." |
|
[ -f "$DESKTOP_FILE" ] && rm -f "$DESKTOP_FILE" && echo "Deleted $DESKTOP_FILE" || echo "Not found: $DESKTOP_FILE" |
|
|
|
echo "[4/4] Removing config dir..." |
|
[ -d "$CONFIG_DIR" ] && rm -rf "$CONFIG_DIR" && echo "Deleted $CONFIG_DIR" || echo "Not found: $CONFIG_DIR" |
|
|
|
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$APP_DIR" || true |
|
command -v kbuildsycoca5 >/dev/null 2>&1 && kbuildsycoca5 || true |
|
|
|
echo "✅ Slack $NUM fully removed." |
|
} |
|
|
|
make_default() { |
|
NUM="$1" |
|
require_variant_number "$NUM" |
|
DESKTOP_FILE="slack-$NUM.desktop" |
|
FULL_DESKTOP_FILE="$APP_DIR/$DESKTOP_FILE" |
|
|
|
if [ ! -f "$FULL_DESKTOP_FILE" ]; then |
|
echo "Error: desktop file not found: $FULL_DESKTOP_FILE" |
|
exit 1 |
|
fi |
|
|
|
echo "Setting $DESKTOP_FILE as default handler for slack:// ..." |
|
xdg-mime default "$DESKTOP_FILE" x-scheme-handler/slack |
|
|
|
# Force-write common user mimeapps locations used by gio/desktop environments. |
|
set_mime_default "$DESKTOP_FILE" "$HOME/.config/mimeapps.list" |
|
set_mime_default "$DESKTOP_FILE" "$HOME/.local/share/applications/mimeapps.list" |
|
command -v gio >/dev/null 2>&1 && gio mime x-scheme-handler/slack "$DESKTOP_FILE" >/dev/null || true |
|
write_local_slack_desktop_variant "$NUM" |
|
|
|
# Some desktop/browser combinations consult xdg-settings for URL handlers. |
|
command -v xdg-settings >/dev/null 2>&1 && xdg-settings set default-url-scheme-handler slack "$DESKTOP_FILE" || true |
|
|
|
update_desktop_cache |
|
|
|
echo "Current handler checks:" |
|
echo " xdg-mime: $(xdg-mime query default x-scheme-handler/slack 2>/dev/null || echo unknown)" |
|
echo " xdg-settings: $(xdg-settings get default-url-scheme-handler slack 2>/dev/null || echo unknown)" |
|
|
|
echo "✅ Now, browser OAuth deep links (slack://) should open in Slack $NUM." |
|
echo "If they still open another instance, that is usually Slack single-instance routing from an already running process." |
|
} |
|
|
|
restore_default() { |
|
echo "Restoring original Slack as default handler for slack:// ..." |
|
xdg-mime default slack.desktop x-scheme-handler/slack |
|
set_mime_default "slack.desktop" "$HOME/.config/mimeapps.list" |
|
set_mime_default "slack.desktop" "$HOME/.local/share/applications/mimeapps.list" |
|
command -v gio >/dev/null 2>&1 && gio mime x-scheme-handler/slack slack.desktop >/dev/null || true |
|
write_local_slack_desktop_original |
|
command -v xdg-settings >/dev/null 2>&1 && xdg-settings set default-url-scheme-handler slack "slack.desktop" || true |
|
update_desktop_cache |
|
echo "✅ Restored original Slack as the handler." |
|
} |
|
|
|
update_desktop_cache() { |
|
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$APP_DIR" || true |
|
command -v kbuildsycoca5 >/dev/null 2>&1 && kbuildsycoca5 || true |
|
} |
|
|
|
# --- Main --- |
|
if [ $# -lt 1 ]; then |
|
echo "Usage: $0 {create|remove|make-default|restore-default} [number]" |
|
exit 1 |
|
fi |
|
|
|
CMD="$1" |
|
case "$CMD" in |
|
create) |
|
[ $# -lt 2 ] && { echo "Usage: $0 create <number>"; exit 1; } |
|
create_slack "$2" |
|
;; |
|
remove) |
|
[ $# -lt 2 ] && { echo "Usage: $0 remove <number>"; exit 1; } |
|
remove_slack "$2" |
|
;; |
|
make-default) |
|
[ $# -lt 2 ] && { echo "Usage: $0 make-default <number>"; exit 1; } |
|
make_default "$2" |
|
;; |
|
restore-default) |
|
restore_default |
|
;; |
|
*) |
|
echo "Unknown command: $CMD" |
|
echo "Usage: $0 {create|remove|make-default|restore-default} [number]" |
|
exit 1 |
|
;; |
|
esac |