WCF Remote Keyboard

Ich wollte für mein Projekt, einen Surface 2 als Couchtisch zu benutzen, eine Steuerung von Mediaportal implementieren. Also setzte ich mich mit dem Plugin MPExtended auseinander. Dieses Plugin kann sehr viel, aber es fehlt die Möglichkeit, einfache Kommandos an Mediaportal zu senden (hoch, runter, links, rechts). Ein anderes Plugin bot dies zwar an, aber für die Sourcen hätte man erst einmal den Developer kontaktieren müssen. Somit entwickelte ich meine eigene Version mittels SendKeys, die im Endeffekt als Remote Keyboard fungieren kann. Hier nun Das Tutorial dazu.

Das ganze besteht aus 3 Teilen:

  • WCF Service
  • Selfhosting für den Service
  • Client

WCF Service

Der Service ist recht einfach, er hat nur eine Methode, welche SendKeys Argumente annimmt und damit einen Aufruf an SendKeys startet.
Wenn ihr ein neues Projekt vom Typ WCF Service erstellt, werden automatisch eine Klasse und ein zugehöriges Interface erstellt.

namespace RemoteKeyboardService
{
    public class Service1 : IService1
    {
        public void SendKeys(string keys)
        {
            System.Windows.Forms.SendKeys.SendWait(keys);
        }
    }
}

Da WCF auf Interfaces arbeitet, hier noch das Interface IService1:

using System.ServiceModel;

namespace RemoteKeyboardService
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        void SendKeys(string keys);
    }
}

Bitte beachtet, dass hier keine Fehlerbehandlung gemacht wird, der Fehler wird also an den Client durchgereicht, sofern man dies in der app.config (kommt später) angegeben hat. Wer es sauberer möchte, kann als Rückgabewert einen bool nehmen und einen try/catch Block um den SendKeys Aufruf machen.

Selfhosting für den Service

Da der Service nicht in einem IIS gehostet werden soll, sondern wir ihn sebler hosten, erstellen wir eine neue Console Application, um ihn selbst zu hosten:

using System;
using System.ServiceModel;

namespace RemoteKeyboardServiceSelfHosting
{
    class Program
    {
        static void Main(string[] args)
        {
            string host = "http://localhost";
            if (args.Length != 0)
            {
                host = args[0];
                if (!(host.StartsWith("http://") || host.StartsWith("https://")))
                    host = "http://" + host;
                host.Replace("\\", "/");
                if (host.EndsWith("/"))
                    host = host.Substring(0, host.Length - 1);
            }
            string uri = host + "/RemoteKeyboard";
            Console.WriteLine("RemoteKeyboard Service wird gestartet unter: " + uri);
            ServiceHost sh = new ServiceHost(typeof(RemoteKeyboardService.Service1),
                                                new Uri(uri));
            sh.Open();
            Console.WriteLine("RemoteKeyboard Service gestartet");
            Console.WriteLine();

            Console.WriteLine("Beliebige Taste zum beenden drücken");

            Console.ReadLine();
        }
    }
}

Wer hier mehr den quick & dirty style will, kann auch das übergeben der URI als Argument aussparen und alles hardcoden.

Im Service Projekt sollte jetzt eine app.config vorhanden sein und im Selfhosting Projekt sollte keine enthalten sein.
Da der Service aber im Selfhosting Projekt gehostet wird und die app.config nicht automatisch übernommen wird, muss dies händisch gemacht werden.
Am Ende sollte die app.config im Selfhosting Projekt etwa so aussehen:

<configuration>

  <system.web>
    <compilation debug="true" />
  </system.web>
  <!-- When deploying the service library project, the content of the config file must be added to the host's
  app.config file. System.Configuration does not support config files for libraries. -->
  <system.serviceModel>
    <services>
      <service name="RemoteKeyboardService.Service1">
        <host>
          <baseAddresses>
            <add baseAddress = "http://localhost:8732/Design_Time_Addresses/RemoteKeyboardService/Service1/" />
          </baseAddresses>
        </host>
        <!-- Service Endpoints -->
        <!-- Unless fully qualified, address is relative to base address supplied above -->
        <endpoint address ="" binding="wsHttpBinding" contract="RemoteKeyboardService.IService1">
          <!--
              Upon deployment, the following identity element should be removed or replaced to reflect the
              identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity
              automatically.
          -->
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <!-- Metadata Endpoints -->
        <!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
        <!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information,
          set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="True"/>
          <!-- To receive exception details in faults for debugging purposes,
          set the value below to true.  Set to false before deployment
          to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

</configuration>

Wenn ihr das ganze nun kompiliert und startet, könnt ihr im Browser eure festgelegte URI eingeben und solltet eine Seite mit Informationen zu eurem Service bekommen.

Client

Um von dem Client auf den Service zugreifen zu können, ist es wichtig, dass dieser auch läuft.
Startet also euren Service und fügt im Client Projekt nun eine Service Reference hinzu.
Gebt als URI die URI von eurem Service an. (ggf. mit „/mex“ dahinter, dies steht für Metadate Exchange und dort werden Metadaten über euren Service übertragen)
Euch werden nun Autmatisch eine Proxyklasse und Einstellungen für diese erzeugt.
Ihr könnt nun eine Instanz des Proxys erzeugen und die Methode darauf aufrufen:

var RemoteKeyboard = new Service1Client();
RemoteKeyboard.Open();
RemoteKeyboard.SendKeys("A");

Ggf. müsst ihr in der app.config eures Clientprojekts noch die Bindings anpassen. Hier empfielt sich fürs erste ein basichttpBinding.
Hier ist euer Service einfach offen für alle und es sollte keine Probleme geben.
Ist euer Service übers Netz erreichbar, solltet ihr allerdings zusätzliche Sicherheitsvorkehrungen treffen.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IService1" />
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost/RemoteKeyboard/"
          binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
          contract="RemoteKeyboardServiceReference.IService1" name="BasicHttpBinding_IService1">
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

Wie ihr nun Tastendrücke auf dem Client abgreift, ist euch überlassen.
Ich habe mir ein ICommand (Siehe MVVM Tutorial) geschrieben, welches als Parameter Die SendKeys Argumente bekommt und weiterleitet, da ich in meiner GUI Buttons für hoch, runter, links, rechts, … anzeigen möchte.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.