Hatena::ブログ(Diary)

shouhの日記

2016-01-15

Windows でコマンドプロンプトの文字色背景色を部分的に 24bit True Color(16777216色) の RGB で設定できるかを試みた

結論: 不可能です

理由

  • コマンドプロンプトが対応している色機構では計1677万も使えない
    • red や black など色名で指定する感じ。その色名も全部で8種類くらいしかない

試したこと1: python ライブラリ colorama を使う

まずは楽チンなライブラリに頼る。

colorama はマルチプラットフォーム対応の、コンソール画面の文字色や背景色を変えるライブラリ

下記は、インストールの後、試しに背景色を赤色にしてみるサンプル。

$ pip install colorama

$ vim colorama_test.py
# -*- coding: utf-8 -*-

import colorama
colorama.init()

from colorama import Back, Style
print 'Red ' + Back.RED + '    '
print Style.RESET_ALL

$ python colorama_test.py
Red     
    ~~~~ この部分の背景色が赤色になっている

色の変え方はわかった、あとは Back.RED の部分を RGB で指定できれば任務完了である。

……が、指定はできないことがわかった。 colorama が用意しているのは RED や BLACK など、代表的な色だけである。色の数を見ても、数十と無い。True Color には到底及ばない。

というわけで没。

試したこと2: コマンドプロンプトの仕組みを調べた

ライブラリがダメなら、自分で自力で試してみるしかない。というわけで colorama のソースを参考に、コマンドプロンプト上で色を変える仕組みについて調べることにした。

