ckw の小さいアイコンをちゃんとタイトルバーやタスクバーに表示させる

以下の環境で動作確認しました。

ckw はコマンド プロンプトを使い易くするソフトで、DOS 窓の画面表示やキー操作などの UI 部分を肩代わりしてくれます。
NYACUS とあわせて便利に使わせてもらってたんですが、ある日アイコンを Resource Hacker で置き換えてみたら、タイトルバーやタスクバーのアイコンが潰れてるのに気付いたので、ちゃんと小さいアイコンが表示されるようにしてみました。
と言っても、ウィンドウクラスを登録するときに渡す WNDCLASSEX 構造体の hIconSm メンバを NULL にしただけですが。
hIconSm メンバが NULL だと、システムは hIcon メンバのアイコンリソースから、小さいアイコンに適切なサイズのアイコンを見繕ってくれます。

左側が修正前、右側が修正後のタイトルバーのアイコン部分のスクリーンショット。下段はアイコンを Terminal Icon | Mac Iconset | Artua.com のものと置き換えた状態。

差分

--- main.cpp	Fri Nov 23 20:30:46 2007
+++ main.cpp	Thu Apr 01 17:30:02 2010
@@ -754,7 +754,7 @@
 	wc.hbrBackground = CreateSolidBrush(gColorTable[0]);
 	wc.lpszMenuName = NULL;
 	wc.lpszClassName = className;
-	wc.hIconSm = wc.hIcon;
+	wc.hIconSm = NULL;
 	if(! RegisterClassEx(&wc))
 		return(FALSE);

ckw改造版の修正版とuberboxの修正版と簡易電卓っぽいの。 - hideden.hatenablog.com で配布されてるソース ckw-0.8.10-mod2-src.zip の差分です。
オリジナルの作者様と改造された方々に感謝。
ckw-0.8.10-mod2-src.zip は VC でビルドできるようになっていたので、Microsoft Visual Studio .NET 2003 の NMAKE でもビルドできました。
しかし、アイコンファイルを変更してビルドしようとしたところ

rsrc.rc(5) : error RC2176 : old DIB in rsrc/icon.ico; pass it through SDKPAINT

と怒られて失敗しました。
どうやら古い RC では 256x256 のアイコンが NG のようなので、アイコンファイルを変更せずにコンパイルした rsrc.res のアイコンを、Resource Hacker で置き換えてからビルドしました。
また、Resource Hacker でも 256x256 のアイコンは想定外らしく、

  • アクセスが拒否されました。
  • システム リソースが足りません.
  • ハンドルが無効です。

などのエラーメッセージが出ましたが、アイコンの置き換え自体は成功しました。
以下のようにコマンドラインで実行すると怒られずに済みます。

ResHacker.exe -addoverwrite rsrc.res, rsrc.res, Terminal.ico, ICONGROUP, 101,

UNICODE アンエスケープする

以下の環境で動作確認しました。

文字列を UNICODE アンエスケープします。
*unescape-unicode-string-default-regexp* は、デフォルトのアンエスケープする文字列にマッチする正規表現です。後述する *unescape-unicode-string-regexp-alist* で、正規表現を決定できなかった場合に使用されます。この変数には、以下の要素からなるリストをセットします。

第 1 要素
エスケープされた文字列にマッチする正規表現
第 2 要素以降
後方参照する数値のグループ番号と基数のペア

正規表現は大文字小文字を区別します。大文字小文字を区別しないときは、関数 compile-regexp の第 2 引数に、t を指定してコンパイルした正規表現をセットしてください。
*unescape-unicode-string-regexp-alist* は、正規表現の連想リストで、関数と *unescape-unicode-string-default-regexp* と同様のリストのペアをセットします。先頭の要素から関数を実行し、関数が non-nil を返した要素の正規表現を使用します。

以下の正規表現が定義済みです。

