Neutral Scent

App developments & Gadgets

Windows Phone 8.1用はてなブックマーク登録アプリNatehaR.Express Version 1.1を公開しました


Windows Phone 8.1用、共有コントラクト型はてなブックマーク登録専用アプリ、NatehaR.ExpressをVersion 1.1へバージョンアップしました。無料です。

Version 1.1での変更点:

  • タグ入力候補リストの実装
  • 起動等パフォーマンスの改善

ようやく、タグ入力の補完機能が付きました。出てきた候補から選んでタップするだけでタグを入力できます。
候補に表示されるタグは、ブックマークの既存タグと、自分のブクマのタグ一覧を取得して合わせて表示しています。これまでのブックマークで多数のタグを使用している場合、候補が出るのが若干遅くなる場合があります(はてブからのタグ取得に時間がかかります)。
Windows Phone Storeからダウンロード:
http://www.windowsphone.com/ja-jp/store/app/natehar-express/95acd3a5-8e04-4a83-9c47-0e755050dd85
http://www.windowsphone.com/ja-jp/store/app/natehar-express/95acd3a5-8e04-4a83-9c47-0e755050dd85
アプリ詳細について、

Windows Phone 8.1用はてなブックマーク登録アプリNatehaR.Expressを公開しました - Neutral Scent
http://d.hatena.ne.jp/kaorun/20140510/1399709947

買ってみた:車を始動できるスターター機能付きモバイルバッテリー チャージアンドスターター(チャースタ)


うちの車わりとバッテリーに厳しく、とくに何かしたわけでなくても、冬場に数回バッテリーが上がります。
まー、非力な1.2Lのイタ車(痛く無い方)にサイバーナビとかETCとか電装系満載なのも良く無いわけですが...。
そんなわけで、冬場はロードサービスとお友達「あ、去年もでしたね」とか言われる始末、結果バッテリースターター系のグッズも幾つか試したのですが、あまり芳しくありませんでした。

そう思いつつも色々試したくなるのはガジェオタの性、スターターにも使えるモバイルバッテリーがあると聞いて、すぐには手を出さなかったモノの、どうなのかなー、と様子を探っていました。で、ん?これはよさそうじゃない? と思えた製品が出てきたので、購入トライしてみたわけです。

今回購入したのは、チャージ&スターター、略称チャースタという製品。
公式ページ: http://charge-and-starter.com/
Amazonで1万5000円ぐらいですかね。ちょっとお高めですが、この手のカー用品だとこんなものかな、という感じ。
ちょっと大き目のモバイルバッテリーに専用バッテリー始動用ジャンブケーブルがぶっ刺さる仕組み。リチウムイオンで8000mAhでふつうのLEDライト搭載モバイルバッテリーとしても利用できます。
見た目がこちら、

パッケージとかもわりとしっかりしてます、

コネクタ類はこういう状態、

で、こんな感じで車のバッテリーのターミナルに接続して始動すれば、エンジンがかかる、というわけです。

説明としては、以上終了という感じ。
あっけないぐらい普通に使えます。こんなちっこいバッテリーでも何気なくエンジンが始動。
今年の大雪の直後にプチ切れて購入したので、実は購入してから3か月ほど経ってますが、今年はもうバッテリーが瀕死らしく4回ほどお世話になってます(どんだけやねん)。その間2回は間に充電してません。始動に使っても1か月放置しても充電ゲージはほぼ減らない感じ、車に乗せっぱなしです。

基本的なスペックは、

容量: 8000mAh
重量: 300g
外寸: 131x75x25mm
出力: 5V/2A, 12V/200〜400A
充電: 専用ACアダプタ, 約4時間
付属品: USB充電用三又ケーブル(miniUSB/microUSB/Apple Dockコネクタ), ACアダプター, ジャンプスタート用ケーブル, アクセサリソケット(シガーライター)充電ケーブル

