[Python] 創作環境を整える ~Windows Terminal タブ管理~(startup.py 編)」

はじめに

ブログ作成をはじめて以降、git コマンドを日常的に叩くようになった事に踏まえ、アプリ開発で複数のプロジェクトを切り替えるのに、powershellやコマンドプロンプトを複数起動するのが日課になりました。

VScodeからもターミナルは起動できますが、コマンドプロンプト 及び powershellを起動時にまとめてタブ化する方法と、Pythonで普段よく使うアプリを一括起動するスタートアップスクリプトの紹介です。

課題と背景

1. プロジェクトごとに作業フォルダが異なる

コマンドでフォルダ移動するより、タブで切り替える方がウインドウに役割分担させ分かりやすい。

2. 起動後に毎回 cd や git status を叩くのが面倒

ターミナル起動時に一連のコマンドを毎回入力するのが面倒なので、その作業を楽にする。

3. フォルダやバッチを毎回手動でクリックしていた…

プログラミングや、ブログ記事作成、画像生成AI、動画生成AIなど、特定の作業をする際に立ち上げるアプリが決まっている為、 ショートカットをフォルダにまとめて手作業で起動してましたが、Pythonで一括起動できると、ワンクリックで完結

4. ウインドウを配置がバラバラ

特定のアプリは決まった場所に配置する事が多い為、ウインドウ座標を設定し、移動の手間を軽減。

5. まとめ

これらをスクリプトで一括管理する事で、PC起動・再起動時の負担を軽減。

AutoIT

昔、AutoITでスタートアップスクリプトを自作してました。

Windows7や10の頃に作ったもので、Windows11 環境や新しいアプリに対応できなくなった為、今回はPyhonで作り直します。

startup.py の導入

実は、以前にスタートアップスクリプトは作成済みです。

このスクリプトを配置したフォルダ内にある、ショートカットをすべて起動し、APPSで設定したウインドウに関しては、自動で移動とリサイズを行う仕組みです。 スクリプト自体を起動すると無限ループしてしまうので、拡張子をみて起動制御を行っています。(→valid_extensions)

その他詳細は、関数名やデバック用ログ出力などで、凡その処理内容は想像がつくと思います。

import os
import time
import win32gui
import win32con

# 移動対象のみ定義(起動対象はすべて自動で処理)
APPS = [
    {"title": "Visual Studio Code", "x": 229, "y": 0, "width": 1371, "height": 1158},
    {"title": " - Cursor", "x": -1313, "y": -1, "width": 1314, "height": 1158},
]

# オープン:ファイル・フォルダー・ターミナル
def open_all_files(folder):
    valid_extensions = ('.bat', '.lnk', '.exe')
    for file in os.listdir(folder):
        full_path = os.path.join(folder, file)
        if os.path.isfile(full_path) and file.lower().endswith(valid_extensions):
            print(f"▶ 一括起動中: {file}")
            try:
                os.startfile(full_path)
                time.sleep(0.5)  # 連続起動防止
            except Exception as e:
                print(f"❌ 起動失敗: {e}")

# ウインドウ移動
def move_window(title, x, y, width, height):
    def enum_handler(hwnd, _):
        window_title = win32gui.GetWindowText(hwnd)
        if title.lower() in window_title.lower():
            print(f"✅ 移動対象: {window_title}")
            win32gui.MoveWindow(hwnd, x, y, width, height, True)
    win32gui.EnumWindows(enum_handler, None)

# 指定秒数待機し、特定アプリを移動・リサイズ
def wait_and_move(apps, delay=7):
    print(f"⏳ アプリの起動を {delay} 秒待機中...")
    time.sleep(delay)
    for app in apps:
        move_window(app["title"], app["x"], app["y"], app["width"], app["height"])

if __name__ == "__main__":
    folder = os.path.dirname(os.path.abspath(__file__))
    open_all_files(folder)      # ← すべての起動対象を一括処理
    wait_and_move(APPS, 7)      # ← 移動対象は限定的に

機能追加

現状の startup.py の追加案・改良ポイントとして「PowerShell/コマンドプロンプト」を複数起動する際に、個別にウインドウを作成せず、 まとめてタブ分け対応し、尚且つ、PowerShellとコマンドプロンプトのタブを分けたい。

解決案

この点については、Windows Terminal のタブ管理機能を使えば、かなり柔軟な制御が可能です。

wt.exe でタブごとに分ける

Windows Terminal(wt)は以下のようにタブごとにアプリを分けて起動できます:

wt `
  -w 0 nt -p "PowerShell" -d . powershell.exe ; `
  nt -p "Command Prompt" -d . cmd.exe

Pythonでの起動例

以下のようなコマンドを os.system() で実行すると、最初から「PowerShellタブ」と「cmdタブ」に分けられます:

import os

# 複数のタブを同時に開く(PowerShellとCMDを分ける)
os.system(
    'wt -w 0 nt -p "PowerShell" -d . powershell.exe ; '
    'nt -p "Command Prompt" -d . cmd.exe'
)

応用:既存の startup.py に組み込む

以下のようにすれば、ショートカット類とは別に「PowerShellタブ」と「cmdタブ」を追加で開くことが可能。

def open_terminal_tabs():
    try:
        os.system(
            'wt -w 0 nt -p "PowerShell" -d . powershell.exe ; '
            'nt -p "Command Prompt" -d . cmd.exe'
        )
        print("✅ Windows Terminal タブ起動完了")
    except Exception as e:
        print(f"❌ Windows Terminal 起動失敗: {e}")

