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

}
}


10 comments:

  1. Hi, since you made yourself the trouble to post some code, I would like to contribute.

    However, is there a possibility to attach a file instead of code only.

    The code got a bit bigger.

    The key for closing the windows is to send a Windows Message like this. It works fine.

    NativeMethods.PostMessage(skypeHomeHandle, (uint)NativeMethods.WMCode.WM_SYSCOMMAND, 0xF060, 0);

    NativeMethods.WMCode.WM_SYSCOMMAND is 0x0112
    0xF060 means SC_CLOSE


    I have also a possibility to inject code directly into skype, so that skype can be modified with an extra thread checking for the Home window. Its a nice snipped from the microsoft tool Snoop.

    However, code gets too long to post in your blog, unless you tell me to do so.

    cheers

    Philipp

    ReplyDelete
  2. Hi Philipp,

    Thanks so much for your feedback. I've done very little with the Windows API and the whole idea of interacting with other processes is still a bit of a mystery to me, although exciting considering we can do handy hacks like this. I suppose asking a window to close is nicer than setting it to hidden. Although the both result in a hidden window, I suppose the better way is if the app that owns it still knows what's going on!

    Your code sounds interesting. I'd like to see it. I don't know about how attaching files could work, though. Maybe if you could upload it somewhere else you could link to it?

    I'd like to get some hosting of my own sorted out so I could upload the source code and even some compiled code.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Hi Philipp,

    For some reason the download doesn't work for me. Perhaps the link expired?

    I'd like to learn how to snoop the resources within skype.exe so that we can find where the text "Skype Home" comes from. Then we could look it up at runtime so that it would work for all languages!

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Hi Ken

    I took the liberty of publishing my code (which is based on yours) here:

    http://sourceforge.net/projects/skypesedator/

    Please feel free to join.

    P.S. Sorry to make your blog looking like a mess.

    ReplyDelete
  9. Numerous people as of now have decreased, or crossed out their property line phone lines for reasons which incorporate comfort, investment funds, and includes; and have changed to web or remote telephone numbers.https://800support.net/sign-up/gmail-sign-up-gmail-register/

    ReplyDelete
  10. I am extremely delighted in for this web journal. Its a useful subject. It help me all that much to take care of a few issues. Its chance are so awesome and working style so rapid. Skype Offline Installer

    ReplyDelete