プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

WindowsでもEmacsをDaemonのように使いたい

 Windowsで何のエディタ使っていますか?私はなぜか結局Emacsです。


 ところで、WindowsのEmacsはdaemonフラグが有効ではありません。
 server使えば似たようなことできるからいいといえばいいのですが、サーバー用のウィンドウが邪魔くさいです。
 最小化してもタスクバーに残るしね。HideItとか使ってみたことあるけど、自分でウィンドウ隠すという作業が面倒くさい。あと、タスクバーにいなくなっても、タスクトレイに存在するのが希に気になる。タスクトレイ実行ツールも試したけど、駄目だった。仮想デスクトップ作って見えないように押し込んだりとかいろいろ試したけど、あぁ、Daemonがほしい。


 というわけで、WindowsでもEmacsをDaemon化したいなーとかずっと思ってたので、ふと何を思ったのか似たような動作になるようにPowerShellでやってみました。

 方法としては

  1. emacs.exeを実行
  2. しばらく待機してから
  3. ウィンドウを非表示にする

ってだけ。まぁ、要するに今までHideItとかタスクトレイ実行ツールや仮想デスクトップに頼ってたのを自分で書いただけですね。とりあえず、自動でウィンドウが見えなくなるので、なんかDeamonっぽいです。
 ※serverの起動は.emacsに書くなりしてください。

コマンド 引数 説明
Emacs-Daemon [ bin-directory [ wait-time [work-directory]] デーモンっぽく起動してみる。
bin-directory:emacs.exeがあるディレクトリ。デフォルトは空文字(Pathに登録されている場合にだけうまく動く)
wait-time:待機ミリ秒
work-directory:作業ディレクトリ
Emacs-Kill Emacsの画面全部閉じる。Emacs開いてなくて、Power Shellだけ開いてるときに使うといいんじゃないかな

 コードは下にはっつけとくので、使ってみたいという希有な人は、適宜profile.ps1にでも貼り付けるなり、スクリプトにするなりしてください 

powershell -Command emacs-daemon

 とかを適当にWindows起動後に実行するようにすれば勝手に動いてくれるからよりデーモンライクだね。ちろちろ画面が出るのがうざいけど。



 ちなみに、「しばらく待機」ってのがやっかいで、早すぎるとウィンドウがとれなくて非表示に失敗するし、かといって待ちすぎるのもあれだし。
 とりあえず、私の環境では2秒じゃだめだけど、5秒だと安定して大丈夫っぽいから5秒待ってる。
 Process.WaitForInputIdleとかでできるかなぁとか思ってた時期が私にもありましたけど、全然無理だった。

 だれかうまい方法ご存じないですかね。誰かWindowsのEmacsでもDaemon化してくれませんかね。


PowerShellのコード

 まぁ、PowerShellというか、ほとんどC#だけど。

$CSCODE=@'
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace EmacsDaemon
{
    public class WindowControl
    {
        [DllImport("user32.dll")]
        public static extern bool SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
        [DllImport("user32.dll")]
        public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
        [DllImport("user32.dll")]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, ref uint lpdwProcessId);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool EnumWindows([MarshalAs(UnmanagedType.FunctionPtr)] EnumWindowsProc lpEnumFunc, IntPtr lParam);

        public static bool KillWindow(IntPtr hWnd) { return SendMessage(hWnd, 0x0112, 0xf060, 0); }
        public static bool HideWindow(IntPtr hWnd) { return ShowWindow(hWnd, 0); }

        class CloseProcess
        {
            Process process;
            public CloseProcess(Process p) { process = p; }
            public bool method(IntPtr hWnd, IntPtr lParam)
            {
                uint p = 0;
                GetWindowThreadProcessId(hWnd, ref p);
                if (p == process.Id)
                {
                    KillWindow(hWnd);
                }
                return true;
            }
        }
        public static void CloseAllWindow(Process process)
        {
            CloseProcess cp = new CloseProcess(process);
            EnumWindowsProc ewp = new EnumWindowsProc(cp.method);
            IntPtr i = new IntPtr();
            EnumWindows(ewp, i);
        }
    }
}
'@
Add-Type -Language CSharp -TypeDefinition $CSCODE