これまでの鉛シールドバッテリーなどを使用したポータブルスターターと比べると格段にコンパクトかつメンテフリーなので、車に乗せっぱなしにしても邪魔にも無駄にもなりませんし、LiIonだけに過放電などになることもないでしょう、普通のスマフォ用モバイルバッテリーや超長時間利用できるLEDランプにもなるので、実用性も高く出先での安心感も上がります。
製品としての見た目もしっかりしており、色の好みはあるかもしれませんが、この手の製品としては上質な部類かと思います。うちにあるQi対応モバイルバッテリー達のような中国産でございます、といった雰囲気もありません(素材からして違う感じ)。それから、付属品(ジャンプケーブルやACアダプタ等)を別売りしてるのもいいですね。

難点としては、

  • USBポートに市販のmicroUSBケーブルなどを接続した場合、充電出力が0.5Aまでしか上がらない点
  • 付属のUSB充電ケーブルがかさばること
  • ジャンプスターターケーブルはさらにかさばること

などがありますが、出力の件は付属のケーブルだと1.0A出たりしているのでおそらくUSBの結線や相手を見て出力をコントロールしているのだと思われます。ケーブルや機器との食い合わせがありそうですが、まぁとりあえずは急速充電には付属ケーブルが必要で、市販ケーブルでも最低限の充電は問題なくできる、という感じで。
とりあえずケーブル類が邪魔なので小さなポーチに入れて車に載せてあります。
ちなみに知人に勧めたところ、3.5L V6の国産車でも問題なく使用できたとのこと。

追記: Wiredに同種の製品が載ってましたが、なんとなくこれケーブル類が似てるので製造元一緒なんじゃないかという気がしています。
クルマのジャンプスタートにも使えるモバイルバッテリー(ただしノートPCは不可) « WIRED.jp
http://wired.jp/2014/04/28/juno-power-jump/
さらに追記: 上記記事の製品がチャージ&スターター Lite(チャースタLite)として発売されたようです。容量が一回り小さくなって、軽量化したようですね。
公式ページ: http://charge-and-starter.com/charge-and-starter-lite/
Amazon: チャージ&スターター Lite (オレンジ/ピンク/ブルー)

AsyncOAuthとWebAuthenticationBrokerで作るuniversal App用Twitter APIラッパークラス

世界同時WP&W8 Devハッカソンイベント//Publish/、がりがり書いてたアプリが結局仕様的に簡単には実現できなそうな袋小道に迷い込んでしまったため、結果出ませんでした、無念...。

とはいえ、会場で結構話題になっていた、universal App(特にWindows Phone版)でのWebAuthenticationBrokerの挙動をまるっとラップして、AsyncOAuthと合わせてモダンにさくっとTwitterアプリが作れるラッパークラスを書いてみたので、universalでTwitterアプリやってみたい人のご参考になれば幸いです。ベースが切り張なので突っ込みどころはあると思いますが。
HTTP周りはAsyncOAuthがまるっと包んでくれますし、OS標準のWebAuthenticationBrokerのおかげでWebBrowserコントロールを張ってごにょごにょする必要もなく、今なら最短に近い組み合わせではないかと思います。

public class TwitterHandler
{
    //neue cc - AsyncOAuth - C#用の全プラットフォーム対応の非同期OAuthライブラリ
    //http://neue.cc/2013/02/27_398.html
    //AsyncOAuth/AsyncOAuth.ConsoleApp/Twitter.cs at master · neuecc/AsyncOAuth
    //https://github.com/neuecc/AsyncOAuth/blob/master/AsyncOAuth.ConsoleApp/Twitter.cs
    internal const string API_CONSUMER_TOKEN = "";
    internal const string API_CONSUMER_SECRET = "";
    internal const string API_CONSUMER_CALLBACK_URL = "";

    private readonly OAuthAuthorizer authorizer;
    private static RequestToken requestToken { get; set; }
    private static AccessToken accessToken { get; set; }
        
