﻿/*
 * ProcessChecker.cs
 * 
 * This class implements a simple and almost-always effective algorithm
 * to find currently running processes with a main window matching a given substring
 * and, if there is a match, to focus it. This code isn't as simple as it should be
 * because Windows will not return a window handle if the window is hidden. Therefore,
 * simpler approaches that do not use window enumeration will fail. To call this code,
 * simply call IsOnlyProcess in your Program.cs code before it creates the inital form.
 * You must pass a substring that the window to be focused in the process must always
 * contain in its .Text property. If your program contains the title "Test Program - {0}",
 * where the {0} is a varying string or number, you should pass "Test Program."
 * The code is not entirely obvious and was adapted from code by the following Microsoft
 * engineer:
 * 
 * http://www.dotnet247.com/247reference/msgs/20/103332.aspx
 * Lion Shi, MCSE, MCSD
 * Microsoft Support Engineer
 * 
 * See my site article at
 * http://dotnetperls.com/Content/Single-Instance-Windows-Form.aspx
 * for information on how to use this code.
 * 
 * Original code by Samuel Allen. Copyright 2008.
 * Dot Net Perls, http://dotnetperls.com/
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 * */

namespace DotNetPerls
{
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Text;
    using System;
    using System.Windows.Forms;

    /// <summary>
    /// Check running processes for an already-running instance.
    /// </summary>
    static class ProcessChecker
    {
        static string _requiredString;

        internal static class NativeMethods
        {
            [DllImport("user32.dll")]
            public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);

            [DllImport("user32.dll")]
            public static extern bool SetForegroundWindow(IntPtr hWnd);

            [DllImport("user32.dll")]
            public static extern bool EnumWindows(EnumWindowsProcDel lpEnumFunc, Int32 lParam);

            [DllImport("user32.dll")]
            public static extern int GetWindowThreadProcessId(IntPtr hWnd, ref Int32 lpdwProcessId);

            [DllImport("user32.dll")]
            public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, Int32 nMaxCount);

            public const int SW_SHOWNORMAL = 1;
        }

        public delegate bool EnumWindowsProcDel(IntPtr hWnd, Int32 lParam);

        // note: must return bool! to match up with system call.
        static private bool EnumWindowsProc(IntPtr hWnd, Int32 lParam)
        {
            int processId = 0;
            NativeMethods.GetWindowThreadProcessId(hWnd, ref processId);

            StringBuilder caption = new StringBuilder(1024);
            NativeMethods.GetWindowText(hWnd, caption, 1024);

            // use IndexOf to make sure our required string is in the title.
            if (processId == lParam && (caption.ToString().IndexOf(_requiredString,
                StringComparison.OrdinalIgnoreCase) != -1))
            {
                // Restore the window
                NativeMethods.ShowWindowAsync(hWnd, NativeMethods.SW_SHOWNORMAL);
                NativeMethods.SetForegroundWindow(hWnd);
            }
            return true;
        }

        /// <summary>
        /// Find out if we need to continue to load the current process. If we don't focus the old process
        /// that is equivalent to this one.
        /// </summary>
        /// <param name="forceTitle">This string must be contained in the window to restore. Use a string
        /// that is not empty but contains the most unique sequence possible. If the program has windows with the
        /// string "Journal", pass that word.</param>
        /// <returns>False if no previous process was activated. True if we did focus a previous process
        /// and should simply exit the current one.</returns>
        static public bool IsOnlyProcess(string forceTitle)
        {
            _requiredString = forceTitle;
            foreach (Process proc in Process.GetProcessesByName(Application.ProductName))
            {
                if (proc.Id != Process.GetCurrentProcess().Id)
                {
                    NativeMethods.EnumWindows(new EnumWindowsProcDel(EnumWindowsProc), proc.Id);
                    return false;
                }
            }
            return true;
        }
    }
}