ぷろじぇくと、みすじら。

Micorosoft Edge(プレビュー)の拡張について調べる、デバッグする方法

Created at:

Micorosoft Edge(プレビュー)の拡張を作ってみるでは未公開の情報を元にノリと勢いでChrome拡張をEdge拡張にしてみましたが、さっくりとできたわけではなくAPIに関しては当然少し調査してから試しました。

その調査方法兼デバッグ方法についてせっかくなのでメモっておきます。もちろん将来のビルドでは手順が変わる可能性がかなり高いです。

まずはChromeが持っているpermissionをすべて列挙したマニフェストを持つ空の拡張を作ります。JavaScript不要です。

{
    "manifest_version": 2,
    "name": "Dummy Extension",
    "version": "1.0",
    "background": {
        "page": "background.html"
    },
    "permissions": [
        "activeTab",
        "alarms",
        /* 長いので省略 (https://gist.github.com/mayuki/5ae554c8e825f32921c7) */
        "webRequest",
        "webRequestBlocking"
    ]
}

バックグラウンドページはこんな感じでtitle要素だけ。

<!DOCTYPE html>
<!-- 名前は付けておいた方がいい -->
<title>Dummy Extension - BackgroundPage</title>

そしてACLを付けて、

icacls . /grant "*S-1-15-2-3624051433-2125758914-1423191267-1740899205-1073925389-3782572162-737981194":"(OI)(CI)(WDAC,WO,GE)"

Edgeに読み込ませます。

デバッグコンソールを開く

これで拡張は読み込まれましたがEdgeのデバッグする口がありません。Chromeなどでは拡張画面からいろいろ開けるのですがそういうインタフェースは見当たりません。

そこでF12開発者ツールをスタンドアローンで立ち上げます。実は結構前からF12開発者ツールは単体で起動できるようになっています。

単体で起動するにはC:\Windows\SysWOW64\F12にあるF12Chooser.exeを実行します。するとF12開発者ツールをどのインスタンスのEdgeにアタッチするか、という画面が表示されます。

F12Chooser

この画面でさっき作った"Dummy Extension - BackgroundPage"を選択します。すると指定したページ、つまり拡張のバックグラウンドページにアタッチされたF12開発者ツールが起動します。

F12開発者ツールが立ち上がる

そうしたらあとはコンソールでいろいろ試してみます。

windowオブジェクトにbrowserやchromeが生えている

chromeオブジェクトにはほとんど機能がないこと、pageActionの実装が中途半端なこと、webRequest/webNavigationがある(使えたとは言っていない)といったことが分かります。コンテキストメニューを足してみるのもここからできます。

ちなみにこのアタッチする手段を使うことで拡張をデバッグできます。…が、読み込み時のエラーのキャッチはできないのでなかなか厳しいです。そのあたりは将来正式に解放されるときに手段が整うのではないかと思います。

Micorosoft Edge(プレビュー)の拡張を作ってみる

Created at:

Microsoft Edgeのブラウザーに対する拡張がついにWindows 10 Insider Preview build 14291からサポートされました。 サポートされたのですが現状は拡張機能のプレビューということで開発者には特に情報は開示されていません。 この辺りはBuild 2016で発表されるのではないかと思います。

せっかくサポートされたので開発情報はないものの試行錯誤で作ってみます。 作り方も「Google Chrome向け拡張を少しの変更で動かせるようになる」という話は出ていて、 公開されているプレビューの拡張機能も適当なところにぶちまけられたパッケージ化されていないものを読み込む感じなので作れそうです。

というわけで以前から「選択した文字列をGoogleで検索する」機能が欲しいと思っていたのでそれを作ることにします。Google Chromeには標準であるアレです。

そもそもGoogle Chrome拡張のAPIのどこまでがサポートされているのかわかりませんが、そのぐらい簡単なものなら動くだろうみたいな勢いです。

出来上がりはこんな感じです。

コンテキストメニューに"Search Google for ..."を追加

便利なのです。#Chromeでは前からできる #IEもアクセラレーターで出来た

Google Chrome向けに作る

いきなりEdgeで作り始めると色々とハマるのでとりあえず最初はまずGoogle Chromeで動くように作ります。

用意するファイルは次の3つです。

まずはマニフェストですが、最低限のことを書いてバックグラウンドページとパーミッションを指定します。