html-mode,
//www1.odn.ne.jp/ymtz/html_plus-mode.html">html+-mode, xml-mode のマイナーモード XHTML1.0/1.1:『&#\(?:\([0-9]+\)\|x\([0-9a-f]+\)\);』(大文字小文字を区別しない)
//www.geocities.jp/kiaswebsite/xyzzy/jscript-mode.html" title="kia's website - xyzzy関連 - jscript-mode.l">jscript-mode:『\\u\([0-9A-F]\{4\}\)』
デフォルト
『%u\([0-9A-F]\{4\}\)』

コード

;; 文字列を UNICODE アンエスケープする

; デフォルトの正規表現
; 第 1 要素にアンエスケープする文字列にマッチする正規表現
; 第 2 要素以降に取り出す数値のグループ番号と基数をペアにしたコンスセル
; 正規表現は大文字小文字を区別する
; 大文字小文字を区別しないようにするには compile-regexp の第 2 引数に t を指定
; してコンパイルした正規表現をセットする
(defvar *unescape-unicode-string-default-regexp*
  `(,(compile-regexp "%u\\([0-9A-F]\\{4\\}\\)") (1 . 16)))

; 正規表現の連想リスト
; car の関数が non-nil を返すとき cdr の正規表現を使用する
(defvar *unescape-unicode-string-regexp-alist*
  `((,#'(lambda ()
          (or (member buffer-mode '("html-mode" "html+-mode") :test #'string=)
              (and (string= buffer-mode "xml-mode")
                   (string-match "^xml:XHTML1\\.\\(?:0-\\(?:Strict\\|Frameset\\|Transitional\\)\\|1\\)$"
                                 mode-name))))
     . (,(compile-regexp "&#\\(?:\\([0-9]+\\)\\|x\\([0-9a-f]+\\)\\);" t)
        (1 . 10) (2 . 16)))
    (,#'(lambda () (string= buffer-mode "jscript-mode"))
     . (,(compile-regexp "\\\\u\\([0-9A-F]\\{4\\}\\)")
        (1 . 16)))
    ))

