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..3dd20ba9f
--- /dev/null
+++ b/packages/internet_detector.vm/internet_detector.vm.nuspec
@@ -0,0 +1,14 @@
+
+
+
+ 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..afa945f5b
--- /dev/null
+++ b/packages/internet_detector.vm/tools/chocolateyinstall.ps1
@@ -0,0 +1,37 @@
+$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
+
+# Move updated `default.ini` with tool added to ProcessBlackList to FakeNet Config Directory
+$fakenetConfigDir = Join-Path ${Env:UserProfile} "Desktop\fakenet_logs.lnk"
+if (Test-Path $fakenetConfigDir) { # Check if the shortcut exists
+ $targetPath = (New-Object -ComObject WScript.Shell).CreateShortcut($fakenetConfigDir).TargetPath
+ Move-Item "$packageToolDir\default.ini" -Destination $targetPath
+}
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/default.ini b/packages/internet_detector.vm/tools/default.ini
new file mode 100644
index 000000000..f743fbd1d
--- /dev/null
+++ b/packages/internet_detector.vm/tools/default.ini
@@ -0,0 +1,352 @@
+###############################################################################
+# Fakenet Configuration
+
+[FakeNet]
+
+# Specify whether or not FakeNet should divert traffic. Disable if you want to
+# just start listeners and direct traffic manually (e.g. modify DNS server)
+DivertTraffic: Yes
+
+###############################################################################
+# Diverter Configuration
+
+[Diverter]
+
+# Specify what mode of operation to use. Options:
+# SingleHost - manipulate local traffic
+# MultiHost - manipulate traffic from foreign hosts
+# Auto - Use SingleMode on Windows or use MultiHost on Linux
+#
+# The current support for these modes on each supported platform is as follows:
+# | Windows | Linux |
+# -----------+------------+--------------+
+# SingleHost | Functional | Experimental |
+# MultiHost | - | Functional |
+# NetworkMode: SingleHost
+# NetworkMode: MultiHost
+NetworkMode: Auto
+
+# DebugLevel: specify fine-grained debug print flags to enable. Enabling all
+# logging when verbose mode is selected results in overwhelming output, hence
+# this setting. Valid values (comma-separated) are:
+#
+# GENPKT Generic packet information
+# GENPKTV Packet analysis, displays IP, TCP, UDP fields, very wide output
+# CB Diverter packet handler callback start/finish logging
+# NONLOC Nonlocal packet verbose logging
+# DPF Dynamic port forwarding decisions
+# DPFV Dynamic port forwarding table activity
+# IPNAT NAT decisions
+# MANGLE Packet mangling (modification) activity
+# PCAP PCAP writes of original and mangled packets
+# IGN Cases where packets are forwarded as is
+# FTP FTP-specific logic
+# IGN-FTP Cases where packets are forwarded as is due to FTP Active Mode
+# MISC Miscellaneous
+# NFQUEUE NetfilterQueue activity (Linux only)
+# PROCFS Procfs read/write activity (Linux only)
+# IPTABLES iptables firewall rule activity (Linux only)
+DebugLevel: Off
+
+# Restrict which interface on which Fakenet-NG will intercept and handle
+# packets. Specify (only) one interface and Fakenet-NG will ignore all other
+# interfaces. This feature only applies to interfaces on different subnets.
+# Specify interface by name only (ex: eth0). To disable, set to "Off". In
+# order to run multiple instance of Fakenet-NG on different interfaces within
+# the same guest, LinuxFlushIptables must be turned off to avoid the latest
+# instance flushing the rules associated with other instances or restoring
+# rules to an incorrect state upon exit.
+LinuxRestrictInterface: Off
+
+# Set LinuxFlushIptables to Yes to have the Linux Diverter flush all iptables
+# rules before adding its FakeNet-NG-specific rules to iptables. This setting
+# also restores rules via `iptables-restore` when it exits, unless its
+# termination is interrupted.
+LinuxFlushIptables: Yes
+
+# Incorporated so that users of the binary release may make this work for
+# various Linux distros. On Ubuntu, this is `service dns-clean restart`. For
+# other distributions, it may be `nscd -I hosts`. Check your manual for
+# details.
+LinuxFlushDNSCommand: service dns-clean restart
+
+# Specify whether or not to save captured traffic. You can also change
+# the file prefix for the generated PCAPs.
+DumpPackets: Yes
+DumpPacketsFilePrefix: packets
+
+# DHCP server running under VMWare Host-Only networking does not configure
+# interface gateway and DNS server. Gateway must be configured to allow
+# Windows to attempt to route external traffic so that FakeNet could
+# could intercept it. This option will automatically generate and set
+# appropriate gateway and DNS addresses to allow normal operation.
+FixGateway: Yes
+FixDNS: Yes
+
+# Enable 'ModifyLocalDNS' to statically set DNS server to the local machine.
+# Linux: Modifies (and restores) /etc/resolv.conf on Linux to make this an
+# ephemeral change.
+ModifyLocalDNS: Yes
+
+# Enable 'StopDNSService' to stop Windows DNS client to see the actual
+# processes resolving domains. This is a no-op on Linux, until such time as DNS
+# caching is observed to interfere with finding the pid associated with a DNS
+# request.
+StopDNSService: Yes
+
+# Enable 'RedirectAllTraffic' to optionally divert traffic going to ports not
+# specifically listed in one of the listeners below. 'DefaultTCPListener' and
+# 'DefaultUDPListener' will handle TCP and UDP traffic going to unspecified ports.
+#
+# NOTE: Setting default UDP listener will intercept all DNS traffic unless you
+# enable a dedicated UDP port 53 DNS listener or add UDP port 53 to the
+# 'BlackListPortsUDP' below so that system's default DNS server is used instead.
+
+RedirectAllTraffic: Yes
+DefaultTCPListener: ProxyTCPListener
+DefaultUDPListener: ProxyUDPListener
+
+# Specify TCP and UDP ports to ignore when diverting packets.
+# For example, you may want to avoid diverting UDP port 53 (DNS) traffic
+# when trying to intercept a specific process while allowing the rest to
+# function normally
+#
+# NOTE: This setting is only honored when 'RedirectAllTraffic' is enabled.
+
+BlackListPortsTCP: 139
+BlackListPortsUDP: 67, 68, 137, 138, 443, 1900, 5355
+
+# Specify processes to ignore when diverting traffic. Windows example used
+# here.
+# ProcessBlackList: java.exe
+
+# Specify processes to consider when diverting traffic (others will be
+# ignored). Linux examples used here.
+# ProcessWhiteList: wget, nc
+
+# Specify hosts to ignore when diverting traffic.
+# HostBlackList: 6.6.6.6
+
+###############################################################################
+# Listener Configuration
+#
+# Listener configuration consists of generic settings used by the diverter which
+# are the same for all listeners and listener specific settings.
+#
+# NOTE: Listener section names will be used for logging.
+#
+# NOTE: Settings labels are not case-sensitive.
+#
+# The following settings are available for all listeners:
+# * Enabled - specify whether or not the listener is enabled.
+# * Port - TCP or UDP port to listen on.
+# * Protocol - TCP or UDP
+# * Listener - Listener name to handle traffic.
+# * ProcessWhiteList - Only traffic from these processes will be modified
+# and the rest will simply be forwarded.
+# * ProcessBlackList - Traffic from all but these processes will be simply forwarded
+# and the rest will be modified as needed.
+# * HostWhiteList - Only traffic to these hosts will be modified and
+# the rest will be simply forwarded.
+# * HostBlackList - Traffic to these hosts will be simply forwarded
+# and the rest will be modified as needed.
+# * ExecuteCmd - Execute command on the first connection packet. This is feature is useful
+# for extending FakeNet-NG's functionality (e.g. launch a debugger on the
+# connecting pid to help with unpacking and decoding.)
+#
+# The following format string variables are made available:
+# * {pid} - process id
+# * {procname} - process executable name
+# * {src_addr} - source address
+# * {src_port} - source port
+# * {dst_addr} - destination address
+# * {dst_port} - destination port
+#
+# Listener entry which does not specify a specific listener service
+# will still redirect all packets to the local machine on the specified port and
+# subject to all the filters (processes, hosts, etc.). However, you must set-up a
+# third party service (e.g. proxy servers) to accept these connections. This feature can be
+# used to provide FakeNet-NG's passive traffic diverting and filtering capabilities to other
+# applications.
+#
+# Listener specific settings:
+#
+# * Timeout - Set connection timeout for any listeners that support
+# TCP connections (e.g. RawListener, DNSListener, HTTPListener
+# SMTPListener).
+# * UseSSL - Enable SSL support on the listener (RawListener, HTTPListener)
+# * Webroot - Set webroot path for HTTPListener.
+# * DumpHTTPPosts - Store HTTP Post requests for the HTTPListener.
+# * DumpHTTPPostsFilePrefix - File prefix for the stored HTTP Post requests used by the HTTPListener.
+# * TFTPFilePrefix - File prefix for the stored tftp uploads used by the TFTPListener.
+# * DNSResponse - IP address to respond with for A record DNS queries. (DNSListener)
+# * NXDomains - A number of DNS requests to ignore to let the malware cycle through
+# all of the backup C2 servers. (DNSListener)
+# * Banner - FTPListener, IRCListener: FTP or IRC banner to display.
+# Valid settings are any banner string, or ! where
+# is a valid key in the BANNERS dictionary within
+# FTPListener.py or IRCListener.py, or !random to
+# randomize among the banners in the BANNERS dictionary.
+# The default value if none is specified is !generic,
+# which selects the banner in the BANNERS dictionary going
+# by that key. Banner string may specify the following
+# escapes/insertions:
+# {servername} - ServerName setting value
+# {tz} - Time zone, currently hard-coded to 'UTC'
+# * ServerName - FTPListener, IRCListener: FTP or IRC server name for
+# insertion into selected default banners or into a
+# user-specified banner string. Valid settings are any
+# hostname string, !hostname to insert the actual hostname
+# of the system, or !random to generate a random hostname
+# between 1 and 15 characters (inclusive).
+
+[ProxyTCPListener]
+Enabled: True
+Protocol: TCP
+Listener: ProxyListener
+Port: 38926
+Listeners: HTTPListener, RawListener, FTPListener, DNSListener, POPListener, SMTPListener, TFTPListener, IRCListener
+Hidden: False
+
+[ProxyUDPListener]
+Enabled: True
+Protocol: UDP
+Listener: ProxyListener
+Port: 38926
+Listeners: RawListener, DNSListener, TFTPListener, FTPListener
+Hidden: False
+
+[Forwarder]
+Enabled: False
+Port: 8080
+Protocol: TCP
+ProcessWhiteList: chrome.exe
+Hidden: False
+
+[RawTCPListener]
+Enabled: True
+Port: 1337
+Protocol: TCP
+Listener: RawListener
+UseSSL: No
+Timeout: 10
+Hidden: False
+# To read about customizing responses, see docs/CustomResponse.md
+# Custom: sample_custom_response.ini
+
+[RawUDPListener]
+Enabled: True
+Port: 1337
+Protocol: UDP
+Listener: RawListener
+UseSSL: No
+Timeout: 10
+Hidden: False
+# To read about customizing responses, see docs/CustomResponse.md
+# Custom: sample_custom_response.ini
+
+[FilteredListener]
+Enabled: False
+Port: 31337
+Protocol: TCP
+Listener: RawListener
+UseSSL: No
+Timeout: 10
+ProcessWhiteList: ncat.exe, nc.exe
+HostBlackList: 5.5.5.5
+Hidden: False
+
+[DNS Server]
+Enabled: True
+Port: 53
+Protocol: UDP
+Listener: DNSListener
+ProcessBlackList: pythonw.exe
+ResponseA: 192.0.2.123
+ResponseMX: mail.evil2.com
+ResponseTXT: FAKENET
+NXDomains: 0
+Hidden: False
+
+[HTTPListener80]
+Enabled: True
+Port: 80
+Protocol: TCP
+Listener: HTTPListener
+UseSSL: No
+Webroot: defaultFiles/
+Timeout: 10
+ProcessBlackList: pythonw.exe
+DumpHTTPPosts: Yes
+DumpHTTPPostsFilePrefix: http
+Hidden: False
+# To read about customizing responses, see docs/CustomResponse.md
+# Custom: sample_custom_response.ini
+
+[HTTPListener443]
+Enabled: True
+Port: 443
+Protocol: TCP
+Listener: HTTPListener
+UseSSL: Yes
+Webroot: defaultFiles/
+Timeout: 10
+ProcessBlackList: pythonw.exe
+DumpHTTPPosts: Yes
+DumpHTTPPostsFilePrefix: http
+Hidden: False
+
+[SMTPListener]
+Enabled: True
+Port: 25
+Protocol: TCP
+Listener: SMTPListener
+UseSSL: No
+Hidden: False
+
+[FTPListener21]
+Enabled: True
+Port: 21
+Protocol: TCP
+Listener: FTPListener
+UseSSL: No
+FTProot: defaultFiles/
+PasvPorts: 60000-60010
+Hidden: False
+Banner: !generic
+ServerName: !gethostname
+
+[FTPListenerPASV]
+Enabled: True
+Port: 60000-60010
+Protocol: TCP
+Hidden: False
+
+[IRCServer]
+Enabled: True
+Port: 6667
+Protocol: TCP
+Listener: IRCListener
+UseSSL: No
+Banner: !generic
+ServerName: !gethostname
+Timeout: 30
+Hidden: False
+
+[TFTPListener]
+Enabled: True
+Port: 69
+Protocol: UDP
+Listener: TFTPListener
+TFTPRoot: defaultFiles/
+Hidden: False
+TFTPFilePrefix: tftp
+
+[POPServer]
+Enabled: True
+Port: 110
+Protocol: TCP
+Listener: POPListener
+UseSSL: No
+Hidden: 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..e65a6e2f1
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..3b22c25ed
--- /dev/null
+++ b/packages/internet_detector.vm/tools/internet_detector.pyw
@@ -0,0 +1,425 @@
+# This tool checks 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 works even with a tool like FakeNet running (provided it uses the default configuration)
+# If internet is detected, the tool:
+# - Changes the background to the image "%VM_COMMON_DIR%\background-dynamic.png".
+# - Change the the taskbar to be transparent and if the theme is dark mode also the color to "Rose".
+# - Shows the icon "%VM_COMMON_DIR%\indicator_on.ico" in the taskbar.
+# If internet is NOT detected, the tool:
+# - Changes the background to the image "%VM_COMMON_DIR%\background.png" (default background image).
+# - Changes the taskbar to be transparent keeping the current theme/color (dark or light).
+# - Shows the icon "%VM_COMMON_DIR%\indicator_off.ico" in the taskbar.
+VERSION = "1.0.0"
+TOOL_NAME = "internet_detector"
+
+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
+ACCENT_PREVALENCE = 1
+ROSE_COLOR = "#C30052" # Rose Color = #ff5200c3 which correlates to #C30052 before parsing
+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_prevalence = 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_prevalence():
+ try:
+ # Open the registry key for accent palette settings
+ key = winreg.OpenKey(
+ winreg.HKEY_CURRENT_USER,
+ r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
+ 0,
+ winreg.KEY_READ
+ )
+ value, _ = winreg.QueryValueEx(key, "ColorPrevalence")
+ winreg.CloseKey(key)
+ return value
+
+ except WindowsError:
+ print("Error accessing registry.")
+ return None
+
+
+# Reads color palette binary data from a registry key and returns it as a hex string.
+def get_current_color_palette():
+ 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
+
+
+# Attempts to get the current taskbar color based on user personalization settings and returns it in hex format.
+def get_current_taskbar_color():
+ 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, color_prevalence):
+ """
+ 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
+ color_prevalence = 0 - don't use accent color; 1 - use accent 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\Themes\Personalize",
+ 0,
+ winreg.KEY_ALL_ACCESS
+ )
+ winreg.SetValueEx(key, "AccentColorPolicy", 0, winreg.REG_DWORD, 1) # Set color for taskbar
+ winreg.SetValueEx(key, "ColorPrevalence", 0, winreg.REG_DWORD, color_prevalence) # Enable accent color
+ winreg.SetValueEx(key, "AccentColorInactive", 0, winreg.REG_DWORD, 0) # Disable inactive color
+ winreg.CloseKey(key)
+
+ 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, ACCENT_PREVALENCE)
+ # 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, default_color_prevalence) # 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__":
+ signal.signal(signal.SIGINT, signal_handler)
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+ default_transparency = get_transparency_effects()
+ default_color_prevalence = get_current_color_prevalence()
+ 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(f"{TOOL_NAME} Version: {VERSION}")
+ print(f"Current wallpaper: {get_wallpaper_path()}")
+ print(f"Current color: {default_color}")
+
+ main_loop()