Emacs on WSL で起動時に最大化したい

# 概要
WSL環境(xserverにはvcxsrvを使用)でEmacsを最大化する方法見つけるまでのログ
# 問題点
最大化起動のために`(set-frame-parameter nil 'fullscreen 'maximized)`しているが位置がおかしい
![emacs-posisin.png](https://qiita-image-store.s3.amazonaws.com/0/242567/6783e21d-7962-e74a-1d18-8f2f3d0d3f29.png)
frame位置を(0,0)に設定してもおかしい
![emacs-position0.png](https://qiita-image-store.s3.amazonaws.com/0/242567/e580e0a9-5f8c-f592-e0e3-7879af1ebd34.png)

ウィンドウのタイトルバーなどはシステム(window manager)が描いているのか？(waylandでそんなんをやった気がするな..)
そもそも、"最大化"や"元のサイズに戻す"ボタンをクリックしても`frame-parameter`の`fullscreen`に変化なし
xのwindow managerではないのでEmacsへ最大化のイベント飛んでいない?サイズの設定だけされていると予想
(vcxsrvが悪いのか?)
# Emacsで用意されている最大化の方法を色々試す
xwindow使っているので
[これ](https://emacs.stackexchange.com/a/7671)を試してみた

```
(when (eq system-type 'gnu/linux)
  (defun x11-maximize-frame ()
    "Maximize the current frame (to full screen)"
    (interactive)
    (x-send-client-message nil 0 nil "_NET_WM_STATE" 32 '(2 "_NET_WM_STATE_MAXIMIZED_HORZ" 0))
    (x-send-client-message nil 0 nil "_NET_WM_STATE" 32 '(2 "_NET_WM_STATE_MAXIMIZED_VERT" 0)))
  (run-with-idle-timer 0.01 nil 'x11-maximize-frame)
  )

(when (eq system-type 'windows-nt)
  (w32-send-sys-command 61488)
  )
```
## x serverへのメッセージで最大化してみる
vcxsrvのログ見る限り、どうもx11のメッセージ送信うまく言ってないっぽい
xのwindow managerを使っていないからか?
windowsの中で他のアプリと同じ見た目で使いたいので、windowsのwindow managerで動かしたい

## windows のwindow managerにメッセージ送る
`w32-send-sys-command`はwindows向けのビルドでしか用意されない
githubでリポジトリ内grep(恥ずかしながらこんなんできること初めて知りました)するとCの関数呼んでることしか分からない
win32apiの`wm_syscommand`の`sc_maximize`を呼んでいると思われる

# win32api の SendMessage() を使う
バイナリよりもスクリプトのほうが良いかと思い`powershell`を使ってみた
## powershellスクリプト起動まで
`powershell`からwin32apiの`SendMessage()`できることがわかったが、なんかセキュリティ関連でめんどうなこともわかった
回避策は2つ。標準入力から与えるか

```
cat path\to\w32_wm_syscmd.ps1 | powershell.exe -command -
(WSL環境なのでLinuxコマンドとWindowsコマンドをpipeできる)
```

もしくは、一時的にpolicyを変更しローカルのファイルの実行を許可する
参照元:[PowerShellスクリプトを簡単実行](https://qiita.com/biginnings/items/f4e22ad275d9adfc1b57)

```
powershell.exe -ExecutionPolicy RemoteSigned -File path\to\w32_wm_syscmd.ps1
(-Fileでスクリプトファイル指定)
```

今回は下を使った

## SendMessage()まで
スクリプトの説明は端折りますが、C#のコードをpowershellから呼び出せるのを利用し、C#でwin32apiのdllをloadして使っています
詳しくは[powershellでWin32 APIを利用する（powershell 2.0以上）](https://qiita.com/y-takano/items/cb752ad6a10e550ec92f)

```powershell:w32_wm_syscmd.ps1
# file : w32_wm_syscmd.ps1 [Windows PowerShell script]
# usage: w32_wm_syscmd.ps1 {"maximize","minimize","restore"} [process-name="vcxsrv"]
# execute win32api::SendMessage(WM_SYSCOMMAND) to Maximize/Minimize/Restore window.
# arg1 selects Maximize, Minimize, Restore. mandatory.
# arg2 specify process-name. optionary.

Param(
    [ValidateSet("maximize","minimize","restore")][String]$cmd,
    [String]$procName = "vcxsrv"
)

# Import win32api by C# syntax
$Win32 = &{
    $cscode = @"
[DllImport("user32.dll")]
public static extern long SendMessage(
    IntPtr hWnd,
    UInt32 Msg,
    int wParam,
    long lParam
);
"@
    return (add-type -memberDefinition $cscode -name "Win32ApiFunctions" -passthru)
}

# Set win32api defines
$WM_SYSCOMMAND = 0x0112
$SC_MAXIMIZE   = 0xF030
$SC_MINIMIZE   = 0xF020
$SC_RESTORE    = 0xF120

switch($cmd){
    "maximize" { $scCmd = $SC_MAXIMIZE; }
    "minimize" { $scCmd = $SC_MINIMIZE; }
    "restore"  { $scCmd = $SC_RESTORE;  }
    default    { $scCmd = $SC_MAXIMIZE; }
}

# Get processes list
$psArray = [System.Diagnostics.Process]::GetProcesses()

# Search SendMessage tartget 
foreach ($ps in $psArray) {
    $hWnd = $ps.MainWindowHandle.ToInt32()
    $name = $ps.Name
  
    if ( $name -eq $procName ) {
        $retval = $Win32::SendMessage( $hWnd, $WM_SYSCOMMAND, $scCmd, 0)
	return "($name $hWnd $retval)"
    }
}
return "()"
```

試してみてわかったことは、wslから起動したx11のwindowのwindow handleは各プロセスは持たずvcxsrvプロセスが持っており、最後にフォーカスされたもののhandleを持っている
(ほかのxserverはどうなっているかはわかりませんが)
Emacsにフォーカス当たっている前提でvcxsrvに最大化メッセージを送る
Emacsの起動中に他のx11プロセスを起動することがなければ問題ないはず

# Emacsから呼び出す
非同期でコマンド呼びたいので`start-process`を試したがうまくいかない
とりあえず`shell-command`でうまくいったのでこちらを使う
(プロセス一覧取って探す処理があるので時間がかかってしまうが仕方ない)

ついでに、既存の`toggle-frame-maximized`を上書きしてしまうためにadvice :around

```emacs-lisp:frame-maximize.el
(defvar frame-wsl-powershell-path nil)
(defvar frame-wsl-w32-sys-cmd-path "path\\\\to\\\\w32_wm_syscmd.ps1")
(defvar frame-wsl-wm-name "vcxsrv")

;; Toggle flag. Do not touch this.
(setq frame-wsl-maximize nil)

(defun frame-maximized-wsl (maximize)
  "If MAXIMIZE is not nil, frame maximize, or frame restore
This function works only with the Windows Subsystem for Linux.
And, use powershell script for win32api::SendMessage().

You can customize these variables for your enviroment.
`frame-wsl-powershell-path' Path to powershell.exe. Maybe not need.
`frame-wsl-w32-sys-cmd-path' Path to powershell script (w32_wm_syscmd.ps1).
`frame-wsl-wm-name' Process name (e.g. vcxsrv) to get window handle.
"
  (let ((powershell (or frame-wsl-powershell-path "powershell.exe"))
	(powershell-opt "-ExecutionPolicy RemoteSigned -File")
	(w32-wm-syscmd (or frame-wsl-w32-sys-cmd-path "w32_wm_syscmd.ps1"))
	(w32-wm-syscmd-arg1 (if maximize "maximize" "restore"))
	(w32-wm-syscmd-arg2 (or frame-wsl-wm-name "")))
    (setq frame-wsl-maximize maximize)
    (shell-command
     (format "%s %s %s %s %s"
	     powershell powershell-opt
	     w32-wm-syscmd w32-wm-syscmd-arg1 w32-wm-syscmd-arg2))
    )
  )

(defun toggle-frame-maximized-advice (orig-func &rest args)
  (if frame-wsl-maximize
      (frame-maximized-wsl nil)
      (frame-maximized-wsl t) ) )

;; override toggle-frame-maximized
(advice-add 'toggle-frame-maximized :around 'toggle-frame-maximized-advice)
```

んで、frameの操作（ツールバー消したり、スクロールバー消したり等）終わった最終段階で`(toggle-frame-maximized)`呼んだらいけるはず
少なくとも私の環境ではいけました
# 最後に
もっと簡単な方法があれば教えてください
あと、emacs-lispのきれいな書き方が知りたいです

# 追記
再起動後やスリープからの復帰後に新たにEmacs起動するとうまくいきませんでした
具体的には、タイトルバーの状態的には最大化状態なのにウィンドウサイズが中途半端
`(toggle-frame-maximized)`の**前**に`(set-frame-parameter nil 'fullscreen 'maximized)`するとうまくいきました
どうもvcxsrvが持つ状態によってうまくいかないみたいです
外(Windowsのウィンドウマネージャ)から最大化だけやっても
内(xwindow)の状態に影響受けるみたいです
詳しいことはわかりませんが...