    public TwitterHandler()
    {
        AsyncOAuth.OAuthUtility.ComputeHash = (key, buffer) =>
        {
            var crypt = Windows.Security.Cryptography.Core.MacAlgorithmProvider.OpenAlgorithm("HMAC_SHA1");
            var keyBuffer = Windows.Security.Cryptography.CryptographicBuffer.CreateFromByteArray(key);
            var cryptKey = crypt.CreateKey(keyBuffer);

            var dataBuffer = Windows.Security.Cryptography.CryptographicBuffer.CreateFromByteArray(buffer);
            var signBuffer = Windows.Security.Cryptography.Core.CryptographicEngine.Sign(cryptKey, dataBuffer);

            byte[] value;
            Windows.Security.Cryptography.CryptographicBuffer.CopyToByteArray(signBuffer, out value);
            return value;
        };
        authorizer = new OAuthAuthorizer(API_CONSUMER_TOKEN, API_CONSUMER_SECRET);
    }

    public async Task Auth()
    {
        accessToken = null;
        var res = await authorizer.GetRequestToken("https://api.twitter.com/oauth/request_token");
        requestToken = res.Token;

        var uriAuthorize = new Uri(authorizer.BuildAuthorizeUrl("https://api.twitter.com/oauth/authorize", requestToken));
        var uriCallback = new Uri(API_CONSUMER_CALLBACK_URL);
#if WINDOWS_PHONE_APP
        WebAuthenticationBroker.AuthenticateAndContinue(uriAuthorize, uriCallback, null, WebAuthenticationOptions.None);
#else // !WINDOWS_PHONE_APP
        var webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, uriAuthorize, uriCallback);
        if (webAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
        {
            Debug.WriteLine(webAuthenticationResult.ResponseData.ToString());
            await GetAccessToken(webAuthenticationResult.ResponseData.ToString());
        }
        else if (webAuthenticationResult.ResponseStatus == WebAuthenticationStatus.ErrorHttp)
        {
            MessageDialog ErrMsg = new MessageDialog(string.Format("HTTP Error returned by AuthenticateAsync() : " + webAuthenticationResult.ResponseErrorDetail.ToString()), "Sorry");
            await ErrMsg.ShowAsync();
        }
        else
        {
            MessageDialog ErrMsg = new MessageDialog(string.Format("Error returned by AuthenticateAsync() : " + webAuthenticationResult.ResponseStatus.ToString()), "Sorry");
            await ErrMsg.ShowAsync();
        }
#endif // !WINDOWS_PHONE_APP
    }

#if WINDOWS_PHONE_APP
    //Windows Phone Web authentication broker sample in C#, C++, JavaScript for Visual Studio 2013
    //http://code.msdn.microsoft.com/wpapps/Web-Authentication-d0485122
    //How to continue your Windows Phone Store app after calling an AndContinue method (Windows)
    //http://msdn.microsoft.com/en-us/library/dn631755.aspx
    //How to Use the WebAuthenticationBroker for oAuth in a Windows Phone Runtime WP8.1 App | .NET Zone
    //http://dotnet.dzone.com/articles/how-use
    public async void ContinueWebAuthentication(WebAuthenticationBrokerContinuationEventArgs args)
    {
        WebAuthenticationResult result = args.WebAuthenticationResult;

        if (result.ResponseStatus == WebAuthenticationStatus.Success)
        {
            await GetAccessToken(result.ResponseData.ToString());
        }
        else if (result.ResponseStatus == WebAuthenticationStatus.ErrorHttp)
        {
            MessageDialog ErrMsg = new MessageDialog(string.Format("There was an error connecting to Twitter: \n {0}", result.ResponseErrorDetail.ToString()), "Sorry");
            await ErrMsg.ShowAsync();
        }
        else
        {
            MessageDialog ErrMsg = new MessageDialog(string.Format("Error returned: \n{0}", result.ResponseStatus.ToString()), "Sorry");
            await ErrMsg.ShowAsync();
        }
    }
#endif // WINDOWS_PHONE_APP