(defun unescape-unicode-string (str)
  (let* ((lst (or (some #'(lambda (x) (if (funcall (car x)) (cdr x)))
                        *unescape-unicode-string-regexp-alist*)
                  *unescape-unicode-string-default-regexp*))
         (re (car lst))
         (alst (cdr lst))
         (getc (if (= (list-length alst) 1)
                   (let ((n (caar alst))
                         (r (cdar alst)))
                     #'(lambda () (parse-integer (match-string n) :radix r)))
                 (let ((pint #'(lambda (x)
                                 (let ((str (match-string (car x))))
                                   (if str (parse-integer str :radix (cdr x)))))))
                   #'(lambda () (some pint alst)))))
         (tmpb (create-new-buffer " *work*"))
         code)
    (set-buffer tmpb)
    (unwind-protect
        (progn
          (insert str)
          (goto-char (point-min))
          (while (scan-buffer re :tail t :regexp t)
            (when (setq code (funcall getc))
              (delete-region (match-beginning 0) (match-end 0))
              (insert (format nil "~C" (unicode-char code)))))
          (buffer-substring (point-min) (point-max)))
      (if tmpb (delete-buffer tmpb)))))

; リージョンを UNICODE アンエスケープする
(defun unescape-unicode-string-region (from to)
  (interactive "*r")
  (let ((str (unescape-unicode-string (buffer-substring from to))))
    (delete-region from to)
    (insert str)))

; セレクションを UNICODE アンエスケープする
(defun unescape-unicode-string-selection ()
  (interactive "*")
  (case (get-selection-type)
    ((1 2)
     (ed::map-selection #'(lambda (start end)
                            (unescape-unicode-string-region start end))))
    (3 (error "セレクションが矩形選択です"))
    (t (error "セレクションがありません"))))

UNICODE エスケープする

以下の環境で動作確認しました。

文字列を UNICODE エスケープします。
*escape-unicode-string-format-alist* は、エスケープに使用するフォーマットの連想リストで、要素に関数とフォーマットのペアをセットします。先頭の要素から関数を実行し、関数が non-nil を返した要素のフォーマットを使用します。
*escape-unicode-string-default-format* はデフォルトのフォーマットです。*escape-unicode-string-format-alist* でフォーマットを決定できなかった場合に、この変数のフォーマットを使用します。
フォーマットは関数 format の出力書式で、引数には UNICODE文字コード値が渡されます。
以下のフォーマットが定義済みです。

html-mode,
//www1.odn.ne.jp/ymtz/html_plus-mode.html">html+-mode, xml-mode のマイナーモード XHTML1.0/1.1:『&#x~4,'0X;』(ex. foo => foo)
//www.geocities.jp/kiaswebsite/xyzzy/jscript-mode.html" title="kia's website - xyzzy関連 - jscript-mode.l">jscript-mode:『\u~:@(~4,'0X~)』(ex. foo => \u0066\u006F\u006F)
デフォルト
『%u~:@(~4,'0X~)』(ex. foo => %u0066%u006F%u006F)

コード

;; 文字列を UNICODE エスケープする

; デフォルトのフォーマット
(defvar *escape-unicode-string-default-format* "%u~:@(~4,'0X~)")

; フォーマットの連想リスト
; car の関数が non-nil を返すとき cdr のフォーマットを使用する
(defvar *escape-unicode-string-format-alist*
  (list
   (cons #'(lambda ()
             (or (member buffer-mode '("html-mode" "html+-mode") :test #'string=)
                 (and (string= buffer-mode "xml-mode")
                      (string-match "^xml:XHTML1\\.\\(?:0-\\(?:Strict\\|Frameset\\|Transitional\\)\\|1\\)$"
                                    mode-name))))
         "&#x~4,'0X;")
   (cons  #'(lambda () (string= buffer-mode "jscript-mode"))
          "\\u~:@(~4,'0X~)")
   ))

; 引数  : str - 文字列 - 対象の文字列
; 引数  : test - 関数 - 指定した関数が non-nil を返した文字のみエスケープする
;         関数は文字の UNICODE の文字コード値を引数とする
;         以下のキーワードパッケージシンボルがプリセットとして指定できる
;         * :non-ascii - 128 (#x80) 以上で non-nil
;         * :non-latin1 - 256 (#x100) 以上で non-nil
;         nil を指定するとテストを行わずに全ての文字をエスケープする
;         省略時は nil
; 戻り値: エスケープされた文字列
(defun escape-unicode-string (str &optional test)
  (and test
       (not (functionp test))
       (setq test
             (let ((n (case test
                        (:non-ascii 127)
                        (:non-latin1 255)
                        (t (error (make-condition 'type-error
                                                  :datum test
                                                  :expected-type 'function))))))
               #'(lambda (code) (> code n)))))
  (let* ((fmt (or (some #'(lambda (x) (if (funcall (car x)) (cdr x)))
                        *escape-unicode-string-format-alist*)
                  *escape-unicode-string-default-format*))
         (cts (if test
                  #'(lambda (c)
                      (let ((code (char-unicode c)))
                        (format nil "~:[~C~;~*~@?~]" (funcall test code) c
                                fmt code)))
                #'(lambda (c) (format nil fmt (char-unicode c))))))
    (apply #'concat (map 'list cts str))))

; リージョンを UNICODE エスケープする
(defun escape-unicode-string-region (from to &optional (test :non-ascii))
  (interactive "*r")
  (let ((str (escape-unicode-string (buffer-substring from to) test)))
    (delete-region from to)
    (insert str)))

; セレクションを UNICODE エスケープする
(defun escape-unicode-string-selection (&optional (test :non-ascii))
  (interactive "*")
  (case (get-selection-type)
    ((1 2)
     (ed::map-selection #'(lambda (start end)
                            (escape-unicode-string-region start end test))))
    (3 (error "セレクションが矩形選択です"))
    (t (error "セレクションがありません"))))

Firefox 3 のデフォルトテーマを Windows クラシックらしくする

インスパイヤされました。

今回の改造にあたって加えられた新たなオリジナリティは以下。

  • Firefox 3 用に書き換えました。

以下の環境で動作確認しました。

タブバーを少し細くしました。
Firefox のデフォルトテーマを Windows クラシックらしくするユーザスタイルです。
このスタイルを適用すると、UI に以下の変更が加えられます。

  • ツールバーやサイドバー、タブバーなどを枠線で囲みます。
  • タブとタブのスクロールボタン、『タブの一覧表示』ボタンをフラットにします。
  • ブラウザ部分をへこませます。

スタイル適用前のスクリーンショットは以下。

スタイル適用後のスクリーンショットは以下。

スタイル適用後のタブバーのスクリーンショットは以下。

左から、『アクティブなタブ』、『ポインティングデバイスで指示中の非アクティブなタブ』、『非アクティブなタブ』です。

スタイル適用後のタブがフォーカスを得たときのスクリーンショットは以下。

コード

userChrome.css に記述するか、Stylish で適用して下さい。

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ツールバー・サイドバー・タブバーを枠線で囲む
 * * hail2u.net - Weblog - FirefoxのuserChrome.cssネタ #7
 *   <http://hail2u.net/blog/software/firefox_userchrome_css_tricks_7.html>
 * から朴りました。
 */
#navigator-toolbox {
	padding-bottom: 0 !important;
	border-width: 1px !important;
	border-color: ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow !important;
	border-style: solid !important;
	-moz-appearance: none !important;
}
#navigator-toolbox > toolbar {
	border-width: 1px !important;
	border-color: ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight !important;
	border-style: solid !important;
	-moz-appearance: none !important;
}
#sidebar-box {
	border-width: 1px 2px 2px !important;
	border-top-color: ThreeDShadow !important;
	border-style: solid !important;
	-moz-border-right-colors: ThreeDHighlight ThreeDShadow !important;
	-moz-border-bottom-colors: ThreeDHighlight ThreeDShadow !important;
	-moz-border-left-colors: ThreeDShadow ThreeDHighlight !important;
}
#sidebar-box > sidebarheader {
	border-bottom-width: 2px !important;
	border-bottom-style: solid !important;
	-moz-appearance: none !important;
	-moz-border-bottom-colors: ThreeDHighlight ThreeDShadow !important;
}
#sidebar-splitter {
	border-right-style: none !important;
	border-left-style: none !important;
	min-width: 4px !important;
}
.tabbrowser-tabs {
	border-width: 0 2px 2px !important;
	border-style: solid !important;
	-moz-appearance: none !important;
	-moz-border-right-colors: ThreeDHighlight ThreeDShadow !important;
	-moz-border-bottom-colors: ThreeDHighlight ThreeDShadow !important;
	-moz-border-left-colors: ThreeDShadow ThreeDHighlight !important;
}
#main-window > #browser > #appcontent > #content > tabbox > .plain > notificationbox notification > .notification-inner.outset {
	border-width: 2px !important;
	-moz-border-top-colors: ThreeDShadow ThreeDHighlight !important;
	-moz-border-right-colors: ThreeDHighlight ThreeDShadow !important;
	-moz-border-bottom-colors: ThreeDHighlight ThreeDShadow !important;
	-moz-border-left-colors: ThreeDShadow ThreeDHighlight !important;
}

/* タブをフラットなボタンにする
 * * hail2u.net - Weblog - FirefoxのuserChrome.cssネタ #11
 *   <http://hail2u.net/blog/software/firefox-userchrome-css-tricks-11.html>
 * から朴りました。
 */
.tabbrowser-tabs {
	background-image: none !important;
}
.tabs-bottom {
	background-image: none !important;
	border-style: none !important;
}
.tabs-container:not([overflow="true"]) {
	-moz-padding-start: 0 !important;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up,
.tabbrowser-arrowscrollbox > .scrollbutton-down,
.tabs-alltabs-button {
	background-image: none !important;
	margin: 0 !important;
	border: 1px transparent solid !important;
	-moz-border-top-colors: none !important;
	-moz-border-right-colors: none !important;
	-moz-border-left-colors: none !important;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled="true"]):hover,
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled="true"]):hover,
.tabs-alltabs-button:hover {
	padding: 0 !important;
	border-color: ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight !important;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled="true"]):active:hover,
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled="true"]):active:hover,
.tabs-alltabs-button[open="true"],
.tabs-alltabs-button[open="true"]:hover {
	border-color: ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow !important;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up {
	-moz-border-radius-topright: 0 !important;
	-moz-margin-end: 1px !important;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up[chromedir="rtl"] {
	-moz-border-radius-topleft: 0 !important;
}
.tabbrowser-arrowscrollbox > .scrollbutton-down,
.tabs-alltabs-button {
	-moz-border-radius-topleft: 0 !important;
	-moz-margin-start: 1px !important;
}
.tabbrowser-arrowscrollbox > .scrollbutton-down[chromedir="rtl"],
.tabs-container > stack[chromedir="rtl"] > .tabs-alltabs-button {
	-moz-border-radius-topright: 0 !important;
}
.tabs-alltabs-box-animate {
	background-image: none !important;
	margin: 0 !important;
	border-width: 1px !important;
	border-color: ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight !important;
	border-style: solid !important;
	width: auto !important;
	-moz-margin-start: 1px !important;
}
.tabbrowser-tab {
	background-image: none !important;
	margin: 0 !important;
	padding: 0 !important;
	border: 1px transparent solid !important;
	min-width: 22px !important;
	-moz-border-top-colors: none !important;
	-moz-border-right-colors: none !important;
	-moz-border-left-colors: none !important;
	-moz-border-radius-topright: 0 !important;
	-moz-border-radius-topleft: 0 !important;
}
.tabbrowser-tab:hover,
.tabbrowser-tab[selected="true"] {
	border-color: ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow !important;
}
.tabbrowser-tab[selected="true"] {
	background-image: url("chrome://global/skin/toolbar/Lighten.png") !important;
	background-repeat: repeat !important;
}
.tabbrowser-tab[selected="true"]:focus {
	outline: 1px -moz-DialogText dotted !important;
	outline-offset: -2px !important;
}
.tabbrowser-tab:not([selected="true"]):hover {
	border-color: ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight !important;
}
.tab-icon-image {
	margin: 2px !important;
	opacity: 0.4 !important;
}
.tabbrowser-tab:hover > .tab-icon-image,
.tabbrowser-tab[selected="true"] > .tab-icon-image {
	opacity: 1.0 !important;
}
.tab-text {
	color: GrayText !important;
	border-style: none !important;
}
.tabbrowser-tab:hover > .tab-text,
.tabbrowser-tab[selected="true"] > .tab-text {
	color: -moz-DialogText !important;
}
.tabbrowser-tab[selected="true"] > .tab-text {
	font-weight: normal !important;
}
.tab-close-button {
	margin: -7px 0 0 !important;
	opacity: 0.8 !important;
	-moz-margin-start: -14px !important;
}
.tab-close-button > .toolbarbutton-icon {
	margin-top: 0 !important;
}
.tabs-container > .tabs-closebutton {
	background-image: none !important;
	margin: 0 !important;
	border-style: none !important;
	-moz-border-radius-topleft: 0 !important;
	-moz-margin-start: 1px !important;
}
.tabs-container > .tabs-closebutton[chromedir="rtl"] {
	border-style: none !important;
	-moz-border-radius-topright: 0 !important;
}

/* ブラウザ部分をへこませる
 * * hail2u.net - Weblog - FirefoxのuserChrome.cssネタ #4
 *   <http://hail2u.net/blog/software/firefox_userchrome_css_tricks_4.html>
 * から朴りました。
 */
#main-window > #browser > #appcontent > #content > tabbox > .plain > notificationbox > browser {
	border-width: 2px !important;
	border-style: solid !important;
	-moz-border-top-colors: ThreeDShadow ThreeDDarkShadow !important;
	-moz-border-right-colors: ThreeDHighlight ThreeDLightShadow !important;
	-moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow !important;
	-moz-border-left-colors: ThreeDShadow ThreeDDarkShadow !important;
}

『タブを閉じる』ボタンは、設定名 browser.tabs.closeButtons に 2 をセットすることで非表示に出来ます。
詳しくは、about:config と user.js による Firefox のカスタマイズ - えむもじら #タブクローズボタンの設定 を参照してください。

『タブの一覧表示』ボタンを非表示にするには、以下を追加してください。

/* タブの一覧表示ボタンを消す */
.tabs-alltabs-box, .tabs-alltabs-box-animate, .tabs-alltabs-button {
	display: none !important;
}

タブがフォーカスを得たときの点線が鬱陶しい方は、『.tabbrowser-tab[selected="true"]:focus { ... }』を削除して下さい。

ttray で通知領域に登録されるアイコンを綺麗にする

以下の環境で動作確認しました。

ttray は通知領域*1にアイコンを登録し、xyzzy を最小化したときにタスクバーに表示されないようにする拡張スクリプトです。
デフォルトでは、32*32 px のアイコンがロードされているので、通知領域に表示する際に強引に縮小されてしまい、汚く見えるようです。
そこで、小さいアイコンをロードするように変更してみました。
また、xyzzy を非表示にするときに、最小化されていなかったので、最小化してから非表示にするようにも変更しています。

変更前と変更後の通知領域のスクリーンショットは以下。

以下の関数を置き換えます。

  • ttray-setup
  • ttray-hide-xyzzy

ついでに、通知領域に表示するアイコンファイルをセットする関数を追加します。

ttray-set-icon-show
xyzzy 表示時の通知領域に表示するアイコンファイルを設定する
ttray-set-icon-hide
xyzzy 非表示時の通知領域に表示するアイコンファイルを設定する

なお、Windows 2000 までは、通知領域に登録できるアイコンの色数は 16 色です。

コード

;;; my-ttray.l: 俺的 ttray

(provide "my-ttray")

; winapi
(in-package "winapi")

(*define-dll-entry HANDLE LoadImage (HINSTANCE LPCSTR UINT int int UINT) "user32" "LoadImageA")

(*define IMAGE_ICON       1)
(*define LR_DEFAULTCOLOR  #x0000)
(*define LR_LOADFROMFILE  #x0010)
(*define SM_CXSMICON      49)
(*define SM_CYSMICON      50)


; win-user
(in-package "win-user")

; setup routine
; 元あるヤツを書き換えてます
(defun ttray-setup ()
  ; icon
  (unless (or *ttray-icon-show*
              *ttray-icon-hide*)
    (flet ((loadicon (i)
             (LoadImage (GetModuleHandle 0)
                        (MAKEINTRESOURCE i)
                        IMAGE_ICON
                        (GetSystemMetrics SM_CXSMICON)
                        (GetSystemMetrics SM_CYSMICON)
                        LR_DEFAULTCOLOR)))
      (unless *ttray-icon-show*
        (setq *ttray-icon-show* (loadicon IDI_FILER)))
      (unless *ttray-icon-hide*
        (setq *ttray-icon-hide* (loadicon IDI_XYZZY)))))
  ; window
  (setq *ttray-wc* (make-WNDCLASS))
  (setf (WNDCLASS-style *ttray-wc*) 0)
  (setf (WNDCLASS-lpfnWndProc *ttray-wc*) #'ttray-wndproc)
  (setf (WNDCLASS-cbClsExtra *ttray-wc*) 0)
  (setf (WNDCLASS-cbWndExtra *ttray-wc*) 0)
  (setf (WNDCLASS-hInstance *ttray-wc*) (GetModuleHandle 0))
  (setf (WNDCLASS-hIcon *ttray-wc*) (LoadIcon 0 (MAKEINTRESOURCE IDI_APPLICATION)))
  (setf (WNDCLASS-hCursor *ttray-wc*) (LoadCursor 0 (MAKEINTRESOURCE IDC_ARROW)))
  (setf (WNDCLASS-hbrBackground *ttray-wc*) (+ 1 COLOR_WINDOW))
  (setf (WNDCLASS-lpszMenuName *ttray-wc*) 0)
  (setf (WNDCLASS-lpszClassName *ttray-wc*) (si:make-string-chunk *ttray-class*))
  (RegisterClass *ttray-wc*)
  (setq *ttray-hwnd*
        (CreateWindow (si:make-string-chunk *ttray-class*)
                      (si:make-string-chunk "XyzzyTTrayWindow")
                      0
                      CW_USEDEFAULT CW_USEDEFAULT
                      CW_USEDEFAULT CW_USEDEFAULT
                      0 0 (GetModuleHandle 0) 0))
  (UpdateWindow *ttray-hwnd*)
  (let ((msg (make-MSG))
        (break-loop nil))
    (declare (special break-loop)))
  ; popup menu
  (ttray-build-popup-menu)
  ; icon
  (setq *ttray-icon* (make-NOTIFYICONDATA))
  (setf (NOTIFYICONDATA-cbSize *ttray-icon*)
        (si:chunk-size *ttray-icon*))
  (setf (NOTIFYICONDATA-hWnd *ttray-icon*)
        *ttray-hwnd*)
  (setf (NOTIFYICONDATA-uID *ttray-icon*) 0)
  (setf (NOTIFYICONDATA-uFlags *ttray-icon*)
        (logior NIF_ICON
                NIF_MESSAGE
                NIF_TIP))
  (setf (NOTIFYICONDATA-uCallbackMessage *ttray-icon*) WM_TTRAY_NOTIFY)
  (setf (NOTIFYICONDATA-hIcon *ttray-icon*) *ttray-icon-show*)
  (si:pack-string *ttray-icon*
                  (c-struct-offset-of NOTIFYICONDATA winapi::szTip)
                  *ttray-tip* 64)
  (Shell_NotifyIcon NIM_ADD *ttray-icon*))

; hide xyzzy
; 元あるヤツを書き換えてます
(defun ttray-hide-xyzzy ()
  (if (and *ttray-status*
           (ed::run-hook-with-args-while-success '*ttray-hide-hook*))
      (let ((hwnd (ed::get-window-handle)))
        (setq *ttray-status* nil)
        (if (zerop (IsIconic hwnd))
            (ShowWindow hwnd SW_MINIMIZE))
        (ShowWindow hwnd SW_HIDE)
        (ttray-change-icon :tip *ttray-tip* :icon-hide *ttray-icon-hide*))))


; user
(in-package "user")

(flet ((loadicon (ico)
         (setq ico
               (map-slash-to-backslash (merge-pathnames ico
                                                        (si:system-root))))
         (win-user::LoadImage 0
                              (si:make-string-chunk ico)
                              win-user::IMAGE_ICON
                              (win-user::GetSystemMetrics win-user::SM_CXSMICON)
                              (win-user::GetSystemMetrics win-user::SM_CYSMICON)
                              win-user::LR_LOADFROMFILE)))
  (defun ttray-set-icon-show (ico)
    "ttray: xyzzy 表示時の通知領域に表示するアイコンファイルを設定する"
    (setq win-user::*ttray-icon-show* (loadicon ico)))
  (defun ttray-set-icon-hide (ico)
    "ttray: xyzzy 非表示時の通知領域に表示するアイコンファイルを設定する"
    (setq win-user::*ttray-icon-hide* (loadicon ico))))

設定例

上記のコードを『my-ttray.l』というファイル名で保存した場合です。

(require "ttray")
; 俺的 ttray
(require "my-ttray")

アイコンファイルを指定する場合は、以下のように追加して下さい。

; 表示時のアイコンファイル
(ttray-set-icon-show "ttray_show.ico")
; 非表示時のアイコンファイル
(ttray-set-icon-hide "ttray_hide.ico")

*1:所謂タスクトレイ

LauncherEX で共通部分自動確定

以下の環境で動作確認しました。

LauncherEX で補完候補リスト作成時に、候補の共通部分を自動的に確定します。

コード

config.py などに記述します。

## LauncherEX
import clmode_lex # `from clconst import *' より前に記述する


from clapi import *
from clconst import *


# 共通部分自動確定
def _DecideCommonParts(event):
    import clmode
    import clcore

    if (clmode.Top().abbrev and event.sel[0] and event.candlist
        and clcore.List_IsShown() and (GetListSelection() != -1)):
        lst = [x.lower() for x in event.candlist if x.find(';') == -1]
        if not lst:
            return
        n = min([len(x) for x in lst])
        if not n or (n <= event.sel[0]):
            return
        start = 0
        for i in xrange(event.sel[0], n):
            for x in lst[1:]:
                if lst[0][i] != x[i]:
                    start = i
                    break
            if start:
                break
        else:
            start = i + 1
        s = event.candlist[0][:start]
        n = event.str.find(';')
        SetValue(s + event.str[n:] if n >= event.sel[0] else s)
        SetSelection(start, len(s))

# 補完リスト作成時
clmode_lex.AddHook(clmode_lex.lex_hook_on_listup_abbrev, _DecideCommonParts)

この関数を作ってるときに気付いたんですが、フック関数を実行する関数 RunHook() は、ShowInfoException 以外の例外を捕捉すると StandardError を返却するので、どんな例外が送出されたのか分かりません。
そこで、捕捉した例外オブジェクトを返却するよう変更してみました。

from clapi import *
from clconst import *

import clmode_lex

def _RunHook(HookTable, event):
    u"""フックを実行する"""
    rv = None
    for func in HookTable.itervalues():
        try:
            func(event)
        except ShowInfoException, e:
            rv = e
        except Exception, e:
            return e
    return rv
clmode_lex.clLauncherEX.RunHook = _RunHook

LauncherEX で BackSpace で補完するときカーソルより後ろの『;』以降が消えないようにする

以下の環境で動作確認しました。

LauncherEX で OPTION_EX_ABBREV_ON_BS を有効にしているとき、BackSpace でも補完が実行されますが、カーソル位置より後ろに『;』があっても、それ以降が消されてしまいます。
これを、CraftLaunch の『&SubCommand;&Backspace,&PopList』に似た挙動にします。
ついでに、コマンド入力エリアが空で、補完候補リストが表示されていないときに、OPTION_EX_SWAP_CTRL_SPACE によるキーバインドの入れ替えが効いていなかったのも修正しました。
クラス LauncherModeEX のメソッド OnKeyDown を置き換えます。

コード

config.py などに記述します。

## LauncherEX
import clmode_lex # `from clconst import *' より前に記述する


from clapi import *
from clconst import *


def _LauncherModeEXOnKeyDown(self, event):
    from clmode_lex import OPTION_EX_SWAP_CTRL_SPACE, OPTION_EX_ABBREV_ON_BS

    # フックの実行
    err = clmode_lex.RunHook(clmode_lex.lex_hook_on_key, event)
    if err: raise err
    if event.skip: return

    # Swap Ctrl+Space to Space
    if (GetOption(OPTION_EX_SWAP_CTRL_SPACE)
        and (event.vk == VK_SPACE)):
        if event.mod == 0:
            s = GetValue()
            pos = GetSelection()
            SetValue(s[:pos[0]] + ' ' + s[pos[1]:])
            SetSelection(*((pos[0]+1, ) * 2))
            event.Skip()
            return
        if event.mod == MODKEY_CTRL:
            import clcore
            import clwindow

            if clcore.List_IsShown():
                clwindow.list.SelNext(True)
            else:
                clwindow.edit.Abbrev(GetValue())
            event.Skip()
            return

    # 独自の実装のため、LauncherMode は呼ばない
    import clmode
    clmode.BaseMode.OnKeyDown(self, event)

    if (GetOption(OPTION_EX_ABBREV_ON_BS)
        and (event.vk == VK_BACK)
        and (event.mod == 0)):

        # カーソル位置と無関係に (min, max) の模様 @0.991
        (start, end) = GetSelection()
        if not start == end == 0:
            s = GetValue()
            if start == end:
                start -= 1
                s = s[:start] + s[end:]
                end -= 1
            else:
                s = s[:start] + s[end:]
                end = start
            SetValue(s)
            SetSelection(start, end)

        import clwindow
        clwindow.edit.Abbrev(None)
        #clwindow.edit.last_action = 1
        event.Skip()
clmode_lex.clLauncherEX.LauncherModeEX.OnKeyDown = _LauncherModeEXOnKeyDown