Ctrl + ALT + S でヘルプが起動するなんて、なんて素敵なことでしょう

三度の飯よりショートカット

朧の森に、世界で最もホットキーを愛する裸族が棲むという

HP のデスクトップパソコンで、「Ctrl + ALT + S」から「HP サポート情報」が起動するので、快適で死にそうだ。Windows 9x 時代に DOS 窓から Ctrl + P で出来もしない印刷が「失敗しました」と言われ続けた快適ぶりを思い出す。

Emacs ユーザにとって、Ctrl + P、Ctrl + Alt + S は、ともに頻繁に使うキーバインドである。

愛してやまないので Ctrl + Alt + S は別の手段で

Emacs ユーザは ALT が使えない状況には慣れている。

  1. ESC が常に代用になる – 遠くてイヤなので多用はしない
  2. Ctrl + 【[】 が ESC の代わりになる – 常用しても良いほど快適

2つめの方法が普段は良いが、screencast などでは悩むことになる、ただそれだけが難点。

Emacsユーザばかりがホットキーを愛するのかと思いきや

This is a pain in the ass, because 【Ctrl+Alt+s】 is used in Second Life virtual world software, Blender 3D Modeling Software, Emacs, all i use daily. All these 3 are major software, with extensive keyboard shortcuts.

Blenderユーザもホットキーを愛してやまないようである。pain in the ass とは最高の褒め言葉である。

大好き過ぎるのでお別れするのは切ないけれど

泣く泣くお別れする方法は以下にも書いてある:

残念だが、「ショートカット」の「ホットキー」設定を外さなければならない。

Pythonを使ってお別れするのはもっと残念だ

30分ほどででっちあげた以下でお別れするのは悲しくて死にそうだ:

editlnk_rm_hotkey.py
 1 # -*- coding: utf-8 -*-
 2 from ctypes import Structure
 3 import ctypes
 4 
 5 
 6 class GUID(Structure):
 7     _fields_ = [
 8         ("data1", ctypes.c_uint32),
 9         ("data2", ctypes.c_uint16),
10         ("data3", ctypes.c_uint16),
11         ("data4_1", ctypes.c_uint8),
12         ("data4_2", ctypes.c_uint8),
13         ("data4_3", ctypes.c_uint8),
14         ("data4_4", ctypes.c_uint8),
15         ("data4_5", ctypes.c_uint8),
16         ("data4_6", ctypes.c_uint8),
17         ("data4_7", ctypes.c_uint8),
18         ("data4_8", ctypes.c_uint8),
19         ]
20 
21     def __str__(self):
22         s = []
23         s.append("%08x" % self.data1)
24         s.append("%04x" % self.data2)
25         s.append("%04x" % self.data3)
26         s.append("%02x%02x" % (self.data4_1, self.data4_2, ))
27         s.append("%02x%02x%02x%02x%02x%02x" % (
28                 self.data4_3, self.data4_4,
29                 self.data4_5, self.data4_6, self.data4_7, self.data4_8,))
30         return "-".join(s)
31 
32 
33 class FILETIME(Structure):
34     _fields_ = [
35         ("Low", ctypes.c_uint32),
36         ("High", ctypes.c_uint32),
37         ]
38 
39 
40 class ShellLinkHeader(Structure):
41     _fields_ = [
42         ("HeaderSize", ctypes.c_uint32),
43         ("LinkCLSID", GUID),
44         ("LinkFlags", ctypes.c_uint32),
45         ("FileAttributes", ctypes.c_uint32),
46         ("CreationTime", FILETIME),
47         ("AccessTime", FILETIME),
48         ("WriteTime", FILETIME),
49         ("FileSize", ctypes.c_uint32),
50         ("IconIndex", ctypes.c_int32),
51         ("ShowCommand", ctypes.c_uint32),
52         ("HotKey", ctypes.c_uint16),
53         ("Reserved", ctypes.c_char * (2 + 4 + 4)),
54         ]
55     def __init__(self, b):
56         # Structure.__init__(args, kw)
57         fit = min(len(b), ctypes.sizeof(self))
58         ctypes.memmove(ctypes.addressof(self), b, fit)
59         if ctypes.sizeof(self) != self.HeaderSize or \
60                 str(self.LinkCLSID) != "00021401-0000-0000-c000-000000000046":
61             raise TypeError("bytes must be ShellLinkHeader")
62 
63     def to_bytes(self):
64         return buffer(self)[:]
65 
66 
67 if __name__ == '__main__':
68     import sys
69     target = sys.argv[1]
70 
71     orig = open(target, "rb").read()
72     lnk = ShellLinkHeader(orig)
73     lnk.HotKey = ctypes.c_uint16(0)
74     with open(target, "wb") as fo:
75         fo.write(lnk.to_bytes())
76         fo.write(orig[lnk.HeaderSize:])

ショートカットファイル(MS-SHLLINK)のバイナリフォーマットの仕様は Microsoft Developer Network に書かれている。今の目的には「ヘッダ部」だけで良く、これはShellLinkHeader

なお、上のスクリプトは ctypes.Structure を使っているが、このように「読みやすい」プログラムではなくワンライナー流儀の方が好みであれば、struct モジュールの pack, unpack が使える。perl を知っていればお馴染みのものだし、ruby でもほとんど同じものが使える。あるいは、おそらく PyWin32 からは、正規の、ショートカットを扱うための COM インターフェイスにアクセス出来るはずである(知ってたはずなんだけど忘れた、ゴメン)。

また、pure Python でなくても良ければ、案外 Cython でも書きやすい。C の構造体を何らか手持ちならば(要は Visual C++ 環境を持っていれば)、swig が楽かもしれない。

Binary Hacks ―ハッカー秘伝のテクニック100選

ほかにこのタイプの親切なアプリケーションはいるのかな?

さきほどのスクリプトを、書き換えない表示オンリーにしてみて、「C:/ProgramData/Microsoft/Windows/Start Menu」(Windows 7 の場合)を

 1 # -*- coding: utf-8 -*-
 2 #
 3 # ..(snip)...
 4 #
 5 
 6 if __name__ == '__main__':
 7     import os
 8     import fnmatch
 9     import sys
10     #target = sys.argv[1]
11     #
12     #orig = open(target, "rb").read()
13     #lnk = ShellLinkHeader(orig)
14     #lnk.HotKey = ctypes.c_uint16(0)
15     #with open(target, "wb") as fo:
16     #    fo.write(lnk.to_bytes())
17     #    fo.write(orig[lnk.HeaderSize:])
18     targetdir = sys.argv[1]
19     for root, dirs, files in os.walk(targetdir):
20         for fn in files:
21             if not fnmatch.fnmatch(fn, "*.lnk"):
22                 continue
23             path = os.path.join(root, fn)
24             orig = open(path, "rb").read()
25             lnk = ShellLinkHeader(orig)
26             if lnk.HotKey:
27                 print(path, lnk.HotKey)

で列挙してみる。

あぁ、さすが HP。世界一親切だ、ほかにはない。愛するホットキー全部とお別れしなければならないかと思ったが残念だ。

いい加減ホットキーを「初心者のために」提供するのはやめて欲しい

lnkでの方式に限らず、エンドユーザのためを思うならば、ホットキーは「明示的に頼むまでは」提供しないで欲しい。

Emacsユーザだけが文句を言うわけではない。初心者ほどふとしたキー操作で「思わぬ」ものが起動することに恐れ慄く、という想像力を働かせて欲しい。初心者フレンドリはドキュメント、ランチャ、タスクトレイ、プログラムメニューの構造などで頑張れば良いのでは。

2023年3月になってまでの追記

うーん、なにゆえにこのページへの訪問が多いのか。と思って試しに「Ctrl + Alt + S ヘルプ」というキーワードで検索してみたら、duckduckgo だと一ページ目に出てくるのね、ワタシのこのページ。ワタシはこのページを書いた後そのとき使っていた HP パソコンをいったん離れて、再び HP パソコンを買って今に至るのだが、この新しい方の HP 機はこの「Ctrl + Alt + S でヘルプ」にはなってなかったと記憶してる。ので、発端となったネタはもう廃れたネタなのではないのかなと思うのだが、それでもここに辿り着く人が一定数いるというのは…、何か別のものを期待しているのかしらね。

そしてその検索で、「word にも Ctrl + Alt + S ショートカットがある」ことを知る。

何度でも言いたいのだけれどもさ、「Ctrl + P で爆速印刷!」なんて合理的な発想を、なんで出来るんだか、てことよ。想像力とか共感力の問題だと思わんか? なんで、「猛烈に印刷する」なんてシチュエーションが馬鹿げた想定であることに気づかないんであろうかね、てことよ。どんなであれ、アプリケーションをまたいだグローバルなホットキーについては、本当に慎重に考えてくれ、と、切に願う。して、今日続きのネタが新たに生まれた。これも根っこは「毎秒爆速で印刷操作出来るなんて素敵だ」に通ずる話。