    private async Task GetAccessToken(string webAuthResultResponseData)
    {
        string responseData = webAuthResultResponseData.Substring(webAuthResultResponseData.IndexOf("oauth_token"));
        string oauthVerifier = null;
        String[] keyValPairs = responseData.Split('&');

        for (int i = 0; i < keyValPairs.Length; i++)
        {
            String[] splits = keyValPairs[i].Split('=');
            switch (splits[0])
            {
                case "oauth_verifier":
                    oauthVerifier = splits[1];
                    break;
            }
        }
        var accessTokenResponse = await authorizer.GetAccessToken("https://api.twitter.com/oauth/access_token", requestToken, oauthVerifier);

        accessToken = accessTokenResponse.Token;
    }

    public HttpClient CreateTwitterHttpClient()
    {
        if (!IsAuthrized)
        {
            return null;
        }
        return OAuthUtility.CreateOAuthClient(API_CONSUMER_TOKEN, API_CONSUMER_SECRET, new AccessToken(accessToken.Key, accessToken.Secret));
    }

    public bool IsAuthrized
    {
        get { return accessToken != null && !string.IsNullOrWhiteSpace(accessToken.Key) && !string.IsNullOrWhiteSpace(accessToken.Secret); }
    }

    // Example API Call Handlers
    // AsyncOAuth/AsyncOAuth.ConsoleApp/Twitter.cs at master &middot; neuecc/AsyncOAuth
    // https://github.com/neuecc/AsyncOAuth/blob/master/AsyncOAuth.ConsoleApp/Twitter.cs
    public async Task<string> GetTimeline(int count, int page)
    {
        var client = CreateTwitterHttpClient();

        var json = await client.GetStringAsync("https://api.twitter.com/1.1/statuses/home_timeline.json?count=" + count + "&page=" + page);
        return json;
    }

    // Your Twitter Application must have Read & Write permission in Twitter API for Post handlers
    public async Task<string> PostUpdate(string status)
    {
        var client = CreateTwitterHttpClient();

        var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("status", status) });

        var response = await client.PostAsync("https://api.twitter.com/1.1/statuses/update.json", content);
        var json = await response.Content.ReadAsStringAsync();
        return json;
    }
}

Windows Phone版では、認証を終わらせるために、アプリが再アクティブ化されるため、App.xaml.csに以下のようなコードを追加する必要があります。(ViewModel.TwitterHandler〜は個々の実装に合わせて変更)

    public sealed partial class App : Application
    {
        protected override void OnActivated(IActivatedEventArgs args)
        {
            base.OnActivated(args);
#if WINDOWS_PHONE_APP
            if (args is WebAuthenticationBrokerContinuationEventArgs)
                ViewModel.TwitterHandler.ContinueWebAuthentication(args as WebAuthenticationBrokerContinuationEventArgs);
#endif // WINDOWS_PHONE_APP
        }
    }

あとは、適当なところでTwitterHandlerのインスタンスを作ってAuth()を呼んで、認証終わったらAPIをたたいていくだけです。詳しくはコード冒頭のneuecc先生のリンク等を参照のこと。
ふつうのuniversal AppテンプレートにnugetでAsyncOAuthを入れて、上記のコードを組み込み、あとは細かいAPIを実装していくだけで、わりとシンプルにTwitterアプリを作成できるかと思います。
(現状、nugetでAsyncOAuthをuniversalのWP8.1アプリに組み込むと依存関係の不足で起動時にFileNotFoundが出るかもしれません。その場合、nugetでさらにBCL Async(Microsoft Async)を追加してみてください)

Windows Phone 8.1用はてなブックマーク登録アプリNatehaR.Expressを公開しました


