LoginSignup
3
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-01-13

概要

WSL環境(xserverにはvcxsrvを使用)でEmacsを最大化する方法見つけるまでのログ

問題点

最大化起動のために(set-frame-parameter nil 'fullscreen 'maximized)しているが位置がおかしい
emacs-posisin.png
frame位置を(0,0)に設定してもおかしい
emacs-position0.png

ウィンドウのタイトルバーなどはシステム(window manager)が描いているのか?(waylandでそんなんをやった気がするな..)
そもそも、"最大化"や"元のサイズに戻す"ボタンをクリックしてもframe-parameterfullscreenに変化なし
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_syscommandsc_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以上)

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

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)の状態に影響受けるみたいです
詳しいことはわかりませんが...

3
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2