function Emacs-Daemon(){
  param($dir="",$wait=-1,$workDir=$env:HOME);
  $emacsexe="emacs.exe";
  if($dir -ne ""){
    $emacsexe = $dir +"\emacs.exe";
  }
  if($workDir -eq ""){
    $workDir = ".";
  }
  if($wait -eq -1){
    $wait = 5000;
  }
  $eproc=Start-Process $emacsexe -WindowStyle Hidden -PassThru  -WorkingDirectory $workDir
  [Threading.Thread]::Sleep($wait);#誰かもっといい方法教えて
  [EmacsDaemon.WindowControl]::HideWindow($eproc.MainWindowHandle);
}
function Emacs-Kill(){
  $eprocess = Get-Process "emacs";
  foreach($pr in $eprocess){
    [EmacsDaemon.WindowControl]::CloseAllWindow($pr);
  }
}

 てか、はてな記法ってPowerShellに対応していないんだね…。

KeySnailのKillLineをEmacs風に改良してみた

 f:id:nodamushi:20151019022132p:plain

 KeySnailネタ。moozさんのアイコンかわいいよね。
 というわけで、なんか、Firefoxがバージョンアップしたら.keysnail.jsの「以前にコピーしたテキスト一覧から選択して貼り付け」のletでエラーが出るらしく、久しぶりに.keysnail.jsを編集しました。

すでに修正されてた(´・д・`)

 自分で修正しんたんすけど、もしかしてって見てみたら、keysnail/functions.js at f22a53fac0c693ccb939b5398494be63d7e1e011 · mooz/keysnail · GitHubにもう一ヶ月ぐらい前に更新されてましたよー。
 うん、Firefox自動更新してないんだ。ごめんね。

 というわけで、コピって貼り付け。

    if (!command.kill.ring.length)
        return;
    
    let ct = command.getClipboardText();
    if (!command.kill.ring.length || ct != command.kill.ring[0]) {
        command.pushKillRing(ct);
    }
    
    prompt.selector(
        {
            message: "Paste:",
            collection: command.kill.ring,
            callback: function (i) { if (i >= 0) key.insertText(command.kill.ring[i]); }
        }
    );



Kill Lineがなんか変

 日頃からどーもなんか気にくわないな、とおもっていたKeySnailのkill-line。


 というのもこれ。ここ(↓)で、Ctrl+Kを押すと………
f:id:nodamushi:20151019012224p:plain
f:id:nodamushi:20151019012230p:plain
 うん、問題ないね。

 でも、ここ(↓)でCtrl+Kを押すと………
f:id:nodamushi:20151019012243p:plain
f:id:nodamushi:20151019012247p:plain
 あっるぅぇえ?(´・д・`) なぜか私のパソコンではこんなんになってなまいます。


 私のパソコンでもこう(↓)なってほしいんですよね。

f:id:nodamushi:20151019012252p:plain


 あと、気になるのこれ。
f:id:nodamushi:20151019031717p:plain
f:id:nodamushi:20151019031726p:plain

 論理行で削除するんじゃなくって、テキストエリアの折り返し行までが削除されるの。これ気に入らないの。




 というわけで、ついでだったので、そうなるように修正したのがこちら。