んで、わかったのは ANSI escape code (https://en.wikipedia.org/wiki/ANSI_escape_code) という仕組みがあることだ。これは、「こういうエスケープコードを書いたら、こういう色を使いますという命令になるよ」というもので、Linux は対応している。Windows は対応していない。

え、してないの? じゃあ Windows ではどうやってんの? ……ええ、Windows API を使います。もっというと GetConsoleScreenBufferInfo 関数 。こいつの使い方だが、 windows - RGB Specific Console Text Color C++ - Stack Overflow によると、こうだ。

  • GetConsoleScreenBufferInfo 関数CONSOLE_SCREEN_BUFFER_INFOEX structure を指定するようになってるが、この構造体の中に RGB を指定する部分がある
  • ただし RGB を指定しても、それがそのまま適用されるわけではない
    • Windows の仕様で True Color(1670万色)には対応してない、対応してるのは 16bit (65536色)のみである

ふむ、True Color ダメなのね。

試したこと3: ConEmu(コマンドプロンプトラッパー)

ConEmu は、コマンドプロンプトをラップして使いやすくしてくれるツールで、強化版コマンドプロンプトみたいなものである。

こいつの機能なら、ひょっとしたら True Color に対応してるんじゃないかと思ったが、どうも対応しているのは 設定画面からウィンドウ全体の色を設定するケース のみのようだ。

部分的な色変更は ConEmu でも16色とか256色程度にしかできない ようだ。

おわりに(雑談)

「そもそもそんなことする必要性があるのか」って話ですが、コマンドで動く Color Picker ってのを作ってみたくてですね、んで指定した色のプレビューをコマンド画面上で行えればカッコイイなあと思ってたんですね。でも、それができないことがわかって、意気消沈。残念。

ああ、以下のようにコマンドでガシガシ色を探せるツールを作ってみたかったんだけどなあ。picolo(ピッコロ/PIck a COLOr) という可愛い名前まで考えてたのに。

pickモード

$ picolo
$ ff0000
赤を表示
$ r -16 g 32
色 #ef2000 を表示
$ print
#ef2000
$ print --rgb
239,32,0

原典からカラー名で探す

$ picolo --search orange --scheme schemename1
___ #xxxxxx xxx.xxx.xxx orange
___ #yyyyyy yyy.yyy.yyy hoge orange
$ picolo --search orange --scheme schemename1 --item hex
#xxxxxx
#yyyyyy
$ picolo --search orange --scheme schemename1 --item hex --nosharp
xxxxxx
yyyyyy
$ picolo --search orange --scheme schemename1 --item hex --nosharp --upper
XXXXXX
YYYYYY
$ picolo --search orange --scheme schemename1 --item rgb
xxx.xxx.xxx
yyy.yyy.yyy
$ picolo --search orange --scheme schemename1 --item rgb --item name
xxx.xxx.xxx orange
yyy.yyy.yyy hoge orange
$ picolo --search orange --scheme schemename1 --item rgb --comma
xxx,xxx,xxx
yyy,yyy,yyy
$ picolo --search orange --scheme schemename1 --item rgb --delim ", "
xxx, xxx, xxx
yyy, yyy, yyy

原典の作成

$ vim schemename1.txt
# comment line
# comment line
name1	#xxxxxx	xxx.xxx.xxx
name2	#yyyyyy	yyy.yyy.yyy
...

ランダムモード

# picolo --random --hex
c4d61f

このツール案考えた時は絶対 Github で流行るぜって思ったけど、私でも思い浮かぶものを他人が思い浮かんでないはずがないんだよね。実際こういうツールが無いのは、技術的に作るのが不可能だったからなんだ。だからこの手のツールは決まって GUI なのである。

うん、勉強になりました。

2016-01-08

Windows の Window Shade(ウィンドウシェード)に関する覚え書き

本記事はウィンドウシェードについての雑多なメモである。概要説明からプログラミング的な実装の話まで、内容は多岐。

ウィンドウシェードとは

ウィンドウをタイトルバーサイズに折りたたむ機能のこと。元々は Mac OS の機能だが、これが便利ということで、Windows 上で実現するフリーソフトが登場した。

Windows Shade ではなく Window Blinds(ブラインド) と書く場合もある。が、日本語でウィンドウブラインドと書いてる人は見たことがない。

ウィンドウシェードなフリーソフト

たとえば以下がある。

他にも探せば色々あると思う。

ウィンドウシェードを実装するには

WindowsAPI の以下を組み合わせる。

  • GetWindowRect() でウィンドウのウィンドウサイズを取得
  • GetSystemMetrics() でウィンドウのタイトルバーの高さを取得
  • MoveWindow() や SetWindowPos() でウィンドウのウィンドウサイズを、Xサイズは「元々のXサイズ」、Yサイズは「タイトルバーの高さ」に変更する

あとはシェードしたウィンドウの元のYサイズを覚えておいてもう一度シェードしたら元に戻すとか、タイトルバーをほげほげしたらシェードを実行するとか、その辺 UI を整備してやればよい。

ウィンドウシェードを行う契機となる操作、の選択肢

タイトルバーに対して何らかの操作を行わせるケースが多い。

右と左については、既に Windows に割り当てられてる働きを無効にしないといけないので面倒くさい(フックという処理が必要らしい。詳しいことは知らない)。中クリックについては、Windows 規定の働きはないため比較的楽。

あ、そうそう、Shiftキーを押しながら左クリック、といった修飾キーと組み合わせるやり方もある。

ウィンドウシェードについて検索したい時に

Windows」という言葉を付けること。でないと、現実世界の窓がたくさんヒットして邪魔。

シェードできないウィンドウがある

たとえば Windows7エクスプローラ(フォルダ)ウィンドウ。なぜか知らないが、こいつはウィンドウ最小サイズが 161 x 243 になっている。MoveWindow() 等を使ってもこれ以上小さくはできない。

……このように、なぜかウィンドウサイズ制限のあるウィンドウがあり、シェードできないことがある。

対処方法は不明。

蛇足: エクスプローラウィンドウをシェードできない件

エクスプローラウィンドウについては、かざぐるマウスや Winroll ではきちんとシェードできる。私が自作したシェードツール(MoveWindow()を使っている) ではシェードできない。

原因は不明。

ググっても何も出てこないので、Stackoverflow で質問してみた。

winapi - Why the lower-limit window size of an explorer(folder) window is 161 x 243 in Windows7? - Stack Overflow
http://stackoverflow.com/questions/34632064/why-the-lower-limit-window-size-of-an-explorerfolder-window-is-161-x-243-in-wi

英語力も表現力もクソなので vote down(お前の質問マジうんこ) されてるけど。

それから WinRoll のソースも見てみた。asmファイル(アセンブラ)なのでちょっと読みづらいが、WindowsAPI の関数名はそのまま書いてあるので何とか読める。

winroll-2.0\winrolldll.asm の WR_Rollup 関数っぽい。使ってるのは…… MoveWindow() と SetWindowPlacement() くらい。SetWindowPlacement() は表示状態(最小化最大化等)の制御なんで関係無いわけで、私と同じく MoveWindow() ってことになるんだよな。

なのになぜ WinRoll ではシェードできて、私のでは出来ないのだろう。解せぬ。

2013-11-22

デバイス名(\Device\HarddiskVolume1)をドライブレター(C:\)に変換する

プロセスのフルパスを取得する際、64bitアプリには GetModuleFileNameEx が効かない。代わりに GetProcessImageFileName を使えばいいのだけど、これで取得したフルパスはドライブレター部分が変な文字列になっている。

こんなの。デバイス名と呼べばいいのだろうか。

\Device\HarddiskVolume1


これをドライブレターに変換しようという話。

有難いことに細かい手順をまとめてくださってるブログ 生産がす: 日記ちゃんGetProcessImageFileName を発見。ここを参考に手順をまとめてみると、

  1. 「ドライブレター」の一覧を取得
  2. 「あるドライブレターに対応するデバイス名」の一覧を取得
  3. デバイス名→ドライブレターに変換するテーブルを作る
  4. GetProcessImageFileName で取得したパスに対して、各デバイス名が含まれるかどうかを探し、含まれていれば、対応するドライブレターに置換する

こんな感じだろうか。

とりあえずテーブルを作るところまではコードを書いた。pywin32を使用。

import win32api
import win32file

ldstrings = win32api.GetLogicalDriveStrings().split("\\0")[0]
drivelist = [elm.strip("\\") for elm in ldstrings.split("\0")]
drivelist.remove("")
print drivelist

devicelist = [
    win32file.QueryDosDevice(elm).split("\0\0")[0]
    for elm in drivelist
]
print devicelist

device2driveletter = {}
for i in range(len(drivelist)):
    device2driveletter[devicelist[i]] = drivelist[i]

print device2driveletter

いくつか補足

  • あちこちで split を使っているのは、取得される値の区切り文字がヌル文字だったりヌル文字二つだったりするから
  • split の戻り値はリストなので、文字列を得るために 0 番目の要素を取り出すという処理も書いている
  • この書き方だと drivelist の末尾に空文字列の要素ができちゃうから remove で削除している
    • 末尾削除なら pop() で良いのだけれど, 末尾以外に空文字列要素が入るかなあという漠然とした不安から remove() にしている

手元の環境では問題無く動いた。他の環境で動くかどうかがちょっと心配。

2013-11-02

WindowsAPI のエラーの詳細を調べる

API の中にはエラーの詳細を教えてくれないものもあります。たとえば TrackPopupMenu 関数で TPM_RETURNCMD を指定した場合は、エラーが起きても戻り値 0 が返ってくるだけです。

こういう時に GetLastError 関数を使うと、直前に実行された*1 API の詳細なエラー(のコード)がわかるそうです。

pywin32 の場合はたとえばこんな感じで。

import win32api
…
selected_id = win32gui.TrackPopupMenu(\
    menuhandle,\
    win32con.TPM_RETURNCMD, \
    posx, \
    posy, \
    0, \
    self._hwnd, \
    None \
)
if selected_id==0:
    print "canceled or failed."
    print "GetLastError:" + str(win32api.GetLastError())
…

こうすると、たとえばエラーコード 1401 が得られます。あとはこのコードの意味を調べるだけです。ググっても良いですが、Win32エラーコード一覧が便利です。

これによると 1401 は「メニュー ハンドルが無効です。」だそうです。無効なメニューハンドルを指定しているとか、指定したメニューハンドルが指すメニューを先に解放しちゃっているとか、そういった原因が考えられます。

*1:というと語弊があるので詳細な意味はMSDNを参照してください。抜粋すると「呼び出し側のスレッドが持つ最新のエラーコードを取得します。エラーコードは、スレッドごとに保持されるため、複数のスレッドが互いの最新のエラーコードを上書きすることはありません。」そうです。 via http://msdn.microsoft.com/ja-jp/library/cc428944.aspx

2013-10-26

キー入力を検知する

方法を二つほど見つけた。

GetAsyncKeyState

GetAsyncKeyState:
http://msdn.microsoft.com/ja-jp/library/cc364583.aspx

指定したキーが実行時に押されているかどうかを判定できる。実際に使う際は無限ループの中で何度もこれを呼び出す形になると思う。

これさえあれば殆どのキー入力を検知できる。作り込みは必要だが「修飾キー + 文字キー」という複合的な検知や「Shift キー連打」の検知なんてことも可能。

ただしキーに割り当てられた既存の働きを無効にすることができない。たとえば GetAsyncKeyState を使って Alt + F4 が押されたら何かするアプリを作ったとしても、Alt + F4 の既存の働きである「ウィンドウを閉じる」動作は働いてしまう。

RegisterHotKey

RegisterHotKey:
http://msdn.microsoft.com/ja-jp/library/cc411006.aspx

指定したキーが押されたら WM_HOTKEY を発行してくれる。GetAsyncKeyState とは違い、既存の働きは無効にしてくれるみたい。

「ホットキー」とは、特定のキーとウィンドウを登録すると、どのウィンドウにフォーカスがあるのかは関係なく、そのキーの入力がキャンセルされ、代わりに登録したウィンドウへとWM_HOTKEYが送られてくるというものです。

http://www.kab-studio.biz/Programing/Codian/MFCTips/13.html

もうひとつの特徴として、これがイベントドリブンであることが挙げられる。無限ループから何度も呼び出すんじゃなくて「とにかくホットキーが押されたら WM_HOTKEY を投げてやるから、あんたは WM_HOTKEY を受け取る口を作って、その先の処理を書いとけ」というスタンス。ただ、そのためにはウィンドウを作って、ウィンドウメッセージを受け取って、といった仕組み(いわゆる Windows プログラミング)が必要になる。GetAsyncKeyState みたいに API 一つだけ呼べば良いってわけじゃない。

最後に複雑な検知について。まずは複合的な検知については楽できる。パラメータとして修飾キーを指定できるので ctrl + alt + hoge みたいな検知は楽。問題は連打。RegisterHotKey を使うとホットキーとして割り当てられたキーの既存の働きが無効になるため、連打が完了していない段階は既存の働きで、なんてことができない。Shift キーの連打を実装するなら、Shift を一回押しただけでは Shift 本来の働きをしてほしいものだが、そうはならない。Shift キー自体が使えなくなる。

まとめ

GetAsyncKeyState

  • 殆どのキーを検出できる
  • 無限ループ中から呼び出す必要アリ
  • 複合的な検知や連打の検知には作り込みが必要
  • 既存の働きは無効にできない

RegisterHotKey

  • イベントドリブンで使える
  • キーの既存の働きを無効にできる
  • 複合的な検知は楽。連打は難しいかも
  • ウィンドウメッセージを受け取る仕組みの実装(Windows プログラミング)が必要