Windows Phone 8.1の新機能、共有コントラクト(Androidで言うIntentですね)を利用したはてなブックマークの登録専用アプリ、NatehaR.ExpressWindows Phone Storeにて公開しました。無料です。
基本的には、8.1対応の各種アプリからURLやWebページの共有(Share)を選択し、そこからはてブへブクマするだけ、というアプリです。
これまでは、Windows Phoneからブクマするには、Webページか別途他のアプリを使うしかありませんでしたが、これからはOS標準のInternet Explorerer 11 Mobileはもちろん、海外製のTwitterクライアントや定番RSSリーダーNextGen Readerなどからもダイレクトにはてブへ登録することが可能になります。
Windows Phone Storeからダウンロード:
http://www.windowsphone.com/ja-jp/store/app/natehar-express/95acd3a5-8e04-4a83-9c47-0e755050dd85
http://www.windowsphone.com/ja-jp/store/app/natehar-express/95acd3a5-8e04-4a83-9c47-0e755050dd85

スクリーンショット:

現在の主な機能:

  • 共有コントラクトからのブックマーク登録
  • スペース(空白)で直前の文字列をタグ表記に変換
  • 英語ローカライズ済(誰が使うのか?)

のみとなっています。
なかなか手が回らず、非常にシンプルなアプリになってしまいましたが、タグの取得はできているので、タグの選択や既存データの編集などは直近で対応予定です。
Windows Universal Appの習作にもなっているので、こちらをベースにMetroHatenaR後継のフルスペックWP8.1アプリにも手を付けていきたいと思っています。

Privacy Policy

(This contents translated by Machine Translation)
NatehaR (this app) is the application running and operating the personal information using your computer and the Microsoft account.

  • Use of personal information

This app is within the range necessary to achieve the purpose of use of the following personal information, we will use.

Involves advance informed consent to use personal information in the purposes of the following:

  1. to confirm that the user intended to service users login information
  2. services available continuously on etc Cookie needed save connection information.
  3. save for the convenience of the application search, browsing history, browsing, configuration information
  • Personal information view, edit, delete

The login information is stored in our app from within the app removal is possible.

Also, concomitantly with the login information and saved information is removed along with the login information delete.

  • Provision to third parties of personal information

If this app is prescribed personal information protection laws without first obtaining the consent personal information disclosure requests from Parties legally binding except, and will not be provided to third parties.

  • Scope of application

Our app is only client applications to use the various services and a bookmark providing by Hatana Inc. in the assumes no responsibility for the protection of personal information used in the service of Hatena.

This privacy policy applies only to within the operating range of our app. On personal information protection in the site viewed from our app assumes no responsibility.

.NETのCookieContainerをシリアライズする

.NETのCookieContainerってなんでSerializeできないんですかね?
HttpWebRequestでcookieを横取りしたり、そもそもそんなことしたいと思うのが筋悪なのか? と、思いつつ幾数年...。
8.1のWindows Universal Appを作っていたら、なんとHttpCookieManagerなどと言うモノが新設されており、こりゃ楽になる! ...と思ったのもつかの間、今作ってるアプリはHTTP関連が全部PCLに入っていたという。
そんなわけで、今更感もありますが、あらためてCookieContainerとは何者かを見直して、この値をDataContractSerializerでシリアライズして保存可能なCookieContainrラッパークラスを実装してみました。
とりあえずDataContractSerializerを使っていますが、XmlSerializerやSerializable用にも書き換えられると思います。
今の所ちゃんと動いているようですが、何かおかしなところがあればツッコミお願いします。

[DataContract]
public class DataContractCookieContainer
{
    private DataContractCookieContainer()
    {
        this.CookieContainer = new CookieContainer();
    }

    public DataContractCookieContainer(string targetDomain, CookieContainer cookieContainer)
    {
        this.CookieContainer = cookieContainer;
        this.Domain = targetDomain;
    }

