ScalaFXでGUIアプリケーションを作ってみる
「JavaFXでGUIアプリケーションを作ってみる」、「JavaFX+FXMLでGUIアプリケーションを作ってみる」では、JavaFXを使って簡単なGUIアプリケーションを作った。 ここでは、JVMベースの言語であるScalaとScalaFXライブラリを用いて、同様のアプリケーションを作ってみる。
環境
Windows 8.1 Pro 64 bit版、JDK 8、Eclipse Mars 2
>systeminfo OS 名: Microsoft Windows 8.1 Pro OS バージョン: 6.3.9600 N/A ビルド 9600 OS ビルドの種類: Multiprocessor Free システムの種類: x64-based PC プロセッサ: 1 プロセッサインストール済みです。 [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz
ScalaおよびScalaFXの概要
Scalaは、Javaバイトコードにコンパイルするタイプのプログラミング言語である。 Scalaの特長として型推論、第一級関数およびlambda式のサポート、パターンマッチが挙げられ、Javaコードと同じ意味を持つコードをより簡潔に書くことができる。
// Java List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> squares = numbers.stream().map(x -> x * x).collect(Collectors.toList());
// Scala val numbers = 1 to 5 val squares = numbers.map(x => x * x)
ScalaFXは、JavaFXをScalaから扱うためのライブラリである。 Scalaから直接JavaFXを扱うことも可能だが、ScalaFXを用いることでより自然な形でコードを書くことができる。
Scalaのインストール
まず、Scalaをインストールする。
ダウンロードページからWindows用MSIインストーラをダウンロードし、実行する。 ここで、PATH環境変数への追加はインストーラが行ってくれる。
REPLを起動し、簡単なコードを動かしてみる。
>scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_91). Type in expressions for evaluation. Or try :help. scala> println("Hello, Scala!") Hello, Scala! scala> for (x <- 1 to 25 if x*x > 50) yield 2*x res1: scala.collection.immutable.IndexedSeq[Int] = Vector(16, 18, 20, 22, 24, 26 , 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50)
二つ目のfor文は、Pythonにおけるリスト内包表記に相当するコードである。
sbtのインストール
次に、Scalaにおけるビルドツールsbtをインストールする。
ダウンロードページからWindows用MSIインストーラをダウンロードし、実行する。 Scala同様に、PATH環境変数への追加はインストーラが行ってくれる。
次のようなHelloWorld.scala
ファイルを作り、ビルド実行を行ってみる。
なお、初回起動時は必要なライブラリのインストールが行われるため、しばらく時間がかかる。
object HelloWorld { def main(args: Array[String]) = println("Hello, Scala!") }
>sbt Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; sup port was removed in 8.0 Getting org.fusesource.jansi jansi 1.11 ... downloading https://repo1.maven.org/maven2/org/fusesource/jansi/jansi/1.11/jansi -1.11.jar ... [SUCCESSFUL ] org.fusesource.jansi#jansi;1.11!jansi.jar (1188ms) :: retrieving :: org.scala-sbt#boot-jansi confs: [default] 1 artifacts copied, 0 already retrieved (111kB/31ms) Getting org.scala-sbt sbt 0.13.11 ... (snip) > run [info] Updating {file:/C:/Users/user/tmp/}tmp... [info] Resolving org.fusesource.jansi#jansi;1.4 ... [info] Done updating. [info] Compiling 1 Scala source to C:\Users\user\tmp\target\scala-2.10\classes.. . [info] 'compiler-interface' not yet compiled for Scala 2.10.6. Compiling... [info] Compilation completed in 12.718 s [info] Running HelloWorld Hello, Scala! [success] Total time: 24 s, completed 2016/04/28 9:14:54 >[Ctrl+D]
ScalaFXのダウンロード
次に、ScalaFXをダウンロードする。
Quick-start guideを参照し、sbtを利用してJARファイルをダウンロードする。
具体的には、次のようなbuild.sbt
ファイルを用意し、上のHelloWorld.scala
をsbtから実行することで、依存ライブラリとしてJARファイルのダウンロードが行われる。
scalaVersion := "2.11.8" libraryDependencies ++= Seq( "org.scalafx" %% "scalafx" % "8.0.92-R10" )
>sbt Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; sup port was removed in 8.0 [info] Set current project to tmp (in build file:/C:/Users/user/tmp/) > run [info] Updating {file:/C:/Users/user/tmp/}tmp... [info] Resolving org.fusesource.jansi#jansi;1.4 ... [info] downloading https://repo1.maven.org/maven2/org/scalafx/scalafx_2.10/8.0.9 2-R10/scalafx_2.10-8.0.92-R10.jar ... [info] [SUCCESSFUL ] org.scalafx#scalafx_2.10;8.0.92-R10!scalafx_2.10.jar (9627 ms) [info] Done updating. [info] Running HelloWorld Hello, Scala! [success] Total time: 13 s, completed 2016/04/28 9:16:30 >[Ctrl+D]
今回の環境では、JARファイルはC:\Users\%USERNAME%\.ivy2\cache\org.scalafx\scalafx_2.11\jars\scalafx_2.11-8.0.92-R10.jar
に保存された。
Eclipseプラグイン(Scala IDE for Eclipse)のインストール
Eclipseを用いてScalaコードを書くために、Scala用のEclipseプラグインをインストールする。
まず、Eclipseを起動し、「Help」→「Install new software」を開く。 ダウンロードページに書いてあるUpdate siteのURLを指定し、「Scala IDE for Eclipse」を選択、インストールする。 ダイアログの問いに合わせて再起動するとScala IDE向けに設定を変更するように問われるが、ここでは変更せずそのままの状態にした。
ScalaFXでGUIアプリケーションを作ってみる
「Java+SwingでGUIアプリケーションを作ってみる」と同様に、ドラッグアンドドロップされたファイルのSHA-1ハッシュ値をテーブルに表示するアプリケーションを書いてみる。
Eclipseを起動した後、ツールバーから「New」→「Other」を開き、「Scala Wizards」→「Scala Project」を選択する。 プロジェクト名を「SHA1CalculatorSFX」としてNextを押し、出てくる画面の「Libraries」→「Add External JARs」から上でダウンロードしたScalaFXのJARファイルを依存ライブラリに加えてFinishを押す。 ここで、Scala perspectiveへの切り替えを問われるので、Yesを選択する。
続けて、ツールバーの「New」→「Scala Class」から「main.scala.SHA1CalculatorSFX」クラスを作成する。 なお、パッケージ名を「main.scala」にするのは、後述するJARファイルの作成時にsbtがこれを標準で参照するためである。
SHA1CalculatorSFX.scala
を開き、実際にコードを書くと次のようになる。
package main.scala // enables conversions by .asScala and .asJava // http://docs.scala-lang.org/overviews/collections/conversions-between-java-and-scala-collections import collection.JavaConverters._ // use JavaFX controls that aren't wrapped by ScalaFX // http://www.scalafx.org/docs/faq_Using_unwrapped_JavaFX_components/ import scalafx.Includes._ import java.io.File import java.io.FileInputStream import java.io.IOException import java.security.MessageDigest import javafx.{concurrent => jfxc} import scalafx.application.JFXApp import scalafx.application.JFXApp.PrimaryStage import scalafx.beans.property.StringProperty import scalafx.collections.ObservableBuffer import scalafx.concurrent.Task import scalafx.scene.Scene import scalafx.scene.control.TableColumn import scalafx.scene.control.TableView import scalafx.scene.control.cell.TextFieldTableCell import scalafx.scene.input.DragEvent import scalafx.scene.input.TransferMode import scalafx.scene.layout.BorderPane class FileInfo(path_ : String, sha1sum_ : String) { val path = new StringProperty(this, "path", path_) val sha1sum = new StringProperty(this, "sha1sum", sha1sum_) } object SHA1CalculatorSFX extends JFXApp { private val pane = new BorderPane() stage = new PrimaryStage { scene = new Scene(600, 400) { title = "SHA-1 Calculator SFX" root = pane } } configureTable(pane) private def configureTable(root: BorderPane) = { val data = ObservableBuffer[FileInfo]() val table = createTableView(data) table.columnResizePolicy = TableView.ConstrainedResizePolicy root.center = table } private def createTableView(data: ObservableBuffer[FileInfo]) = { new TableView[FileInfo] { editable = true columns ++= List( new TableColumn[FileInfo, String] { text = "File Path" cellValueFactory = { _.value.path } cellFactory = TextFieldTableCell.forTableColumn[FileInfo]() }.delegate, new TableColumn[FileInfo, String] { text = "sha1sum" cellValueFactory = { _.value.sha1sum } cellFactory = TextFieldTableCell.forTableColumn[FileInfo]() }.delegate ) items = data onDragOver = (event: DragEvent) => { val db = event.getDragboard() if (db.hasFiles()) { event.acceptTransferModes(TransferMode.COPY) } else { event.consume() } } onDragDropped = (event: DragEvent) => { val db = event.getDragboard() var success = false if (db.hasFiles()) { success = true for (file <- db.getFiles().asScala) { val fileinfo = new FileInfo(file.getAbsolutePath(), "") data.add(fileinfo) val task = new SHA1CalculationTask(fileinfo) fileinfo.sha1sum <== task.message new Thread(task).start() } } event.setDropCompleted(success) event.consume() } } } } class SHA1CalculationTask(fileinfo: FileInfo) extends Task(new jfxc.Task[Unit] { protected def call() = { val md = MessageDigest.getInstance("SHA-1") // update digest with each 8MB chunk val path = fileinfo.path.value val istream = new FileInputStream(path) val totalBytes = (new File(path)).length() var currentBytes = 0L try { val buf = new Array[Byte](8*1024*1024) Iterator.continually(istream.read(buf, 0, buf.length)) .takeWhile(_ != -1) .foreach { n => md.update(buf.slice(0, n)) currentBytes += n val message = "calculating %d%%...".format(100 * currentBytes / totalBytes) updateMessage(message) } } catch { case e: IOException => e.printStackTrace() } finally { istream.close() } val hexdigest = md.digest().map("%02x".format(_)).mkString updateMessage(hexdigest) } })
最初の二つのimport文は、JavaクラスとScalaクラスの変換を行う上で重要なため、常にimportしておくとよい。 Scalaにおいて、valは再代入できない変数(const変数)、varは再代入可能な変数の宣言を意味する。 また、Scalaでは行末のセミコロンを省略することができ、一般に書かないことが多い。
「JavaFXでGUIアプリケーションを作ってみる」のコードと比較すると、次のような箇所でコードがより簡潔に書けることがわかる。
// Java byte[] digest = md.digest(); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); }
// Scala val hexdigest = md.digest().map("%02x".format(_)).mkString
また、ScalaFXを用いることで、JavaFXにおけるPropertyのバインディングをより直感的に書くことができる。
// Java fileinfo.sha1sumProperty().bind(task.messageProperty());
// Scala fileinfo.sha1sum <== task.message
なお、ScalaFXにおいてTaskクラスを使う際は、scalafx.concurrent.Task
に加えjavafx.concurrent.Task
もimportして使う必要がある。
コードを保存した後「Run」を押して実行し、適当にファイルをドラッグアンドドロップした後のスクリーンショットを次に示す。
JavaFXで書いた場合と同じように動作していることが確認できる。
実行可能なJARファイルを作ってみる
Scalaで実行可能なJARファイルを作成する際は、Eclipseからではなく、sbtとsbt-assemblyプラグインを用いて行う。
まずはじめに、JAVA_HOME
環境変数をC:\Program Files\Java\jdk1.8.0_91
として作成しておく。
次に、プロジェクトフォルダを開き、project\assembly.sbt
を次のような内容で作成する。
これにより、sbt-assemblyプラグインが有効になる。
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3")
そして、ビルド設定ファイルbuild.sbt
を次のような内容で作成する。
name := "SHA-1 Calculator SFX" version := "1.0.0" scalaVersion := "2.11.8" unmanagedJars in Compile += Attributed.blank(file(System.getenv("JAVA_HOME") + "/jre/lib/ext/jfxrt.jar")) libraryDependencies ++= Seq( "org.scalafx" %% "scalafx" % "8.0.92-R10" ) fork := true
JavaFXを利用するには、unmanagedJarsにjfxrt.jarを追加する必要があることに注意。
最後に、コマンドプロンプトから次のコマンドを実行してJARファイルを作成する。
>sbt assembly Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; sup port was removed in 8.0 [info] Loading project definition from C:\Users\user\workspace\SHA1CalculatorSFX \project [info] Set current project to SHA-1 Calculator SFX (in build file:/C:/Users/user /workspace/SHA1CalculatorSFX/) [info] Updating {file:/C:/Users/user/workspace/SHA1CalculatorSFX/}sha1calculator sfx... [info] Resolving org.scala-sbt.ivy#ivy;2.3.0-sbt-2cc8d2761242b072cedb0a04cb39435 [info] Resolving org.fusesource.jansi#jansi;1.4 ... [info] downloading https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/com.e ed3si9n/sbt-assembly/scala_2.10/sbt_0.13/0.14.3/jars/sbt-assembly.jar ... [info] [SUCCESSFUL ] com.eed3si9n#sbt-assembly;0.14.3!sbt-assembly.jar (5117ms) (snip) [info] Done updating. [info] Compiling 1 Scala source to C:\Users\user\workspace\SHA1CalculatorSFX\tar get\scala-2.11\classes... [warn] there was one deprecation warning; re-run with -deprecation for details [warn] one warning found [info] Including: scalafx_2.11-8.0.92-R10.jar [info] Including: scala-reflect-2.11.8.jar [info] Including: scala-library-2.11.8.jar [info] Including: jfxrt.jar [info] Checking every *.class/*.jar file's SHA-1. [info] Merging files... [warn] Merging 'com\sun\webkit\network\about' with strategy 'rename' [warn] Merging 'META-INF\INDEX.LIST' with strategy 'discard' [warn] Merging 'META-INF\MANIFEST.MF' with strategy 'discard' [warn] Strategy 'discard' was applied to 2 files [warn] Strategy 'rename' was applied to a file [info] SHA-1: a3f61d268f0ea843866218070f9a3e97d0ce749c [info] Packaging C:\Users\user\workspace\SHA1CalculatorSFX\target\scala-2.11\SHA -1 Calculator SFX-assembly-1.0.0.jar ... [info] Done packaging. [success] Total time: 90 s, completed 2016/04/29 3:27:46
生成されたJARファイルを実行すると、上と同様のウィンドウが表示されることが確認できる。 なお、Scala用のライブラリも必要とすることから、Javaで作った場合よりファイルサイズは大きくなる。
関連リンク
- Scala | mwSoft
- Custom cells - ScalaFX • simpler way to use JavaFX from Scala
- ProScalaFX/WorkerAndTaskExample.scala at master · scalafx/ProScalaFX
- Iterator.continually()を使おう - kmizuの日記
- How to deploy a Scala project from Eclipse? - Stack Overflow
- Build a JavaFX 8 app with sbt
- mog project: Scala: Tackling with sbt-assembly
「Self Introduction & The Story that I Tried to Make Sayonara ROP Chain in Linux」というタイトルで発表した
Lightning TalkでLow Layer経験に関する自己紹介とSayonara ROP ChainをLinuxで作ろうとした話について発表した。
内容はAVTOKYO 2014での発表と重複するが、改めて自分のやろうとしたことと結果を整理した。 デモの準備もしていたのだけど、忘れてしまったのが残念。 別に準備していたネタもあったのだけど、そちらは直前で差し替えることに決め、お蔵入りになった。
質疑応答の内容も参考になった。 ありがとうございました。
JavaFX+FXMLでGUIアプリケーションを作ってみる
「JavaFXでGUIアプリケーションを作ってみる」では、JavaFXによるGUIアプリケーションを作った。 JavaFXではコードから直接GUI画面を構築するほかに、FXMLと呼ばれるXMLファイルを使って構築することもでき、より見通しのよいコードを書くことができる。 ここでは、JavaFX+FXMLを使い、同様のGUIアプリケーションを作ってみる。
環境
Windows 8.1 Pro 64 bit版、JDK 8、Eclipse Mars 2 + e(fx)clipse
>systeminfo OS 名: Microsoft Windows 8.1 Pro OS バージョン: 6.3.9600 N/A ビルド 9600 OS ビルドの種類: Multiprocessor Free システムの種類: x64-based PC プロセッサ: 1 プロセッサインストール済みです。 [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz
Scene Builderのインストール
FXMLでGUI画面を構築する際、おおまかなレイアウトに関してはGUIツールのScene Builderを使って編集すると楽である。 ここでは、e(fx)clipseとも連携できるexe版のScene Builderをインストールする。
まず、プロジェクトページから「Windows Installer (x64)」をダウンロードする。
インストーラを実行すると、プログラム一式は自動的にC:\Users\%USERNAME%\AppData\Local\SceneBuilder
にインストールされる。
インストールが完了したら、続けてEclipseと連携できるように設定を行う。
メニューの「Window」→「Preference」を開き、「JavaFX」を選択して「SceneBuilder executable」にSceneBuilder.exe
のパスを指定する。
JavaFX+FXMLでGUIアプリケーションを作ってみる
「Java+SwingでGUIアプリケーションを作ってみる」と同様に、ドラッグアンドドロップされたファイルのSHA-1ハッシュ値をテーブルに表示するアプリケーションを書いてみる。
まず、ツールバーから「New」→「Project」を開き、「JavaFX」→「JavaFX Project」を選択する。 プロジェクト名を「SHA1CalculatorFXML」としてNextを2回を押すとDeclative UIを選択できる画面が表示されるので、Languageを「FXML」に変更し、ファイル名、コントローラ名をそれぞれ「MainWindow」「MainWindowController」に指定してFinishを押す。
src/applicaion/MainWindow.fxml
を右クリックして「Open with SceneBuilder」を開くとScene Builderが起動するので、BorderPaneの中央にTableViewを配置し、カラム名をそれぞれ編集する。
さらにTableViewのプロパティを開いてeditableにチェックを入れ、ドラッグアンドドロップを有効にするためにfx:idにTableViewの変数名、onDragOver、onDragDroppedに適当な関数名を入力しておく。
このときのスクリーンショットを次に示す。
編集が終わったら、保存して終了する。
これに他のコードの編集も交えながら手作業での修正を加えると、最終的には次のようなコードとなる。
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.collections.FXCollections?> <?import javafx.scene.control.TableColumn?> <?import javafx.scene.control.TableView?> <?import javafx.scene.control.cell.PropertyValueFactory?> <?import javafx.scene.layout.BorderPane?> <BorderPane xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainWindowController"> <center> <TableView fx:id="tableView" editable="true" onDragDropped="#handleDragDropped" onDragOver="#handleDragOver"> <columnResizePolicy> <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" /> </columnResizePolicy> <columns> <TableColumn fx:id="filePathCol" text="File Path"> <cellValueFactory><PropertyValueFactory property="path" /></cellValueFactory> </TableColumn> <TableColumn fx:id="sha1sumCol" text="sha1sum"> <cellValueFactory><PropertyValueFactory property="sha1sum" /></cellValueFactory> </TableColumn> </columns> <items> <FXCollections fx:factory="observableArrayList" /> </items> </TableView> </center> </BorderPane>
新しいクラスとしてFileInfo
を作成し、src/applicaion/FileInfo
にテーブルの各行を表すクラスを記述する。
package application; import javafx.beans.property.SimpleStringProperty; public class FileInfo { private final SimpleStringProperty path = new SimpleStringProperty(); private final SimpleStringProperty sha1sum = new SimpleStringProperty(); public FileInfo(String path, String sha1sum) { setPath(path); setSha1sum(sha1sum); } public String getPath() { return path.get(); } public void setPath(String newPath) { path.set(newPath); } public String getSha1sum() { return sha1sum.get(); } public void setSha1sum(String newSha1sum) { sha1sum.set(newSha1sum); } public SimpleStringProperty sha1sumProperty() { return sha1sum; } }
FXMLファイルに対応するコントローラとなるsrc/applicaion/MainWindowController.java
を書くと、次のようになる。
package application; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; public class MainWindowController { @FXML private TableView<FileInfo> tableView; @FXML private TableColumn<FileInfo, String> filePathCol; @FXML private TableColumn<FileInfo, String> sha1sumCol; @FXML private void initialize() { filePathCol.setCellFactory(TextFieldTableCell.forTableColumn()); sha1sumCol.setCellFactory(TextFieldTableCell.forTableColumn()); } public void handleDragOver(DragEvent event) { Dragboard db = event.getDragboard(); if (db.hasFiles()) { event.acceptTransferModes(TransferMode.COPY); } else { event.consume(); } } public void handleDragDropped(DragEvent event) { ObservableList<FileInfo> data = tableView.getItems(); Dragboard db = event.getDragboard(); boolean success = false; if (db.hasFiles()) { success = true; for (File file : db.getFiles()) { FileInfo fileinfo = new FileInfo(file.getAbsolutePath(), ""); data.add(fileinfo); SHA1CalculationTask task = new SHA1CalculationTask(fileinfo); fileinfo.sha1sumProperty().bind(task.messageProperty()); new Thread(task).start(); } } event.setDropCompleted(success); event.consume(); } class SHA1CalculationTask extends Task<Void> { private final FileInfo fileinfo; public SHA1CalculationTask(FileInfo fileinfo) { this.fileinfo = fileinfo; } @Override protected Void call() throws Exception { MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } // update digest with each 8MB chunk String path = fileinfo.getPath(); long totalBytes = (new File(path)).length(); long currentBytes = 0; try (FileInputStream istream = new FileInputStream(path)) { byte[] buf = new byte[8*1024*1024]; int n; while ((n = istream.read(buf, 0, buf.length)) != -1) { md.update(Arrays.copyOf(buf, n)); currentBytes += n; String message = String.format("calculating %d%%...", 100 * currentBytes / totalBytes); updateMessage(message); } } catch (IOException e) { e.printStackTrace(); } byte[] digest = md.digest(); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } updateMessage(sb.toString()); return null; } } }
@FXML
のついたメンバ宣言は、FXMLでfx:id
を指定した要素と対応する。
また、CellFactoryについてはTextFieldTableCell.forTableColumn()
の結果を用いるため、initializeメソッドを定義してコードから指定を行っている。
src/applicaion/Main.java
を編集し、ウィンドウタイトルの指定を追加しておく。
package application; import javafx.application.Application; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; import javafx.fxml.FXMLLoader; public class Main extends Application { @Override public void start(Stage primaryStage) { try { primaryStage.setTitle("SHA-1 Calculator FXML"); BorderPane root = (BorderPane)FXMLLoader.load(getClass().getResource("MainWindow.fxml")); Scene scene = new Scene(root,600,400); // expanded default size scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); primaryStage.setScene(scene); primaryStage.show(); } catch(Exception e) { e.printStackTrace(); } } public static void main(String[] args) { launch(args); } }
ここで、それぞれのファイルの役割を整理すると、次のようになる。
- Main.java: FXMLのロード
- MainWindow.fxml: GUI画面の定義
- MainWindowController.java: GUI画面でのイベントを処理
- FileInfo.java: データモデルの定義
コードを保存した後「Run」を押して実行し、適当にファイルをドラッグアンドドロップした後のスクリーンショットを次に示す。
コードから直接GUI画面を構築した場合と同じユーザインタフェースになっていることが確認できる。
実行可能なJARファイルを作ってみる
build.fxbuildファイルを開き、表示されるフォームに対して次の例のように必須項目を入力する。
- Vendor name: inaz2
- Application title: SHA-1 Calculator FXML
- Application version: 1.0.0
- Application class: application.Main
そして、右側にある「Generate ant build.xml and run」をクリックするとビルドが行われ、完了するとbuild/dist
にJARファイルが生成される。
生成されたJARファイルを実行すると、上と同様のウィンドウが表示されることが確認できる。
関連リンク
JavaFXでGUIアプリケーションを作ってみる
「Java+SwingでGUIアプリケーションを作ってみる」では、JDKとEclipseをインストールし、Java+SwingによるGUIアプリケーションを作った。 SwingはJava 1.2から存在する標準のGUIライブラリであるが、Java 8からは新たな標準としてJavaFXへの置き換えが進められている。 ここでは、JavaFXを使い、同様のGUIアプリケーションを作ってみる。
環境
Windows 8.1 Pro 64 bit版、JDK 8、Eclipse Mars 2
>systeminfo OS 名: Microsoft Windows 8.1 Pro OS バージョン: 6.3.9600 N/A ビルド 9600 OS ビルドの種類: Multiprocessor Free システムの種類: x64-based PC プロセッサ: 1 プロセッサインストール済みです。 [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz
e(fx)clipseのインストール
EclipseでJavaFXプロジェクトを扱えるようにするために、JavaFX用のプラグインであるe(fx)clipseをインストールする。
まず、メニューの「Help」→「Install New Software」を選択する。 ウィザードが表示されたら、リポジトリとしてhttp://download.eclipse.org/efxclipse/updates-released/2.3.0/siteを追加し、「e(fx)clipse -IDE」にチェックを入れてNextを押す。 ユーザライセンスへの同意を求められるので、同意にチェックを入れてFinishを押すとインストールが行われる。
JavaFXでGUIアプリケーションを作ってみる
「Java+SwingでGUIアプリケーションを作ってみる」と同様に、ドラッグアンドドロップされたファイルのSHA-1ハッシュ値をテーブルに表示するアプリケーションを書いてみる。
まず、ツールバーから「New」→「Project」を開き、「JavaFX」→「JavaFX Project」を選択して「SHA1CalculatorFX」プロジェクトを作成する。
src/application/Main.java
にファイルの雛形が作成されるので、続けて「Run」を押し、空のウィンドウが表示されることを確認する。
JavaFXでは、メインとなるクラスはプロジェクト名に関係なくMainクラスとなる。 雛形を編集し、実際にコードを書いてみると次のようになる。
package application; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import javafx.application.Application; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; public class Main extends Application { private final TableView<FileInfo> table = new TableView<>(); @Override @SuppressWarnings("unchecked") public void start(Stage primaryStage) { try { BorderPane root = new BorderPane(); Scene scene = new Scene(root,600,400); // expanded default size scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); primaryStage.setScene(scene); primaryStage.setTitle("SHA-1 Calculator FX"); // TableView settings table.setEditable(true); table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); TableColumn<FileInfo, String> filePathCol = new TableColumn<>("File Path"); filePathCol.setCellValueFactory(new PropertyValueFactory<>("path")); filePathCol.setCellFactory(TextFieldTableCell.forTableColumn()); TableColumn<FileInfo, String> sha1sumCol = new TableColumn<>("sha1sum"); sha1sumCol.setCellValueFactory(new PropertyValueFactory<>("sha1sum")); sha1sumCol.setCellFactory(TextFieldTableCell.forTableColumn()); table.getColumns().addAll(filePathCol, sha1sumCol); ObservableList<FileInfo> data = FXCollections.observableArrayList(); table.setItems(data); root.setCenter(table); // enable file drop scene.setOnDragOver(new DragOverHandler()); scene.setOnDragDropped(new DragDroppedHandler()); primaryStage.show(); } catch(Exception e) { e.printStackTrace(); } } public static void main(String[] args) { launch(args); } private class DragOverHandler implements EventHandler<DragEvent> { @Override public void handle(DragEvent event) { Dragboard db = event.getDragboard(); if (db.hasFiles()) { event.acceptTransferModes(TransferMode.COPY); } else { event.consume(); } } } private class DragDroppedHandler implements EventHandler<DragEvent> { @Override public void handle(DragEvent event) { Dragboard db = event.getDragboard(); boolean success = false; if (db.hasFiles()) { success = true; ObservableList<FileInfo> data = table.getItems(); for (File file : db.getFiles()) { FileInfo fileinfo = new FileInfo(file.getAbsolutePath(), ""); data.add(fileinfo); SHA1CalculationTask task = new SHA1CalculationTask(fileinfo); fileinfo.sha1sumProperty().bind(task.messageProperty()); new Thread(task).start(); } } event.setDropCompleted(success); event.consume(); } } class SHA1CalculationTask extends Task<Void> { private final FileInfo fileinfo; public SHA1CalculationTask(FileInfo fileinfo) { this.fileinfo = fileinfo; } @Override protected Void call() throws Exception { MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } // update digest with each 8MB chunk String path = fileinfo.getPath(); long totalBytes = (new File(path)).length(); long currentBytes = 0; try (FileInputStream istream = new FileInputStream(path)) { byte[] buf = new byte[8*1024*1024]; int n; while ((n = istream.read(buf, 0, buf.length)) != -1) { md.update(Arrays.copyOf(buf, n)); currentBytes += n; String message = String.format("calculating %d%%...", 100 * currentBytes / totalBytes); updateMessage(message); } } catch (IOException e) { e.printStackTrace(); } byte[] digest = md.digest(); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } updateMessage(sb.toString()); return null; } } public static class FileInfo { private final SimpleStringProperty path = new SimpleStringProperty(); private final SimpleStringProperty sha1sum = new SimpleStringProperty(); private FileInfo(String path, String sha1sum) { setPath(path); setSha1sum(sha1sum); } public String getPath() { return path.get(); } public void setPath(String newPath) { path.set(newPath); } public String getSha1sum() { return sha1sum.get(); } public void setSha1sum(String newSha1sum) { sha1sum.set(newSha1sum); } public SimpleStringProperty sha1sumProperty() { return sha1sum; } } }
JavaFXではデータバインディングの概念が取り入れられており、次のような特徴がある。
- TableViewにitemsとしてobservableArrayListを登録することで、リストへの変更が直接テーブルに反映される
- Tableの各セルはクラスのPropertyメンバで表現し、これをTaskクラスのPropertyにbindすることにより非同期での画面更新を行う
コードを保存した後「Run」を押して実行し、適当にファイルをドラッグアンドドロップした後のスクリーンショットを次に示す。
Swingと比較して、より現代的なUIデザインになっていることが見て取れる。
実行可能なJARファイルを作ってみる
JavaFXプロジェクトで実行可能なJARファイルを作成する際は、プロジェクトのExportではなくビルドツールのAntを用いる。
まず、build.fxbuildファイルを開き、表示されるフォームに対して次の例のように必須項目を入力する。
- Vendor name: inaz2
- Application title: SHA-1 Calculator FX
- Application version: 1.0.0
- Application class: application.Main
そして、右側にある「Generate ant build.xml and run」をクリックするとビルドが行われ、完了するとbuild/dist
にJARファイルが生成される。
生成されたJARファイルを実行すると、上と同様のウィンドウが表示されることが確認できる。
関連リンク
Java+SwingでGUIアプリケーションを作ってみる
JavaとSwingを使い、JRE(Java Runtime Environment)をインストールしたコンピュータで実行可能なGUIアプリケーションを作ってみる。
環境
Windows 8.1 Pro 64 bit版
>systeminfo OS 名: Microsoft Windows 8.1 Pro OS バージョン: 6.3.9600 N/A ビルド 9600 OS ビルドの種類: Multiprocessor Free システムの種類: x64-based PC プロセッサ: 1 プロセッサインストール済みです。 [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz
JDK 8のインストール
まず、JDKのダウンロードページからWindows x64用のインストーラをダウンロードし、JDK 8をインストールする。
インストール後、PATH
環境変数にC:\Program Files\Java\jdk1.8.0_91\bin
を追加しておく。
コマンドプロンプトを起動し、次のコマンドが使えることを確認する。
>javac -version javac 1.8.0_91
Eclipseのインストール
次に、EclipseのダウンロードページからWindows 64 bit用のzipファイルをダウンロードし、適当なディレクトリに展開する。 続けて、eclipse.exeを起動するとコード一式を置くパス(workspace)の設定を求められるので、適当なディレクトリを設定し、「Use this as default and do not ask again」にチェックを入れてOKを押す。 Tutorialsの「Create a Hello World application」を選択し、簡単にEclipseの使い方を確認しておくとよい。
SwingでGUIアプリケーションを作ってみる
ここでは、ドラッグアンドドロップされたファイルのSHA-1ハッシュ値をテーブルに表示するアプリケーションを書いてみる。
まず、ツールバーの「New」→「Java Project」から「SHA1Calculator」プロジェクトを作成し、続けて「Create new visual classes」から「Swing」→「JFrame」を選択する。 クラス名を「SHA1Calculator」にしてOKを押すとファイルの雛形が作成されるので、続けて「Run」を押し、空のウィンドウが表示されることを確認する。
雛形を編集し、実際にコードを書いてみると次のようになる。 javax.swing.JTableなど、使用するクラスを新たにimportする必要があることに注意。 なお、EclipseではCtrl+Spaceで補完リストの表示、Ctrl+Shift+Fでコードの自動整形、Ctrl+Shift+Oで不要なimport文の削除を行うことができる。
import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.SwingWorker; import javax.swing.TransferHandler; import javax.swing.border.EmptyBorder; import javax.swing.table.DefaultTableModel; public class SHA1Calculator extends JFrame { private JPanel contentPane; private DefaultTableModel model; /** * Launch the application. */ public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { try { SHA1Calculator frame = new SHA1Calculator(); frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } /** * Create the frame. */ public SHA1Calculator() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 600, 300); // expanded default frame size contentPane = new JPanel(); contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); contentPane.setLayout(new BorderLayout(0, 0)); setContentPane(contentPane); setTitle("SHA-1 Calculator"); // create scrollable JTable model = new DefaultTableModel(new String[]{"File Path", "sha1sum"}, 0); JTable table = new JTable(model); JScrollPane scrollPane = new JScrollPane(table); contentPane.add(scrollPane); // enable file drop contentPane.setTransferHandler(new DropFileHandler()); } private class DropFileHandler extends TransferHandler { @Override public boolean canImport(TransferSupport support) { // Check if dropped data is files return (support.isDrop() && support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)); } @Override @SuppressWarnings("unchecked") public boolean importData(TransferSupport support) { if (!canImport(support)) { return false; } Transferable t = support.getTransferable(); List<File> files = null; try { files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor); } catch (UnsupportedFlavorException | IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } for (File file : files) { String path = file.getAbsolutePath(); model.addRow(new String[]{path, "calculating..."}); ModelUpdater mu = new ModelUpdater(path, model.getRowCount()-1); mu.execute(); } return true; } } private class ModelUpdater extends SwingWorker<String, Long> { private String path; private Integer rowindex; public ModelUpdater(String path, Integer rowindex) { this.path = path; this.rowindex = rowindex; } @Override public String doInBackground() throws IOException { MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } // update digest with each 8MB chunk long totalBytes = (new File(path)).length(); long currentBytes = 0; try (FileInputStream istream = new FileInputStream(path)) { byte[] buf = new byte[8*1024*1024]; int n; while ((n = istream.read(buf, 0, buf.length)) != -1) { md.update(Arrays.copyOf(buf, n)); currentBytes += n; publish(100*currentBytes/totalBytes); } } catch (IOException e) { e.printStackTrace(); } byte[] digest = md.digest(); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); } @Override protected void process(List<Long> values) { // only last one is effective String message = String.format("calculating %d%%...", values.get(values.size()-1)); model.setValueAt(message, rowindex, 1); } @Override protected void done() { try { model.setValueAt(get(), rowindex, 1); } catch (Exception ignore) { } } } }
上のコードでは、SHA1Calculatorクラスでウィンドウとコンポーネントを生成し、そのサブクラスのDropFileHandlerクラスでドラッグアンドドロップに対する処理を記述している。 また、SwingWorkerクラスを継承したModelUpdaterクラスでSHA-1ハッシュ値の計算を行っている。 Swingでハッシュ値計算などの時間がかかる処理を実行する場合、メインスレッドで実行すると画面描画がブロックしてしまうため、SwingWorkerを使ってバックグラウンドで実行させる必要がある。
コードを保存した後「Run」を押して実行し、適当にファイルをドラッグアンドドロップした後のスクリーンショットを次に示す。
計算されたハッシュ値を別の方法で調べたハッシュ値と照らし合わせることにより、正しく計算できていることが確認できる。
実行可能なJARファイルを作ってみる
上のコードをJREで実行可能なJARファイルにするには、Package Explorerのプロジェクト名(SHA1Calculator)を右クリックし、「Export」を選択する。 表示されるウィザードから「Java」→「Runnable JAR file」を選択し、configurationと出力先パスを指定してFinishを押すと、JARファイルが生成される。
生成されたJARファイルを実行すると、上と同様のウィンドウが表示されることが確認できる。
関連リンク
Raspberry PiにインストールしたブラウザをRDPから操作する
Raspberry PiでWebブラウザを全画面表示し、RDP(リモートデスクトップ接続)から操作できるようにしてみる。
環境
Raspberry Pi 3 (Raspbian Jessie Lite)
$ uname -a Linux raspberrypi 4.1.19-v7+ #858 SMP Tue Mar 15 15:56:00 GMT 2016 armv7l GNU/Linux $ cat /etc/os-release PRETTY_NAME="Raspbian GNU/Linux 8 (jessie)" NAME="Raspbian GNU/Linux" VERSION_ID="8" VERSION="8 (jessie)" ID=raspbian ID_LIKE=debian HOME_URL="http://www.raspbian.org/" SUPPORT_URL="http://www.raspbian.org/RaspbianForums" BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
Xorgのインストール
ブラウザを表示するにはGUI環境が必要になるので、まずXorgをインストールする。 合わせて、fbturboドライバをインストールして画面表示を高速化する。
$ sudo apt-get install xorg xserver-xorg-video-fbturbo
GUIログインを行うために、ディスプレイマネージャLightDMをインストールする。
$ sudo apt-get install lightdm
ここで、/etc/lightdm/lightdm.conf
を開き、以下のような設定を加えておくとよい。
- 一定時間操作がないとき画面が消える動作をオフにする
- ログイン画面においてユーザをリストから選択できるようにする
[SeatDefaults] xserver-command=X -s 0 -dpms greeter-hide-users=false
raspi-configを起動し、「3.3 Desktop」を選択してグラフィカルログインを有効にする。
$ sudo raspi-config
合わせて、「9.3 Memory Split」からGPU用のメモリをデフォルトの64MBから256MBに増やしておくとよい。
x11vncのインストール
Raspberry Piに接続したディスプレイ画面をリモートで操作できるようにするため、x11vncをインストールする。 続けて、VNCパスワードを設定する。
$ sudo apt-get install x11vnc $ x11vnc -storepasswd Enter VNC password: Verify password: Write password to /home/pi/.vnc/passwd? [y]/n Password written to: /home/pi/.vnc/passwd
起動時に自動実行されるよう、/lib/systemd/system/x11vnc.service
を新規作成し、次のように記述する。
[Unit] Description=Start x11vnc at startup. After=multi-user.target [Service] Type=simple ExecStart=/usr/bin/x11vnc -auth guess -forever -loop -noxdamage -repeat -rfbauth /home/pi/.vnc/passwd -rfbport 5900 -shared [Install] WantedBy=multi-user.target
次のコマンドで、作成したサービスを有効化する。
$ sudo systemctl daemon-reload $ sudo systemctl enable x11vnc.service
xrdpのインストール
x11vncだけでもVNCプロトコルで接続が可能だが、Windows標準の「リモートデスクトップ接続」から接続できるとより便利である。 そこで、xrdpをインストールし、RDP経由でVNCサーバに接続できるようにする。
xrdpパッケージをインストールするには次のようにする。
$ sudo apt-get install xrdp
接続時のダイアログにおいて、VNC経由のコンソール接続がデフォルトになるように、/etc/xrdp/xrdp.ini
を開き、次の2つのセクションを入れ替えておく。
[xrdp1] name=console lib=libvnc.so ip=127.0.0.1 port=5900 username=na password=ask [xrdp2] name=sesman-Xvnc lib=libvnc.so username=ask password=ask ip=127.0.0.1 port=-1
再起動した後、LAN内のWindows端末からRDP接続を行い、Raspberry Piのデスクトップを操作できるか確認する。
$ sudo reboot
Epiphanyブラウザのインストール
Raspberry Piでは、GPUアクセラレーションが有効なブラウザとしてEpiphanyが用意されている。 ここでは、browserユーザでログインすることでEpiphanyブラウザが全画面表示されるようにしてみる。
まず、Epiphanyブラウザとこれが利用するgnome-keyring、全画面表示のために使うxdotoolをインストールする。
$ sudo apt-get install epiphany-browser gnome-keyring xdotool
次に、音声、映像、キーボード・マウス入力が有効なbrowserユーザを作成し、パスワードを設定する。
$ sudo useradd -G audio,video,input -m browser $ sudo passwd browser Enter new UNIX password: Retype new UNIX password: passwd: password updated successfully
browserユーザに切り替え、~/.bashrc
をバックアップした後、全画面でEpiphanyブラウザを表示するように編集する。
$ sudo su - browser $ mv .bashrc{,.bak} $ vi .bashrc xdotool search --sync --onlyvisible --class "epiphany-browser" windowsize 100% 100% & exec epiphany-browser
RDPで接続してbrowserユーザでログインした後、適当なWebサイトを表示したときの写真を次に示す。
課題
以上の設定である程度ブラウジングできるようになるが、以下のような課題も残されている。
- Input Methodが入っていないため、日本語入力ができない
- Flashが再生できない
- クラッシュして見れないページがある(Netflixのログインページなど)
関連リンク
- Screen sharing between Raspberry PI and Mac OSx - Stack Overflow
- VNC/Servers - Community Help Wiki
- xinitrc - ArchWiki
- Web browser released! - Raspberry Pi
- Disable screen blanking in X-Windows on Raspbian - Raspberry Pi Stack Exchange
Special Thanks
Raspberry PiにKodiをインストールしてネットテレビを作る
Raspberry PiにメディアセンターKodi(旧XMBC)をインストールして、接続されたディスプレイでYouTubeなどを見れるようにしてみる。 また、AndroidのリモコンアプリKoreからKodiを操作できるようにしてみる。
環境
Raspberry Pi 3 (Raspbian Jessie Lite)
$ uname -a Linux raspberrypi 4.1.19-v7+ #858 SMP Tue Mar 15 15:56:00 GMT 2016 armv7l GNU/Linux $ cat /etc/os-release PRETTY_NAME="Raspbian GNU/Linux 8 (jessie)" NAME="Raspbian GNU/Linux" VERSION_ID="8" VERSION="8 (jessie)" ID=raspbian ID_LIKE=debian HOME_URL="http://www.raspbian.org/" SUPPORT_URL="http://www.raspbian.org/RaspbianForums" BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
Raspbianの設定
まず、raspi-configで「9.3 Memory Split」を選択し、GPU用のメモリをデフォルトの64MBから256MBに増やしておく。
$ sudo raspi-config
合わせて、HDMIディスプレイとUSBキーボードを接続しておく。
Kodiのインストール
パッケージをインストールし、起動時に自動起動するように設定する。
また、自動起動時のユーザであるkodi
をinput
グループに追加し、キーボード入力を扱えるようにする。
$ sudo apt-get update $ sudo apt-get install kodi $ sudo vi /etc/default/kodi # Set this to 1 to enable startup ENABLED=1 $ sudo usermod -a -G input kodi $ sudo reboot
再起動すると、接続されたディスプレイにKodiのホーム画面が表示される。
日本語を表示できるようにする
標準の状態では日本語が文字化けしてしまうため、フォントの設定を変更する。
- System→Appearance→Skin→Fontsで「Arial based」を選択
Androidから操作できるようにする
USBキーボードではなくAndroidから操作できるように、リモートコントロールの設定を行う。
- System→Service→Remote controlで「Allow remote control by programs on other systems」をオン
- System→Service→Web serverで「Allow remote control via HTTP」をオン
- System→Service→Zeroconfで「Announce services to other systems」をオン
- 標準でオンになっている
LANに接続したAndroidにリモコンアプリKoreをインストールし、実行する。 設定ウィザードにおいて自動でKodiが発見されるので、これに接続し、アプリの上下左右ボタンでKodiを操作できることを確認する。
Add-onのインストール
KodiはHDD内の動画や音楽の再生に加え、Add-onのインストールによりさまざまなWebサービスを利用できるようになる。 ここでは、YouTube用Add-onのインストールについて説明する。
まず、Videos→Add-ons→Get more...と移動し、公式Add-onの一覧を表示する。 次に、YouTubeを選択し、表示された詳細画面からInstallを選択してインストールする。
インストール後、Add-onsの画面に戻るとYouTubeが追加されているので、これを選択する。 セットアップウィザードを実行するか聞かれるので、Yesを選択する。 ここでは次のように設定するとよい。
- View: Default: Thumbnail
- View: Episodes: Media info
- Language: English
- Region: Japan
続けてメニューが表示されるので、Sign Inを選択する。 画面にアクティベートコードが表示されるので、適当な端末からhttps://youtube.com/activateにアクセスしアクティベートを行う。 なお、Sign Inしないと正しく動作しないので注意。
以上の設定が終わると、次の写真のようにYouTubeが見れるようになる。
また、Androidからは次のようにコントロールできる。
他にもUdacityやKhan Academy、音楽についてはSHOUTcastやSoundCloud、MixcloudなどのAdd-onがあるので、必要に応じて有効にしておくとよい。