{
    "manifest_version": 2,
    "name": "Google Search for Context Menu",
    "version": "1.0",
    "background": {
        "page": "background.html"
    },
    "permissions": [
        "contextMenus"
    ]
}

background.htmlもbackground.jsを読むだけの人に。

<!DOCTYPE html>
<html>
<head>
<title>Background</title>
<script src="background.js"></script>
</head>
<body>
</body>
</html>

メインの処理であるところのbackground.jsではコンテキストメニューを作成します。ちなみにUTF-8とかで保存しておかないと事故が起きるかもしれません。

// コンテキストメニューに "Search Google for "%s"" を追加する
chrome.contextMenus.create({
    id: 'searchGoogleForSelection',
    title: 'Search Google for "%s"', // %s は選択している文字列で置き換わる
    contexts: ['selection'], // 選択しているときのみメニューに表示される
    onclick: (info, tab) => {
        // クリックのイベント
        // 新しいタブを開いてGoogleで検索する
        chrome.tabs.create({
            url: 'https://www.google.com/search?q=' + info.selectionText
        });
    }
});

コンテキストメニューのAPIはchrome.contextMenus - Google Chromeにある通りで、項目がクリックされたらタブを開く処理を書いておきます。

で、この一式をChromeに読み込ませます。Chromeの拡張の画面から"Developer Mode"を有効にして"Load unpacked extension..."で読み込ませることができます。

特にエラーがなければ適当なページで文字列を選択してコンテキストメニューを開くと項目が増えているはずです(まあ同じものをChromeが提供しているので被ってますが)。

コンテキストメニューに"Search Google for ..."を追加

Microsoft Edgeで動かす

というわけでChromeで無事動作したらEdgeで動かせるように対応します。

まずはbackground.jsを修正する必要があります。修正点は2点でchrome.contextMenusbrowser.contextMenusに、chrome.tabs.createbrowser.tabs.createに変更します。 要するにchromebrowserに変更です。

次に拡張の入っているフォルダーをコマンドプロンプトで開いて次のコマンドを実行します。

icacls . /grant "*S-1-15-2-3624051433-2125758914-1423191267-1740899205-1073925389-3782572162-737981194":"(OI)(CI)(WDAC,WO,GE)"

このコマンドはEdgeがフォルダーとその配下のファイルにアクセスできるようにするための権限付与です。EdgeはAppContainerに閉じこまれててファイルアクセスに制限がかかっているので必要なのでしょう。そのあたりはNyaRuRuさんのエントリーが詳しいです。

変更はこれだけで、あとは拡張から読み込ませて、コンテキストメニューに出てくれば成功です。簡単ですね。

拡張の一覧に追加される コンテキストメニューに"Search Google for ..."を追加

というわけで本当にChrome拡張を移植できるらしいことを確認できました。早くいろいろなAPIがそろって、情報が公開されるのが楽しみです。

既知の問題

どうもEdgeを再起動すると起動時にバックグラウンドページを自動では読み込んでくれないらしく、再読み込みが必要になります。Chromeではそういうこともないので多分プレビューだからなのでしょう。たぶん…。

ついでに

そういえばGitHubのドキュメントにも拡張周りが少し追加されていたので訳しておきました。

さらについでにMicorosoft Edge(プレビュー)の拡張について調べる、デバッグする方法についても書いておきました。

MicrosoftEdge Documentationを訳してみる

Created at:

GitHubにMicrosoftEdge DocumentationというEdgeについてのドキュメントがあるのですが、 それをふと英語の勉強もかねて訳してみたりしています。

https://github.com/mayuki/MicrosoftEdge-Documentation

まああまりうまくないし間違ってるかもしれませんがそこまで極端には間違ってないと思いたいところ…。

今のところ以下のあたりをやってあります。

Intel Edisonを買ったのでC#でも触りたいのです

Created at:

Intel Edison

最近、Intel Edisonをおすすめされたので買ってみました。

Arduinoなどと比べるとお高いのですが、その分性能も高くてSDカードのひと回り大きいぐらいのサイズで500MHzで動作するAtomとRAMが1GB、ストレージとして4GBを持ち、Wi-FiとBluetooth 4.0も持っていて技適も通っていてLinuxが動くという代物。すごい。ちなみに写真はBreakout Board Kitに乗せた状態のものです。