key.setEditKey('C-k', function (ev) {
    if (command.marked(ev))
            command.resetMark(ev);
    let ta = ev.originalTarget;
    let str = ta.value;

    let i = ta.selectionStart;
    for(let e=ta.value.length;i<e;i++){
        let c = str.charAt(i);
        if(c == "\r" || c == "\n" || c=="\u0085"){
            break;
        }
    }
    ta.selectionEnd = i;
    let cstr = ta.value.substring(ta.selectionStart,ta.selectionEnd).trim();
    command.copySelectedText(ta);
    goDoCommand('cmd_delete');
    if(!cstr){
        goDoCommand('cmd_beginLine');
        let pos = ta.selectionStart;
        goDoCommand('cmd_endLine');
        if(pos != ta.value.length){
            goDoCommand('cmd_deleteCharForward');   
        }else{
            goDoCommand('cmd_deleteCharBackward');
        }
    }
}, 'カーソルから先を一行カット (Kill line)');

 Emacsのkill-lineは削除した文字が空(空白のみの文字列含む)だと、改行文字も消すっぽいので、その処理を追加してあります。


 ちなみに、どうもうまく動かない戦犯は"cmd_deleteToEndOfLine"らしく、こいつを使わないようにすることが重要。なんか、このコマンドって条件はよくわからないんだけど、正しく動作しないことがあるっぽいです。
 最初は、if(!str)ブロックの中身も"cmd_deleteToEndOfLine"を呼び出してただけなんだけど、なぜか2行目にカーソルがあるときに、正しく動かないという状況がまれに発生しました。発生条件は全くもって不明。というわけで、上記の形になっています。文字列処理は嫌ので、キャレットの位置変化から最後の行かどうか判定しています。



 あ、なお、これはFirefoxのバグか、私のプライグイン環境との相性のバグなのかのどっちかです。
 KeySnailのバグではありません。





 あと、ついでに、Ctrl+Jで次の行を生成して移動するってのを作っておいた。
 単に改行だけにしたい人は、goDoCommandを削除してください。

key.setEditKey("C-j",function(ev){
    goDoCommand("cmd_endLine");
    key.generateKey(ev.originalTarget,KeyEvent.DOM_VK_RETURN,true);
},"新しい行を作成",true);


 以上です。




 ところで、EdgeでFirefoxのプラグインが動かせるかもという噂ですが、KeySnailがEdgeで動くかなぁ。
 動けばいいなぁ~ヽ( ・∀・)ノ

かゆいところに手が届かないATOKタッチキーボード

 すっかり忘れてたんですが、私Surface Pro3を随分前に買っていました。
 Pro4が出るらしいですね。Pro4は筆圧1024段間検知だとか。めっちゃ欲しい。


 さて、普段IMEにAtokを愛用している私。Surfaceでも利用しようとインストールしたんですが、

 SurfaceのタッチキーボードでのAtokの使いにくいのなんのって

 ダントツで使いにくいです。
 というわけで、愚痴がほぼすべてのレポートを記しておきます。




予測変換が邪魔くさい

普通に入力すると、予測変換が出てきます。

f:id:nodamushi:20151011233517j:plain

 最初はこれすら「うぜぇ」とか思っていたんですが、意外と慣れました。
 慣れると、まぁ、使い勝手が良いときもあるし、お節介が過ぎるときもある半々な感じです。

 が、問題は次。

f:id:nodamushi:20151011234330j:plain

 「予測変換」を確定した後に、何も押していないのに出てくるこれね。
 (ちなみに、デスクトップに増えていっているのは自動キャプチャ画像。)
 これで、空白を開けようとスペースを押そうものならこうなる。

f:id:nodamushi:20151011234458j:plain
 だあああ うぜぇええええ
 


 
 スペースって検索ボックスでは良く打つよね。その度にこうなるわけ。ストレスマッハ。

JustSystemsに聞いてみた

 これを解決する方法をJustSystemsに聞いてみました。返事によると、以下の自動表示の項目を「しない」に設定しろとのこと。

f:id:nodamushi:20151011235318j:plain

 するとこうな………
f:id:nodamushi:20151011235606j:plain

 変わらん。(デスクトップのキャプチャ画像が増えてるので時系列はちゃんと指示通り設定した後である)
 どうも、一回ソフトごと起動し直す必要があるらしい。
 というわけで、もう一回メモ帳を起動し直し、トライ。


f:id:nodamushi:20151011235815j:plain
 なんかよくわからん何もないウィンドウが表示される。うざい。

