読者です 読者をやめる 読者になる 読者になる

blechmusikの日記

キー・カスタマイズ・ソフトウェア "DvorakJ" の覚え書きをはじめとして様々なことを書いています。

環境変数を一時的に設定後プログラムを起動するためのスクリプト

AutoHotkey

msys2 の bash環境変数を気軽に編集できるようにしたいと考え、標記の AutoHotkeyスクリプトを書いた。
以前の msys2 の Emacs 向けスクリプトを改変したものである

が、コンソールへのドラッグ・アンド・ドロップにまだ対応できていない……とはいえ、後述するようにファイル名をもとに挙動を自動的に変えるようにしたので、スクリプトのファイルをコピーし startup_config.ini に起動するプログラムのパスを記述すればよくなった。これでかなり使いやすくなったと思う。

startup_config.ini の記述例は

[Local Variable]
    msys64_directory = C:\msys64
    username = testtest
    start_menu = C:\Users\%username%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs
    
    msys2 = %start_menu%\MSYS2 64bit\MSYS2 Shell.lnk
    Emacs = %msys64_directory%\usr\local\bin\runemacs.exe

[Process Variable]
    msys64_directory = C:\msys64
    
    HOME = %msys64_directory%\home\%USERNAME%

    PATH = %msys64_directory%\usr\bin;%PATH%;
    PATH = %msys64_directory%\usr\local\bin;%PATH%;
    PATH = %msys64_directory%\opt\bin;%PATH%;
    PATH = %msys64_directory%\mingw64\bin;%PATH%;
    PATH = %msys64_directory%\usr\bin\site_perl;%PATH%;
    PATH = %msys64_directory%\usr\bin\vendor_perl;%PATH%;
    PATH = %msys64_directory%\usr\bin\core_perl;%PATH%;

; ========================================
; msys2 で PATHを直接上書きすることができないため
; MYPATH に PATH の内容をすべて書き出しておく
MYPATH = %PATH%

;.bashrc には 
;showMYPATH () {
;  echo $MYPATH | perl -pe 's/;/\n/g; s/^\s*$//g; s/(.):/\\$1/g; s/\\/\//g;  s/\/\n/\n/g;s/\n/:/g'
;}
;export PATH=`showMYPATH`
;と書く

;source ~/.bashrc
;を実行する必要はない

autohotkeyスクリプト名は startup_run***.ahk (startup_run***.exe) にすること。

  • startup_runEmacs.exe の場合 *** の箇所は "Emacs" であり、前述の startup_config.ini の [Local Variable] セクションのキー "Emacs" の値(パス)をもとに、プログラムを実行する。
  • また、"Emacs" というテキストをもとに "ahk_class Emacs" というコマンドを組み立て、それによって情報を取得できるプログラムが実行中かを判定し、またドラッグ・アンド・ドロップを試みる。
  • 上述の startup_config.ini を使うと、startup_runEmacs.exe で runemacs.exe を実行でき、そして startup_runmsys2.exe でスタート・メニュー内の MSYS2 Shell のリンクを呼び出すことができる。
/*
-----環境変数を一時的に設定後プログラムを起動するためのスクリプト---

startup_run***.ahk (startup_run***.exe) によって
任意の環境変数を一時的に設定し、その状態でプログラムを起動する

ファイル名中の***の文字列は
後述する startup_config.ini の起動プログラムの指定時に利用される


プログラムが起動してないときには起動、起動してる時はアクティブ化、ファイルを渡すこともできる。
AutoHotKey_Lw / Ahk2Exe_Lで動作

[1] コンパイルして適当な場所に置く
msys2と同一のフォルダ内に置く必要は無い

[2] iniファイルを作成する
このプログラムと同じ場所に startup_config.iniを作成する

そして、セクション名は "Local Variable" とし
*** キーにプログラムの起動パスを記述する

[Local Variable]
    *** = <起動するプログラムのパス>

この<起動するプログラムのパス>は変数を利用した表記方法も可能だ。
たとえば "emacs" をこのように設定すると

[Local Variable]
    msys64_directory = C:\msys64
    emacs = %msys64_directory%\usr\local\bin\runemacs.exe


実際に展開されるのは
    emacs = C:\msys64\usr\local\bin\runemacs.exe
である


また、HOME や PATH のような環境変数を上書きしたうえで
任意のプログラムを起動することもできる。
"Process Variable" セクションに書く。

[Process Variable]
    msys64_directory = C:\msys64
    
    HOME = %msys64_directory%\home\%USERNAME%

    PATH = %msys64_directory%\usr\bin;%PATH%;
    PATH = %msys64_directory%\usr\local\bin;%PATH%;
    PATH = %msys64_directory%\opt\bin;%PATH%;
    PATH = %msys64_directory%\mingw64\bin;%PATH%;
    PATH = %msys64_directory%\usr\bin\site_perl;%PATH%;
    PATH = %msys64_directory%\usr\bin\vendor_perl;%PATH%;
    PATH = %msys64_directory%\usr\bin\core_perl;%PATH%;



[3] 起動する
ファイルを送れば開く(複数ファイルの編集可)。
ショートカット作成や関連付けに使う。


以下のページを参考にしました。
[Windows]gnupackのemacsclientw.exeを快適に使えるようにする。 - Qiita
http://qiita.com/acple@github/items/4be7eb774a28d574281e
----------------------------------------------------------------
*/

