diff --git a/packages/internet_detector.vm/internet_detector.vm.nuspec b/packages/internet_detector.vm/internet_detector.vm.nuspec
new file mode 100644
index 000000000..6eaa8461f
--- /dev/null
+++ b/packages/internet_detector.vm/internet_detector.vm.nuspec
@@ -0,0 +1,13 @@
+
+
+
+ internet_detector.vm
+ 1.0.0
+ Elliot Chernofsky and Ana Martinez Gomez
+ Tool that changes the background and a taskbar icon if it detects internet connectivity
+
+
+
+
+
+
diff --git a/packages/internet_detector.vm/tools/chocolateyinstall.ps1 b/packages/internet_detector.vm/tools/chocolateyinstall.ps1
new file mode 100644
index 000000000..2669c5175
--- /dev/null
+++ b/packages/internet_detector.vm/tools/chocolateyinstall.ps1
@@ -0,0 +1,30 @@
+$ErrorActionPreference = 'Stop'
+Import-Module vm.common -Force -DisableNameChecking
+
+$toolName = 'internet_detector'
+$category = 'Utilities'
+
+# Install dependency for windows api
+VM-Pip-Install "pywin32"
+
+# Create tool directory
+$toolDir = Join-Path ${Env:RAW_TOOLS_DIR} $toolName
+New-Item -Path $toolDir -ItemType Directory -Force -ea 0
+VM-Assert-Path $toolDir
+
+$packageToolDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
+
+# Move images to %VM_COMMON_DIR% directory
+$imagesPath = Join-Path $packageToolDir "Images"
+Move-Item "$imagesPath\*" ${Env:VM_COMMON_DIR} -Force
+
+# Get path to pythonw for executing Python without terminal popup
+$executablePath = (Get-Command pythonw).Source
+$filePath = Join-Path $packageToolDir "$toolName.pyw"
+
+VM-Install-Shortcut $toolName $category $executablePath -arguments $filePath
+
+# Create scheduled task for tool to run every 2 minutes.
+$action = New-ScheduledTaskAction -Execute $executablePath -Argument "$filePath"
+$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 2)
+Register-ScheduledTask -Action $action -Trigger $trigger -TaskName 'Internet Detector' -Force
diff --git a/packages/internet_detector.vm/tools/chocolateyuninstall.ps1 b/packages/internet_detector.vm/tools/chocolateyuninstall.ps1
new file mode 100644
index 000000000..94da3c136
--- /dev/null
+++ b/packages/internet_detector.vm/tools/chocolateyuninstall.ps1
@@ -0,0 +1,8 @@
+$ErrorActionPreference = 'Continue'
+Import-Module vm.common -Force -DisableNameChecking
+
+$toolName = 'internet_detector'
+$category = 'Utilities'
+
+VM-Uninstall $toolName $category
+Unregister-ScheduledTask -TaskName 'Internet Detector' -Confirm:$false
diff --git a/packages/internet_detector.vm/tools/images/background-internet.png b/packages/internet_detector.vm/tools/images/background-internet.png
new file mode 100644
index 000000000..b380cb483
Binary files /dev/null and b/packages/internet_detector.vm/tools/images/background-internet.png differ
diff --git a/packages/internet_detector.vm/tools/images/indicator_off.ico b/packages/internet_detector.vm/tools/images/indicator_off.ico
new file mode 100644
index 000000000..c58bc6871
Binary files /dev/null and b/packages/internet_detector.vm/tools/images/indicator_off.ico differ
diff --git a/packages/internet_detector.vm/tools/images/indicator_on.ico b/packages/internet_detector.vm/tools/images/indicator_on.ico
new file mode 100644
index 000000000..abf03f0e4
Binary files /dev/null and b/packages/internet_detector.vm/tools/images/indicator_on.ico differ
diff --git a/packages/internet_detector.vm/tools/internet_detector.pyw b/packages/internet_detector.vm/tools/internet_detector.pyw
new file mode 100644
index 000000000..569ffcd05
--- /dev/null
+++ b/packages/internet_detector.vm/tools/internet_detector.pyw
@@ -0,0 +1,394 @@
+# This tool is intended to check if internet connectivity exists by reaching out to specific websites and checking if they return expected values and
+# display the current state via changes to the background, theme, and icon in the taskbar.
+# * It will work even with a tool like FakeNet running (provided it is running in default configuration)
+# If internet is detected, it will:
+# 1. Change the background to a new background called "background-dynamic.png".
+# 2. Change the the taskbar to be transparent and Rose colored and the theme to dark mode.
+# 3. Show an icon called "indicator_on.ico" in the taskbar.
+# If internet is not detected, it will:
+# 1. Change the background to a new background called "background.png".
+# 2. Change the taskbar to be transparent and keep the current theme/color (dark or light).
+# 3. Show an icon called "indicator_off.ico" in the taskbar.
+import threading
+import requests
+import win32api
+import win32gui
+import win32con
+import urllib3
+import winreg
+import signal
+import ctypes
+import time
+import os
+import re
+
+
+# Define constants
+CHECK_INTERVAL = 2 # Seconds
+CONNECT_TEST_URL_AND_RESPONSES = {
+ "https://www.msftconnecttest.com/connecttest.txt": "Microsoft Connect Test", # HTTPS Test #1
+ "http://www.google.com": "Google", # HTTP Test
+ "https://www.wikipedia.com": "Wikipedia", # HTTPS Test #2
+ "https://www.youtube.com": "YouTube", # HTTPS Test #3
+}
+SPI_SETDESKWALLPAPER = 20
+SPIF_UPDATEINIFILE = 0x01
+SPIF_SENDWININICHANGE = 0x02
+WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320
+ROSE_COLOR = "#C30052"
+ROSE_ACCENT_PALETTE = "FFABCE00FF7FB400F74A9200C30052008C003A0069002C004D002000567C7300"
+ICON_INDICATOR_ON = os.path.join(os.environ.get("VM_COMMON_DIR"), "indicator_on.ico")
+ICON_INDICATOR_OFF = os.path.join(os.environ.get("VM_COMMON_DIR"), "indicator_off.ico")
+DEFAULT_BACKGROUND = os.path.join(os.environ.get("VM_COMMON_DIR"), "background.png")
+INTERNET_BACKGROUND = os.path.join(os.environ.get("VM_COMMON_DIR"), "background-internet.png")
+
+# Global variables
+tray_icon = None
+stop_event = threading.Event() # To signal the background thread to stop
+hwnd = None # We'll assign the window handle here later
+check_thread = None
+tray_icon_thread = None
+default_transparency = None
+default_color = None
+default_palette = None
+# Win32 API icon handles
+hicon_indicator_off = None
+hicon_indicator_on = None
+
+
+def signal_handler(sig, frame):
+ global check_thread, tray_icon_thread, tray_icon
+ print("Ctrl+C detected. Exiting...")
+ stop_event.set() # Signal the background thread to stop
+ if check_thread:
+ check_thread.join()
+ if tray_icon_thread:
+ tray_icon_thread.join()
+ if tray_icon:
+ del tray_icon
+ exit(0)
+
+
+def load_icon(icon_path):
+ try:
+ return win32gui.LoadImage(None, icon_path, win32con.IMAGE_ICON, 0, 0, win32con.LR_LOADFROMFILE)
+ except Exception as e:
+ print(f"Error loading indicator icon: {e}")
+ return None
+
+
+class SysTrayIcon:
+ def __init__(self, hwnd, icon, tooltip):
+ self.hwnd = hwnd
+ self.icon = icon
+ self.tooltip = tooltip
+ # System tray icon data structure
+ self.nid = (
+ self.hwnd,
+ 0,
+ win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP,
+ win32con.WM_USER + 20,
+ self.icon,
+ self.tooltip,
+ )
+ # Add the icon to the system tray
+ win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self.nid)
+
+ def set_tooltip(self, new_tooltip):
+ self.tooltip = new_tooltip
+ self.nid = (
+ self.hwnd,
+ 0,
+ win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP,
+ win32con.WM_USER + 20,
+ self.icon,
+ self.tooltip,
+ )
+ win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self.nid)
+
+ def set_icon(self, icon):
+ self.icon = icon
+ self.nid = (
+ self.hwnd,
+ 0,
+ win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP,
+ win32con.WM_USER + 20,
+ self.icon,
+ self.tooltip,
+ )
+ win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self.nid)
+
+ def __del__(self):
+ # Remove the icon from the system tray when the object is destroyed
+ win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, self.nid)
+
+
+def get_current_color_palette():
+ """
+ Reads color palette binary data from a registry key and returns it as a hex string.
+
+ Returns:
+ The binary data as hex string, or None if the value doesn't exist or an error occurs.
+ """
+ try:
+ # Open the registry key for accent palette settings
+ key = winreg.OpenKey(
+ winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Accent", 0, winreg.KEY_READ
+ )
+ value, _ = winreg.QueryValueEx(key, "AccentPalette")
+ winreg.CloseKey(key)
+ # return the data as hex string
+ return value.hex()
+
+ except WindowsError:
+ print("Error accessing registry.")
+ return None
+
+
+def get_current_taskbar_color():
+ """
+ Attempts to get the current taskbar color based on user personalization settings.
+
+ Returns:
+ The color in hex format (e.g., '#RRGGBB') if inferred successfully, or None otherwise.
+ """
+ try:
+ # Open the registry key for personalization settings
+ key = winreg.OpenKey(
+ winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Accent", 0, winreg.KEY_READ
+ )
+ accent_color, _ = winreg.QueryValueEx(key, "AccentColorMenu")
+ winreg.CloseKey(key)
+
+ # Convert the accent color value (DWORD) to RGB
+ r = accent_color & 0xFF
+ g = (accent_color >> 8) & 0xFF
+ b = (accent_color >> 16) & 0xFF
+ # Convert RGB to hex
+ return f"#{r:02X}{g:02X}{b:02X}"
+
+ except WindowsError:
+ print("Error accessing registry.")
+ return None
+
+
+def set_taskbar_accent_color(hex_color, hex_color_palette):
+ """
+ Sets the accent color location in the Windows registry.
+
+ Args:
+ hex_color = the hex value of RGB color with starting "#" (e.g., '#RRGGBB')
+ hex_color_palette = the hex string of the corresponding color paletter for the provided hex_color
+ """
+ try:
+ print("Setting taskbar color to:", hex_color)
+ # Convert hex color to RGB
+ r = int(hex_color[1:3], 16)
+ g = int(hex_color[3:5], 16)
+ b = int(hex_color[5:7], 16)
+
+ # Convert RGB to DWORD
+ color_value = (b << 16) | (g << 8) | r
+
+ key = winreg.OpenKey(
+ winreg.HKEY_CURRENT_USER,
+ r"Software\Microsoft\Windows\CurrentVersion\Explorer\Accent",
+ 0,
+ winreg.KEY_ALL_ACCESS,
+ )
+
+ binary_data = bytes.fromhex(hex_color_palette)
+ winreg.SetValueEx(key, "AccentColorMenu", 0, winreg.REG_DWORD, color_value)
+ winreg.SetValueEx(key, "AccentPalette", 0, winreg.REG_BINARY, binary_data)
+ winreg.CloseKey(key)
+
+ except WindowsError as e:
+ print(f"Error accessing or modifying registry: {e}")
+
+
+def get_transparency_effects():
+ try:
+ key = winreg.OpenKey(
+ winreg.HKEY_CURRENT_USER,
+ r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
+ 0,
+ winreg.KEY_ALL_ACCESS,
+ )
+ value, _ = winreg.QueryValueEx(key, "EnableTransparency")
+ winreg.CloseKey(key)
+ return value
+
+ except WindowsError as e:
+ print(f"Error accessing or modifying registry: {e}")
+
+
+def set_transparency_effects(value):
+ try:
+ key = winreg.OpenKey(
+ winreg.HKEY_CURRENT_USER,
+ r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
+ 0,
+ winreg.KEY_ALL_ACCESS,
+ )
+
+ winreg.SetValueEx(key, "EnableTransparency", 0, winreg.REG_DWORD, value)
+ winreg.CloseKey(key)
+ except WindowsError as e:
+ print(f"Error accessing or modifying registry: {e}")
+
+
+# Attempt to extract a known good value in response.
+def extract_title(data):
+ match = re.search(r"
(.*?)", data)
+ if match:
+ return match.group(1)
+ else:
+ return None
+
+
+def check_internet():
+ for url, expected_response in CONNECT_TEST_URL_AND_RESPONSES.items():
+ try:
+ # Perform internet connectivity tests
+ response = requests.get(url, timeout=5, verify=False)
+ if expected_response in (extract_title(response.text) or response.text):
+ print(f"Internet connectivity detected via URL: {url}")
+ return True
+ except:
+ pass
+ return False
+
+
+def check_internet_and_update_tray_icon():
+ global tray_icon, hicon_indicator_off, hicon_indicator_on, default_color
+ if check_internet():
+ tray_icon.set_icon(hicon_indicator_on)
+ tray_icon.set_tooltip("Internet Connection: Detected")
+ set_transparency_effects(1)
+ set_taskbar_accent_color(ROSE_COLOR, ROSE_ACCENT_PALETTE) # Rose Color = #ff5200c3
+ # Set the background to internet connection background
+ if get_wallpaper_path() != INTERNET_BACKGROUND: # Checked so program isn't continuously setting the wallpaper
+ set_wallpaper(INTERNET_BACKGROUND)
+ else:
+ tray_icon.set_icon(hicon_indicator_off)
+ tray_icon.set_tooltip("Internet Connection: Not Detected")
+ set_transparency_effects(default_transparency)
+ set_taskbar_accent_color(default_color, default_palette) # change color back to what user had originally
+ # Reset background when internet is not detected
+ if get_wallpaper_path() != DEFAULT_BACKGROUND: # Checked so program isn't continuously setting the wallpaper
+ set_wallpaper(DEFAULT_BACKGROUND)
+
+
+def check_internet_loop():
+ while not stop_event.is_set():
+ check_internet_and_update_tray_icon()
+ time.sleep(CHECK_INTERVAL)
+
+
+def tray_icon_loop():
+ global hwnd, tray_icon, hicon_indicator_off, hicon_indicator_on, stop_event
+ # Load icons
+ hicon_indicator_off = load_icon(ICON_INDICATOR_OFF)
+ hicon_indicator_on = load_icon(ICON_INDICATOR_ON)
+
+ # Wait for hwnd to be initialized
+ while hwnd is None:
+ time.sleep(0.1)
+
+ if hicon_indicator_off is None or hicon_indicator_on is None:
+ print("Error: Failed to load icons. Exiting TrayIconThread.")
+ return
+
+ tray_icon = SysTrayIcon(hwnd, hicon_indicator_off, "Internet Detector")
+
+ while not stop_event.is_set():
+ msg = win32gui.PeekMessage(hwnd, 0, 0, 0)
+ if msg and len(msg) == 6:
+ win32gui.TranslateMessage(msg)
+ win32gui.DispatchMessage(msg)
+ time.sleep(0.1)
+
+
+def get_wallpaper_path():
+ """Attempts to retrieve the path to the current wallpaper image."""
+ # Try to get the path from the registry (for wallpapers set through Windows settings)
+ try:
+ key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop", 0, winreg.KEY_READ)
+ value, _ = winreg.QueryValueEx(key, "Wallpaper")
+ winreg.CloseKey(key)
+ if value:
+ return value
+ except WindowsError:
+ pass
+
+ # Check for cached wallpaper files (if the above fails)
+ cached_files_dir = os.path.join(os.getenv("APPDATA"), r"Microsoft\Windows\Themes\CachedFiles")
+ transcoded_wallpaper_path = os.path.join(os.getenv("APPDATA"), r"Microsoft\Windows\Themes\TranscodedWallpaper")
+
+ for file in os.listdir(cached_files_dir):
+ if file.endswith((".jpg", ".jpeg", ".bmp", ".png")):
+ return os.path.join(cached_files_dir, file)
+
+ if os.path.exists(transcoded_wallpaper_path):
+ return transcoded_wallpaper_path
+
+ # If all else fails, return None
+ return None
+
+
+def set_wallpaper(image_path):
+ """Sets the desktop wallpaper to the image at the specified path."""
+ print(f"Setting wallpaper to: {image_path}")
+ result = ctypes.windll.user32.SystemParametersInfoW(
+ SPI_SETDESKWALLPAPER, 0, image_path, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE
+ )
+ if not result:
+ print("Error setting wallpaper. Make sure the image path is correct.")
+
+
+def main_loop():
+ global stop_event, check_thread, tray_icon_thread, tray_icon
+ # Create and start the threads
+ tray_icon_thread = threading.Thread(target=tray_icon_loop)
+ check_thread = threading.Thread(target=check_internet_loop)
+
+ tray_icon_thread.start()
+ # Wait for the tray icon to finish initializing
+ while tray_icon is None:
+ time.sleep(0.1)
+
+ check_thread.start()
+
+ while not stop_event.is_set():
+ time.sleep(1)
+
+
+if __name__ == "__main__":
+ # print(get_current_taskbar_color())
+ signal.signal(signal.SIGINT, signal_handler)
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+ default_transparency = get_transparency_effects()
+ default_color = get_current_taskbar_color()
+ default_palette = get_current_color_palette()
+
+ # Create a hidden window to receive messages (required for system tray icons)
+ def wndProc(hwnd, msg, wparam, lparam):
+ if lparam == win32con.WM_LBUTTONDBLCLK:
+ print("Left button double clicked")
+ elif msg == win32con.WM_COMMAND:
+ if wparam == 1023: # Example menu item ID
+ print("Exit selected")
+ win32gui.DestroyWindow(hwnd)
+ return win32gui.DefWindowProc(hwnd, msg, wparam, lparam)
+
+ wc = win32gui.WNDCLASS()
+ hinst = wc.hInstance = win32api.GetModuleHandle(None)
+ wc.lpszClassName = "Internet Detector"
+ wc.lpfnWndProc = wndProc
+ classAtom = win32gui.RegisterClass(wc)
+ hwnd = win32gui.CreateWindow(classAtom, "Internet Detector", 0, 0, 0, 0, 0, 0, 0, hinst, None)
+
+ print("Current wallpaper: {}".format(get_wallpaper_path()))
+ print("Current color: {}".format(get_current_taskbar_color()))
+
+ main_loop()