    // Order is very important factor in deserializing order
    [DataMember(Order=0)]
    public string Domain { get; set; }
    [IgnoreDataMember]
    public CookieContainer CookieContainer { get; set; }
    [DataMember(Order=1)]
    private List<DataContractCookie> Cookies
    {
        get
        {
            if (string.IsNullOrWhiteSpace(Domain) || !Domain.StartsWith("http"))
                throw new InvalidOperationException("Domain must be set Uri initializable URL start with http...");
            var cookielist = new List<DataContractCookie>();
            var collection = this.CookieContainer.GetCookies(new Uri(Domain));
            foreach (Cookie c in collection)
            {
                cookielist.Add(new DataContractCookie(c));
            }
            return cookielist;
        }
        set
        {
            this.CookieContainer = new CookieContainer();
            if (value != null && !string.IsNullOrWhiteSpace(Domain))
            {
                var collection = new CookieCollection();
                foreach (var cookie in value)
                {
                    collection.Add(cookie.ToCookie());
                }
                this.CookieContainer.Add(new Uri(Domain), collection);
            }
        }
    }

    [DataContract]
    private sealed class DataContractCookie
    {
        // Error message when you serialize a class by using the XMLSerializer class: "System.InvalidOperationException"
        // http://support.microsoft.com/kb/330592/en
        // http://support.microsoft.com/kb/330592/ja (Japanese)
        private DataContractCookie()
        {
        }

        public DataContractCookie(Cookie cookie)
        {
            Comment = cookie.Comment;
            CommentUri = cookie.CommentUri;
            Discard = cookie.Discard;
            Domain = cookie.Domain;
            Expired = cookie.Expired;
            Expires = cookie.Expires;
            HttpOnly = cookie.HttpOnly;
            Name = cookie.Name;
            Path = cookie.Path;
            Port = cookie.Port;
            Secure = cookie.Secure;
            Value = cookie.Value;
            Version = cookie.Version;
        }

        public Cookie ToCookie()
        {
            var cookie = new Cookie()
            {
                Name = Name,
                Value = Value,
                Path = Path,
                Domain = Domain,
                Comment = Comment,
                CommentUri = CommentUri,
                Discard = Discard,
                Expired = Expired,
                Expires = Expires,
                HttpOnly = HttpOnly,
                Port = Port,
                Secure = Secure,
                Version = Version,
            };
            return cookie;
        }

        [DataMember]
        public string Comment { get; set; }
        [DataMember]
        public string CommentUrl { get; set; }
        // Uri == null cause exception
        [IgnoreDataMember]
        public Uri CommentUri
        {
            get { return string.IsNullOrWhiteSpace(CommentUrl) ? null : new Uri(CommentUrl); }
            set { CommentUrl = (value == null) ? "" : value.ToString(); }
        }
        [DataMember]
        public bool Discard { get; set; }
        [DataMember]
        public string Domain { get; set; }
        [DataMember]
        public bool Expired { get; set; }
        [DataMember]
        public DateTime Expires { get; set; }
        [DataMember]
        public bool HttpOnly { get; set; }
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public string Path { get; set; }
        [DataMember]
        public string Port { get; set; }
        [DataMember]
        public bool Secure { get; set; }
        [DataMember]
        public string Value { get; set; }
        [DataMember]
        public int Version { get; set; }
    }
}

使い方は、↓こんな感じ。
DataContractCookieContainerはアプリで使用するcookieドメインのURL(http〜)を指定して初期化してください。クッキーを列挙するのにCookieContainer.GetCookies(uri)を使うしか無いもので。(緩募: Uriで全ドメインを指定する方法)

[DataContract]
public class SampleSettings
{
    private SampleSettings()
    {
    }

    public SampleSettings(string targetDomain, CookieContainer cookieContainer = null)
    {
        CookieData = new DataContractCookieContainer(targetDomain, cookieContainer ?? new CookieContainer());
    }

