Processing Ajax...

Title

Message

Confirm

Confirm

Confirm

Confirm

Are you sure you want to delete this item?

Confirm

Are you sure you want to delete this item?

Confirm

Are you sure?

Replace Windows Taskbar with DisplayFusion Taskbar

Description
This script will set the opacity of the Windows taskbar to 0 and then enable the DisplayFusion taskbar on that monitor.
Language
C#.net
Minimum Version
Created By
XTheocharis
Contributors
-
Date Created
Jan 15, 2025
Date Last Modified
5d ago

Scripted Function (Macro) Code

using System;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using Microsoft.Win32;

/// <summary>
/// DisplayFusion Function script that enables complete replacement of the Windows taskbar.
/// 
/// This script:
/// 1. Makes the Windows taskbar transparent (which inherently makes it stop responding to user input)
/// 2. Configures required DisplayFusion and Explorer settings for proper system tray icon visibility
/// 3. Ensures the DisplayFusion taskbar is correctly positioned on the appropriate monitor
/// 4. Monitors and corrects taskbar position during an initialization period
/// 
/// Compatibility: Works on Windows 7 and later versions
/// 
/// Setup Recommendations:
/// - Add a "DisplayFusion Starts" trigger to run this script at startup
/// - Add the "Toggle Windows Taskbar" script as a "DisplayFusion Exits" trigger to restore
///   the Windows taskbar when DisplayFusion closes (otherwise, you may need to restart Explorer)
/// 
/// First-run behavior: The script may restart Explorer and/or DisplayFusion to apply initial settings.
/// </summary>
public static class DisplayFusionFunction
{
    // P/Invoke for SetWindowPos since BFS.Window.SetSizeAndLocation doesn't work reliably on DisplayFusion taskbars
    [DllImport("user32.dll")]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

    // P/Invoke for sending shutdown messages to DisplayFusion for clean restart
    [DllImport("user32.dll")]
    private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll")]
    private static extern uint RegisterWindowMessage(string lpString);

    // Constants for Windows API calls
    // SWP_NOZORDER (0x0004): Maintains Z-order
    // SWP_NOACTIVATE (0x0010): Doesn't activate the window
    // SWP_SHOWWINDOW (0x0040): Displays the window
    // SWP_ASYNCWINDOWPOS (0x4000): Posts the request rather than processing synchronously
    private const uint SWP_FLAGS = 0x4054;
    