f:id:nodamushi:20151011235908j:plain
 「よそく」と打っても、予測変換は出てこない。
 違うんだよ。ここの予測変換は出てきてもいいんだよ。害悪ではなかったんだよ。

f:id:nodamushi:20151012000140j:plain
 やりました。予測変換、と打ち込んだ後に、余計な連中が出てきません。
 ですが、ちょっとキャプチャのタイミング上移りませんでしたが………

f:id:nodamushi:20151012000240j:plain
 入力後になんか何もないウィンドウが出てきます。うぜぇ


かゆいところに手が届かないAtok

 最大のストレス源である、変換確定後に余計な予測変換が出てくる機能を抑制することはできました。
 が、出てきてもいい予測変換まで抑制されるし、なんかうざいウィンドウは出てくるし、どうもかゆいところに手が届きません。
 あと、日本語入力用のATOKタッチキーボードが選択不可になったりして使いにくいことこの上ないです。(いったんMicrosoft IMEに変更してから、ATOKにすると選択できたり、よくわからん)

 あぁ、かゆいところに手が届きませんな。

 まぁ、結局Surfaceでは私はMicrosoftを使うことにして、ATOKはアンインストールしたんで、もう関係ないんですけどね。(この記事のためにわざわざインストールし直した)



 バージョンアップする度に余計な機能が追加されて馬鹿になっていくともっぱら評判のATOKさんですが、私の信仰心はいつまで持つのか。今年か来年中に見切りをつけようかと思っています。


 ところで、最近のMicrosoftは結構頑張ってるよね。
 今年度中に行われると噂のMicrosoftのEdgeで本当にFirefox,Chromeの既存拡張機能取り込みが来れば、Firefoxへのかすかに残っていた信仰心は完全に捨てる予定です。
 仮想デスクトップもいいね。MacやLinuxの仮想デスクトップにはまだ負けるけど。アイコンクリック一つで移動できる便利さが足りない。あとはGnuEmacsがWindowsでもdaemon使えるようになればもう何も怖くないね。serverは使えるのになんでdaemonは使えないんだろ?

 あと、環境変数のPathの設定をしやすくするみたい。地味にいい機能。

 Microsoftを信仰することにしましょうか。

JavaFXでConsole作ってみた

 JavaFXってConsole的な物なくね?
 ググっても出てこなくね?


 と、いうわけで、作ってみました、こんなもの。
f:id:nodamushi:20150926211739p:plain


 System.outを今回作ったSimpleConsole.outに変更可能。
 マルチスレッド対応(たぶん)


 最低限の機能はあるんでない?誰かがしっかりした物を作ってくれるまではこれで我慢しましょうぞ。

NodamushiFXControls/SimpleConsole.java at master · nodamushi/NodamushiFXControls · GitHub

NodamushiFXControls/ConsoleTest.java at master · nodamushi/NodamushiFXControls · GitHub

JavaScriptで作ったちょっとしたスクリプトからファイルを保存したい

 JavaScriptはちょこっと何かを作ろうと思うと、一番使いやすいと思っている。なんせ特に何もいらない。エディタとブラウザがあればGUIを持った簡単なスクリプトなんかすぐ作れる。最近は簡単な物ならブラウザ内で全部完結して、エディタすらいらない。
 Excelマクロを習ってGUIの作成を習うよりずっと簡単だと私は思っている。なお、異論は認める。

 が、このJavaScriptは簡単ではあるが、ファイルの保存が出来ない。File APIもChromeしかない。
 私ならJavaScriptで何かして保存したいときはGreasemonkey使ったり、keysnail使ったりするって手段もあるっちゃある。
 でも、あまりプログラミングに詳しくない人が簡単に作って試して保存まで勢いで出来なければ意味が無い。


 だが、そんな方法はない。と、思っていたら意外とHTML5には別の手段で保存する方法があるようだ。
 以下のsaveTextを実行するだけで、とりあえずローカルに保存できる。ただし、ブラウザ設定によっては何も聞かずにDownloadフォルダに勝手に保存しちゃうのが玉にキズだけどね。