    public string ToXml()
    {
        try
        {
            var serializer = new DataContractSerializer(typeof(SampleSettings));
            using (var ms = new MemoryStream())
            {
                serializer.WriteObject(ms, this);
                return Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length);
            }
        }
        catch (Exception ex)
        {
            Debug.Assert(false, "Something Error Handling");
            return string.Empty;
        }
    }

    public static SampleSettings ParseXml(string xml)
    {
        if (string.IsNullOrWhiteSpace(xml))
            return null;
        var bytes = Encoding.UTF8.GetBytes(xml);
        try
        {
            var serializer = new DataContractSerializer(typeof(SampleSettings));
            using (var stm = new MemoryStream(bytes))
            {
                return serializer.ReadObject(stm) as SampleSettings;
            }
        }
        catch (Exception ex)
        {
            Debug.Assert(false,"Something Error Handling");
            return new SampleSettings();
        }
    }

    [DataMember]
    public string Username { get; set; }
    [DataMember]
    public int UserID { get; set; }
    [DataMember]
    private DataContractCookieContainer CookieData { get; set; }
    [IgnoreDataMember]
    public CookieContainer Cookie 
    {
        get { return CookieData.CookieContainer; }
        set { CookieData.CookieContainer = value; } 
    }
}

順に書いて行ってわかったのは、どうもCookieContainerがシリアライズできないのは、最終的に各cookieが格納されているCookieクラスのCommentUriメンバーにnullが入ってるのが原因っぽいですね。そんなことかよ、という...。もしかしたらもっと深遠な理由があるのかもですが。

Windows Phone 8.1の画面をPCにリアルタイム投影するProject My Screen


全国のWindows Phone & Lumiaファンのみなさん、Windows Phone 8.1 Developer Previewはもうインストールされましたか?
特に、開発系や勉強会で登壇される方々に朗報です。
ついに、ついについに、Windows Phoneの画面をリアルタイムでUSB接続したPCにミラー表示する(いちおう公式)ツールが公開されました。
Windows Phone 7が登場した頃から熱烈要望されていた機能をようやく我々ユーザーレベルで利用可能になったのです。
(他のプラットフォームから見れば何を今更でしょうが...)

Projecting your Windows Phone screen to a PC | Monkey Slaps
http://www.monkeyslaps.com/projecting-your-windows-phone-screen-to-a-pc/

まぁ、Windows Phone 8.1自体がMiracast対応でWiFi経由のプロジェクションにも対応しているのですが、自宅のテレビを持ち歩くわけにもいきませんしHDMIドングルなんてまだるっこしい、USB接続でPCにリアルタイム表示できるというのは素晴らしい!

というわけで使い方。

手順A:

もし、USB接続しても無反応、設定のProject my screenでもSearchingのまま何も出ない場合は以下の手順をトライしてみてください。

手順B:

いかがでしょうか?
Windows Phone側の設定でWindows 8と同様にタッチ部分を表示したり、画面の回転向きを変更することも可能。
Project My Screenアプリは基本フルスクリーンですが、Alt-Enterでウインドウモードにできます。

ショートカットキーの一覧:

F1 Help
ESC Quit to windowed mode
B Toggle background image on or off
E Toggle expanded screen mode
F or Alt-Enter Toggle full screen mode
R Display current frame rate
Left arrow key Force device orientation to landscape left
Right arrow key Force device orientation to landscape right
Up/Down arrow key Force device orientation to portrait up
Spacebar Reset device orientation (automatically follows phone)

手元で試したかぎり、Expression Encoder 4 Screen Captureで動画撮影も可能です。実機の操作動画をそのままPCで記録できるのは開発者的にデモ動画が作りやすくなるのでかなり大きいのでは?
また、スタート画面に登録される定義ファイルで色々設定もできるようです。

ともあれ、これで、勉強会の講演台で書画カメラをゴソゴソ操作したり、まごまごして操作ミスしたりすることが無くなりますね。
Cortanaとお喋りするデモだってらくちんですよ!
via:
Windows Phone 8.1 Tip: Use USB Screen Projection | Windows Phone content from Paul Thurrott's SuperSite for Windows
http://winsupersite.com/windows-phone/windows-phone-81-tip-use-usb-screen-projection?utm_source=twitterfeed&utm_medium=twitter