    // HWND_BROADCAST special value to send messages to all top-level windows
    private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);
    
    // Enum to track which edge of the monitor the taskbar is positioned on
    private enum TaskbarPosition { Bottom, Top, Left, Right }

    /// <summary>
    /// Main entry point for the DisplayFusion scripted function.
    /// Coordinates the entire taskbar replacement process including registry settings,
    /// process management, and taskbar positioning.
    /// </summary>
    /// <param name="windowHandle">Handle passed by DisplayFusion when the script is triggered</param>
    public static void Run(IntPtr windowHandle)
    {
        Log("Script started");

        // Check and update registry settings for Explorer and DisplayFusion
        // Returns tuple indicating if restarts are needed
        var (explorerRestartNeeded, dfRestartNeeded) = UpdateRegistrySettings();
        
        // If needed, restart Explorer before continuing to apply registry changes
        if (explorerRestartNeeded)
        {
            Log("Restarting Explorer to apply registry changes");
            RestartExplorer();
        }

        // If DisplayFusion registry was changed, restart it and exit
        // The script will be re-run when DisplayFusion restarts if triggered by startup
        if (dfRestartNeeded)
        {
            Log("Registry changes require DisplayFusion restart");
            RestartDisplayFusion();
            return;
        }
        
        // Find Windows taskbar and make it transparent
        IntPtr winTaskbar = BFS.Window.GetWindowByClass("Shell_TrayWnd");
        if (winTaskbar == IntPtr.Zero)
        {
            Log("Error: Could not find Windows taskbar");
            return;
        }
        
        // Determine which monitor contains the Windows taskbar
        // We need this to ensure we create the DisplayFusion taskbar on the same monitor
        uint monitorId = BFS.Monitor.GetMonitorIDByWindow(winTaskbar);
        Rectangle monitorBounds = BFS.Monitor.GetMonitorBoundsByID(monitorId);
        Log($"Windows taskbar found on monitor {monitorId}");
        
        // Set Windows taskbar to completely transparent (0% opacity)
        // This effectively disables it while keeping system tray functionality
        BFS.Window.SetTransparency(winTaskbar, 0.0m);
        Log("Set Windows taskbar to transparent");
        
        // Find existing DisplayFusion taskbar or create a new one
        IntPtr dfTaskbar = FindOrCreateTaskbar(monitorId);
        if (dfTaskbar == IntPtr.Zero)
        {
            Log("Error: Could not find or create DisplayFusion taskbar");
            return;
        }

        // Check and fix the initial taskbar position if needed
        bool initialCorrected = CheckAndFixTaskbarPosition(dfTaskbar, monitorBounds);
        if (initialCorrected)
            Log("Initial position successfully corrected");
        else
            Log("No initial position correction needed");

        // Continue monitoring taskbar position for a short period to ensure stability
        // Testing has shown that if position issues occur, they happen within ~10 seconds
        MonitorTaskbarPosition(monitorId, monitorBounds);
        
        Log("Script execution completed successfully");
    }

    /// <summary>
    /// Logs a message to the DisplayFusion log for debugging and tracking purposes
    /// </summary>
    private static void Log(string message)
    {
        BFS.General.LogText(message);
    }

    /// <summary>
    /// Updates registry settings for Explorer and DisplayFusion to optimize taskbar replacement
    /// </summary>
    /// <returns>
    /// Tuple indicating if Explorer and/or DisplayFusion need to be restarted to apply changes
    /// </returns>
    private static (bool explorerNeeded, bool dfNeeded) UpdateRegistrySettings()
    {
        bool explorerRestartNeeded = false;
        bool dfRestartNeeded = false;
        
        // Check Explorer AutoTray setting - When set to 0, it ensures all system tray icons are visible
        // This is critical for taskbar replacement as otherwise some system tray icons might not be 
        // accessible after making the Windows taskbar transparent
        try
        {
            using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer", true))
            {
                if (key != null)
                {
                    object currentValue = key.GetValue("EnableAutoTray", null);
                    // If the value doesn't exist, isn't an integer, or isn't set to 0, update it
                    if (currentValue == null || !(currentValue is int) || ((int)currentValue != 0))
                    {
                        Log("Setting EnableAutoTray=0 to show all system tray icons");
                        key.SetValue("EnableAutoTray", 0, RegistryValueKind.DWord);
                        explorerRestartNeeded = true;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Log($"Error accessing Explorer registry: {ex.Message}");
        }
        
        // Check DisplayFusion settings to ensure taskbars appear on all monitors
        // including the primary monitor that has the Windows taskbar
        try
        {
            using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Binary Fortress Software\DisplayFusion", true))
            {
                if (key != null)
                {
                    string taskbarsValue = key.GetValue("TaskbarsAllMonitors", "0").ToString();
                    if (taskbarsValue != "1")
                    {
                        Log("Setting DisplayFusion registry values");
                        // Enable taskbars on all monitors, including primary
                        key.SetValue("TaskbarsAllMonitors", "1", RegistryValueKind.String);
                        // Set a faster polling interval for more responsive taskbar updates
                        key.SetValue("TaskbarPollInterval", "150", RegistryValueKind.String);
                        dfRestartNeeded = true;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Log($"Error accessing DisplayFusion registry: {ex.Message}");
        }
        
        return (explorerRestartNeeded, dfRestartNeeded);
    }

    /// <summary>
    /// Gracefully restarts Explorer to apply registry changes.
    /// This method specifically targets only the Explorer process running the taskbar,
    /// rather than killing all Explorer instances (which would close all open file windows).
    /// </summary>
    private static void RestartExplorer()
    {
        try
        {
            // Find Windows taskbar process
            IntPtr taskbarWindow = BFS.Window.GetWindowByClass("Shell_TrayWnd");
            if (taskbarWindow != IntPtr.Zero)
            {
                uint taskbarPid = BFS.Application.GetAppIDByWindow(taskbarWindow);
                if (taskbarPid != 0)
                {
                    // Kill only the taskbar's Explorer process, not all Explorer windows
                    Process.GetProcessById((int)taskbarPid).Kill();
                    Log("Explorer taskbar process terminated");
                }
            }
            
            // Start a new Explorer.exe process
            Process.Start("explorer.exe");
            
            // Wait for taskbar to reappear, with a timeout of 5 seconds
            DateTime timeout = DateTime.Now.AddSeconds(5);
            while (DateTime.Now < timeout && BFS.Window.GetWindowByClass("Shell_TrayWnd") == IntPtr.Zero)
            {
                BFS.General.ThreadWait(250);
            }
            
            // Give Explorer a moment to fully initialize
            BFS.General.ThreadWait(500);
        }
        catch (Exception ex)
        {
            Log($"Error restarting Explorer: {ex.Message}");
            // Fallback: try to start Explorer with no error handling
            try { Process.Start("explorer.exe"); } catch { }
        }
    }

    /// <summary>
    /// Gracefully restarts DisplayFusion to apply registry changes.
    /// Uses PowerShell to handle the restart sequence, as there's no direct BFS method
    /// that allows restarting DisplayFusion and preserving the script's execution state.
    /// </summary>
    private static void RestartDisplayFusion()
    {
        try
        {
            Log("Restarting DisplayFusion to apply registry changes...");
            
            // Get the path to DisplayFusion executable
            string dfPath = BFS.General.GetAppExecutable();

            // Create a separate PowerShell process to handle the restart sequence
            // This allows our script to exit while the restart process continues
            string psScript = $@"
                $dfPath = '{dfPath.Replace("'", "''")}';

                # Wait for DisplayFusion to exit
                while (Get-Process -Name 'DisplayFusion' -ErrorAction SilentlyContinue) {{ 
                    Start-Sleep -Milliseconds 100 
                }};

                # Start DisplayFusion again with RestartApp flag
                Start-Process -FilePath $dfPath -ArgumentList '-RestartApp 1';
            ";

            // Start PowerShell with the restart script
            ProcessStartInfo psi = new ProcessStartInfo();
            psi.FileName = "powershell.exe";
            psi.Arguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"{psScript}\"";
            psi.WindowStyle = ProcessWindowStyle.Hidden;
            psi.CreateNoWindow = true;

            Process.Start(psi);

            // Send the graceful shutdown message to DisplayFusion
            // This uses DisplayFusion's custom window message for clean shutdown
            uint message = RegisterWindowMessage("BFS_DISPLAYFUSION_CLOSE");
            PostMessage(HWND_BROADCAST, message, IntPtr.Zero, IntPtr.Zero);
            
            Log("Restart signal sent to DisplayFusion");
        }
        catch (Exception ex)
        {
            Log($"Error restarting DisplayFusion: {ex.Message}");
        }
    }

    /// <summary>
    /// Finds an existing DisplayFusion taskbar on the specified monitor
    /// or creates a new one if none exists.
    /// </summary>
    /// <param name="monitorId">The ID of the monitor where the taskbar should be located</param>
    /// <returns>Handle to the DisplayFusion taskbar window, or IntPtr.Zero if failed</returns>
    private static IntPtr FindOrCreateTaskbar(uint monitorId)
    {
        // Look for existing taskbar first
        IntPtr dfTaskbar = FindDFTaskbarOnMonitor(monitorId);
        if (dfTaskbar != IntPtr.Zero)
        {
            Log($"Found existing DisplayFusion taskbar on monitor {monitorId}");
            return dfTaskbar;
        }
        
        // Create new taskbar if not found
        Log($"Creating DisplayFusion taskbar on monitor {monitorId}");
        BFS.DisplayFusion.EnableTaskbar(monitorId);
        
        // Wait for taskbar creation with a maximum wait time of 5 seconds
        // The taskbar creation isn't instant and we need to allow time for it to appear
        for (int i = 0; i < 50; i++) // 5 seconds maximum (100ms × 50)
        {
            BFS.General.ThreadWait(100);
            dfTaskbar = FindDFTaskbarOnMonitor(monitorId);
            if (dfTaskbar != IntPtr.Zero)
                return dfTaskbar;
        }
        
        return IntPtr.Zero;
    }
    
    /// <summary>
    /// Searches for a DisplayFusion taskbar window on the specified monitor.
    /// DisplayFusion taskbars have a specific window class name format "DFTaskbar:*"
    /// </summary>
    /// <param name="monitorId">The ID of the monitor to search for a taskbar</param>
    /// <returns>Handle to the DisplayFusion taskbar window, or IntPtr.Zero if not found</returns>
    private static IntPtr FindDFTaskbarOnMonitor(uint monitorId)
    {
        // Iterate through all top-level windows
        foreach (IntPtr handle in BFS.Window.GetAllWindowHandles())
        {
            string windowClass = BFS.Window.GetClass(handle);
            // DisplayFusion taskbars have window class names starting with "DFTaskbar:"
            if (windowClass.StartsWith("DFTaskbar:") && BFS.Monitor.GetMonitorIDByWindow(handle) == monitorId)
                return handle;
        }
        return IntPtr.Zero;
    }

    /// <summary>
    /// Checks if the taskbar is positioned correctly, and adjusts it if needed.
    /// This ensures the DisplayFusion taskbar spans the full width/height of the monitor
    /// and is properly aligned to the appropriate edge.
    /// </summary>
    /// <param name="taskbarWindow">Handle to the DisplayFusion taskbar window</param>
    /// <param name="monitorBounds">Rectangle representing the monitor's bounds</param>
    /// <returns>True if position was corrected, false if no correction was needed</returns>
    private static bool CheckAndFixTaskbarPosition(IntPtr taskbarWindow, Rectangle monitorBounds)
    {
        if (taskbarWindow == IntPtr.Zero)
            return false;

        // Get current taskbar bounds and determine which position it's in
        Rectangle currentBounds = BFS.Window.GetBounds(taskbarWindow);
        TaskbarPosition position = DetermineTaskbarPosition(currentBounds, monitorBounds);
        Rectangle targetBounds = new Rectangle(currentBounds.X, currentBounds.Y, currentBounds.Width, currentBounds.Height);
        bool needsCorrection = false;

        // Calculate correct taskbar position based on which edge it's attached to
        // The taskbar should span the full width/height of the monitor and
        // be perfectly aligned with the corresponding edge
        switch (position)
        {
            case TaskbarPosition.Left:
                // Left taskbar should be aligned with left edge of monitor and span full height
                if (currentBounds.X != monitorBounds.X || currentBounds.Height != monitorBounds.Height)
                {
                    targetBounds.X = monitorBounds.X;
                    targetBounds.Y = monitorBounds.Y;
                    targetBounds.Height = monitorBounds.Height;
                    needsCorrection = true;
                }
                break;
            case TaskbarPosition.Right:
                // Right taskbar should be aligned with right edge of monitor and span full height
                int rightEdge = monitorBounds.Right - currentBounds.Width;
                if (currentBounds.X != rightEdge || currentBounds.Height != monitorBounds.Height)
                {
                    targetBounds.X = rightEdge;
                    targetBounds.Y = monitorBounds.Y;
                    targetBounds.Height = monitorBounds.Height;
                    needsCorrection = true;
                }
                break;
            case TaskbarPosition.Top:
                // Top taskbar should be aligned with top edge of monitor and span full width
                if (currentBounds.Y != monitorBounds.Y || currentBounds.Width != monitorBounds.Width)
                {
                    targetBounds.X = monitorBounds.X;
                    targetBounds.Y = monitorBounds.Y;
                    targetBounds.Width = monitorBounds.Width;
                    needsCorrection = true;
                }
                break;
            case TaskbarPosition.Bottom:
                // Bottom taskbar should be aligned with bottom edge of monitor and span full width
                int bottomEdge = monitorBounds.Bottom - currentBounds.Height;
                if (currentBounds.Y != bottomEdge || currentBounds.Width != monitorBounds.Width)
                {
                    targetBounds.X = monitorBounds.X;
                    targetBounds.Y = bottomEdge;
                    targetBounds.Width = monitorBounds.Width;
                    needsCorrection = true;
                }
                break;
        }

        // Apply correction if taskbar isn't flush against monitor edge
        // Using SetWindowPos directly as BFS methods don't reliably work with DisplayFusion taskbars
        if (needsCorrection)
        {
            Log($"Correcting taskbar position for {position} taskbar");
            try
            {
                return SetWindowPos(
                    taskbarWindow,
                    IntPtr.Zero,
                    targetBounds.X,
                    targetBounds.Y,
                    targetBounds.Width,
                    targetBounds.Height,
                    SWP_FLAGS
                );
            }
            catch (Exception ex)
            {
                Log($"Position correction failed: {ex.Message}");
                return false;
            }
        }
        return false;
    }
    
    /// <summary>
    /// Determines which side of the monitor the taskbar is positioned on
    /// based on its dimensions and position relative to the monitor center.
    /// </summary>
    /// <param name="taskbarBounds">Rectangle representing the taskbar's bounds</param>
    /// <param name="monitorBounds">Rectangle representing the monitor's bounds</param>
    /// <returns>TaskbarPosition enum value indicating the taskbar's position</returns>
    private static TaskbarPosition DetermineTaskbarPosition(Rectangle taskbarBounds, Rectangle monitorBounds)
    {
        // Check if taskbar is horizontal or vertical based on dimensions
        bool isHorizontal = taskbarBounds.Width > taskbarBounds.Height;
        
        if (isHorizontal)
        {
            // If horizontal, determine if it's at top or bottom based on Y position
            // relative to the vertical center of the monitor
            return taskbarBounds.Y < monitorBounds.Y + monitorBounds.Height / 2 ? 
                   TaskbarPosition.Top : TaskbarPosition.Bottom;
        }
        else
        {
            // If vertical, determine if it's at left or right based on X position
            // relative to the horizontal center of the monitor
            return taskbarBounds.X < monitorBounds.X + monitorBounds.Width / 2 ? 
                   TaskbarPosition.Left : TaskbarPosition.Right;
        }
    }

    /// <summary>
    /// Monitors taskbar position for a short period to ensure it remains properly positioned.
    /// Testing has shown that if the taskbar position is going to shift unexpectedly,
    /// it typically happens within about 10 seconds of creation. This method monitors for
    /// 15 seconds to ensure stability.
    /// </summary>
    /// <param name="monitorId">The ID of the monitor containing the taskbar</param>
    /// <param name="monitorBounds">Rectangle representing the monitor's bounds</param>
    private static void MonitorTaskbarPosition(uint monitorId, Rectangle monitorBounds)
    {
        Log("Beginning position monitoring (15 seconds max)");
        DateTime endTime = DateTime.Now.AddSeconds(15);

        while (DateTime.Now < endTime)
        {
            BFS.General.ThreadWait(100);
            
            // Re-find the taskbar to get a fresh handle
            // This ensures we're working with the current window even if it was recreated
            IntPtr dfTaskbar = FindDFTaskbarOnMonitor(monitorId);
            if (dfTaskbar == IntPtr.Zero)
                continue;

            // Check if position correction was needed
            if (CheckAndFixTaskbarPosition(dfTaskbar, monitorBounds))
            {
                Log("Taskbar position corrected during monitoring");
                return; // Exit early on successful correction
            }
        }
        
        Log("Monitoring complete - no corrections needed");
    }
}