#NoEnv
SendMode Input
SetWorkingDir %A_ScriptDir%
#SingleInstance force

DetectHiddenWindows,On


;; iniファイル名を指定
ini_file := "startup_config.ini"

; 起動するプログラムのパスを記すセクション名
ini_local_section := "Local Variable"
; 一時的に環境変数を設定するセクション名
ini_process_section := "Process Variable"


;; =============================================================

;; 自分自身のファイル名から
;; 起動すべきプログラムの手がかりとなる文字列を取得
target_text := getTextfromFileName(A_ScriptName)

;; iniファイルをもとに、一時的に環境変数を設定する
updateEnv(storeVariables(readIniVariables(ini_file, ini_process_section)))

obj := storeVariables(readIniVariables(ini_file, ini_local_section))

;; プログラムを起動する(既に起動していない場合)
;  既に起動しているならば、フォアグラウンドに移す
ret := startProgram(find_item(obj, target_text), target_text)

; 起動後すぐに drag and drop をすると
; ファイルの編集に失敗することがあったので
; 一定時間をあけておく
if (TRUE == ret )
{
    sleep, 500
}

;; 引数からファイルの情報を取得する
files := GetArgs()

;; ファイルを個別にドラッグ・アンド・ドロップ
drag_and_drop_files_to_program(files, target_text)

ExitApp


; ========================================
; join(obj)
; {
;     s:= ""
;     For k, v in obj
;         s .= k "=" v "`n"
;     return s
; }

getTextfromFileName(Target)
{
    RegExMatch(Target, "startup_run(.+)(?:\.ahk|\.exe)$", $)
    return $1
}


find_item(obj, key)
{
    if ({} == obj)
    {
        return ""
    }

    ret := get_next_index(obj, key)
    if (FALSE == ret)
    {
        return ""
    }

    return obj[ret]
}


get_next_index(obj, key)
{
    for i, v in obj
    {
        if (key == v)
        {
            return i + 1
        }
    }

    return False
}

storeVariables(lines)
{
    obj := {}

    Loop, Parse, lines, `n, `r
    {
        Try
        {
            ; "<left hand side (lhs)> = <right hand side (rhs)>"
            ret :=  RegExMatch(Trim(A_LoopField), "^(.+?)=(.*)$", $)

            ; 行頭に = があるときか
            ; 行中に = がないとき
            if (0 == ret)
            {
             Throw "不正な設定です`n"
                       . "設定値:" . Trim(A_LoopField)
            }
    
            lhs := Trim($1)
            rhs := expand_variables(Trim($2), obj)
    
            ; キーと値がまだ設定されていなければ
            ; 登録
            next_index := get_next_index(obj, lhs)
            if (FALSE == next_index)
            {
                obj.push(lhs)
                obj.push(rhs)

                continue
            }

            obj[next_index] := rhs
        }
        Catch e
        {
            MsgBox, % "環境変数の一時的な設定のエラー`n"
                     . e . "`n"
                     . "プログラムを続行します"
        }
    }


    return obj
}