Edisonのセットアップは簡単で、ちょこちょこと初期設定するとsshで入れるようになり、Node.jsも標準で使えます。EdisonのピンというかIOを操作するためにMRAAというライブラリが用意されていてNode.jsのバインディングも用意されていて簡単に扱えるようになっています。

Intel Edison (Breakout Board Kit)でLチカ - Qiitaといった記事を参考にLチカをするのも簡単です。

C#を書けるようにする

やはりC#書きたいですね。LチカするにもC#でやりたい!…ということで最初はCoreCLRのネイティブコンパイルしたバイナリを持っていくか、CoreCLR自体を持っていこうと思っていたのですがそもそもLinux x86 portはまだのようなのできっとあるであろうMonoを探すことに。

探してみるとMonoのパッケージを提供している方がいらっしゃるのでありがたく利用させていただきます(EdisonのLinuxにはopkgというパッケージマネージャーが入っています)。

…と、簡単に入ります。

Monoがインストールできればあとはmonoコマンドでいろいろ実行できてこっちのものです。ちなみにMono 4.0なので少し古いのですが概ね問題ないでしょう。

C#からピンを操作できるようにする

しかしMonoからいざEdisonのGPIOのようなものを操作するにはsysfs経由 (/sys/class/gpio/)で操作する必要がありあまり使い勝手がよくありません。そこでNode.jsのようにMRAA(libmraa)をC#から使えるようにするライブラリがあれば…!と思ったので作ったのがMraaSharpです。

MraaSharpはだいぶ作りかけですが、libmraaをP/Invokeで呼び出すためMono以外に必要としません。本来であればMRAAはSWIGで各言語のバインディングを生成できるのですが、いろいろと(入れるのもビルドも)面倒なのでピュアC#で完結するものにしました。

使い方は簡単で、MraaSharp.dllを参照したうえで大よそMRAAと同じようなAPIを呼び出せます(ほとんど未実装ですが)。

using MraaSharp;

// 初期化 (mraa_init)
Mraa.Initialize();
// 情報を表示する (mraa_get_version, mraa_get_platform_name)
Console.WriteLine("Version: {0}", Mraa.Version);
Console.WriteLine("PlatformName: {0}", Mraa.PlatformName);

// ピンをGPIOの出力として開く
var gpio = new Gpio(MraaIntelEdisonMiniboard.J17_8, MraaGpioDir.Out);
Console.WriteLine("Gpio Pin: {0}", gpio.Pin);
Console.WriteLine("Gpio PinRaw: {0}", gpio.PinRaw);

while (true)
{
    gpio.Write(MraaGpioValue.High); // 出力をHIGHにする
    Thread.Sleep(1000);
    gpio.Write(MraaGpioValue.Low); // 出力をLOWにする
    Thread.Sleep(1000);
}

なおMonoをインストールするとC# ShellというMonoのC# REPLが一緒に入るので、それを起動してMraaSharpを読み込むとインタラクティブに試すことができます。Node.jsに負けてない!(MraaSharpがすべて実装済みとは言っていない)

というわけでC#でLチカできてよかったのです(1.8Vなので直接つなげない分、若干難易度高かった)。

MRAA + C#

A video posted by Mayuki Sawatari (@misuzilla) on

ILI9340な液晶をNetduinoで使う

Created at:

液晶+Netduino plus 2

aitendoで2.2インチのQVGAなSPI接続のTFT液晶を買いまして、 Arduinoで使おうと思っていろいろやったついでにNetduino plus 2で使えるかどうか試してみました。 Adafruitの2.2インチ液晶と同じコントローラ(ILI9340)を持っているものっぽいです。

ILI9340の利用例はRaspberry PiとかArduinoではちょこちょこあるしArduinoにはライブラリがあるのですが、 Netduinoでの利用例は見当たらなかったので直接コントロールして表示しています。

ちなみにこの液晶はロジックの電圧に3.3Vを要求していて5Vでつなぐとダメで、 Arduino Unoに直結すると動かないのですが(なのでTrinket ProやArduino M0 Proで先に動かした)、 Netduinoは3.3Vなので動かせそうとだなと思ってつないでコードを書いたところちゃんと動作しました。

接続はArduinoのSPIと同じで以下のような感じ。