そして main 関数で:

if __name__ == "__main__":
    folder = os.path.dirname(os.path.abspath(__file__))
    open_all_files(folder)
    open_terminal_tabs()  # ← 新たにタブで端末を開く
    wait_and_move(APPS, 7)

プロファイルの確認方法

プロファイル名 “PowerShell” や “Command Prompt” は環境によって異なる場合があるので、正確に知りたい場合は以下で確認可能です:

wt --list

🔚 まとめ

  • wt コマンドでタブごとの起動が可能。
  • Python から os.system() で柔軟に制御可能。
  • 今後の拡張(Git Bash や WSL 起動など)にも対応しやすい。
  • startup.py の一部として非常に自然に統合可能。

subprocess.Popen

os.system以外に、subprocess.Popenを使う方法

import subprocess

subprocess.Popen([
    "wt",
    # 🔸コマンドプロンプト(バッチ)
    "nt", "--title", "FramePack", "-d", "C:\\AI\\framepack_cu126_torch26", "cmd", "/k", "run_endframe_ichi.bat",
    ";", "nt", "--title", "WebUI", "-d", "C:\\AI\\stable-diffusion-webui", "cmd", "/k", "webui-user.bat",

    # 🔹PowerShell タブ
    ";", "nt", "--title", "my-blog", "-d", "C:\\Users\\user-name\\my-blog", "powershell",
    ";", "nt", "--title", "matching-game", "-d", "C:\\Users\\user-name\\matching-game", "powershell",
    ";", "nt", "--title", "project-stg01", "-d", "C:\\Users\\user-name\\my-blog\\cursor\\project-stg01", "powershell"
])

補足

  • “cmd”, “/k”, “バッチ名.bat” の形式で記述しないと bat が無視されます。
  • –title は表示用のタブ名で、実行内容に影響しません。
  • wt コマンドは Windows Terminal が必要です
  • wt は Windows Terminal の実行ファイル
  • パスやファイル名に全角文字が混ざっていると失敗ケースがあるので注意
  • 各タブで -d によって起動パスを指定しているため、バッチ実行やスクリプト編集にすぐ移れます

完成品

フォルダ内にbatファイル配置してまとめて起動->タブでグループ化を目指しましたが難しいようなので、今回は、スクリプト内にパスを設定る方法で回避しました。

まだ改良の余地があるので修正バージョンを再度公開するかもしれません。

import os
import time
import subprocess
import win32gui
import win32con

# ウィンドウの移動対象リスト
APPS = [
    {"title": "Visual Studio Code", "x": 229, "y": 0, "width": 1371, "height": 1158},
    {"title": " - Cursor", "x": -1313, "y": -1, "width": 1314, "height": 1158},
]

# ✅ Windows Terminal 一括タブ起動関数
def launch_terminal_tabs_fixed():
    subprocess.Popen([
        "wt",
        # 🔸コマンドプロンプト(バッチ)
             "nt", "--title", "FramePack",     "-d", "C:\\AI\\framepack_cu126_torch26", "cmd", "/k", "run_endframe_ichi.bat",
        ";", "nt", "--title", "WebUI",         "-d", "C:\\AI\\stable-diffusion-webui", "cmd", "/k", "webui-user.bat",

        # 🔹PowerShell タブ
        ";", "nt", "--title", "my-blog",       "-d", "C:\\Users\\fixjp\\my-blog", "powershell",
        ";", "nt", "--title", "matching-game", "-d", "C:\\Users\\fixjp\\matching-game", "powershell",
        ";", "nt", "--title", "project-stg01", "-d", "C:\\Users\\fixjp\\my-blog\\cursor\\project-stg01", "powershell"
    ])

def open_all_files(folder):
    valid_extensions = ('.bat', '.lnk', '.exe')
    for file in os.listdir(folder):
        full_path = os.path.join(folder, file)
        if os.path.isfile(full_path) and file.lower().endswith(valid_extensions):
            print(f"▶ 一括起動中: {file}")
            try:
                os.startfile(full_path)
                time.sleep(0.5)  # 連続起動防止
            except Exception as e:
                print(f"❌ 起動失敗: {e}")

def move_window(title, x, y, width, height):
    def enum_handler(hwnd, _):
        window_title = win32gui.GetWindowText(hwnd)
        if title.lower() in window_title.lower():
            print(f"✅ 移動対象: {window_title}")
            win32gui.MoveWindow(hwnd, x, y, width, height, True)
    win32gui.EnumWindows(enum_handler, None)

def wait_and_move(apps, delay=7):
    print(f"⏳ アプリの起動を {delay} 秒待機中...")
    time.sleep(delay)
    for app in apps:
        move_window(app["title"], app["x"], app["y"], app["width"], app["height"])

if __name__ == "__main__":
    folder = os.path.dirname(os.path.abspath(__file__))
    launch_terminal_tabs_fixed()         # ← PowerShell/CMD を一括タブ起動
    open_all_files(folder)               # ← .batや.exeなどを追加で起動(オプション)
    wait_and_move(APPS, 7)               # ← ウィンドウ位置を整理

免責

スクリプトの利用は、自由にしていただいて構いませんが、トラブルが発生された場合、責任は持てませんので自己責任でお願いいたします。