Thursday, August 25, 2011

SilentSkype v0.2

Here's a revised listing for SilentSkype.exe. This one fixes the main bug from the previous code which was that after it hid the Skype window, you couldn't actually restore it again. A bit of a significant problem there. It turned 'silent' Skype into 'coma Skype'. Thanks go to Philipp who helped with that part. I'm still new to the Windows API, but getting there!


using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace SilentSkype
{
class Program
{
#region Constants

///
/// Tells the window that the other parameter is a system command.
///

private const uint WM_SYSCOMMAND = 0x0112;

///
/// Closes the window.
///

private const uint SC_CLOSE = 0xF060;

#endregion

#region Private Variables

///
/// The executable path to Skype. (The original shortcut had "/nosplash /minimized" on the end.)
///

private static string _exePath = null;

///
/// The caption of the secondary window. This defaults to "Skype Home"
/// but can be specified by the user via the command line.
///

private static string _captionText = "Skype Home";

///
/// The handle to the main Skype window.
///

private static IntPtr _mainWindowHandle = IntPtr.Zero;

///
/// The handle to the infamous "Skype Home" window.
///

private static IntPtr _skypeHomeHandle = IntPtr.Zero;

///
/// The name of the Skype process, to allow checking for it already running.
/// If left as null, the program will not check for an existing instance
/// and will always attempt to launch a new process.
///

private static string _processName = null;

///
/// The maximum length of time to wait for the Skype Home window to appear before giving up.
///

private static TimeSpan _maxWaitTime = new TimeSpan(0, 0, 30);

///
/// The time the process started, so we can ensure we don't spend forever waiting.
/// This defaults to MinValue so that when we subtract it from DateTime.Now,
/// if it hasn't been initiated it'll fail the first time.
///

private static DateTime _startTime = DateTime.MinValue;

///
/// Details of the Skype process we are starting.
///

private static Process _skypeProcess = null;

///
/// The number of ms to wait between trying things.
///

private static int _pauseTime = 500;

#endregion

///
/// The main entrypoint into the application.
/// Everything is done with static variables and methods rather than bothering with
/// creating an instance of something.
///

/// static void Main(string[] args)
{
try
{
if (!ProcessArgs(args))
return; // Something was wrong with the args, so we quit here.

try
{
_startTime = DateTime.Now; // Start timing the overall process, so we don't run too long.

// Start the Skype process.
Console.WriteLine("Checking Skype is running.");
_skypeProcess = StartSkype();

// Ensure the window handles are zero to begin with.
_mainWindowHandle = IntPtr.Zero;
_skypeHomeHandle = IntPtr.Zero;

// Wait for the "Skype Home" window first of all, to avoid getting the handle on the wrong main window.
// The login window appears first of all, and if we're not careful, we can get the handle on that instead
// of the compact view.
while (_skypeHomeHandle == IntPtr.Zero && CanKeepGoing())
{
Console.WriteLine("Waiting for \"Skype Home\" window to be created...");
// Wait a short time.
Thread.Sleep(_pauseTime);
_skypeHomeHandle = FindWindowByCaption(IntPtr.Zero, _captionText);
}

// Update the handle to the main Skype window.
while (_mainWindowHandle == IntPtr.Zero && CanKeepGoing())
{
Console.WriteLine("Waiting for main window to be created...");
// Wait a short time.
Thread.Sleep(_pauseTime);
_skypeProcess.Refresh();
_mainWindowHandle = _skypeProcess.MainWindowHandle;
}

Console.WriteLine("Done getting handles.");

if (_mainWindowHandle != IntPtr.Zero && _skypeHomeHandle != IntPtr.Zero && CanKeepGoing())
{
// Wait for the windows to become visible.
bool mainWindowVisible = false;
bool skypeHomeVisible = false;

do
{
Console.WriteLine("Waiting for windows to become visible.");
// Wait a short time.
Thread.Sleep(_pauseTime);

if (!mainWindowVisible)
mainWindowVisible = IsWindowVisible(_mainWindowHandle);

if (!skypeHomeVisible)
skypeHomeVisible = IsWindowVisible(_skypeHomeHandle);

} while ((!mainWindowVisible || !skypeHomeVisible) && CanKeepGoing());

Console.WriteLine("Closing Skype windows...");

// Close the "Skype Home" window.
if (_skypeHomeHandle != IntPtr.Zero && skypeHomeVisible)
SendMessage(_skypeHomeHandle, WM_SYSCOMMAND, (IntPtr)SC_CLOSE, IntPtr.Zero);

// Close the main window.
if (_mainWindowHandle != IntPtr.Zero && mainWindowVisible)
SendMessage(_mainWindowHandle, WM_SYSCOMMAND, (IntPtr)SC_CLOSE, IntPtr.Zero);

Console.WriteLine("All done.");
}
}
finally
{
// Dispose the process here.
if (_skypeProcess != null)
{
_skypeProcess.Dispose();
_skypeProcess = null;
}
}
}
catch (Exception ex)
{
Console.WriteLine();
Console.WriteLine("Unhandled Exception: " + ex.Message);
Console.WriteLine(ex.StackTrace);
}
}

///
/// This processes the command line arguments and populates variables where relevant.
/// If any problems are detected, the user is notified and validation fails.
///

/// The command line arguments/// True if validation was successful, false otherwise
private static bool ProcessArgs(string[] args)
{
if (args.Length < 1) { Console.WriteLine("Usage: SilentSkype.exe ExePath [CaptionText] [ProcessName] [MaxWaitTime]"); Console.WriteLine(); Console.WriteLine("ExePath: The path to the executable, including the filename."); Console.WriteLine("CaptionText: The caption of the window to be located."); Console.WriteLine("ProcessName: The name of the process, to check if it's already running."); Console.WriteLine("MaxWaitTime: The maximum length of time (in seconds) that the program will wait for the UI to appear."); return false; } _exePath = args[0]; if (args.Length > 1)
_captionText = args[1];

if (args.Length > 2)
_processName = args[2];

if (args.Length > 3)
{
int maxWaitTime = 0;

if (int.TryParse(args[3], out maxWaitTime))
{
_maxWaitTime = new TimeSpan(0, 0, maxWaitTime);
}
else
{
Console.WriteLine("Could not parse MaxWaitTime as an integer: {0}", args[3]);
return false;
}
}

if (!File.Exists(_exePath))
{
// Return if the executable does not exist.
Console.WriteLine("File not found: {0}", _exePath);
return false;
}

return true;
}

///
/// Checks to see if Skype is already running and start if it's not.
///

/// Details of the Skype process
private static Process StartSkype()
{
Process skypeProcess = null;

if (!String.IsNullOrEmpty(_processName))
{
// Look for a matching process.
Console.WriteLine("Looking for matching process...");
var matchingProcesses = Process.GetProcessesByName(_processName).Where(mp => !mp.HasExited);

if (matchingProcesses.Count() > 0)
{
Console.WriteLine("Skype is already running.");
// Match found - Skype must be running already.
skypeProcess = matchingProcesses.First(); // Take the first instance.

// Dispose of any processes we don't need to keep.
foreach (var item in matchingProcesses)
{
if (item != skypeProcess)
item.Dispose();
}

matchingProcesses = null;
}
else
{
Console.WriteLine("Skype is not running.");
}
}

if (skypeProcess != null)
return skypeProcess;

Console.Write("Launching Skype...");
skypeProcess = Process.Start(_exePath);
Console.WriteLine("done.");

return skypeProcess;
}

///
/// True if we can keep waiting for handles to be created,
/// windows to be visible etc. We should stop if:
/// a) The process has exited. (in case we pick up a closing process by accident)
/// b) We have run out of time
///

/// True if we can keep going, false otherwise
private static bool CanKeepGoing()
{
if (_skypeProcess.HasExited)
return false; // The process has exited for some reason or other.
else if (DateTime.Now - _startTime >= _maxWaitTime)
return false; // Ran out of time
else
return true; // No other reason to stop.
}

#region Interop

[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsWindowVisible(IntPtr hWnd);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

#endregion

}
}

No comments:

Post a Comment