概要
WSL環境(xserverにはvcxsrvを使用)でEmacsを最大化する方法見つけるまでのログ
問題点
最大化起動のために(set-frame-parameter nil 'fullscreen 'maximized)
しているが位置がおかしい
frame位置を(0,0)に設定してもおかしい
ウィンドウのタイトルバーなどはシステム(window manager)が描いているのか?(waylandでそんなんをやった気がするな..)
そもそも、"最大化"や"元のサイズに戻す"ボタンをクリックしてもframe-parameter
のfullscreen
に変化なし
xのwindow managerではないのでEmacsへ最大化のイベント飛んでいない?サイズの設定だけされていると予想
(vcxsrvが悪いのか?)
Emacsで用意されている最大化の方法を色々試す
xwindow使っているので
これを試してみた
(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スクリプトを簡単実行
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以上)
# 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
(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)の状態に影響受けるみたいです
詳しいことはわかりませんが...