LCD側ピンNetduino側ピン備考
VCC 3.3V
GND GND
CS D10 SPI / チップセレクト(SS)
RESET D8
D/C D9 コマンド用
SDI/MOSI D11 SPI
SCK D13 SPI / クロック(SCLK)
LED 3.3V
SDO/MISO D12 SPI

実装にはaitendoのデモコードとImageWriter: Raspberry Pi (9) LCD表示 1Adafruitのライブラリを参考にして、 SPIでひたすらコマンドとデータを転送する形になっています。実際のコードは以下の通りです。

なお、Netduino plus 2はRAMが100KBちょっとということもあり、QVGAのピクセルデータを全部保持することもできないので、 線や図形を適宜描画するか、画像はSDカードから読み込んで表示するといったことが必要となります。

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using System.IO;

namespace NetduinoApplication2
{
    public class Program
    {
        // DC はコマンドを発行するときに操作する用の線
        static OutputPort _dcPort;
        // RST はリセット用の線
        static OutputPort _resetPort;
        // SPI
        static SPI _spi;

        public static void Main()
        {
            _dcPort = new OutputPort(Pins.GPIO_PIN_D9, true);
            _resetPort = new OutputPort(Pins.GPIO_PIN_D8, false);

            // SPIをセットアップ
            _spi = new SPI(new SPI.Configuration(Pins.GPIO_PIN_D10, false, 0, 0, true, true, 8000, SPI_Devices.SPI1));

            // リセットをかける
            _resetPort.Write(false); // LOW (Reset)
            Thread.Sleep(100);
            _resetPort.Write(true); // HIGH (Reset off)
            Thread.Sleep(100);

            // ILI9340を初期化する
            InitializeLcd();

            // ちなみに初期化後は白でも黒でもない中途半端な状態になるので塗りつぶしをしないとしましました風に見える

            if (false)
            { 
                // パターン1: SDカードのファイルから読み込んで画像を表示する
                // ファイルの中身は240x320の RRGGGBB のバイト列

                // まず描画先のアドレスを指定する: (0, 0) -> (239, 319)
                SetAddress(0, 0, 239, 319);

                // ファイルを読んで流し込む
                var path = @"SD\nanikabitmapdata.bin";
                var buffer = new byte[16 * 1024]; // 16KB ごとに読み出す
                using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
                {
                    int readLen = 0;

                    while (true)
                    {
                        readLen = stream.Read(buffer, 0, buffer.Length);

                        if (readLen == 0) break;

                        // byte列で送信しないととても遅いので注意
                        if (readLen != buffer.Length)
                        {
                            var tmp = new byte[readLen];
                            Array.Copy(buffer, tmp, readLen);

                            WriteData(tmp);
                        }
                        else
                        {
                            WriteData(buffer);
                        }
                    }
                }
            }
            else
            {
                // パターン2: ピクセルを直接操作して矩形を書く
                ushort x = 100;
                ushort y = 100;
                ushort width = 40;
                ushort height = 40;
                ushort color = 0x07E0; // 緑

                // まず描画先のアドレスを指定する: (100, 100) -> (139, 139)
                SetAddress(x, y, (ushort)(x + width - 1), (ushort)(y + height - 1));
                // ピクセルデータを流し込む
                for (var i = 0; i < width * height; i++)
                {
                    WriteData(color);
                }
            }
        }

