Seject script is used which poweroff the drive, if you do not want that replace it with udisksctl command.
Features
1. USb Icon pops up on Taskbar on USB insert from clicking that icon, we can eject (poweroff the drive).
2. Usb Icon vanishes on all USB eject.
3. Yad notifications for, drive name ejected, waiting for sync, if drive used then msg of cannot disconnect, etc.
4. Right click the icon also has yad menu
5. If one usb is there direct poweroff, if more than one it will display two drives connected and you can double click to eject the selected one.
Setup required
1. Udev rule - File name - 99-usb-tray.rules - /etc/udev/rules.d/
Code: Select all
SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="usb", ACTION=="add", RUN+="/usr/local/bin/usb-tray-handler.sh add"
SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="usb", ACTION=="remove", RUN+="/usr/local/bin/usb-tray-handler.sh remove"# place this file in /etc/polkit-1/rules.d otherwise the script executed will show polit/authentication error
Code: Select all
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.udisks2.eject-media" ||
action.id == "org.freedesktop.udisks2.filesystem-unmount" ||
action.id == "org.freedesktop.udisks2.power-off-drive") &&
(subject.isInGroup("wheel") || subject.isInGroup("storage") || subject.isInGroup("plugdev"))) {
return polkit.Result.YES;
}
});Code: Select all
#!/bin/bash
# Copy icon as svg file to this path /usr/share/icons/kora/devices/scalable to show tray icon bigger.
# Have rule created at /etc/polkit-1/rules.d/10-porteus-udisks.rules, otherwise authentication error will come.
# Have rule here, /etc/udev/rules.d/99-usb-tray.rules
# --- VARIABLES & HOOKS ---
ICON_USB="drive-removable-media-usb"
PIPE="/tmp/yad_usb_pipe"
ERR_FILE="/tmp/usb_eject_err"
# Automatically determine desktop user context when run by root (udev)
if [ "$(id -u)" -eq 0 ]; then
USER=$(loginctl list-sessions | awk 'NR>1 {print $3}' | grep -v 'root' | head -n 1)
[ -z "$USER" ] && USER=$(who | awk '{print $1}' | head -n 1 | tr -d '\n')
else
USER=$(whoami)
fi
USER_ID=$(id -u "$USER")
GROUP=$(id -gn "$USER")
HOME_DIR=$(getent passwd "$USER" | cut -d: -f6)
# Environment variables needed when root/udev spawns YAD for the user
export DISPLAY=:0
export XAUTHORITY="$HOME_DIR/.Xauthority"
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus"
# Helper functions
count_usb_drives() {
lsblk -d -o TRAN -n | grep -c "usb"
}
list_usb_drives() {
lsblk -d -o NAME,MODEL,TRAN -nr | \
awk '$3=="usb" {printf "%s\n%s\n", $1, $2}'
}
notify_error() {
local model="$1"
local err_msg=$(cat "$ERR_FILE" 2>/dev/null)
yad --geometry 448x1-32+32 --sticky --skip-taskbar --on-top --undecorated --borders 5 \
--image "$ICON_USB" --no-buttons --timeout 5 \
--text "Failed to remove $model: ${err_msg:-Unknown error}"
}
notify_remove() {
local model="$1"
yad --geometry 448x1-32+32 --sticky --skip-taskbar --on-top --undecorated --borders 5 \
--image "$ICON_USB" --no-buttons --timeout 7 \
--text "Safely removed $model"
}
notify_none() {
yad --geometry 448x1-32+32 --sticky --skip-taskbar --on-top --undecorated --borders 5 \
--image "$ICON_USB" --no-buttons --timeout 4 \
--text "No USB drives found"
}
auto_close() {
if [ "$(count_usb_drives)" -eq 0 ]; then
if [ -p "$PIPE" ]; then
echo "quit" > "$PIPE" 2>/dev/null
rm -f "$PIPE"
fi
fi
}
remove_usb() {
local dev="$1"
local model=$(lsblk -d -o NAME,MODEL -nr | awk -v d="$dev" '$1==d {print $2}')
# 1. Spawn immediate background notice that auto-closes after 5 seconds
yad --geometry 448x1-32+32 --sticky --skip-taskbar --on-top --undecorated --borders 5 \
--image "$ICON_USB" --no-buttons --timeout 5 \
--text "Syncing data to $model... Do not unplug!" &
local sync_notify_pid=$!
# 2. seject runs and blocks here until sync completes
if /usr/local/bin/seject "/dev/$dev" 2>"$ERR_FILE"; then
kill "$sync_notify_pid" 2>/dev/null
notify_remove "$model"
else
kill "$sync_notify_pid" 2>/dev/null
notify_error "$model"
fi
rm -f "$ERR_FILE"
}
show_menu() {
selection=$(list_usb_drives |\
yad --list \
--window-icon="$ICON_USB" \
--title="USB Drives" \
--width=400 --height=300 \
--image="$ICON_USB" --image-on-top \
--column="Device" \
--column="Model" \
--button="Close:1" --button="Remove Selected:0" \
--print-column=1 \
--separator="")
[[ $? -ne 0 ]] && return
[[ -z "$selection" ]] && return
remove_usb "$selection"
}
# --- STAGE 2: THE GUI MENU EXECUTION ---
if [ "$1" == "--gui-menu" ]; then
usb_data=$(list_usb_drives)
if [[ -z "$usb_data" ]]; then
notify_none
auto_close
exit 1
fi
usb_count=$(echo "$usb_data" | grep -c '^')
if (( usb_count == 2 )); then
dev=$(echo "$usb_data" | head -n 1)
remove_usb "$dev"
else
show_menu
fi
auto_close
exit 0
fi
# --- STAGE 1: BACKGROUND EVENT HANDLER (UDEV) ---
case "$1" in
add)
exec > /tmp/usb_udev.log 2>&1
set -x
if [ "$(count_usb_drives)" -gt 0 ] && ! pgrep -u "$USER" -f "yad --notification.*$PIPE" > /dev/null; then
rm -f "$PIPE"
mkfifo "$PIPE"
chown "$USER":"$GROUP" "$PIPE"
sudo -u "$USER" DISPLAY="$DISPLAY" XAUTHORITY="$XAUTHORITY" DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
bash -c '
exec 3<>"'$PIPE'"
/usr/bin/yad \
--notification \
--listen \
--image="usb-tray" \
--text="External USB Storage Connected. Click to Eject." \
--menu="Eject Menu!/usr/local/bin/usb-tray-handler.sh --gui-menu||Exit Icon!quit" \
--command="/usr/local/bin/usb-tray-handler.sh --gui-menu" <&3 &'
fi
;;
remove)
if [ "$(count_usb_drives)" -eq 0 ] && pgrep -u "$USER" -f "yad --notification.*$PIPE" > /dev/null; then
echo "quit" > "$PIPE" 2>/dev/null
rm -f "$PIPE"
fi
;;
esac






