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

}
}

Wednesday, August 24, 2011

Introducing Silent Skype

I've been working on my own alternative to Kill Skype Home since I don't really like downloading mystery .exe files. Mine is called SilentSkype.exe and can be run from the command line as follows:

SilentSkype.exe [ExePath] [Window Caption] [Process Name] [Max Wait Time]

e.g.

SilentSkype.exe Skype.exe "Skype Home" Skype 10

The path to the .exe can be absolute or relative, depending on where you put SilentSkype.exe to begin with.

The Window Caption is used to locate the Skype Home window. If for any reason the caption changes in a future version, change the value passed here. The program will always attempt to close the main window. (I'm assuming compact mode here, it's not perfect yet!)

The Process Name can be used to help locate an instance already running. If you supply this parameter, SilentSkype will attempt to use the existing instance of Skype rather than spawning a new one.

Finally, Max Wait Time is the maximum length of time (in seconds) that the program will wait for the Skype Home window to appear.

Here's the source code for anyone who wants to compile it. It's written in C#.NET. Note that this is the very first release, so it's probably full of bugs!


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

namespace SilentSkype
{
class Program
{
///
/// Hides the window and activates another window.
///

private const uint SW_HIDE = 0;

#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 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);

#endregion

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

Process skypeProcess = null;
IntPtr mainWindowHandle = IntPtr.Zero;
IntPtr skypeHomeHandle = IntPtr.Zero;

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

Console.WriteLine("Looking for window handles.");

// Get a handle to the main Skype window.
mainWindowHandle = skypeProcess.MainWindowHandle;

// Get a handle to the "Skype Home" window.
if (!String.IsNullOrEmpty(_captionText))
skypeHomeHandle = FindWindowByCaption(IntPtr.Zero, _captionText);

Console.WriteLine("Hiding windows...");

// Hide the "Skype Home" window.
if (skypeHomeHandle != IntPtr.Zero)
ShowWindow(skypeHomeHandle, SW_HIDE);

// Hide the main window.
if (mainWindowHandle != IntPtr.Zero)
ShowWindow(mainWindowHandle, SW_HIDE);

Console.WriteLine("Windows hidden!");
}
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);

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

// Dispose of any processes we don't need to keep.
for (int i = 1; i < matchingProcesses.Length; i++)
{
matchingProcesses[i].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.");

// Wait for the main window to become visible.
DateTime start = DateTime.Now; // Start waiting for the window to open.

while (String.IsNullOrEmpty(skypeProcess.MainWindowTitle) && DateTime.Now - start < _maxWaitTime)
{
Console.WriteLine("Waiting for UI to load...");
Thread.Sleep(500);
// Refresh the process to see if the main window has now been loaded.
skypeProcess.Refresh();
}

Console.WriteLine("UI detected.");
return skypeProcess;
}

#region Interop

///
/// Find window by Caption only. Note you must pass IntPtr.Zero as the first parameter.
///

/// This must be IntPtr.Zero
/// The caption of the window to be found
/// A handle for the window, or IntPtr.Zero if no match was found.
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
public static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);

///
/// Changes the state of the window. Can be used to show, hide, close etc.
///

/// A handle for the window in question
/// The command to be sent to the window
/// True if the operation was a success, false otherwise.
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hwnd, uint cmdShow);

#endregion

}
}