function saveText(text, fileName) {
  var a, blob, event;
  if (fileName == null) {
    fileName = "textfile.txt";
  }
  blob = new Blob([text], {
    type: "text/plain"
  });
  if (window.navigator.msSaveBlob != null) {
    window.navigator.msSaveBlob(blob, fileName);
  } else {
    a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.target = "_blank";
    a.download = fileName;
    event = document.createEvent("MouseEvents");
    event.initEvent("click", false, true);
    a.dispatchEvent(event);
  }
};


CoffeeScriptバージョン

saveText = (text,fileName)->
  if !fileName? then fileName = "textfile.txt"
  
  blob = new Blob [text],{type:"text/plain"}
  if window.navigator.msSaveBlob? then window.navigator.msSaveBlob blob,fileName
  else
    a = document.createElement "a"
    a.href = URL.createObjectURL blob
    a.target = "_blank"
    a.download = fileName
    event = document.createEvent "MouseEvents"
    event.initEvent "click",false,true
    a.dispatchEvent event
  return


参考:
JavaScriptだけでファイルの保存機能を実装する - 新人Webエンジニアの記録。
JavaScriptのクリックイベントを発火させる方法 - ゆうなんとかさんの雑記帳的な。yuuxxxx.hatenablog.com





ちょっと根性あるならnode-webkitという選択肢も

 去年ぐらいからちょくちょく耳にすることがあるnode-webkit。要するにHTML5とJavaScriptとそれの実行環境をひとまとめにしてアプリケーションにしましょうというものですかね。
 node.jsの機能が使えるのでファイルの保存とか出来ます

 詳しくはよく知らないけどね!(え 個人的には単体で動くブラウザのプラグインだと思っています。

 liginc.co.jp




まだ根性あるなら好みの関数を持つブラウザ作っちゃおうぜ

 回りくどいことせずに、JavaScriptからダイレクトに保存関数を呼べるブラウザを自作しちゃうってのも一つの手だ(え?


 例えば、Qtとか、JavaFXにはWebEngineがあるから、それを使うと、意外に簡単にJavaScriptで保存することが出来るブラウザを作れる。
 まぁ、確かに、最初のブラウザを作るのはちょい骨折れるけど、一回作ってしまったら、node-webkitの用に毎回アプリケーションにする必要なんて無いぞ!これは楽だ!




 ………いや、やっぱそれぐらいならJavaやらC#やらExcel VBAを覚えた方が絶対早いよ




 最後に載せたJavaFXのWebEngineを使った例に次のHTMLファイルを開かせると、ファイルの保存画面が開いてテキストファイルが保存される。





 さぁ、私の匙は投げられた。好きな方法を選ぶがよい。


test用HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
Files.saveText("内容");
</script>
</body>
</html>


自作ブラウザ

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.util.*;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.web.*;
import javafx.stage.*;
import javafx.stage.FileChooser.ExtensionFilter;
import netscape.javascript.JSObject;

public class Browser extends Application{

  public static void main(final String[] args){
    System.setProperty("prism.lcdtext", "false");
    launch(args);
  }
  private WebView view;
  private TextField url;

  @Override
  public void start(final Stage stage) throws Exception{
    final WebView v = view=new WebView();
    JSFiles.install(v);

    final TextField url = this.url = new TextField();
    final Button open = new Button("Open");
    url.setOnAction(this::openURL);
    open.setOnAction(this::openURL);

    final BorderPane p = new BorderPane(v);
    HBox.setHgrow(url, Priority.ALWAYS);
    p.setTop(new HBox(url,open));
    p.setPrefSize(800, 600);

    stage.setScene(new Scene(p));
    stage.show();
  }


  public void openURL(final ActionEvent e){
    final String text = url.getText().trim();
    if(text == null) {
      return;
    }

    String url;
    try{
      url = Paths.get(text).toUri().toURL().toString();
    }catch(final Exception e1){
      url = text;
    }


    final WebEngine engine = view.getEngine();
    if(url.equals(engine.getLocation())){
      engine.reload();
    }else{
      engine.load(url);
    }

  }



  public static class JSFiles{

    public static void install(final WebView v){
      final JSFiles jsf = new JSFiles(v);
      final WebEngine engine = v.getEngine();
      final JSObject window = (JSObject)engine.executeScript("window");
      window.setMember("JavaFiles", jsf);

      final JSObject eval = (JSObject)engine.executeScript(
          "var Files = {};"
          + "Files.saveText = function(str,charset){"
          + "JavaFiles.__saveText__(str,charset);"
          + "};");
      System.out.println(eval);
    }

    private WebView view;

    public JSFiles(final WebView view){
      this.view = view;
    }


    public boolean __saveText__(final String str,final String charset){
      final FileChooser chooser = new FileChooser();
      chooser.getExtensionFilters().addAll(TEXTFILTER,ALL);
      final Window w = view.getScene().getWindow();
      final File file = chooser.showSaveDialog(w);

      if(file!=null){

        Charset c =null;
        try{
          if(charset != null){
            c= Charset.forName(charset);
          }
        }catch(final Exception e){}
        if(c == null) {
          c = Charset.defaultCharset();
        }

        @SuppressWarnings("resource")
        final Scanner scan = new Scanner(str);


        final Iterator<CharSequence> i = new Iterator<CharSequence>(){
          @Override
          public CharSequence next(){
            return scan.nextLine();
          }
          @Override
          public boolean hasNext(){
            return scan.hasNextLine();
          }
        };

        try {
          Files.write(file.toPath(),()->i, c);
          return true;
        } catch (final IOException e) {
          e.printStackTrace();
          return false;
        }
      }else{
        return false;
      }
    }
    private static ExtensionFilter TEXTFILTER=new ExtensionFilter("*.txt", "*.txt");
    private static ExtensionFilter ALL=new ExtensionFilter("*", "*");
  }

}

ファイル選択のControl作った

 ウィーっす。
 さて、昨日は入力候補が出るTextFieldを作ってみました。nodamushi.hatenablog.com

 で、これを利用してファイル選択用のConrolを作ってみました。
 こんな感じに動作します。
f:id:nodamushi:20150814173654p:plain

 うん、使いやすい。私はやっぱりマウスでクリックするより、キーボードで打ち込んだ方が楽だからねー。
 昨日と同じくソースコードはこちら。NodamushiFXControls
 クラス名はnodamushi.jfx.path.PathChooserが今回のControlです。

 FileChooserとかDialogChooserとか自分で操作しなくても勝手にやってくれます。
 もともとこれを作りたいが為に、CompletionTextFieldを作りました。こう言うのって、ありそうで、検索してみても出てこなかったんだよね。
 (既にあったらすみません)






 しかし、まー、だいたい、目的の動作はするんだけど、まだ微妙にバグってたりはします。
 時々ListViewが上手くレンダリングされなかったり、ScrollBarが表示されているかどうかの判定にミスってるみたいなんだよねぇ。
 f:id:nodamushi:20150814175004p:plain
 f:id:nodamushi:20150814175006p:plain

 どっちも常に起こるわけじゃないんだよね。その発生条件もよく分かってない。
 直し方が分からんし、根性が切れたので、ここでいったん公開して、記事にしました。
 

入力候補が出るTextField作ってみた

 夏風邪ひいて熱が出たり咳が出たり鼻水出たりでしんどいですが、皆さん体調いかがでしょうか。


 さて、風邪ひいていようと暇なものは暇なので、タイトル通りの物を作っていました。
 イメージとしてはこんなのね。(Firefoxの検索窓)
f:id:nodamushi:20150813230113p:plain:w240

 ソースコードはこちら。NodamushiFXControls
 クラスは CompletionTextFieldです。
 候補や挿入方法はCandidateインターフェースで定義してあるだけなので、よく言えば自由がある、悪く言えばユーザーが用意しないといけない。

 デモ用に作ったCandidateインターフェースの実装(ForwardMatchCandidate)ではこんな感じに動作します。
f:id:nodamushi:20150813230150p:plain:w240
f:id:nodamushi:20150813230152p:plain:w240
f:id:nodamushi:20150813230154p:plain:w240





自作PopupControlの作り方

 意外とここの部分でつまった。
 最も単純な方法では、TooltipのGraphicに表示させたいNodeを突っ込んで表示するってのが楽そうだけど、やっぱそれだと微妙だよね。
 Tooltipの元となっているPopupWindowないし、PopupControlから派生させたい物です。


 今回はPopupControlにNodeを表示させる方法を調べたところ、単純にSkinを登録するだけでした。
 私が作ったコードではこんな感じ。ComboBoxSkinのコードを参考にしています。(ていうか、ほとんどここはまんまか)

    final PopupControl p = new PopupControl(){
      @Override
      public Styleable getStyleableParent(){
        return getSkinnable();//ポップアップ元のノードを返しています
      }
      {
        setSkin(new Skin<Skinnable>(){
          @Override public Skinnable getSkinnable(){return CompletionTextFieldSkin.this.getSkinnable();}
          @Override public Node getNode(){return getPopupContent();}
          @Override public void dispose(){}
          });
      }
    };





JavaFX8ではListViewの行の高さを取得できなかった

 ListViewを表示するときに、ある一定の行までは表示して、それ以上はスクロールバーで対応させたい。
 つまり、n行を表示するための高さを取得して、それをPrefHeightとすれば良いのだが、この1行の高さというのが実は取得できない。

 でも、ComboBoxとかではそれを実現しているように見える。ではどうやっているのかとソースコードを調べ、最終的に次のコードに落ち着いた。

  private static final Class<?> VIRTUALCONTAINERBASE;
  private static final Method
  GET_VIRTUAL_FLOW_PREFERRED_HEIGHT,//n行分の高さを取得するメソッド
  UPDATE_ROW_COUNT;//今何行分のアイテムがあるのかSkinに強制的に更新させる
  static{
    Class<?> c;
    Method m,m2;
    try {
      c=Class.forName("com.sun.javafx.scene.control.skin.VirtualContainerBase");
      m = c.getDeclaredMethod("getVirtualFlowPreferredHeight", int.class);
      m2 =c.getDeclaredMethod("updateRowCount");
      m.setAccessible(true);
      m2.setAccessible(true);
    } catch (final Exception e) {
      c = null;m=null;m2=null;
    }
    VIRTUALCONTAINERBASE=c;
    GET_VIRTUAL_FLOW_PREFERRED_HEIGHT=m;
    UPDATE_ROW_COUNT=m2;
  }


  private double getListViewPrefHeight() {
    double ph;
    final int maxRows = min(textField.getVisibleRowCount(),getCandidateSize());
    if (VIRTUALCONTAINERBASE!=null &&listView.getSkin()!=null &&
        VIRTUALCONTAINERBASE.isAssignableFrom(listView.getSkin().getClass())) {
        try{
          //これ↓を挟まないと次のgetVirtualFlowPrefreredHeightでちゃんと高さが出なかった。
          UPDATE_ROW_COUNT.invoke(listView.getSkin());
          ph =(double) GET_VIRTUAL_FLOW_PREFERRED_HEIGHT.invoke(listView.getSkin(), maxRows);
        }catch(final Exception e){
          final double ch = maxRows * 25;
          ph = Math.min(ch, 200);
        }
    } else {
      final double ch = maxRows * 25;
      ph = Math.min(ch, 200);
    }
    return ph;
  }


 う~ん、リフレクションかぁ。com.sunパッケージかぁ(´Д⊂ヽ

 とりあえず、これに期待するしかないっすね。。。いったいどの程度JavaFX9でPublic APIが増えるんでしょうかね。
 
JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization



 特に落ちもまとめもありませんが、以上。