        static void InitializeLcd()
        {
            // 診断用コード
            Debug.Print("Display Power Mode: 0x" + ReadCommand(0x0A).ToString("x"));
            Debug.Print("MADCTL Mode: 0x" + ReadCommand(0x0B).ToString("x"));
            Debug.Print("Pixel Format: 0x" + ReadCommand(0x0C).ToString("x"));
            Debug.Print("Image Format: 0x" + ReadCommand(0x0A).ToString("x"));
            Debug.Print("Self Diagnostic: 0x" + ReadCommand(0x0F).ToString("x"));

            // この辺はILI9340の初期化コード
            // コマンドとデータはAdafruitのライブラリを参考にするとてっとり早いです
            // https://github.com/adafruit/Adafruit_ILI9340/blob/master/Adafruit_ILI9340.h
            WriteCommandAndData(0xEF, new byte[] { 0x03, 0x80, 0x02 });
            WriteCommandAndData(0xCF, new byte[] { 0x00, 0xC1, 0x30 });
            WriteCommandAndData(0xED, new byte[] { 0x64, 0x03, 0x12, 0x81 });
            WriteCommandAndData(0xE8, new byte[] { 0x85, 0x00, 0x78 });
            WriteCommandAndData(0xCB, new byte[] { 0x39, 0x2C, 0x00, 0x34, 0x02 });
            WriteCommandAndData(0xF7, new byte[] { 0x20 });
            WriteCommandAndData(0xEA, new byte[] { 0x00, 0x00 });
            WriteCommandAndData(0xC0, new byte[] { 0x23 });
            //Power control
            //SAP[2:0];BT[3:0]
            WriteCommandAndData(0xC1, new byte[] { 0x10 });
            //VCM control
            WriteCommandAndData(0xC5, new byte[] { 0x3e, 0x28 });
            //VCM control2
            WriteCommandAndData(0xC7, new byte[] { 0x86 });
            // Memory Access Control
            WriteCommandAndData(0x36, new byte[] { 0x48 }); // 回転はこの辺
            WriteCommandAndData(0x3A, new byte[] { 0x55 });
            WriteCommandAndData(0xB1, new byte[] { 0x00, 0x18 });
            // Display Function Control
            WriteCommandAndData(0xB6, new byte[] { 0x08, 0x82, 0x27 });
            // 3Gamma Function Disable
            WriteCommandAndData(0xF2, new byte[] { 0x00 });
            //Gamma curve selected 
            WriteCommandAndData(0x26, new byte[] { 0x01 });
            //Set Gamma
            WriteCommandAndData(0xE0, new byte[] { 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00 });
            //Set Gamma
            WriteCommandAndData(0XE1, new byte[] { 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F });
            //Exit Sleep
            WriteCommand(0x11);

            Thread.Sleep(120);

            //Display on
            WriteCommand(0x29);
        }

        /// <summary>
        /// 描画先アドレスを設定する
        /// </summary>
        /// <param name="x">開始X座標</param>
        /// <param name="y">開始Y座標</param>
        /// <param name="x2">終了X座標</param>
        /// <param name="y2">終了Y座標</param>
        static void SetAddress(ushort x, ushort y, ushort x2, ushort y2)
        {
            WriteCommand(0x2A);
            WriteData(x);
            WriteData(x2);
            WriteCommand(0x2B);
            WriteData(y);
            WriteData(y2);
            WriteCommand(0x2C);
        }

        /// <summary>
        /// コマンドを発行して結果を取得します。
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        static byte ReadCommand(byte command)
        {
            var buffer = new byte[1];
            buffer[0] = command;

            _dcPort.Write(false);
            _spi.WriteRead(buffer, buffer);

            _dcPort.Write(true);
            buffer[0] = 0x0;
            _spi.WriteRead(buffer, buffer);

            return buffer[0];
        }

        /// <summary>
        /// コマンドを発行します。
        /// </summary>
        /// <param name="command"></param>
        static void WriteCommand(byte command)
        {
            var buffer = new byte[1];
            buffer[0] = command;

            _dcPort.Write(false);
            _spi.WriteRead(buffer, buffer);
        }

        /// <summary>
        /// 符号なし16bit整数データを書き込みます。
        /// </summary>
        /// <param name="data"></param>
        static void WriteData(ushort data)
        {
            var hi = (byte)(data >> 8);
            var lo = (byte)(data & 0xFF);

            WriteData(new[] { hi, lo });
        }

        /// <summary>
        /// バイトデータを書き込みます。
        /// </summary>
        /// <param name="data"></param>
        static void WriteData(byte data)
        {
            WriteData(new[] { data });
        }

        /// <summary>
        /// バイト列データを書き込みます
        /// </summary>
        /// <param name="data"></param>
        static void WriteData(byte[] data)
        {
            _dcPort.Write(true);
            _spi.Write(data);
        }

        /// <summary>
        /// コマンドを発行してデータを書き込みます。
        /// </summary>
        /// <param name="command"></param>
        /// <param name="data"></param>
        static void WriteCommandAndData(byte command, byte[] data)
        {
            WriteCommand(command);
            WriteData(data);
        }
    }
}

液晶で何か表示できると結構面白そうな感じなのです。