summary refs log tree commit diff
path: root/bin/netm
diff options
context:
space:
mode:
Diffstat (limited to 'bin/netm')
-rwxr-xr-xbin/netm1560
1 files changed, 1560 insertions, 0 deletions
diff --git a/bin/netm b/bin/netm
new file mode 100755
index 0000000..fab7e95
--- /dev/null
+++ b/bin/netm
@@ -0,0 +1,1560 @@
+#!/usr/bin/env bash
+#    __  __                 __   ________  ___       _   __    __
+#   / / / /_  ______  _____/ /  /_  __/  |/  /      / | / /___/ /_
+#  / /_/ / / / / __ \/ ___/ /    / / / /|_/ /_____ /  |/ / _  / __/
+# / __  / /_/ / /_/ / /  / /___ / / / /  / /_____ / /|  /  __/ /_
+#/_/ /_/\__, / .___/_/  /_____//_/ /_/  /_/      /_/ |_/\___/\__/
+#      /____/_/
+#
+
+# Copyright © 2025 Djalel Oukid (sniper1720)
+#
+# Mods:
+# - replace rofi with fuzzel
+# - remove all theme references
+# - fix wifi interface detection
+# - hide extra search icon
+# - replace close and check icons (due to problematic width)
+
+# --- Dependencies Check ---
+if ! command -v fuzzel &>/dev/null; then
+	echo "Error: fuzzel is not installed. Please install it to use this script." >&2
+	exit 1
+fi
+if ! command -v nmcli &>/dev/null; then
+	echo "Error: nmcli is not installed. Please install NetworkManager and nmcli." >&2
+	exit 1
+fi
+
+# --- Icon Variables (Nerd Fonts - Minimalist & Beautiful) ---
+# --- Icons: General UI ---
+icon_search="${icon_search:-""}"
+icon_close="${icon_close:-"󰅗 "}"
+icon_check="${icon_check:-" "}"
+icon_on="${icon_on:-""}"
+icon_off="${icon_off:-""}"
+icon_info="${icon_info:-"󰖐"}"
+icon_refresh="${icon_refresh:-"󰑐"}"
+icon_config="${icon_config:-""}"
+
+# --- Icons: Network Types ---
+icon_network="${icon_network:-"󱂇"}"
+icon_wifi_prompt="${icon_wifi_prompt:-"󱚾"}"
+icon_ethernet="${icon_ethernet:-"󰈀"}"
+icon_vpn="${icon_vpn:-"󰖃"}"
+icon_wireguard="${icon_wireguard:-"󰖆"}"
+icon_hotspot="${icon_hotspot:-"󱄙"}"
+icon_airplane="${icon_airplane:-"󰀝"}"
+
+# --- Icons: Wi-Fi Signal ---
+icon_wifi_full="${icon_wifi_full:-"󰤨"}"
+icon_wifi_good="${icon_wifi_good:-"󰤥"}"
+icon_wifi_medium="${icon_wifi_medium:-"󰤢"}"
+icon_wifi_low="${icon_wifi_low:-"󰤠"}"
+icon_wifi_disconnected="${icon_wifi_disconnected:-"󰤮"}"
+icon_wifi_enable="${icon_wifi_enable:-"󰖩"}"
+icon_wifi_disable="${icon_wifi_disable:-"󰖪"}"
+
+# --- Icons: Security & Status ---
+icon_wifi_secure="${icon_wifi_secure:-""}"
+icon_wifi_open="${icon_wifi_open:-"󰤠"}"
+icon_unlock="${icon_unlock:-""}"
+icon_password="${icon_password:-""}"
+icon_eye="${icon_eye:-"󰛐"}"
+icon_eye_closed="${icon_eye_closed:-"󰛑"}"
+icon_bookmark_saved="${icon_bookmark_saved:-"󰢭"}"
+icon_saved="${icon_saved:-"󰋋"}"
+
+# --- Icons: Menus & Actions ---
+icon_connect="${icon_connect:-"󰚫"}"
+icon_disconnect="${icon_disconnect:-"qa"}"
+icon_vpn_disconnect="${icon_vpn_disconnect:-"󰖂"}"
+icon_trash="${icon_trash:-""}"
+icon_pen="${icon_pen:-"󰑕"}"
+icon_import="${icon_import:-"󰋺"}"
+icon_qrcode="${icon_qrcode:-"󰐲"}"
+
+# --- Icons: Details & Config ---
+icon_active_details="${icon_active_details:-"󰋼"}"
+icon_status_chart="${icon_status_chart:-"󱖫"}"
+icon_interface="${icon_interface:-"󰛨"}"
+icon_devices="${icon_devices:-"󰋽"}"
+icon_chip="${icon_chip:-"󰢮"}"
+icon_ipv4_config="${icon_ipv4_config:-"󰒓"}"
+icon_ipv4_dns="${icon_ipv4_dns:-"󰒍"}"
+icon_ipv6_config="${icon_ipv6_config:-"󰒓"}" # Reuse
+icon_ipv6_dns="${icon_ipv6_dns:-"󰒍"}"       # Reuse
+icon_auto_ip="${icon_auto_ip:-"󰑘"}"
+icon_auto_dns="${icon_auto_dns:-"󰒍"}"
+icon_address="${icon_address:-"󰒓"}"
+icon_gateway="${icon_gateway:-"󰞡"}"
+icon_plug="${icon_plug:-"󱘖"}"
+icon_wireless="${icon_wireless:-"󰑩"}"
+icon_automatic="${icon_automatic:-"󰑘"}"
+
+# --- Icons: Miscellaneous ---
+icon_hidden_network="${icon_hidden_network:-"󰲊"}"
+icon_connect_wired="${icon_connect_wired:-"󱂇"}"
+icon_wired_status="${icon_wired_status:-"󰈁"}"
+
+# --- Translatable / Customizable Messages ---
+tr_checking_wifi_status='Checking Wi-Fi status... Please be patient.'
+tr_scanning_networks='Scanning networks... Please be patient.'
+tr_connecting_to='Connecting... Please be patient.'
+tr_disconnecting_from='Disconnecting... Please be patient.'
+tr_submenu_message='More options'
+tr_disable_message='Disable Wi-Fi'
+tr_enable_message='Enable Wi-Fi'
+tr_interface_message='Interface:'
+tr_known_connections_message='Known connections' # FIX: Removed colon
+tr_available_networks_message='Available Networks'
+tr_available_vpn_profiles_message='Available VPN Profiles'
+tr_autoconnect_message='Autoconnect'
+tr_ipv4_config_message='IPv4 Configuration'
+tr_ipv6_config_message='IPv6 Configuration'
+tr_dns4_message='DNS IPv4'
+tr_dns6_message='DNS IPv6'
+tr_connection_details_message='Connection Details' # New Header
+tr_ip_addr='IP Address'
+tr_gateway='Gateway'
+tr_signal_strength='Signal Strength'
+tr_speed='Link Speed'
+tr_frequency='Frequency'
+tr_mac_addr='MAC Address'
+tr_device='Interface'
+tr_autoip_message='Automatic IP'
+tr_autodns_message='Automatic DNS'
+tr_address_message='Addresses'
+tr_gateway_message='Gateway:'
+tr_forget_message='Forget connection'
+tr_wireguard_enable_message='Toggle VPN'
+tr_rename_connection_message='Rename connection'
+tr_hidden_message='Connect to a hidden network'
+tr_refresh_scan_message='Refresh Scan'                     # New message for refresh option
+tr_no_known_wifi_connections='No known Wi-Fi connections.' # New message
+tr_no_configured_vpns='No VPN connections configured.'     # New message
+tr_no_active_vpns='No active VPN connections.'             # New message
+tr_no_saved_connections='No saved connections found.'      # New message
+tr_no_active_connection='No active connection.'            # New message
+tr_no_ethernet_device='No Ethernet device found.'          # New message
+tr_no_wifi_networks_found='No Wi-Fi networks found.'       # New message for empty scan
+tr_wired_status_message='Wired Status'                     # New message for wired status option
+tr_manage_wired_profile='Manage Active Wired Profile'      # New message for wired profile management
+tr_connect_wired_connection='Connect to Wired Connection:' # Refined prompt
+tr_manage_wired_connections='Manage Wired Connections'     # New message for wired connections management
+tr_import_vpn_message='Import VPN from file'
+tr_import_vpn_prompt='Enter full path to .conf or .ovpn file:'
+tr_status_message=''
+tr_status_connected_to='Connected to'
+tr_status_connected='Connected'
+tr_status_disconnected='Disconnected'
+
+tr_status_disabled='Disabled'
+tr_connect_now_message='Connect Now'
+tr_disconnect_message='Disconnect Now'
+tr_notice_import_success_summary='VPN Imported'
+tr_notice_import_success_body='Successfully imported VPN connection.'
+tr_notice_import_error_summary='Import Error'
+tr_notice_import_error_body='Failed to import VPN connection.'
+tr_notice_file_not_found_body='File not found at specified path.'
+
+# Airplane Mode
+tr_airplane_mode_message='Airplane Mode'
+tr_airplane_on='Airplane Mode Enabled'
+tr_airplane_off='Airplane Mode Disabled'
+
+# QR Code
+tr_edit_password_message='Edit Password' # New translation
+tr_password_prompt='Enter password for'
+tr_password_updated='Password Updated'
+tr_password_update_failed='Failed to update password'
+tr_connection_failed_retry='Connection failed. Update password?'
+tr_qrcode_message='Share via QR Code'
+tr_qrcode_generating='Generating QR Code...'
+tr_qrcode_error='Could not generate QR code. Is qrencode installed?'
+tr_qrcode_no_password='Cannot share network without saved password.'
+
+# Hotspot
+tr_hotspot_message='Create Hotspot'
+tr_hotspot_ssid_prompt='Enter Hotspot SSID:'
+tr_hotspot_password_prompt='Enter Hotspot Password (min 8 chars):'
+tr_hotspot_creating='Creating Hotspot...'
+tr_hotspot_success='Hotspot created successfully!'
+tr_hotspot_error='Failed to create hotspot.'
+tr_notice_unknown_vpn_type_body='Unknown VPN file type. Use .conf or .ovpn.'
+
+# Main menu prompt now includes network and search icons
+tr_main_menu_prompt="$icon_network Network Manager: $icon_search"
+tr_wifi_menu_prompt="$icon_wifi_prompt Wi-Fi: $icon_search"
+tr_wired_menu_prompt="$icon_ethernet Wired: $icon_search"                               # NEW: Wired sub-menu prompt
+tr_vpn_menu_prompt="$icon_vpn_disconnect VPN: $icon_search"                             # NEW: VPN sub-menu prompt
+tr_saved_connections_menu_prompt="$icon_bookmark_saved Saved Connections: $icon_search" # NEW: Saved Connections sub-menu prompt
+tr_status_menu_prompt="$icon_status_chart Status: $icon_search"                         # NEW: Status sub-menu prompt
+
+tr_select_interface_prompt='Select Interface:'
+tr_ask_password_prompt='Enter password:'        # Refined prompt
+tr_menu_dns_prompt='Enter DNS (e.g., 8.8.8.8):' # Refined prompt
+tr_menu_dns_sure_prompt_1='Remove DNS '
+tr_menu_dns_sure_prompt_2='?'
+tr_menu_ip_config_addresses_prompt='Enter address (e.g., 192.168.1.10/24):' # Refined prompt
+tr_menu_ip_config_gateway_prompt='Enter gateway (e.g., 192.168.1.1):'       # Refined prompt
+tr_menu_addresses_prompt='Type or select address (e.g., 192.168.1.10/24):'  # Refined prompt
+tr_menu_addresses_sure_prompt_1='Remove address '
+tr_menu_addresses_sure_prompt_2='?'
+tr_forget_connection_sure_prompt_1='Forget '
+tr_forget_connection_sure_prompt_2='?'
+tr_forget_connection_confirm='Yes, forget'                   # New confirmation text
+tr_rename_connection_prompt='Enter new name for connection:' # Refined prompt
+tr_connect_hidden_prompt='Enter hidden network name:'        # Refined prompt
+
+tr_notice_connected_summary='Connected'
+tr_notice_disconnected_summary='Disconnected'
+tr_notice_error_summary='Connection Error'
+tr_notice_connected_body='Successfully connected to'
+tr_notice_disconnected_body='Successfully disconnected from'
+tr_notice_error_body='Failed to connect to'
+tr_notice_error_disconnect_body='Failed to disconnect from'
+
+# NEW: Messages for password input actions
+tr_show_password_message='Show Password'
+tr_hide_password_message='Hide Password'
+tr_confirm_password_message='Confirm Password'
+tr_edit_password_message='Edit Password'
+tr_edit_password_prompt='Enter new password:'
+
+# --- Global Variables ---
+program_name="$(basename "$0")"
+LOADING_ROFI_PID=""
+mapfile -t interfaces < <(nmcli --colors no -t -f TYPE,DEVICE device status | awk -F ':' '$1 == "wifi" {print $2}')
+if [ -z "${interfaces[0]}" ]; then
+	echo "$program_name: No Wi-Fi interfaces detected." >&2
+	exit 2
+fi
+interface_to_use="${interfaces[0]}"
+
+# --- Helper Functions ---
+
+# Function to display a menu (form 1: selection, 2: text input with list, 3: password input, 4: message box, 5: text input no list)
+# Now accepts optional extra flags to pass to rofi.
+display_menu() {
+	local form="$1"
+	local prompt_text="$2"
+	local prompt_icon="${3:-}" # Optional: icon to prepend to prompt
+	local extra_flags="${4:-}" # Optional: extra flags for the fuzzel command
+	local rofi_prompt rofi_flags options_list
+
+	# If a prompt_icon is provided and the prompt_text doesn't already start with it, prepend it.
+	if [ -n "$prompt_icon" ] && ! echo "$prompt_text" | grep -qE "^$prompt_icon"; then
+		rofi_prompt="$prompt_icon $prompt_text"
+	else
+		rofi_prompt="$prompt_text"
+	fi
+
+	local result
+
+	case $form in
+	1 | 2 | 4)
+		options_list=$(cat)
+		maxw=$(($(wc -L <<<"$rofi_prompt\n$options_list") + 2))
+		;&
+	1) # Selection menu with a list of options passed via STDIN
+		result=$(echo -e "$options_list" | fuzzel -di -w $maxw $extra_flags -p "$rofi_prompt")
+		;;
+	2) # Generic text input menu with a list
+		result=$(echo -e "$options_list" | fuzzel -d -w $maxw $extra_flags -p "$rofi_prompt")
+		;;
+	3) # Password input menu (minimalist)
+		result=$(fuzzel -d --password $extra_flags -p "$rofi_prompt")
+		;;
+	4) # Message box menu (no input bar)
+		result=$(echo -e "$options_list" | fuzzel -di -w $maxw --hide-prompt $extra_flags -p "$rofi_prompt")
+		;;
+	5) # Text input menu (no list view)
+		result=$(fuzzel -d $extra_flags -p "$rofi_prompt")
+		;;
+	esac
+	echo "$result"
+}
+
+# --- Rofi-based Notification System ---
+
+# Shows a non-blocking "loading" Rofi window and stores its PID.
+show_loading_notification() {
+	local message="$1"
+	# Use dmenu mode to get the themed window, but completely override the
+	# mainbox layout to show only a single, centered textbox.
+	echo "..." | fuzzel -d -p "$message" &
+	LOADING_ROFI_PID=$!
+}
+
+# Kills the loading notification Rofi window if it's running.
+kill_loading_notification() {
+	if [ -n "$LOADING_ROFI_PID" ] && ps -p "$LOADING_ROFI_PID" >/dev/null; then
+		kill "$LOADING_ROFI_PID"
+		LOADING_ROFI_PID=""
+	fi
+}
+
+# Shows a final, blocking Rofi message for results (Success, Error, etc.).
+# Optionally sends a system notification if notify-send is available.
+send_notification() {
+	local summary="$1"
+	local body="$2"
+
+	# Try system notification first (non-blocking)
+	if command -v notify-send &>/dev/null; then
+		fyi -a netm "$icon_network $summary" "$body"
+	else
+		# Fallback to Rofi message
+		show_message "$body" "$summary"
+	fi
+}
+
+# Function to show a message (stays open until dismissed, with "OK" button)
+# Now accepts an optional custom_prompt and a flag to include the "OK" button.
+show_message() {
+	local msg="$1"
+	local custom_prompt="${2:-Info}" # Default to "Info" if no custom prompt is provided
+	local include_ok="${3:-true}"    # Default to true
+	local options="$msg"
+
+	if [ "$include_ok" = "true" ]; then
+		options+="\n$icon_check OK"
+	fi
+	# Use form 4 to disable the input bar for these messages
+	echo -e "$options" | display_menu 4 "$custom_prompt" ""
+}
+
+# Function to display an informational message with a "Back" button
+# Now accepts an optional custom_prompt
+display_info_message() {
+	local msg="$1"
+	local custom_prompt="${2:-Info}" # Default to "Info" if no custom prompt is provided
+	local options="$msg\n$icon_close Back"
+	# Use form 4 to disable the input bar for these messages
+	local chosen=$(echo -e "$options" | display_menu 4 "$custom_prompt" "")
+	if [[ "$chosen" =~ "^$icon_close Back" ]]; then
+		return
+	fi
+}
+
+# Handles password input with a dynamic, interactive menu for showing, hiding, and editing.
+ask_password() {
+	local password_input
+	local password_shown="false" # 'true' or 'false'
+
+	# Step 1: Initial password prompt
+	password_input=$(echo "" | display_menu 3 "$tr_ask_password_prompt" "")
+
+	# If user cancels the initial prompt (presses Esc or enters nothing), return empty.
+	if [ -z "$password_input" ]; then
+		return 1
+	fi
+
+	# Step 2: Interactive action menu loop
+	while true; do
+		local options=""
+		local password_display=""
+
+		# Determine how to display the password
+		if [ "$password_shown" = "true" ]; then
+			password_display="$password_input"
+			options+="$icon_eye_closed $tr_hide_password_message: $password_display\n"
+		else
+			# Create a bullet point string of the same length as the password
+			password_display=$(printf '•%.0s' $(seq 1 ${#password_input}))
+			options+="$icon_eye $tr_show_password_message: $password_display\n"
+		fi
+
+		# Add other actions
+		options+="$icon_pen $tr_edit_password_message\n"
+		options+="$icon_unlock $tr_confirm_password_message\n"
+		options+="$icon_close Back"
+
+		# Display the interactive menu
+		local action_choice=$(echo -e "$options" | display_menu 1 "Password Actions" "")
+
+		# Handle user's choice
+		if [ -z "$action_choice" ] || [[ "$action_choice" =~ "^$icon_close Back" ]]; then
+			# User cancelled at action menu
+			return 1
+		fi
+
+		case "$action_choice" in
+		*"$tr_show_password_message"* | *"$tr_hide_password_message"*)
+			# Toggle password visibility
+			if [ "$password_shown" = "true" ]; then
+				password_shown="false"
+			else
+				password_shown="true"
+			fi
+			# Loop continues, redrawing the menu
+			;;
+		*"$tr_edit_password_message"*)
+			# Ask for a new password, pre-filling the input with the current password.
+			local new_password=$(echo "" | display_menu 2 "$tr_edit_password_prompt" "" "-filter \"$password_input\"")
+			# Only update if the user entered something and didn't cancel
+			if [ -n "$new_password" ]; then
+				password_input="$new_password"
+				password_shown="false" # Hide new password by default
+			fi
+			# Loop continues, redrawing the menu
+			;;
+		*"$tr_confirm_password_message"*)
+			# Return the confirmed password
+			echo "$password_input"
+			return
+			;;
+		esac
+	done
+}
+
+# Show details for the active connection
+show_connection_details() {
+	local active_ssid="$1"
+	local device="$2"
+
+	show_loading_notification "Gathering details..."
+
+	# Get details using nmcli device show
+	# We grep for specific fields
+	local info=$(nmcli -t -f GENERAL,IP4,IP6 device show "$device")
+
+	# Extract fields (hacky but effective for standard nmcli output)
+	# MAC often contains colons, so we strip the field name "GENERAL.HWADDR:"
+	local ipv4=$(echo "$info" | grep "IP4.ADDRESS\[1\]" | cut -d':' -f2)
+	local gateway=$(echo "$info" | grep "IP4.GATEWAY" | cut -d':' -f2)
+	local hwaddr=$(echo "$info" | grep "GENERAL.HWADDR" | sed 's/^GENERAL.HWADDR://')
+	local state=$(echo "$info" | grep "GENERAL.STATE" | cut -d':' -f2)
+
+	# Wifi specific details
+	local wifi_info=$(nmcli -t -f IN-USE,SSID,MODE,CHAN,RATE,SIGNAL,BARS,SECURITY device wifi list | grep "^\*")
+	# Format: *:SSID:Infra:Chan:Rate:Signal:Bars:Sec
+	local chan=$(echo "$wifi_info" | cut -d':' -f4)
+	local rate=$(echo "$wifi_info" | cut -d':' -f5)
+	local signal=$(echo "$wifi_info" | cut -d':' -f6)
+	local bars=$(echo "$wifi_info" | cut -d':' -f7)
+	local freq=""
+
+	# Determine frequency band roughly by channel
+	if [ "$chan" -gt 14 ]; then freq="5 GHz"; else freq="2.4 GHz"; fi
+
+	kill_loading_notification
+
+	local details=""
+	details+="$icon_address $tr_ip_addr: $ipv4\n"
+	details+="$icon_gateway $tr_gateway: $gateway\n"
+	details+="$icon_wifi_full $tr_signal_strength: $signal% ($bars)\n"
+	details+="$icon_on $tr_speed: $rate\n"
+	details+="$icon_wireless $tr_frequency: $freq (Ch $chan)\n"
+	details+="$icon_devices $tr_mac_addr: $hwaddr\n"
+	details+="$icon_chip $tr_device: $device"
+
+	display_info_message "$details" "$tr_connection_details_message"
+}
+
+# --- Dedicated Wi-Fi Scan Function ---
+perform_wifi_scan() {
+	show_loading_notification "$tr_scanning_networks"
+
+	# Get list of known SSIDs, delimited by |
+	# We use tr to replace newline with |
+	local known_ssids=$(nmcli -t -f 802-11-wireless.ssid connection show | tr '\n' '|')
+	# Add leading/trailing pipe for exact matching
+	known_ssids="|$known_ssids"
+
+	local scan_output=$(nmcli --colors no --get-values SECURITY,SIGNAL,SSID,IN-USE device wifi list --rescan auto | awk -F ':' \
+		-v icon_wifi_secure="$icon_wifi_secure" \
+		-v icon_wifi_open="$icon_wifi_open" \
+		-v icon_wifi_full="$icon_wifi_full" \
+		-v icon_wifi_good="$icon_wifi_good" \
+		-v icon_wifi_medium="$icon_wifi_medium" \
+		-v icon_wifi_low="$icon_wifi_low" \
+		-v icon_check="$icon_check" \
+		-v icon_unlock="$icon_unlock" \
+		-v known_ssids="$known_ssids" \
+		'
+    BEGIN { x = 1 }
+    {
+        # 1. First Icon: Signal Strength (Always shown)
+        wifi_signal_icon = icon_wifi_low;
+        if ($2 > 75) wifi_signal_icon = icon_wifi_full;
+        else if ($2 > 50) wifi_signal_icon = icon_wifi_good;
+        else if ($2 > 25) wifi_signal_icon = icon_wifi_medium;
+
+        ssid = $3;
+        if (ssid == "") ssid = "<hidden>";
+
+        # 2. Second Icon: Status (Priority: Active > Secure > Open/Known)
+        # User Logic: 
+        # - Active -> Checkmark
+        # - Secure (Even if known) -> Lock
+        # - Open -> Unlock
+
+        status_icon = icon_unlock; # Default to Unlock (Open)
+
+        if ($4 == "*") {
+             status_icon = icon_check; # Active -> Check
+        } else if ($1 ~ /^WPA/) {
+             status_icon = icon_wifi_secure; # Secure -> Lock
+        }
+        # Else remains Unlock (Open)
+
+        # Layout: [Signal] [Status] SSID (Sig%)
+        # Note: We purposely put two icons on the left as requested.
+        formatted_entry = wifi_signal_icon " " status_icon " " ssid " (" $2 "%)";
+
+        if ($4 == "*") {
+            networks[0] = formatted_entry;
+        } else {
+            networks[x++] = formatted_entry;
+        }
+    }
+    END {
+        if (networks[0] != "") {
+            print networks[0];
+        }
+        for (i = 1; i < x; i++) {
+            print networks[i];
+        }
+    }
+    ')
+	kill_loading_notification
+	echo "$scan_output"
+}
+
+# --- Menu for Available Wi-Fi Networks ---
+menu_available_wifi_networks() {
+	local wifi_list options chosen
+	while true; do
+		wifi_list=$(perform_wifi_scan)
+
+		options="$icon_refresh  $tr_refresh_scan_message\n"
+		if [ -z "$wifi_list" ]; then
+			options+="$icon_wifi_disconnected  $tr_no_wifi_networks_found\n"
+		else
+			options+="$wifi_list\n"
+		fi
+		options+="$icon_close Back" # Always at the bottom
+
+		# Prompt for this menu is just the search icon
+		chosen=$(echo -e "$options" | display_menu 1 "$tr_available_networks_message" "$icon_search")
+
+		# Handle Esc or "Back" selection
+		if [ -z "$chosen" ] || [[ "$chosen" =~ "^$icon_close Back" ]]; then
+			return # Go back to parent menu (menu_wifi)
+		fi
+
+		case "$chosen" in
+		"$icon_refresh  $tr_refresh_scan_message")
+			continue # Restart the while loop to re-run scan and redisplay menu
+			;;
+		"$icon_wifi_disconnected  $tr_no_wifi_networks_found")
+			# Do nothing if "No Wi-Fi networks found" is selected
+			continue
+			;;
+		*)
+			# This block should only be reached when a network is explicitly chosen
+			connect_wifi "$chosen"
+			;;
+		esac
+	done
+}
+
+# --- Main Wi-Fi menu (toggle, hidden, known, available networks) ---
+menu_wifi() {
+	local connection_state options chosen
+	while true; do
+		show_loading_notification "$tr_checking_wifi_status"
+		connection_state=$(nmcli --colors no --get-values WIFI general)
+		local active_connection_info=$(nmcli -t -f NAME,DEVICE connection show --active | grep -E "wl*" | head -n 1)
+		kill_loading_notification
+
+		local status_line=""
+		if [ "$connection_state" = "disabled" ]; then
+			status_line="$icon_wifi_disable $tr_status_message $tr_status_disabled"
+			options="$icon_wifi_enable  $tr_enable_message\n"
+		else
+			if [ -n "$active_connection_info" ]; then
+				local active_ssid=$(echo "$active_connection_info" | cut -d':' -f1)
+				local active_device=$(echo "$active_connection_info" | cut -d':' -f2)
+				status_line="$icon_wifi_full $tr_status_message $tr_status_connected_to '$active_ssid' ($active_device)"
+			else
+				status_line="$icon_wifi_disconnected $tr_status_message $tr_status_disconnected"
+			fi
+			options="$icon_wifi_disable  $tr_disable_message\n"
+			${interfaces[1]:+options+="$icon_interface  $tr_interface_message ${interface_to_use}\n"}
+			# Add "Available Networks" as a sub-menu
+			options+="$icon_wireless  $tr_available_networks_message\n"
+		fi
+
+		local full_options="$status_line\n"
+		full_options+="$options"
+		full_options+="$icon_hotspot  $tr_hotspot_message\n"                  # NEW: Create Hotspot
+		full_options+="$icon_bookmark_saved  $tr_known_connections_message\n" # FIX: Using icon_bookmark_saved
+		full_options+="$icon_hidden_network  $tr_hidden_message\n"            # FIX: Using icon_hidden_network
+		full_options+="$icon_close Back"                                      # Always at the bottom
+
+		chosen=$(echo -e "$full_options" | display_menu 1 "$tr_wifi_menu_prompt" "") # Pass "" for prompt_icon
+
+		# Handle Esc or "Back" selection
+		if [ -z "$chosen" ] || [[ "$chosen" =~ "^$icon_close Back" ]]; then
+			return # Go back to parent menu (main_menu)
+		fi
+
+		case "$chosen" in
+		"$icon_wifi_enable  $tr_enable_message")
+			nmcli radio wifi on
+			;;
+		"$icon_wifi_disable  $tr_disable_message")
+			nmcli radio wifi off
+			;;
+		*"$tr_interface_message"*) select_interface ;;
+		"$icon_wireless  $tr_available_networks_message") menu_available_wifi_networks ;;
+		*"$tr_known_connections_message"*) menu_known_connections "wifi" ;;
+		*"$tr_hidden_message"*) connect_hidden ;;
+		*"$tr_hotspot_message"*) create_hotspot ;;
+		*"$tr_status_message"*)
+			# Show details if connected
+			if [ -n "$active_connection_info" ]; then
+				local active_ssid=$(echo "$active_connection_info" | cut -d':' -f1)
+				local active_device=$(echo "$active_connection_info" | cut -d':' -f2)
+				show_connection_details "$active_ssid" "$active_device"
+			fi
+			;;
+		*)
+			show_message "Invalid option selected: $chosen"
+			;;
+		esac
+	done
+}
+
+select_interface() {
+	local chosen_interface=$( (
+		for ((i = 0; i < ${#interfaces[@]}; i++)); do echo "$icon_interface  ${interfaces[$i]}"; done
+		echo "$icon_close Back"
+	) | display_menu 1 "$tr_select_interface_prompt" "") # Pass "" for prompt_icon
+
+	if [ -z "$chosen_interface" ]; then
+		return
+	elif [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+		return
+	else
+		interface_to_use="${chosen_interface:3}"
+	fi
+}
+
+# Connect to a hidden Wi-Fi network
+connect_hidden() {
+	local wifi_name=$(echo "" | display_menu 5 "$tr_connect_hidden_prompt" "") # Pass "" for prompt_icon
+
+	if [ -z "$wifi_name" ]; then
+		return # User cancelled or pressed back
+	fi
+
+	local wifi_password=$(ask_password)
+
+	if [ -z "$wifi_password" ]; then
+		show_message "Connection cancelled. No password provided."
+		return
+	fi
+
+	show_loading_notification "$tr_connecting_to"
+	if nmcli --wait 15 device wifi connect "$wifi_name" hidden yes password "$wifi_password"; then
+		kill_loading_notification
+		send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$wifi_name'"
+		exit 0
+	else
+		kill_loading_notification
+		send_notification "$tr_notice_error_summary" "$tr_notice_error_body '$wifi_name'"
+	fi
+}
+
+# Connect to a visible Wi-Fi network
+connect_wifi() {
+	local chosen_entry="$1"
+
+	if [[ "$chosen_entry" == *"$icon_check"* ]]; then
+		local ssid_from_active=$(nmcli -t -f active,ssid dev wifi | grep "^yes" | cut -d':' -f2)
+		local active_uuid=$(nmcli -t -f UUID,TYPE,ACTIVE connection show | grep ":802-11-wireless:yes" | cut -d':' -f1 | head -n1)
+
+		if [ -n "$active_uuid" ]; then
+			if [ -z "$ssid_from_active" ]; then
+				local temp_ssid=$(echo "$chosen_entry" | sed -E 's/ \([0-9]+%\).*$//')
+				ssid_from_active=$(echo "$temp_ssid" | sed -E 's/^(󰄬 |󰤫 |󰤪 |󰤩 |󰤨 |󰤧 |󰤦 |󰤥 |󰤤 |󰤣 |󰤢 |󰤡 |󰤠 )+//' | xargs)
+			fi
+			menu_connection "$ssid_from_active" "$active_uuid"
+			return
+		fi
+	fi
+
+	local temp_ssid=$(echo "$chosen_entry" | sed -E 's/ \([0-9]+%\).*$//')
+	local wifi_ssid=$(echo "$temp_ssid" | sed -E 's/^(󰤨 |󰤥 |󰤢 |󰤯 |󰤫 |󰤪 | | | )+//' | xargs)
+
+	local is_secure=$(echo "$chosen_entry" | grep -q "$icon_wifi_secure" && echo "yes" || echo "no")
+
+	# 1. Check if already connected
+	local active_ssid=$(nmcli -t -f active,ssid dev wifi | grep "^yes" | cut -d':' -f2)
+	if [ "$wifi_ssid" = "$active_ssid" ]; then
+		local active_uuid=$(nmcli -t -f UUID,TYPE,ACTIVE connection show | grep ":802-11-wireless:yes" | cut -d':' -f1 | head -n1)
+		if [ -n "$active_uuid" ]; then
+			menu_connection "$wifi_ssid" "$active_uuid"
+			return
+		fi
+		show_message "Already connected to $wifi_ssid."
+		return
+	fi
+
+	# 2. Check if we have a saved connection (even if not active)
+	local saved_uuid=""
+	# Use standard fields (UUID, TYPE) that are guaranteed to exist
+	local saved_list=$(nmcli -t -f UUID,TYPE connection show)
+
+	while IFS=: read -r uuid type; do
+		# Check against both known Wi-Fi type strings
+		if [[ "$type" == "802-11-wireless" || "$type" == "wifi" ]]; then
+			# Get the SSID for this connection specifically
+			local ssid_check=$(nmcli -t -f 802-11-wireless.ssid connection show "$uuid" 2>/dev/null)
+			# FIX: output is '802-11-wireless.ssid:SSID', we must strip the prefix
+			ssid_check="${ssid_check#*:}"
+
+			if [ "$ssid_check" = "$wifi_ssid" ]; then
+				saved_uuid="$uuid"
+				break
+			fi
+		fi
+	done <<<"$saved_list"
+
+	if [ -n "$saved_uuid" ]; then
+		menu_connection "$wifi_ssid" "$saved_uuid"
+		return
+	fi
+
+	# 3. New Connection: Ask for password if secure
+	local connection_result
+	if [ "$is_secure" = "yes" ]; then
+		local wifi_password=$(ask_password)
+		if [ -z "$wifi_password" ]; then
+			show_message "Connection cancelled. No password provided."
+			return
+		fi
+
+		show_loading_notification "$tr_connecting_to"
+		nmcli --wait 15 device wifi connect "$wifi_ssid" ifname "$interface_to_use" password "$wifi_password"
+		connection_result=$?
+	else
+		show_loading_notification "$tr_connecting_to"
+		nmcli --wait 15 device wifi connect "$wifi_ssid" ifname "$interface_to_use"
+		connection_result=$?
+	fi
+	kill_loading_notification
+
+	if [ $connection_result -eq 0 ]; then
+		send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$wifi_ssid'"
+		exit 0
+	else
+		send_notification "$tr_notice_error_summary" "$tr_notice_error_body '$wifi_ssid'"
+	fi
+}
+
+# --- IP Configuration Menus ---
+
+# Manage individual IP addresses for a connection
+menu_addresses() {
+	local connection_uuid="$1"
+	local ipv="$2"
+	local -a addresses_list
+	local sure chosen
+
+	while true; do
+		mapfile -t addresses_list < <(nmcli --get-values ipv${ipv}.addresses connection show "$connection_uuid" | sed 's/,/\n/g')
+		local options=$(printf "%s\n" "${addresses_list[@]}")
+		options+="$icon_close Back"
+
+		chosen=$(echo -e "$options" | display_menu 1 "$tr_menu_addresses_prompt" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		local found=0
+		for addr in "${addresses_list[@]}"; do
+			if [ "$chosen" = "$addr" ]; then
+				found=1
+				sure=$(echo -e "$icon_check\n$icon_close Back" | display_menu 1 "$tr_menu_addresses_sure_prompt_1 ${chosen}$tr_menu_addresses_sure_prompt_2" "") # Pass "" for prompt_icon
+				if [ -z "$sure" ] || [[ "$sure" =~ ^"$icon_close Back" ]]; then continue; fi                                                                     # User cancelled confirmation
+				if [[ "$sure" =~ ^"$icon_check" ]]; then
+					nmcli connection modify uuid "$connection_uuid" -ipv${ipv}.addresses "$chosen"
+					if [ ${#addresses_list[@]} -eq 1 ]; then
+						nmcli connection modify uuid "$connection_uuid" ipv${ipv}.gateway ''
+						nmcli connection modify uuid "$connection_uuid" ipv${ipv}.method auto
+					fi
+				fi
+				break
+			fi
+		done
+
+		if [ "$found" -eq 0 ]; then
+			nmcli connection modify uuid "$connection_uuid" +ipv${ipv}.addresses "$chosen"
+			nmcli connection modify uuid "$connection_uuid" ipv${ipv}.method manual
+		fi
+	done
+}
+
+# Configure IP method (auto/manual), gateway
+menu_ip_config() {
+	local chosen_connection_name="$1"
+	local connection_uuid="$2"
+	local ipv="$3"
+	local autoip_state autodns_state message new_gateway chosen
+
+	while true; do
+		autoip_state="$([ "$(nmcli --get-values ipv${ipv}.method connection show "$connection_uuid")" = "auto" ] && echo "$icon_on" || echo "$icon_off")"
+
+		local current_gateway=$(nmcli --get-values ipv${ipv}.gateway connection show "$connection_uuid")
+		local current_addresses=$(nmcli --get-values ipv${ipv}.addresses connection show "$connection_uuid" | sed 's/,/\n/g')
+
+		local options=""
+		options+="$icon_auto_ip  $tr_autoip_message  $autoip_state\n"
+
+		if [ "$autoip_state" = "$icon_on" ]; then
+			autodns_state="$([ "$(nmcli --get-values ipv${ipv}.ignore-auto-dns connection show "$connection_uuid")" = "no" ] && echo "$icon_on" || echo "$icon_off")"
+			options+="$icon_auto_dns  $tr_autodns_message  $autodns_state\n"
+		else
+			options+="$icon_address  $tr_address_message: ${current_addresses:-N/A}\n"
+			options+="$icon_gateway  $tr_gateway_message ${current_gateway:-N/A}\n"
+			options+="$icon_gateway  $tr_gateway_message ${current_gateway:-N/A}\n"
+		fi
+		options+="$icon_ipv4_dns  DNS Configuration\n"
+		options+="$icon_close Back"
+
+		chosen=$(echo -e "$options" | display_menu 1 "$chosen_connection_name (IPv$ipv)" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		case "$chosen" in
+		*"DNS Configuration"*)
+			menu_dns "$connection_uuid" "$ipv"
+			;;
+		*"$tr_autoip_message"*)
+			if [ "$autoip_state" = "$icon_on" ]; then
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.method manual
+			else
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.method auto
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.gateway ''
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.addresses ''
+			fi
+			;;
+		*"$tr_autodns_message"*)
+			if [ "$autodns_state" = "$icon_on" ]; then
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.ignore-auto-dns yes
+			else
+				nmcli connection modify uuid "$connection_uuid" ipv${ipv}.ignore-auto-dns no
+			fi
+			;;
+		*"$tr_address_message"*)
+			menu_addresses "$connection_uuid" "$ipv"
+			;;
+		*"$tr_gateway_message"*)
+			new_gateway=$(echo "$icon_close Back" | display_menu 1 "$tr_menu_ip_config_gateway_prompt" "") # Pass "" for prompt_icon
+			if [ -z "$new_gateway" ] || [[ "$new_gateway" =~ ^"$icon_close Back" ]]; then continue; fi
+			nmcli connection modify uuid "$connection_uuid" ipv${ipv}.gateway "$new_gateway"
+			;;
+		esac
+	done
+}
+
+# Configure DNS servers for a connection
+menu_dns() {
+	local connection_uuid="$1"
+	local ipv="$2"
+	local -a dns_list
+	local sure chosen
+
+	while true; do
+		mapfile -t dns_list < <(nmcli --get-values ipv${ipv}.dns connection show "$connection_uuid" | sed 's/,/\n/g')
+		local options=$(printf "%s\n" "${dns_list[@]}")
+		options+="$icon_close Back"
+
+		chosen=$(echo -e "$options" | display_menu 1 "$tr_menu_dns_prompt" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		local found=0
+		for dns_entry in "${dns_list[@]}"; do
+			if [ "$chosen" = "$dns_entry" ]; then
+				found=1
+				sure=$(echo -e "$icon_check\n$icon_close Back" | display_menu 1 "$tr_menu_dns_sure_prompt_1 ${chosen}$tr_menu_dns_sure_prompt_2" "") # Pass "" for prompt_icon
+				if [ -z "$sure" ] || [[ "$sure" =~ ^"$icon_close Back" ]]; then continue; fi                                                         # User cancelled confirmation
+				if [[ "$sure" =~ ^"$icon_check" ]]; then
+					nmcli connection modify uuid "$connection_uuid" -ipv${ipv}.dns "$chosen"
+				fi
+				break
+			fi
+		done
+
+		if [ "$found" -eq 0 ]; then
+			nmcli connection modify uuid "$connection_uuid" +ipv${ipv}.dns "$chosen"
+		fi
+	done
+}
+
+forget_connection() {
+	local chosen_connection_name="$1"
+	local connection_uuid="$2"
+	local options="$icon_check $tr_forget_connection_confirm\n$icon_close Back"
+	local sure=$(echo -e "$options" | display_menu 4 "$tr_forget_connection_sure_prompt_1 ${chosen_connection_name}$tr_forget_connection_sure_prompt_2" "")
+
+	if [ -z "$sure" ] || [[ "$sure" =~ ^"$icon_close Back" ]]; then return 1; fi # User cancelled or pressed back
+	if [[ "$sure" =~ ^"$icon_check $tr_forget_connection_confirm" ]]; then
+		nmcli connection delete uuid "$connection_uuid" && return 0
+	fi
+	return 1
+}
+
+rename_connection() {
+	local connection_uuid="$1"
+	local new_name=$(echo "" | display_menu 5 "$tr_rename_connection_prompt" "") # Pass "" for prompt_icon
+
+	if [ -z "$new_name" ]; then return 1; fi # User cancelled or pressed back
+
+	nmcli connection modify uuid "$connection_uuid" connection.id "$new_name"
+	return $?
+}
+
+edit_connection_password() {
+	local connection_uuid="$1"
+	local connection_name="$2"
+
+	# Prompt for new password
+	local pass=$(ask_password "$connection_name")
+
+	if [ -z "$pass" ]; then return 1; fi # Cancelled
+
+	# Update the connection
+	if nmcli connection modify uuid "$connection_uuid" wifi-sec.psk "$pass"; then
+		send_notification "$tr_password_updated" "$tr_notice_connected_body '$connection_name'"
+		return 0
+	else
+		send_notification "$tr_password_update_failed" "$tr_notice_error_body '$connection_name'"
+		return 1
+	fi
+}
+
+# Menu for individual connection settings (Wi-Fi)
+menu_connection() {
+	local chosen_connection_name="$1"
+	local connection_uuid="$2"
+	local autoconnect_state chosen
+
+	while true; do
+		autoconnect_state="$([ "$(nmcli --get-values connection.autoconnect connection show "$connection_uuid")" = "yes" ] && echo "$icon_on" || echo "$icon_off")"
+
+		# Check if this connection is currently active
+		local is_active=$(nmcli -t -f UUID connection show --active | grep -q "$connection_uuid" && echo "yes" || echo "no")
+
+		local options="$icon_automatic  $tr_autoconnect_message  $autoconnect_state\n"
+
+		if [ "$is_active" = "yes" ]; then
+			options+="$icon_wifi_disconnected  $tr_disconnect_message\n"
+		else
+			options+="$icon_connect  $tr_connect_now_message\n"
+		fi
+
+		options+="$icon_ipv4_config  $tr_ipv4_config_message\n"
+		options+="$icon_ipv6_config  $tr_ipv6_config_message\n"
+		options+="$icon_trash  $tr_forget_message\n"
+		options+="$icon_pen  $tr_rename_connection_message\n"
+		options+="$icon_password  $tr_edit_password_message\n" # NEW: Edit Password option
+		# Only show QR code for Wi-Fi connections
+		if [ "$(nmcli -g connection.type connection show "$connection_uuid")" = "802-11-wireless" ]; then
+			options+="$icon_qrcode  $tr_qrcode_message\n"
+		fi
+		options+="$icon_close Back"
+
+		chosen=$(echo -e "$options" | display_menu 1 "$chosen_connection_name" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		case "$chosen" in
+		*"$tr_autoconnect_message"*)
+			if [ "$autoconnect_state" = "$icon_on" ]; then
+				nmcli connection modify uuid "$connection_uuid" autoconnect no
+			else
+				nmcli connection modify uuid "$connection_uuid" autoconnect yes
+			fi
+			;;
+		"$icon_connect  $tr_connect_now_message")
+			show_loading_notification "$tr_connecting_to"
+			if nmcli connection up uuid "$connection_uuid"; then
+				kill_loading_notification
+				send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$chosen_connection_name'"
+				exit 0
+			else
+				kill_loading_notification
+				# Smart Recovery: Prompt to update password
+				# Check if user wants to retry with new password
+				local retry_choice=$(echo -e "$icon_check Yes\n$icon_close No" | display_menu 2 "$tr_connection_failed_retry" "")
+				if [[ "$retry_choice" =~ ^"$icon_check" ]]; then
+					if edit_connection_password "$connection_uuid" "$chosen_connection_name"; then
+						# Retry connection
+						show_loading_notification "$tr_connecting_to"
+						if nmcli connection up uuid "$connection_uuid"; then
+							kill_loading_notification
+							send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$chosen_connection_name'"
+							exit 0
+						fi
+						kill_loading_notification
+					fi
+				fi
+				send_notification "$tr_notice_error_summary" "$tr_notice_error_body '$chosen_connection_name'"
+			fi
+			;;
+		"$icon_wifi_disconnected  $tr_disconnect_message")
+			# Disconnect logic
+			show_loading_notification "Disconnecting..."
+			if nmcli connection down uuid "$connection_uuid"; then
+				kill_loading_notification
+				send_notification "Disconnected" "Disconnected from '$chosen_connection_name'"
+				return
+			else
+				kill_loading_notification
+				show_message "Failed to disconnect."
+			fi
+			;;
+		*"$tr_ipv4_config_message"*) menu_ip_config "$chosen_connection_name" "$connection_uuid" "4" ;;
+		*"$tr_ipv6_config_message"*) menu_ip_config "$chosen_connection_name" "$connection_uuid" "6" ;;
+		*"$tr_forget_message"*) forget_connection "$chosen_connection_name" "$connection_uuid" && return ;;
+		*"$tr_rename_connection_message"*)
+			if rename_connection "$connection_uuid"; then
+				show_message "Connection renamed."
+				chosen_connection_name=$(nmcli --get-values connection.id connection show "$connection_uuid")
+			else
+				show_message "Failed to rename connection."
+			fi
+			;;
+		*"$tr_edit_password_message"*)
+			edit_connection_password "$connection_uuid" "$chosen_connection_name"
+			;;
+		*"$tr_qrcode_message"*)
+			local ssid=$(nmcli -g 802-11-wireless.ssid connection show "$connection_uuid")
+			local security=$(nmcli -g 802-11-wireless-security.key-mgmt connection show "$connection_uuid" | sed 's/wpa-psk/WPA/; s/None/nopass/')
+			local password=$(nmcli -s -g 802-11-wireless-security.psk connection show "$connection_uuid")
+			show_qrcode "$ssid" "$security" "$password"
+			;;
+		esac
+	done
+}
+
+# Menu for Wireguard connection settings
+menu_wireguard_connection() {
+	local chosen_connection_name="$1"
+	local connection_uuid="$2"
+	local state autoconnect_state chosen
+
+	while true; do
+		state="$([ "$(nmcli --get-values GENERAL.STATE connection show "$connection_uuid")" = "activated" ] && echo "$icon_on" || echo "$icon_off")"
+		autoconnect_state="$([ "$(nmcli --get-values connection.autoconnect connection show "$connection_uuid")" = "yes" ] && echo "$icon_on" || echo "$icon_off")"
+		local options="$icon_plug  $tr_wireguard_enable_message  $state\n"
+		options+="$icon_automatic  $tr_autoconnect_message  $autoconnect_state\n"
+		options+="$icon_pen  $tr_rename_connection_message\n"
+		options+="$icon_trash  $tr_forget_message\n"
+		options+="$icon_close Back"
+
+		chosen=$(echo -e "$options" | display_menu 1 "$chosen_connection_name" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		case "$chosen" in
+		*"$tr_wireguard_enable_message"*)
+			if [ "$state" = "$icon_on" ]; then
+				nmcli connection down uuid "$connection_uuid"
+			else
+				nmcli connection up uuid "$connection_uuid"
+			fi
+			;;
+		*"$tr_autoconnect_message"*)
+			if [ "$autoconnect_state" = "$icon_on" ]; then
+				nmcli connection modify uuid "$connection_uuid" autoconnect no
+			else
+				nmcli connection modify uuid "$connection_uuid" autoconnect yes
+			fi
+			;;
+		*"$tr_rename_connection_message"*)
+			if rename_connection "$connection_uuid"; then
+				show_message "Connection renamed."
+				chosen_connection_name=$(nmcli --get-values connection.id connection show "$connection_uuid")
+			else
+				show_message "Failed to rename connection."
+			fi
+			;;
+		*"$tr_forget_message"*) forget_connection "$chosen_connection_name" "$connection_uuid" && return ;;
+		esac
+	done
+}
+
+# Menu for listing and managing known/saved connections
+menu_known_connections() {
+	local connection_filter="$1"
+	local icon_for_type
+	local menu_type_function
+	local profiles_list_raw chosen
+	local prompt_to_use
+
+	case "$connection_filter" in
+	"wifi")
+		icon_for_type="$icon_wireless"
+		menu_type_function="menu_connection"
+		profiles_list_raw=$(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' -v icon="$icon_for_type" '$1 ~ /^(wifi|802-11-wireless).*/ {print $2 "\\0" icon "  " $3}')
+		prompt_to_use="$tr_known_connections_message" # Use descriptive text for prompt
+		;;
+	"wireguard")
+		icon_for_type="$icon_wireguard"
+		menu_type_function="menu_wireguard_connection"
+		profiles_list_raw=$(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' -v icon="$icon_for_type" '$1 == "wireguard" {print $2 "\\0" icon "  " $3}')
+		prompt_to_use="$tr_vpn_menu_prompt" # Use new VPN prompt
+		;;
+	"ethernet")
+		icon_for_type="$icon_ethernet"
+		menu_type_function="menu_connection"
+		profiles_list_raw=$(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' -v icon="$icon_for_type" '$1 ~ /^(ethernet|802-3-ethernet).*/ {print $2 "\\0" icon "  " $3}')
+		prompt_to_use="$tr_manage_wired_connections"
+		;;
+	*) # All saved connections
+		icon_for_type="$icon_saved"
+		menu_type_function="menu_connection"
+		profiles_list_raw=$(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' -v icon_saved="$icon_saved" -v icon_wireless="$icon_wireless" -v icon_ethernet="$icon_ethernet" -v icon_vpn_disconnect="$icon_vpn_disconnect" '{
+                icon_local = icon_saved;
+                if ($1 == "wifi" || $1 == "802-11-wireless") icon_local = icon_wireless;
+                else if ($1 == "ethernet" || $1 == "802-3-ethernet") icon_local = icon_ethernet;
+                else if ($1 == "vpn" || $1 == "wireguard") icon_local = icon_vpn_disconnect;
+                print $2 "\\0" icon_local "  " $3 " (" $1 ")"
+            }')
+		prompt_to_use="$tr_saved_connections_menu_prompt" # Use new Saved Connections prompt
+		;;
+	esac
+
+	mapfile -t profiles_list < <(echo "$profiles_list_raw")
+
+	if [ "${#profiles_list[@]}" -eq 0 ] || ([ "${#profiles_list[@]}" -eq 1 ] && [ -z "${profiles_list[0]}" ]); then
+		display_info_message "$tr_no_saved_connections" "$prompt_to_use" # Pass custom prompt
+		return
+	fi
+
+	while true; do
+		local options=$(for i in "${profiles_list[@]}"; do echo -e "$i" | cut --delimiter $'\0' --fields 2; done)
+		options+="\n$icon_close Back" # FIX: Added newline before the Back entry
+
+		# Prompt for this menu is explicitly set by prompt_to_use, no extra icon prepended by display_menu
+		chosen=$(echo -e "$options" | display_menu 1 "$prompt_to_use" "") # Pass "" for prompt_icon
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return # User cancelled or pressed back
+		fi
+
+		for i in "${profiles_list[@]}"; do
+			if [ "$chosen" = "$(echo -e "$i" | cut --delimiter $'\0' --fields 2)" ]; then
+				local conn_uuid=$(echo -e "$i" | cut --delimiter $'\0' --fields 1)
+				local conn_name_display=$(echo "$chosen" | sed -E 's/^(󰋋|󰖩|󰈀|󰖂) //; s/ \(.+\)$//') # Changed 󰖃 to 󰖂
+				eval "$menu_type_function \"$(sed 's/"/\\"/g' <<<"$conn_name_display")\" \"$conn_uuid\""
+				break
+			fi
+		done
+	done
+}
+
+# NEW: Menu for connecting to a specific wired connection
+menu_connect_wired_connection() {
+	local profiles_list_raw=$(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' -v icon="$icon_ethernet" '$1 ~ /^(ethernet|802-3-ethernet).*/ {print $2 "\\0" icon "  " " " $3}')
+	mapfile -t profiles_list < <(echo "$profiles_list_raw")
+
+	if [ "${#profiles_list[@]}" -eq 0 ] || ([ "${#profiles_list[@]}" -eq 1 ] && [ -z "${profiles_list[0]}" ]); then
+		display_info_message "No saved wired connections to connect to." "$tr_connect_wired_connection" # Pass custom prompt
+		return
+	fi
+
+	local options=$(for i in "${profiles_list[@]}"; do echo -e "$i" | cut --delimiter $'\0' --fields 2; done)
+	options+="\n$icon_close Back"
+
+	# Prompt for this menu is just the search icon
+	local chosen=$(echo -e "$options" | display_menu 1 "$tr_connect_wired_connection" "$icon_search")
+
+	if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+		return # User cancelled or pressed back
+	fi
+
+	local conn_uuid_to_connect=""
+	local conn_name_to_connect=""
+
+	for i in "${profiles_list[@]}"; do
+		if [ "$chosen" = "$(echo -e "$i" | cut --delimiter $'\0' --fields 2)" ]; then
+			conn_uuid_to_connect=$(echo -e "$i" | cut --delimiter $'\0' --fields 1)
+			conn_name_to_connect=$(echo -e "$i" | cut --delimiter $'\0' --fields 2 | sed -E 's/^(󰈀) //') # Remove icon
+			break
+		fi
+	done
+
+	if [ -n "$conn_uuid_to_connect" ]; then
+		show_loading_notification "$tr_connecting_to '$conn_name_to_connect'..."
+		if nmcli connection up uuid "$conn_uuid_to_connect"; then
+			kill_loading_notification
+			send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$conn_name_to_connect'"
+			exit 0
+		else
+			kill_loading_notification
+			send_notification "$tr_notice_error_summary" "$tr_notice_error_body '$conn_name_to_connect'"
+		fi
+	else
+		show_message "Could not find connection profile for $conn_name_to_connect." "$tr_connect_wired_connection" # Pass custom prompt
+	fi
+}
+
+# NEW: Wired Menu
+menu_wired() {
+	while true; do
+		local active_wired_conn=$(nmcli -t -f TYPE,DEVICE connection show --active | grep -vE "(wireless|vpn|wireguard)" | head -n 1)
+		local status_line=""
+		if [ -n "$active_wired_conn" ]; then
+			local active_device=$(echo "$active_wired_conn" | cut -d':' -f2)
+			status_line="$icon_ethernet $tr_status_message $tr_status_connected ($active_device)"
+		else
+			status_line="$icon_ethernet $tr_status_message $tr_status_disconnected"
+		fi
+
+		local options="$status_line\n"
+		options+="$icon_connect_wired Connect to Wired Connection\n" # FIX: Using icon_connect_wired
+		options+="$icon_config $tr_manage_wired_connections\n"       # Option to manage profiles
+		options+="$icon_close Back"
+
+		local choice=$(echo -e "$options" | display_menu 1 "$tr_wired_menu_prompt" "") # Pass "" for prompt_icon
+
+		if [ -z "$choice" ] || [[ "$choice" =~ ^"$icon_close Back" ]]; then
+			return # Go back to parent menu (main_menu)
+		fi
+
+		case "$choice" in
+		*"Connect to Wired Connection"*)
+			menu_connect_wired_connection # Call the function to connect to a wired network
+			;;
+		*"$tr_manage_wired_connections"*)
+			menu_known_connections "ethernet" # Reuse existing function for managing wired profiles
+			;;
+		*"$tr_status_message"*) # Do nothing if status line is selected
+			;;
+		*)
+			show_message "Invalid option selected: $choice" "$tr_wired_menu_prompt" # Pass custom prompt
+			;;
+		esac
+	done
+}
+
+# --- Status Functions ---
+
+show_active_connection_details() {
+	local device="$1"
+	local details_raw=$(nmcli --colors no device show "$device")
+
+	# Display the raw list in a filterable menu
+	local prompt_text="$icon_active_details Active Connection Details: $icon_search"
+	echo -e "${details_raw}\n${icon_close} Back" | display_menu 1 "$prompt_text" ""
+}
+
+status_menu() {
+	local options="$icon_active_details Active Connection Details\n$icon_devices All Device Status\n$icon_close Back"
+	# Use new status menu prompt
+	local choice=$(echo -e "$options" | display_menu 1 "$tr_status_menu_prompt" "") # Pass "" for prompt_icon
+
+	if [ -z "$choice" ] || [[ "$choice" =~ ^"$icon_close Back" ]]; then
+		return # User cancelled or pressed back
+	fi
+
+	case "$choice" in
+	*"Active Connection Details")
+		local active_conn_device=$(nmcli -t -f DEVICE connection show --active | head -n 1)
+		if [ -z "$active_conn_device" ]; then
+			display_info_message "$tr_no_active_connection"
+		else
+			show_active_connection_details "$active_conn_device"
+		fi
+		;;
+	*"All Device Status")
+		local device_status=$(nmcli device status)
+		display_info_message "$device_status" "$tr_status_menu_prompt" # Pass custom prompt
+		;;
+	esac
+}
+
+# --- Airplane Mode ---
+toggle_airplane_mode() {
+	local wifi_state=$(nmcli radio wifi)
+	local wwan_state=$(nmcli radio wwan 2>/dev/null || echo "disabled") # WWAN may not exist
+
+	if [ "$wifi_state" = "enabled" ] || [ "$wwan_state" = "enabled" ]; then
+		# Disable all radios
+		nmcli radio wifi off
+		nmcli radio wwan off 2>/dev/null
+		show_message "$tr_airplane_on" "$tr_airplane_mode_message"
+	else
+		# Enable all radios
+		nmcli radio wifi on
+		nmcli radio wwan on 2>/dev/null
+		show_message "$tr_airplane_off" "$tr_airplane_mode_message"
+	fi
+}
+
+# --- QR Code Sharing ---
+show_qrcode() {
+	local ssid="$1"
+	local security="$2"
+	local password="$3"
+
+	if ! command -v qrencode &>/dev/null; then
+		show_message "$tr_qrcode_error" "$tr_qrcode_message"
+		return
+	fi
+
+	if [ -z "$password" ]; then
+		show_message "$tr_qrcode_no_password" "$tr_qrcode_message"
+		return
+	fi
+
+	local qr_string="WIFI:T:${security};S:${ssid};P:${password};;"
+	local qr_file="/tmp/hyprltm-net-qr-${ssid}.png"
+
+	show_loading_notification "$tr_qrcode_generating"
+	qrencode -o "$qr_file" -s 10 -m 2 "$qr_string"
+	kill_loading_notification
+
+	# Display QR code inside Rofi
+	# -i: Enable icons
+	# -theme-str: Override theme to show large icon and no text input
+	# Display QR code inside Rofi
+	# -i: Enable icons
+	# -show-icons: Required to show icons in dmenu mode
+	# -theme-str: Override theme to show large icon and no text input
+
+	# We use the successful configuration from the test script
+	local rofi_override="
+        window { width: 500px; }
+        listview { lines: 1; scrollbar: false; }
+        element { orientation: vertical; padding: 20px; children: [ element-icon, element-text ]; }
+        element-icon { enabled: true; size: 300px; horizontal-align: 0.5; }
+        element-text { horizontal-align: 0.5; }
+        entry { enabled: false; } 
+        inputbar { enabled: false; }
+    "
+
+	# We pass an entry with the icon set to the QR file path
+	# Format: Valid Text \0icon\x1f/path/to/image.png
+	# We use "Scan to Connect" as the text under the QR code
+	echo -e "Scan to Connect\0icon\x1f${qr_file}" |
+		fuzzel --dmenu -i -show-icons -p "$tr_qrcode_message"
+
+	# Cleanup after Rofi closes
+	rm -f "$qr_file" 2>/dev/null
+}
+
+# Get password for a known Wi-Fi connection
+get_wifi_password() {
+	local ssid="$1"
+	local uuid=$(nmcli -t -f NAME,UUID connection show | grep "^${ssid}:" | cut -d':' -f2)
+	if [ -n "$uuid" ]; then
+		nmcli -s -g 802-11-wireless-security.psk connection show "$uuid" 2>/dev/null
+	fi
+}
+
+# --- Hotspot Creation ---
+create_hotspot() {
+	local ssid=$(echo "" | display_menu 5 "$tr_hotspot_ssid_prompt" "")
+	if [ -z "$ssid" ]; then
+		return
+	fi
+
+	local password=$(echo "" | display_menu 3 "$tr_hotspot_password_prompt" "")
+	if [ -z "$password" ] || [ ${#password} -lt 8 ]; then
+		show_message "Password must be at least 8 characters." "$tr_hotspot_message"
+		return
+	fi
+
+	show_loading_notification "$tr_hotspot_creating"
+	if nmcli device wifi hotspot ifname "$interface_to_use" ssid "$ssid" password "$password"; then
+		kill_loading_notification
+		show_message "$tr_hotspot_success\nSSID: $ssid" "$tr_hotspot_message"
+	else
+		kill_loading_notification
+		show_message "$tr_hotspot_error" "$tr_hotspot_message"
+	fi
+}
+
+# --- VPN Functions ---
+
+import_vpn() {
+	local vpn_file_path=$(echo "" | display_menu 5 "$tr_import_vpn_prompt" "")
+
+	if [ -z "$vpn_file_path" ]; then
+		return
+	fi
+
+	if [ ! -f "$vpn_file_path" ]; then
+		send_notification "$tr_notice_import_error_summary" "$tr_notice_file_not_found_body"
+		return
+	fi
+
+	local vpn_type=""
+	case "$vpn_file_path" in
+	*.conf) vpn_type="wireguard" ;;
+	*.ovpn) vpn_type="openvpn" ;;
+	*)
+		send_notification "$tr_notice_import_error_summary" "$tr_notice_unknown_vpn_type_body"
+		return
+		;;
+	esac
+
+	if nmcli connection import type "$vpn_type" file "$vpn_file_path"; then
+		send_notification "$tr_notice_import_success_summary" "$tr_notice_import_success_body"
+	else
+		send_notification "$tr_notice_import_error_summary" "$tr_notice_import_error_body"
+	fi
+}
+
+toggle_vpn_connection() {
+	local uuid="$1"
+	local name="$2"
+	local state="$3"
+
+	if [ "$state" = "activated" ]; then
+		show_loading_notification "$tr_disconnecting_from '$name'..."
+		if nmcli connection down uuid "$uuid"; then
+			kill_loading_notification
+			send_notification "$tr_notice_disconnected_summary" "$tr_notice_disconnected_body '$name'"
+			exit 0
+		else
+			kill_loading_notification
+			send_notification "$tr_notice_error_summary" "$tr_notice_error_disconnect_body '$name'"
+		fi
+	else
+		show_loading_notification "$tr_connecting_to"
+		if nmcli connection up uuid "$uuid"; then
+			kill_loading_notification
+			send_notification "$tr_notice_connected_summary" "$tr_notice_connected_body '$name'"
+			exit 0
+		else
+			kill_loading_notification
+			send_notification "$tr_notice_error_summary" "$tr_notice_error_body '$name'"
+		fi
+	fi
+}
+
+menu_available_vpns() {
+	while true; do
+		mapfile -t vpn_list < <(nmcli --colors no -t -f TYPE,UUID,NAME connection show | awk -F ':' '$1 == "vpn" || $1 == "wireguard" {print $2 "\\0" $3}')
+
+		if [ "${#vpn_list[@]}" -eq 0 ] || [ -z "${vpn_list[0]}" ]; then
+			display_info_message "$tr_no_configured_vpns" "$tr_available_vpn_profiles_message"
+			return
+		fi
+
+		local options=""
+		for i in "${vpn_list[@]}"; do
+			local uuid=$(echo -e "$i" | awk 'BEGIN{FS="\x00"}{print$1}')
+			local name=$(echo -e "$i" | awk 'BEGIN{FS="\x00"}{print$2}')
+			local state=$(nmcli --get-values GENERAL.STATE connection show uuid "$uuid")
+			local state_icon="$([ "$state" = "activated" ] && echo "$icon_on" || echo "$icon_off")"
+			options+="$state_icon  $name\n"
+		done
+		options+="$icon_close Back"
+
+		local chosen=$(echo -e "$options" | display_menu 1 "$tr_available_vpn_profiles_message" "$icon_search")
+
+		if [ -z "$chosen" ] || [[ "$chosen" =~ ^"$icon_close Back" ]]; then
+			return
+		fi
+
+		local chosen_name=$(echo "$chosen" | sed -E 's/^(|)  //')
+		for i in "${vpn_list[@]}"; do
+			local uuid=$(echo -e "$i" | awk 'BEGIN{FS="\x00"}{print$1}')
+			local name=$(echo -e "$i" | awk 'BEGIN{FS="\x00"}{print$2}')
+			if [ "$name" = "$chosen_name" ]; then
+				local state=$(nmcli --get-values GENERAL.STATE connection show uuid "$uuid")
+				toggle_vpn_connection "$uuid" "$name" "$state"
+				break
+			fi
+		done
+	done
+}
+
+vpn_menu() {
+	while true; do
+		local options="$icon_vpn_disconnect  $tr_available_vpn_profiles_message\n"
+		options+="$icon_import  $tr_import_vpn_message\n"
+		options+="$icon_close Back"
+
+		local choice=$(echo -e "$options" | display_menu 1 "$tr_vpn_menu_prompt" "")
+
+		if [ -z "$choice" ] || [[ "$choice" =~ ^"$icon_close Back" ]]; then
+			return
+		fi
+
+		case "$choice" in
+		*"$tr_available_vpn_profiles_message"*)
+			menu_available_vpns
+			;;
+		*"$tr_import_vpn_message"*)
+			import_vpn
+			;;
+		esac
+	done
+}
+
+main_menu() {
+	local options="$icon_wifi_full  Wi-Fi\n"
+	options+="$icon_ethernet  Wired\n"
+	options+="$icon_vpn_disconnect  VPN\n"
+	options+="$icon_bookmark_saved  Saved Connections\n"
+	options+="$icon_status_chart  Status\n"
+	options+="$icon_airplane  $tr_airplane_mode_message\n"
+	options+="$icon_close Exit"
+
+	while true; do                                                                 # Keep looping for the main menu
+		local choice=$(echo -e "$options" | display_menu 1 "$tr_main_menu_prompt" "") # Pass "" for prompt_icon
+
+		if [ -z "$choice" ]; then
+			# If user presses Esc on the main menu, exit the script
+			exit 0
+		fi
+
+		case "$choice" in
+		"$icon_close Exit") exit 0 ;; # Only exit if 'Exit' is explicitly chosen
+		*"Wi-Fi")
+			menu_wifi
+			;;
+		*"Wired")
+			menu_wired # Call the new wired sub-menu
+			;;
+		*"VPN")
+			vpn_menu
+			;;
+		*"Saved Connections")
+			menu_known_connections "all"
+			;;
+		*"Status")
+			status_menu
+			;;
+		*"$tr_airplane_mode_message")
+			toggle_airplane_mode
+			;;
+		esac
+	done # End of main menu while loop
+}
+
+# --- Run ---
+[ -n "${DEBUG:-}" ] && set -x
+main_menu