Focus-Window.psm1

Add-Type -Language CSharp -TypeDefinition @"
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using System.Text;

    public static class FocusWindowHelpers
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        public static void BringToFront(IntPtr handle)
        {
            ShowWindow(handle, 5);
            SetForegroundWindow(handle);
        }


        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left, Top, Right, Bottoom;
        }

        public static bool WindowExists(IntPtr handle)
        {
            RECT r;

            return GetWindowRect(handle, out r);
        }


        private delegate bool EnumDesktopWindowsDelegate(IntPtr hWnd, int lParam);

        [DllImport("user32.dll")]
        private static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumDesktopWindowsDelegate lpfn, IntPtr lParam);
        [DllImport("user32.dll")]
        private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount);

        public static List<KeyValuePair<string, IntPtr>> GetAllExistingWindows()
        {
            var windows = new List<KeyValuePair<string, IntPtr>>();

            EnumDesktopWindows(IntPtr.Zero, (h, _) =>
            {
                StringBuilder title = new StringBuilder(256);
                int titleLength = GetWindowText(h, title, 512);

                if (titleLength > 0)
                    windows.Add(new KeyValuePair<string, IntPtr>(title.ToString(), h));

                return true;
            }, IntPtr.Zero);

            return windows;
        }
    }
"@


function Find-WindowHandle {
    <#
    .SYNOPSIS
    Finds the handle of the window matching the given query.


    .PARAMETER Query
    A query that has one of the following formats:
        - A window handle.
        - A window title followed by its handle enclosed in parentheses.
        - A RegEx pattern that is tested against all existing windows.


    .RETURNS
    An `IntPtr` matching the given query if a window is found; `IntPtr.Zero` otherwise.


    .EXAMPLE
    Find the handle of the first window having 'powershell' in its name.

    Find-WindowHandle powershell

    
    .EXAMPLE
    Find the handle of the first window named 'powershell'.

    Find-WindowHandle '^powershell$'


    .EXAMPLE
    Return the given handle, if a window exists with this handle.

    Find-WindowHandle 10101010


    .EXAMPLE
    Return the given handle, if a window exists with the handle at the end of the given string.

    Find-WindowHandle 'powershell (10101010)'

    #>

    param([String] $Query)

    # Find handle in title (either the whole title is the handle, or enclosed in parenthesis).
    if ($Query -match '^\d+$') {
        $Handle = [IntPtr]::new($Query)
    } elseif ($Query -match '^.+ \((\d+)\)\s*$') {
        $Handle = [IntPtr]::new($Matches[1])
    } else {
        # Find handle in existing processes.
        $MatchingWindows = [FocusWindowHelpers]::GetAllExistingWindows() | ? { $_.Key -match $Query }

        if (-not $MatchingWindows) {
            return [IntPtr]::Zero
        }

        # No need to ensure the window does exist, return immediately.
        return $MatchingWindows[0].Value
    }

    # Make sure the handle exists.
    if ([FocusWindowHelpers]::WindowExists($Handle)) {
        return $Handle
    } else {
        return [IntPtr]::Zero
    }
}

function Focus-Window {
    <#
    .SYNOPSIS
    Focuses the window having the given handle.


    .PARAMETER Query
    A window title query that will be resolved using `Find-WindowHandle`.


    .EXAMPLE
    Focus the first window having 'powershell' in its name.

    Focus-Window powershell

    #>

    param(
        [ValidateScript({
            if ( (Find-WindowHandle $_) -ne 0 ) {
                $true
            } else {
                throw "Cannot find window handle for query '$_'."
            }
        })]
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [String] $Query
    )

    $Handle = Find-WindowHandle $Query

    [FocusWindowHelpers]::BringToFront($Handle)
}


Register-ArgumentCompleter -CommandName Focus-Window -ParameterName Query -ScriptBlock {
    param($CommandName, $ParameterName, $WordToComplete, $CommandAST, $FakeBoundParameter)

    function GetWindowTitleScore {
        <#
        .SYNOPSIS
        Returns the score of the given title when compared with the word we're currently completing.

        The score will be higher depending on which of these conditions is true:
            - The titles are equal (case-sensitive).
            - The titles are equal (case-insensitive).

            - The titles are similar (case-sensitive).
            - The titles are similar (case-insensitive).

            - The title starts with the given word (case-sensitive).
            - The title starts with the given word (case-insensitive).

            - The title contains the given word (case-sensitive).
            - The title contains the given word (case-insensitive).

        #>

        param([String] $Title)

        if (-not $Title.Length) { return 0 }

        if ($WordToComplete -ceq $Title) { return 950 }
        if ($WordToComplete -ieq $Title) { return 900 }
        if ($Title -clike $WordToComplete) { return 850 }
        if ($Title -ilike $WordToComplete) { return 800 }

        if ($Title.StartsWith($WordToComplete)) { return 750 }
        if ($Title.StartsWith($WordToComplete, $True, [cultureinfo]::InvariantCulture)) { return 700 }

        if ($Title -ccontains $WordToComplete) { return 650 }
        if ($Title -icontains $WordToComplete) { return 600 }

        0
    }

    $WindowsScores = [FocusWindowHelpers]::GetAllExistingWindows() | % {
        @{
            Title  = $_.Key;
            Handle = $_.Value;
            Score  = GetWindowTitleScore $_.Key
        }
    }

    $MatchingWindows = $WindowsScores                                               `
                        | ? { $_.Score -gt 0 }                                      `
                        | sort @{ Expression = { $_.Score }; Ascending = $False },  `
                               @{ Expression = { $_.Title }; Ascending = $True  }

    $MatchingWindows | % {
        $Title = "'$($_.Title -creplace "'", "''") ($($_.Handle))'"

        [System.Management.Automation.CompletionResult]::new(
            $Title, $Title, 'ParameterValue', $Title
        )
    }
}