expand_variables(string, obj)
{
    result := ""
    Loop, Parse, string, `%
    {
        ; A_Index が偶数の場合に、その文字列をもとに環境変数を取得
        ; 奇数の場合には処理中断
        if (1==Mod(A_Index, 2))
        {
            result .= A_LoopField
            continue
        }


        ; 変数の値を明示的に設定していない場合
        next_index := get_next_index(obj, A_LoopField)
        if (FALSE == next_index)
        {
			; 環境変数の値を利用する
            result .= getEnv(A_LoopField)
            continue
        }
        
        result .= find_item(obj, A_LoopField)
    }

    return result
}

readIniVariables(ini_file, ini_section)
{
    Try
    {
        If not (FileExist(ini_file))
        {
             Throw "ini_file のパスが見つかりません`n"
                       . "設定値:" . ini_file
        }
    
        IniRead, Variables, %ini_file%, %ini_section%
    }
    Catch e
    {
        MsgBox,% "ファイル指定のエラー`n"
                 . e . "`n"
                 . "プログラムを終了します"
        ExitApp
    }

    return Variables
}

updateEnv(obj)
{
    for i, v in obj
    {
        ; A_Index が偶数の場合に、その文字列をもとに環境変数を取得
        ; 偶数の場合には処理中断
        if (0==Mod(A_Index, 2))
        {
            continue
        }

        key := obj[i]
        value := obj[i + 1]
        EnvSet, %key%, %value%
    }
}

getEnv(string)
{
    EnvGet, OutputVar, %string%
    return OutputVar
}



GetArgs()
{
    global

    _ArgCount=%0%
    _Args := {}

    Loop, %_ArgCount%
    {
      _Args.push(%A_Index%)
    }

    return _Args
}


; ========================================

startProgram(path, target_text)
{
        Run, % path
        return True

    hwnd := WinExist("ahk_class " . target_text)
    
    ; プログラム が既に起動している場合
    if (hwnd)
    {
        ; プログラムのアクティブ化
        WinActivate, ahk_id %hwnd%
        return False
    }

    Try
    {
        if not (FileExist(path))
        {
             Throw "不正確なパスです`n"
                       . "設定値:" . path
        }

        Run, % path
        return True
    }
    Catch e
    {
        MsgBox, % "実行プログラムが見つかりません`n"
                 . e . "`n"
                 . "プログラムを終了します"
        ExitApp
    }

}

drag_and_drop_files_to_program(files, target_text)
{
    Try
    {
        Loop, % files.length()
        {
            the_file := files[A_Index]

            ; 複数ファイルの存在を確認する
            if not (FileExist(the_file))
            {
                 Throw "編集対象のファイルが見つかりません`n"
                        . "ファイル名:" . the_file
            }

            ; ファイルをプログラムに一つだけドロップする
            PostMessage, 0x233, HDrop(the_file), 0,, ahk_class %target_text%
            sleep, 100
        }

        return
    }
    Catch e
    {
        MsgBox, % "ファイル指定のエラー`n"
                 . e . "`n"
                 . "プログラムを終了します"
        ExitApp
    }
}

; ========================================

; My Scripts
; http://lukewarm.s101.xrea.com/myscripts/
; ファイルドロップ関数
;     指定ウィンドウに改行区切りで指定されたファイルをドロップする 
; #17 [Make AHK drop files into other applications: post #17] - Posted 19 March 2014 - 07:54 AM
; http://autohotkey.com/board/topic/41467-make-ahk-drop-files-into-other-applications/?p=638376

; #Include DragDrop.ahk

HDrop(fnames,x=0,y=0) 
{
    characterSize := 2
    fns:=RegExReplace(fnames,"\n$")
    fns:=RegExReplace(fns,"^\n")
    hDrop:=DllCall("GlobalAlloc"
            ,"UInt",0x42
            ,"UInt",20+(StrLen(fns)*characterSize)+characterSize*2)
    p:=DllCall("GlobalLock","UInt",hDrop)
    NumPut(20, p+0)  ;offset
    NumPut(x,  p+4)  ;pt.x
    NumPut(y,  p+8)  ;pt.y
    NumPut(0,  p+12) ;fNC
    NumPut(1,  p+16) ;fWide
   
    p2:=p+20
    Loop,Parse,fns,`n,`r
    {
       DllCall("RtlMoveMemory"
               ,"UInt",p2
               ,"Str",A_LoopField
               ,"UInt",StrLen(A_LoopField)*characterSize)
       p2+=StrLen(A_LoopField)*characterSize + characterSize
    }
    DllCall("GlobalUnlock","UInt",hDrop)
    Return hDrop
}