├── .gitignore ├── Cargo.toml ├── ReadMe.md ├── build.rs ├── gif ├── 1.gif ├── 2.gif ├── ani.gif └── shell.gif └── src ├── .clang-format ├── CMakeLists.txt ├── ShellContextMenu.cpp ├── ShellContextMenu.h ├── Utils.cpp ├── Utils.h ├── beacon.rs ├── cmake_build.bat ├── gui.rs ├── lib.rs ├── res.rc ├── resource.h ├── shellmenu.rs ├── shellmenu_wrap.cpp └── transparent.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /TAGS 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pop-select" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | emacs = "0.11.0" 10 | native-windows-gui = "1.0.12" 11 | winapi = {version="0.3", features=["minwindef", "windef", "wingdi", "winuser", "libloaderapi", "processthreadsapi", "basetsd", "debugapi", "errhandlingapi", "ole2"]} 12 | lazy_static = "1.4" 13 | detours-sys = {version="0.1.2"} 14 | 15 | [lib] 16 | crate-type = ["cdylib"] 17 | 18 | [build-dependencies] 19 | embed-resource = "1" 20 | cc = "1.0" 21 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | 加载 2 | === 3 | ```lisp 4 | (when (and (eq system-type 'windows-nt) 5 | (or (display-graphic-p) 6 | (daemonp))) 7 | (ignore-error 'file-missing 8 | ;; 把 DLL 加到 `load-path' 里. 9 | (load-library "pop_select.dll"))) 10 | ``` 11 | 12 | 编译 13 | === 14 | 自行编译的话c++编译器至少需要支持c++17。可以直接用Release里编译好的64位dll。 15 | 16 | 功能介绍 17 | === 18 | 本Emacs module专为windows设计。主要功能有: 19 | 20 | (!!文档更新可能不及时,请自行F1 f搜索查看`pop-select/`开头的函数定义) 21 | 22 | # 1. 设置Emacs窗口透明 # 23 | 有两种方式 24 | - 设置整个emacs透明。 25 | ```lisp 26 | (pop-select/transparent-set-all-frame ALPHA) ;; 对所有frame设置透明,ALPHA范围0-255,0全透明,255不透明 27 | (pop-select/transparent-set-current-frame ALPHA) ;; 只对当前frame设置透明,其它同上 28 | ``` 29 | 示例设置: 30 | ```lisp 31 | (when (functionp 'pop-select/transparent-set-all-frame) 32 | (pop-select/transparent-set-all-frame 220)) 33 | ``` 34 | - [**有bug不建议使用!**]设置文字不透明,背景透明。由于实现的限制,该功能打开时会使Emacs置顶,当设置为255即不透明时取消置顶 35 | ```lisp 36 | (pop-select/transparent-set-background ALPHA R G B) ;; ALPHA范围0-255,0全透明,255不透明。R G B为rgb拆分数值。 37 | ``` 38 | 示例设置,用CTRL+鼠标滚轮调整当前的透明度: 39 | ```lisp 40 | (ignore-errors (module-load "pop_select.dll全路径,如果没有加入bin路径的话")) 41 | (when (functionp 'pop-select/transparent-set-background) 42 | (defvar cur-transparent 255) 43 | (defconst step-transparent 20) 44 | (defun dec-transparent() 45 | (interactive) 46 | (setq cur-transparent (min 255 (+ cur-transparent step-transparent))) 47 | (let* ((rgb (color-name-to-rgb (face-background 'default))) 48 | (r (round (*(nth 0 rgb) 255))) 49 | (g (round (*(nth 1 rgb) 255))) 50 | (b (round (*(nth 2 rgb) 255)))) 51 | (pop-select/transparent-set-background cur-transparent r g b) 52 | ) 53 | ) 54 | (defun inc-transparent() 55 | (interactive) 56 | (setq cur-transparent (max 0 (- cur-transparent step-transparent))) 57 | (let* ((rgb (color-name-to-rgb (face-background 'default))) 58 | (r (round (*(nth 0 rgb) 255))) 59 | (g (round (*(nth 1 rgb) 255))) 60 | (b (round (*(nth 2 rgb) 255)))) 61 | (pop-select/transparent-set-background cur-transparent r g b) 62 | )) 63 | (global-set-key (kbd "") 'dec-transparent) 64 | (global-set-key (kbd "") 'inc-transparent) 65 | ) 66 | ``` 67 | 后面这种效果图: 68 | 69 | ![gif](gif/1.gif) 70 | 71 | 72 | # 2. CTRL+TAB弹出窗口选择列表 # 73 | `pop-select/pop-select`弹出一个竖型列表窗口,然后可以按ctrl+tab切换到下一项,ctrl+tab+shift切换到上一项,释放按键后返回所选项给emacs 74 | ```lisp 75 | (pop-select/pop-select NAME TO-SEL) ;; NAME为vector列表,TO-SEL是初始选中哪项 76 | ``` 77 | ```lisp 78 | (when (fboundp 'pop-select/pop-select) 79 | (defun my-pop-select(&optional backward) 80 | (interactive) 81 | (let* ((myswitch-buffer-list (copy-sequence (buffer-list) 82 | ) 83 | ) (vec_name []) 84 | sel 85 | ) 86 | (cl-dolist (buf myswitch-buffer-list) 87 | (setq vec_name (vconcat vec_name (list (buffer-name buf))))) 88 | ;; 返回序号 89 | (setq sel (pop-select/pop-select vec_name (if backward 90 | (1- (length vec_name)) 91 | 1 92 | ))) 93 | (let ((buf (switch-to-buffer (nth sel myswitch-buffer-list)))) 94 | (when (and (bufferp buf) (featurep 'wcy-desktop)) 95 | (with-current-buffer buf 96 | (when (eq major-mode 'not-loaded-yet) 97 | (wcy-desktop-load-file)))) 98 | ) 99 | ) 100 | ) 101 | (global-set-key (kbd "") 'my-pop-select) 102 | (global-set-key (if (string-equal system-type "windows-nt") 103 | (kbd "") 104 | (kbd "")) 105 | (lambda ()(interactive) 106 | (my-pop-select t))) 107 | ) 108 | ``` 109 | 效果图: 110 | 111 | ![gif](gif/2.gif) 112 | 113 | # 3. "异步"beacon效果 # 114 | 用于替换beacon的闪烁效果,完全不卡Emacs窗口,因为是另起了一个专门的ui线程来画beacon。实现函数`pop-select/beacon-blink`和`pop-select/beacon-set-parameters` 115 | ```lisp 116 | (pop-select/beacon-set-parameters WIDTH HEIGHT R G B DURATION-STEP) ;; 可设置宽度,高度,rgb色,以及blink效果显示时间 117 | (pop-select/beacon-blink X Y TIMER DELAY) ;; 在X, Y坐标显示TIMER时长,DELAY是延迟显示时间 118 | ``` 119 | ```lisp 120 | (when (fboundp 'pop-select/beacon-set-parameters) 121 | ;; 51afef 122 | (pop-select/beacon-set-parameters 300 20 #x51 #xaf #xef 50) 123 | (use-package beacon 124 | :defer 1.5 125 | :init 126 | (setq beacon-blink-when-focused t) 127 | (setq beacon-blink-delay 0.01) 128 | (setq beacon-blink-duration 0.2) 129 | (setq beacon-blink-when-window-scrolls nil) ; 开启了auto save,保存时都会闪故而屏蔽 130 | :config 131 | (beacon-mode 1) 132 | (defadvice beacon-blink (around my-beacon-blink activate) 133 | ;; 目前偶尔不是emacs时也弹窗 134 | ;; (message (concat (symbol-name this-command) " " (symbol-name last-command))) 135 | (when (frame-visible-p (window-frame)) ;; 可以阻止最小化时弹窗 136 | (let ((p (window-absolute-pixel-position))) 137 | (when p 138 | (pop-select/beacon-blink (car p) ; x 139 | (cdr p) ; y 140 | (truncate (* beacon-blink-duration 1000)) ; timer 141 | (truncate (* beacon-blink-delay 1000)) ; delay 142 | )))))) 143 | ) 144 | ``` 145 | 效果图跟上面一样: 146 | 147 | ![gif](gif/2.gif) 148 | 149 | # 4. 开启win10的dark mode(emacs29版本自带,适合29以下版本) 150 | 调用`(pop-select/ensure-all-window-dark-mode)`即可,不过目前标题可能不会立即刷新,建议加个`(w32-send-sys-command #xf030)`最大化就可以了 151 | 152 | # 5. shell相关功能 153 | 弹出shell右键菜单 154 | ```lisp 155 | (pop-select/popup-shell-menu PATHS X Y SHOW-EXTRA-HEAD) ; PATHS是路径vector,X、Y即屏幕座标,如果都是0,那么会在当前鼠标指针位置弹出。SHOW-EXTRA-HEAD是不否显示额外的菜单。 156 | ``` 157 | 参考配置,仅供参考,我自用的不会及时更新在这里: 158 | ```lisp 159 | (when (functionp 'pop-select/popup-shell-menu) 160 | (defun get-region-select-path() 161 | "获取选中的路径,抄的dired-mark和dired-mark-files-in-region" 162 | (let (paths) 163 | (when (region-active-p) 164 | (setq paths []) 165 | (save-excursion 166 | (let* ((beg (region-beginning)) 167 | (end (region-end)) 168 | (start (progn (goto-char beg) (line-beginning-position))) 169 | (end (progn (goto-char end) 170 | (if (if (eq dired-mark-region 'line) 171 | (not (bolp)) 172 | (get-text-property (1- (point)) 'dired-filename)) 173 | (line-end-position) 174 | (line-beginning-position)))) 175 | ) 176 | (goto-char start) ; assumed at beginning of line 177 | (while (< (point) end) 178 | ;; Skip subdir line and following garbage like the `total' line: 179 | (while (and (< (point) end) (dired-between-files)) 180 | (forward-line 1)) 181 | (if (and (not (looking-at-p dired-re-dot)) 182 | (dired-get-filename nil t)) 183 | (setq paths (vconcat paths (list (replace-regexp-in-string "/" "\\\\" (dired-get-filename nil t))))) 184 | ) 185 | (forward-line 1)) 186 | ))) 187 | paths)) 188 | (defun get-select-or-current-path() 189 | (let ((paths (get-region-select-path)) 190 | current) 191 | (unless paths 192 | (setq current (dired-get-filename nil t)) 193 | (when current 194 | (setq paths []) 195 | (setq paths (vconcat paths (list (replace-regexp-in-string "/" "\\\\" current))))) 196 | ) 197 | paths)) 198 | 199 | (define-key dired-mode-map (kbd "") 200 | (lambda (event) 201 | (interactive "e") 202 | (let ((pt (posn-point (event-end event))) 203 | (paths (get-region-select-path)) 204 | path) 205 | (if paths 206 | (pop-select/popup-shell-menu paths 0 0 1) 207 | ;; 单个文件直接跳过去 208 | (select-window (posn-window (event-end event))) 209 | (goto-char pt) 210 | (setq path (dired-get-filename nil t)) 211 | (unless path ;可能是点击了空白处,那么就取当前目录 212 | (setq path (dired-current-directory))) 213 | (setq paths (vconcat paths (list (replace-regexp-in-string "/" "\\\\" path)))) 214 | ;; 延迟调用使当前选中项更新hl-line等 215 | (run-at-time 0.1 nil (lambda () 216 | (pop-select/popup-shell-menu paths 0 0 1) 217 | )))))) 218 | (defun print-paths(vec) 219 | (let ((len (length vec)) 220 | (s "") 221 | (i 0)) 222 | (while (< i len) 223 | (setq s (concat s "\n" (file-name-nondirectory (aref vec i)) )) 224 | (setq i (1+ i))) 225 | s)) 226 | (define-key dired-mode-map "c" 227 | (lambda() 228 | (interactive) 229 | (let ((paths (get-select-or-current-path))) 230 | (when paths 231 | (pop-select/shell-copyfiles paths) 232 | (message (concat "Copy: " (print-paths paths))))))) 233 | (define-key dired-mode-map (kbd "C-w") 234 | (lambda() 235 | (interactive) 236 | (let ((paths (get-select-or-current-path))) 237 | (when paths 238 | (pop-select/shell-cutfiles paths) 239 | (message (concat "Cut: " (print-paths paths))))))) 240 | (define-key dired-mode-map "v" 241 | (lambda() 242 | (interactive) 243 | (let ((current-dir (dired-current-directory))) 244 | (when current-dir 245 | (pop-select/shell-pastefiles current-dir) 246 | (message "Paste in: %S" current-dir))))) 247 | ) 248 | ``` 249 | 效果图: 250 | 251 | ![gif](gif/shell.gif) 252 | 253 | shell copy功能,即explorer里按CTRL+C一样的效果: 254 | ```lisp 255 | (pop-select/shell-copyfiles PATHS) ; PATHS是路径vector 256 | ``` 257 | 258 | shell cut功能,即explorer里剪切功能: 259 | ```lisp 260 | (pop-select/shell-cutfiles PATHS) ; PATHS是路径vector 261 | ``` 262 | 263 | shell paste功能,即explorer里的CTRL+V一样的效果: 264 | ```lisp 265 | (pop-select/shell-pastefiles PATH) ; PATH目标路径 266 | ``` 267 | # 6. 类似neovide的光标移动效果 268 | ``` 269 | (pop-select/beacon-animation X Y W H TIMER STEP R G B DIFF-MIN) 270 | X: 坐标x值 271 | Y: 坐标y值 272 | W: 光标宽度 273 | H: 光标高度 274 | TIMER: 动画持续时间 275 | STEP: 动画持续时间按多少份处理 276 | R: 光标颜色RGB的R值 277 | G: 光标颜色RGB的G值 278 | B: 光标颜色RGB的B值 279 | DIFF-MIN: 坐标差值最小值,小于这个值就不显示动画,可以排除光标小范围移动时显示动画 280 | 注意R G B不能设置为0 0 0即黑色,这是透明色会看不见 281 | (pop-select/beacon-animation-update-pos X Y W H) 282 | 更新光标位置,比如一些命令你不想让它显示动画,就需要调用该函数更新位置,不然下次有光标动画时起始位置不对 283 | ``` 284 | 配置参考: 285 | ```lisp 286 | (when (fboundp 'pop-select/beacon-animation) 287 | (defun show-cursor-animation () 288 | (ignore-errors 289 | (let* ((p (window-absolute-pixel-position)) 290 | (pp (point)) 291 | (x (car p)) 292 | (w 293 | (if (equal cursor-type 'bar) 294 | 1 295 | (if-let ((glyph 296 | (when (< pp (point-max)) 297 | (aref 298 | (font-get-glyphs 299 | (font-at pp) pp (1+ pp)) 300 | 0)))) 301 | (aref glyph 4) 302 | (window-font-width)))) 303 | (h (line-pixel-height)) 304 | (y 305 | (if header-line-format 306 | (- (cdr p) h) ;; 修复开启`header-line-format'时y值不正确 307 | (cdr p)))) 308 | (when p 309 | (if (memq 310 | this-command 311 | '(mwheel-scroll 312 | move-beginning-of-line 313 | move-end-of-line)) ;; 某些命令不显示动画 314 | (pop-select/beacon-animation-update-pos x y w h) ;; 不显示动画时只更新位置 315 | (pop-select/beacon-animation 316 | x y w h 317 | 100 ; timer 318 | 50 ; timer step 319 | 233 86 120 ; r g b 320 | 20 ; diff min,根据自己需要试验 321 | )))))) 322 | (add-hook 'post-command-hook #'show-cursor-animation)) 323 | ``` 324 | 325 |
326 | 根据背景色调整光标的残影的颜色 327 | 328 | ```lisp 329 | ;;; -*- lexical-binding: t; -*- 330 | 331 | (require 'cl-lib) 332 | 333 | (when (and (eq system-type 'windows-nt) 334 | (or (display-graphic-p) 335 | (daemonp))) 336 | (ignore-error 'file-missing 337 | ;; 把 DLL 加到 `load-path' 里. 338 | (load-library "pop_select.dll"))) 339 | 340 | (with-eval-after-load "pop_select.dll" 341 | (let ((cursor-animation-color-R 0) 342 | (cursor-animation-color-G 0) 343 | (cursor-animation-color-B 0) 344 | cursor-animation?) ; 是否开启光标残影. 345 | (add-hook 'window-scroll-functions 346 | (lambda (_window _position) 347 | "滚屏时关闭残影: 1. 节约性能; 2. 设置 `scroll-margin' 后, 滚屏时残影位置不准确." 348 | (setq cursor-animation? nil))) 349 | (add-hook 'post-command-hook 350 | (lambda () 351 | (when-let ((window-absolute-pixel-position 352 | (when (or cursor-animation? 353 | (eq this-command 'recenter-top-bottom)) 354 | (window-absolute-pixel-position)))) 355 | (let ((line-pixel-height (line-pixel-height))) 356 | (pop-select/beacon-animation 357 | (car window-absolute-pixel-position) (if header-line-format 358 | (- (cdr window-absolute-pixel-position) 359 | line-pixel-height) 360 | (cdr window-absolute-pixel-position)) 361 | (if (eq cursor-type 'bar) 362 | 1 363 | (if-let ((glyph (let ((point (point))) 364 | (when (< point (point-max)) 365 | (aref (font-get-glyphs (font-at point) 366 | point (1+ point)) 0))))) 367 | (aref glyph 4) 368 | (window-font-width))) line-pixel-height 369 | 180 100 370 | cursor-animation-color-R cursor-animation-color-G cursor-animation-color-B 371 | ;; 排除大约是单个半角字符的距离: 372 | 24))) 373 | (setq cursor-animation? t))) 374 | (letrec ((cursor-animation-color-setter 375 | (lambda () 376 | (remove-hook 'server-after-make-frame-hook cursor-animation-color-setter) 377 | (let ((cursor-animation-color-RGB 378 | (cl-mapcar (let* ((ratio 0.5) ; 只需要修改此值. 379 | (1-ratio (- 1 ratio))) 380 | (lambda (cursor-color default-color) 381 | "按照 ratio:(1-ratio) 的比例混合光标颜色和背景色." 382 | (floor (* (+ (* ratio cursor-color) 383 | (* 1-ratio default-color)) 384 | 255.9999999999999)))) 385 | (color-name-to-rgb (face-background 'cursor)) 386 | (color-name-to-rgb (face-background 'default))))) 387 | (setq cursor-animation-color-R (cl-first cursor-animation-color-RGB) 388 | cursor-animation-color-G (cl-second cursor-animation-color-RGB) 389 | cursor-animation-color-B (cl-third cursor-animation-color-RGB)))))) 390 | ;; 如果是 daemon, 则必须等到第一个 visible frame 创建之后再设置残影的颜色. 391 | (add-hook 'server-after-make-frame-hook cursor-animation-color-setter) 392 | (unless (daemonp) 393 | ;; 如果不是 daemon, 确保大部分有关 face 的设置生效后再设置残影的颜色. 394 | (add-hook 'emacs-startup-hook cursor-animation-color-setter 90)) 395 | (when (eq this-command 'eval-buffer) 396 | ;; 若要测试本文件, 直接将其拷贝到单独的 buffer, 然后执行 `eval-buffer'. 397 | (funcall cursor-animation-color-setter))))) 398 | ``` 399 | 400 |
401 | 402 | 效果图: 403 | 404 | ![gif](gif/ani.gif) 405 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate embed_resource; 2 | 3 | fn main() { 4 | embed_resource::compile("src/res.rc"); 5 | cc::Build::new() 6 | .define("UNICODE", "1") 7 | .file("src/ShellContextMenu.cpp") 8 | .file("src/Utils.cpp") 9 | .file("src/shellmenu_wrap.cpp") 10 | .compile("foo"); 11 | } 12 | -------------------------------------------------------------------------------- /gif/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynnux/pop-select/b2ffbef7a905480bdaed71e8aa3ae1c0a9c12e2d/gif/1.gif -------------------------------------------------------------------------------- /gif/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynnux/pop-select/b2ffbef7a905480bdaed71e8aa3ae1c0a9c12e2d/gif/2.gif -------------------------------------------------------------------------------- /gif/ani.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynnux/pop-select/b2ffbef7a905480bdaed71e8aa3ae1c0a9c12e2d/gif/ani.gif -------------------------------------------------------------------------------- /gif/shell.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynnux/pop-select/b2ffbef7a905480bdaed71e8aa3ae1c0a9c12e2d/gif/shell.gif -------------------------------------------------------------------------------- /src/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | IndentWidth: 4 4 | SortIncludes: false 5 | ObjCBlockIndentWidth: 4 6 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynnux/pop-select/b2ffbef7a905480bdaed71e8aa3ae1c0a9c12e2d/src/CMakeLists.txt -------------------------------------------------------------------------------- /src/ShellContextMenu.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynnux/pop-select/b2ffbef7a905480bdaed71e8aa3ae1c0a9c12e2d/src/ShellContextMenu.cpp -------------------------------------------------------------------------------- /src/ShellContextMenu.h: -------------------------------------------------------------------------------- 1 | // grepWin - regex search and replace for Windows 2 | 3 | // Copyright (C) 2007-2008, 2011-2015, 2021 - Stefan Kueng 4 | 5 | // This program is free software; you can redistribute it and/or 6 | // modify it under the terms of the GNU General Public License 7 | // as published by the Free Software Foundation; either version 2 8 | // of the License, or (at your option) any later version. 9 | 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program; if not, write to the Free Software Foundation, 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | // 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include "Utils.h" 24 | 25 | // ReSharper disable once CppInconsistentNaming 26 | class CIShellFolderHook; 27 | class CSearchInfo; 28 | struct LineData; 29 | 30 | class CShellContextMenu 31 | { 32 | public: 33 | void SetObjects(const std::vector &strVector// , const std::vector &lineVector 34 | ); 35 | UINT ShowContextMenu(HWND hWnd, POINT pt, bool addExtraHead = false); 36 | CShellContextMenu(); 37 | virtual ~CShellContextMenu(); 38 | 39 | private: 40 | size_t m_nItems; 41 | BOOL bDelete; 42 | HMENU m_menu; 43 | IShellFolder * m_psfFolder; 44 | LPITEMIDLIST * m_pidlArray; 45 | int m_pidlArrayItems; 46 | std::vector m_strVector; 47 | // std::vector m_lineVector; 48 | 49 | static void InvokeCommand(LPCONTEXTMENU pContextMenu, UINT idCommand); 50 | BOOL GetContextMenu(HWND hWnd, void **ppContextMenu, int &iMenuType); 51 | static LRESULT CALLBACK HookWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 52 | static void FreePIDLArray(LPITEMIDLIST *pidlArray, int nItems); 53 | 54 | static HRESULT CALLBACK dfmCallback(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam); 55 | 56 | CIShellFolderHook *m_pFolderHook; 57 | 58 | friend class CIShellFolderHook; 59 | }; 60 | 61 | // ReSharper disable once CppInconsistentNaming 62 | class CIShellFolderHook : public IShellFolder 63 | { 64 | public: 65 | CIShellFolderHook(LPSHELLFOLDER sf, CShellContextMenu *pShellContextMenu) 66 | { 67 | sf->AddRef(); 68 | m_iSf = sf; 69 | m_pShellContextMenu = pShellContextMenu; 70 | } 71 | 72 | virtual ~CIShellFolderHook() { m_iSf->Release(); } 73 | 74 | // IUnknown methods -------- 75 | HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, __RPC__deref_out void **ppvObject) override { return m_iSf->QueryInterface(riid, ppvObject); } 76 | ULONG STDMETHODCALLTYPE AddRef() override { return m_iSf->AddRef(); } 77 | ULONG STDMETHODCALLTYPE Release() override { return m_iSf->Release(); } 78 | 79 | // IShellFolder methods ---- 80 | HRESULT STDMETHODCALLTYPE GetUIObjectOf(HWND hwndOwner, UINT cidl, LPCITEMIDLIST *apidl, REFIID riid, UINT *rgfReserved, void **ppv) override; 81 | 82 | HRESULT STDMETHODCALLTYPE CompareIDs(LPARAM lParam, __RPC__in PCUIDLIST_RELATIVE pidl1, __RPC__in PCUIDLIST_RELATIVE pidl2) override { return m_iSf->CompareIDs(lParam, pidl1, pidl2); } 83 | HRESULT STDMETHODCALLTYPE GetDisplayNameOf(__RPC__in_opt PCUITEMID_CHILD pidl, SHGDNF uFlags, __RPC__out STRRET *pName) override { return m_iSf->GetDisplayNameOf(pidl, uFlags, pName); } 84 | HRESULT STDMETHODCALLTYPE CreateViewObject(__RPC__in_opt HWND hwndOwner, __RPC__in REFIID riid, __RPC__deref_out_opt void **ppv) override { return m_iSf->CreateViewObject(hwndOwner, riid, ppv); } 85 | HRESULT STDMETHODCALLTYPE EnumObjects(__RPC__in_opt HWND hwndOwner, SHCONTF grfFlags, __RPC__deref_out_opt IEnumIDList **ppenumIDList) override { return m_iSf->EnumObjects(hwndOwner, grfFlags, ppenumIDList); } 86 | HRESULT STDMETHODCALLTYPE BindToObject(__RPC__in PCUIDLIST_RELATIVE pidl, __RPC__in_opt IBindCtx *pbc, __RPC__in REFIID riid, __RPC__deref_out_opt void **ppv) override { return m_iSf->BindToObject(pidl, pbc, riid, ppv); } 87 | HRESULT STDMETHODCALLTYPE ParseDisplayName(__RPC__in_opt HWND hwnd, __RPC__in_opt IBindCtx *pbc, __RPC__in_string LPWSTR pszDisplayName, __reserved ULONG *pchEaten, __RPC__deref_out_opt PIDLIST_RELATIVE *ppidl, __RPC__inout_opt ULONG *pdwAttributes) override { return m_iSf->ParseDisplayName(hwnd, pbc, pszDisplayName, pchEaten, ppidl, pdwAttributes); } 88 | HRESULT STDMETHODCALLTYPE GetAttributesOf(UINT cidl, __RPC__in_ecount_full_opt(cidl) PCUITEMID_CHILD_ARRAY apidl, __RPC__inout SFGAOF *rgfInOut) override { return m_iSf->GetAttributesOf(cidl, apidl, rgfInOut); } 89 | HRESULT STDMETHODCALLTYPE BindToStorage(__RPC__in PCUIDLIST_RELATIVE pidl, __RPC__in_opt IBindCtx *pbc, __RPC__in REFIID riid, __RPC__deref_out_opt void **ppv) override { return m_iSf->BindToStorage(pidl, pbc, riid, ppv); } 90 | HRESULT STDMETHODCALLTYPE SetNameOf(__in_opt HWND hwnd, __in PCUITEMID_CHILD pidl, __in LPCWSTR pszName, __in SHGDNF uFlags, __deref_opt_out PITEMID_CHILD *ppidlOut) override { return m_iSf->SetNameOf(hwnd, pidl, pszName, uFlags, ppidlOut); } 91 | 92 | protected: 93 | LPSHELLFOLDER m_iSf; 94 | CShellContextMenu *m_pShellContextMenu; 95 | }; 96 | -------------------------------------------------------------------------------- /src/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "Utils.h" 2 | 3 | void* g_hInst = 0; 4 | #define AUTOOUTOFSCOPE_TOKEN_PASTEx(x, y) x##y 5 | #define AUTOOUTOFSCOPE_TOKEN_PASTE(x, y) AUTOOUTOFSCOPE_TOKEN_PASTEx(x, y) 6 | 7 | template 8 | class AutoOutOfScope 9 | { 10 | public: 11 | AutoOutOfScope(T& destructor) 12 | : m_destructor(destructor) 13 | { 14 | } 15 | ~AutoOutOfScope() { m_destructor(); } 16 | // no copies of this class, also to avoid compiler warnings 17 | AutoOutOfScope(const AutoOutOfScope&) = delete; 18 | AutoOutOfScope& operator=(const AutoOutOfScope& tmp) = delete; 19 | 20 | private: 21 | T& m_destructor; 22 | }; 23 | 24 | #define AUTOOUTOFSCOPE__INTERNAL(Destructor, counter) \ 25 | auto AUTOOUTOFSCOPE_TOKEN_PASTE(auto_func_, counter) = [&]() { Destructor; }; \ 26 | AutoOutOfScope AUTOOUTOFSCOPE_TOKEN_PASTE(auto_, counter)(AUTOOUTOFSCOPE_TOKEN_PASTE(auto_func_, counter)); 27 | 28 | #define OnOutOfScope(Destructor) AUTOOUTOFSCOPE__INTERNAL(Destructor, __COUNTER__) 29 | 30 | bool WriteAsciiStringToClipboard(const wchar_t* sClipdata, HWND hOwningWnd) 31 | { 32 | // OpenClipboard may fail if another application has opened the clipboard. 33 | // Try up to 8 times, with an initial delay of 1 ms and an exponential back off 34 | // for a maximum total delay of 127 ms (1+2+4+8+16+32+64). 35 | for (int attempt = 0; attempt < 8; ++attempt) 36 | { 37 | if (attempt > 0) 38 | { 39 | ::Sleep(1 << (attempt - 1)); 40 | } 41 | if (OpenClipboard(hOwningWnd)) 42 | { 43 | OnOutOfScope( 44 | CloseClipboard();); 45 | EmptyClipboard(); 46 | size_t sLen = wcslen(sClipdata); 47 | HGLOBAL hClipboardData = GlobalAlloc(GMEM_MOVEABLE, (sLen + 1) * sizeof(wchar_t)); 48 | if (hClipboardData) 49 | { 50 | wchar_t* pchData = static_cast(GlobalLock(hClipboardData)); 51 | if (pchData) 52 | { 53 | wcscpy_s(pchData, sLen + 1, sClipdata); 54 | GlobalUnlock(hClipboardData); 55 | if (SetClipboardData(CF_UNICODETEXT, hClipboardData) == nullptr) 56 | return true; 57 | } 58 | } 59 | } 60 | } 61 | 62 | return false; 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/Utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | #include 4 | #include 5 | 6 | extern void* g_hInst; 7 | 8 | #define IDS_OPENCONTAININGFOLDER L"Open containing folder" 9 | #define IDS_COPYPATH L"Copy path to clipboard" 10 | #define IDS_COPYPATHS L"Copy paths to clipboard" 11 | #define IDS_COPYFILENAME L"Copy filename to clipboard" 12 | #define IDS_COPYFILENAMES L"Copy filenames to clipboard" 13 | 14 | class CSearchInfo{ 15 | public: 16 | std::wstring filePath; 17 | }; 18 | 19 | inline std::wstring TranslatedString(void*, const wchar_t* s){ 20 | return s; 21 | } 22 | 23 | bool WriteAsciiStringToClipboard(const wchar_t* sClipdata, HWND hOwningWnd); 24 | 25 | 26 | class CStringUtils { 27 | public: 28 | static inline std::string& trim(std::string& s) { return ltrim(rtrim(s)); } 29 | 30 | static inline std::string& trim(std::string& s, 31 | const std::string& trimchars) { 32 | return ltrim(rtrim(s, trimchars), trimchars); 33 | } 34 | static inline std::string& trim(std::string& s, wint_t trimchar) { 35 | return ltrim(rtrim(s, trimchar), trimchar); 36 | } 37 | 38 | // trim from start 39 | static inline std::string& ltrim(std::string& s) { 40 | s.erase(s.begin(), std::find_if(s.begin(), s.end(), 41 | [](wint_t c) { return !iswspace(c); })); 42 | return s; 43 | } 44 | static inline std::string& ltrim(std::string& s, 45 | const std::string& trimchars) { 46 | s.erase(s.begin(), 47 | std::find_if(s.begin(), s.end(), [&trimchars](wint_t c) { 48 | return trimchars.find(static_cast(c)) == 49 | std::string::npos; 50 | })); 51 | return s; 52 | } 53 | static inline std::string& ltrim(std::string& s, wint_t trimchar) { 54 | s.erase(s.begin(), 55 | std::find_if(s.begin(), s.end(), 56 | [&trimchar](wint_t c) { return c != trimchar; })); 57 | return s; 58 | } 59 | 60 | // trim from end 61 | static inline std::string& rtrim(std::string& s) { 62 | s.erase(std::find_if(s.rbegin(), s.rend(), 63 | [](wint_t c) { return !iswspace(c); }) 64 | .base(), 65 | s.end()); 66 | return s; 67 | } 68 | static inline std::string& rtrim(std::string& s, 69 | const std::string& trimchars) { 70 | s.erase(std::find_if(s.rbegin(), s.rend(), 71 | [&trimchars](wint_t c) { 72 | return trimchars.find(static_cast(c)) == 73 | std::string::npos; 74 | }) 75 | .base(), 76 | s.end()); 77 | return s; 78 | } 79 | static inline std::string& rtrim(std::string& s, wint_t trimchar) { 80 | s.erase(std::find_if(s.rbegin(), s.rend(), 81 | [&trimchar](wint_t c) { return c != trimchar; }) 82 | .base(), 83 | s.end()); 84 | return s; 85 | } 86 | 87 | // trim from both ends 88 | static inline std::wstring& trim(std::wstring& s) { 89 | return ltrim(rtrim(s)); 90 | } 91 | static inline std::wstring& trim(std::wstring& s, 92 | const std::wstring& trimchars) { 93 | return ltrim(rtrim(s, trimchars), trimchars); 94 | } 95 | static inline std::wstring& trim(std::wstring& s, wint_t trimchar) { 96 | return ltrim(rtrim(s, trimchar), trimchar); 97 | } 98 | 99 | // trim from start 100 | static inline std::wstring& ltrim(std::wstring& s) { 101 | s.erase(s.begin(), std::find_if(s.begin(), s.end(), 102 | [](wint_t c) { return !iswspace(c); })); 103 | return s; 104 | } 105 | static inline std::wstring& ltrim(std::wstring& s, 106 | const std::wstring& trimchars) { 107 | s.erase(s.begin(), 108 | std::find_if(s.begin(), s.end(), [&trimchars](wint_t c) { 109 | return trimchars.find(c) == std::wstring::npos; 110 | })); 111 | return s; 112 | } 113 | static inline std::wstring& ltrim(std::wstring& s, wint_t trimchar) { 114 | s.erase(s.begin(), 115 | std::find_if(s.begin(), s.end(), 116 | [&trimchar](wint_t c) { return c != trimchar; })); 117 | return s; 118 | } 119 | 120 | // trim from end 121 | static inline std::wstring& rtrim(std::wstring& s) { 122 | s.erase(std::find_if(s.rbegin(), s.rend(), 123 | [](wint_t c) { return !iswspace(c); }) 124 | .base(), 125 | s.end()); 126 | return s; 127 | } 128 | static inline std::wstring& rtrim(std::wstring& s, 129 | const std::wstring& trimchars) { 130 | s.erase(std::find_if(s.rbegin(), s.rend(), 131 | [&trimchars](wint_t c) { 132 | return trimchars.find(c) == std::wstring::npos; 133 | }) 134 | .base(), 135 | s.end()); 136 | return s; 137 | } 138 | static inline std::wstring& rtrim(std::wstring& s, wint_t trimchar) { 139 | s.erase(std::find_if(s.rbegin(), s.rend(), 140 | [&trimchar](wint_t c) { return c != trimchar; }) 141 | .base(), 142 | s.end()); 143 | return s; 144 | } 145 | }; 146 | 147 | #endif /* UTILS_H */ 148 | -------------------------------------------------------------------------------- /src/beacon.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | use crate::to_wstring; 3 | use emacs::{defun, Result}; 4 | use winapi::ctypes::*; 5 | use winapi::shared::basetsd::*; 6 | use winapi::shared::minwindef::*; 7 | use winapi::shared::windef::*; 8 | #[cfg(debug_assertions)] 9 | use winapi::um::debugapi::*; 10 | #[cfg(debug_assertions)] 11 | use winapi::um::errhandlingapi::*; 12 | use winapi::um::libloaderapi::*; 13 | use winapi::um::processthreadsapi::*; 14 | use winapi::um::wingdi::*; 15 | use winapi::um::winuser::*; 16 | 17 | static mut UI_THREAD_ID: DWORD = 0; 18 | 19 | static mut BEACON_WND: HWND = std::ptr::null_mut(); 20 | static mut BEACON_WIDTH: DWORD = 300; 21 | static mut BEACON_HEIGHT: DWORD = 20; 22 | static mut BEACON_R: u8 = 255; 23 | static mut BEACON_G: u8 = 0; 24 | static mut BEACON_B: u8 = 0; 25 | static mut BEACON_ARG_X: usize = 0; 26 | static mut BEACON_ARG_Y: usize = 0; 27 | static mut BEACON_ARG_DURATION: usize = 0; 28 | static mut BEACON_DURATION_LEFT_COUNT: isize = 0; // 百分比 29 | static mut TIMER_BEACON_DURATION_STEP: usize = 100; 30 | const WM_SHOW_BEACON: UINT = WM_USER + 0x0001; 31 | const WM_BEACON_SET_SIZE: UINT = WM_USER + 0x0002; 32 | const TIMER_BEACON_DURATION: UINT_PTR = 1; 33 | const TIMER_BEACON_DELAY: UINT_PTR = 2; 34 | 35 | // cursor animation,参考https://github.com/manateelazycat/holo-layer/blob/master/plugin/cursor_animation.py 36 | const WM_SHOW_ANIMATION: UINT = WM_USER + 0x0003; 37 | static mut ANIMATION_WND: HWND = std::ptr::null_mut(); 38 | static mut ANIMATION_ARG_DURATION: usize = 0; 39 | static mut ANIMATION_ARG_DURATION_STEP: usize = 100; 40 | 41 | const TIMER_ANIMATION_DURATION: UINT_PTR = 3; 42 | static mut ANIMATION_DURATION_LEFT_COUNT: isize = 0; // 百分比 43 | static mut cursor_color: HBRUSH = std::ptr::null_mut(); 44 | static mut cursor_color_r_old: u8 = 0; 45 | static mut cursor_color_g_old: u8 = 0; 46 | static mut cursor_color_b_old: u8 = 0; 47 | static mut cursor_color_r: u8 = 255; 48 | static mut cursor_color_g: u8 = 255; 49 | static mut cursor_color_b: u8 = 255; 50 | static mut cursor_info_x: usize = 0; 51 | static mut cursor_info_y: usize = 0; 52 | static mut cursor_info_w: usize = 0; 53 | static mut cursor_info_h: usize = 0; 54 | 55 | static mut cursor_info_prev_x: usize = 0; 56 | static mut cursor_info_prev_y: usize = 0; 57 | static mut cursor_info_prev_w: usize = 0; 58 | static mut cursor_info_prev_h: usize = 0; 59 | static mut cursor_animation_percent: f32 = 1.0; 60 | 61 | static mut cursor_start_x: usize = 0; 62 | static mut cursor_start_y: usize = 0; 63 | static mut cursor_end_x: usize = 0; 64 | static mut cursor_end_y: usize = 0; 65 | 66 | pub unsafe extern "system" fn window_proc( 67 | hwnd: HWND, 68 | msg: UINT, 69 | wparam: WPARAM, 70 | lparam: LPARAM, 71 | ) -> LRESULT { 72 | if msg == WM_DESTROY { 73 | // PostQuitMessage(0); 74 | return 0; 75 | } 76 | 77 | return DefWindowProcW(hwnd, msg, wparam, lparam); 78 | } 79 | fn on_beacon_timer(left: isize) { 80 | unsafe { 81 | let left = (left as f64 / (BEACON_ARG_DURATION as f64 / TIMER_BEACON_DURATION_STEP as f64) 82 | * BEACON_WIDTH as f64) as i32; 83 | #[cfg(debug_assertions)] 84 | { 85 | let right = BEACON_WIDTH as i32 - left; 86 | let msg = format!("left:{}, right:{}", left, right); 87 | OutputDebugStringW(to_wstring(&msg).as_slice().as_ptr()); 88 | } 89 | 90 | let mut vertex: [TRIVERTEX; 2] = std::mem::zeroed(); 91 | vertex[0].x = 0; 92 | vertex[0].y = 0; 93 | vertex[0].Red = (BEACON_R as u16) << 8; 94 | vertex[0].Green = (BEACON_G as u16) << 8; 95 | vertex[0].Blue = (BEACON_B as u16) << 8; 96 | vertex[0].Alpha = 0x0000; 97 | 98 | vertex[1].x = left as i32; 99 | vertex[1].y = BEACON_HEIGHT as i32; 100 | vertex[1].Red = 0; 101 | vertex[1].Green = 0; 102 | vertex[1].Blue = 0; 103 | vertex[1].Alpha = 0x0000; 104 | 105 | // Create a GRADIENT_RECT structure that 106 | // references the TRIVERTEX vertices. 107 | let mut g_rect: GRADIENT_RECT = std::mem::zeroed(); 108 | g_rect.UpperLeft = 0; 109 | g_rect.LowerRight = 1; 110 | let hdc = GetDC(BEACON_WND); 111 | if !hdc.is_null() { 112 | GradientFill( 113 | hdc, 114 | &mut vertex as *mut _, 115 | 2, 116 | &mut g_rect as *mut _ as *mut _, 117 | 1, 118 | GRADIENT_FILL_RECT_H, 119 | ); 120 | let bb = GetStockObject(BLACK_BRUSH as i32); 121 | let mut rc: RECT = std::mem::zeroed(); 122 | rc.left = left; 123 | rc.top = 0; 124 | rc.right = BEACON_WIDTH as i32; 125 | rc.bottom = BEACON_HEIGHT as i32; 126 | FillRect(hdc, &rc, bb as *mut _); 127 | ReleaseDC(BEACON_WND, hdc); 128 | } 129 | } 130 | } 131 | 132 | fn show_beacon_wnd() { 133 | unsafe { 134 | if BEACON_WND.is_null() { 135 | return; 136 | } 137 | #[cfg(debug_assertions)] 138 | { 139 | OutputDebugStringA(b"WM_SHOW_BEACON!\0" as *const _ as *const _); 140 | } 141 | ShowWindow(BEACON_WND, SW_SHOW); 142 | 143 | BEACON_DURATION_LEFT_COUNT = 144 | (BEACON_ARG_DURATION as f64 / TIMER_BEACON_DURATION_STEP as f64) as isize; 145 | SetTimer( 146 | BEACON_WND, 147 | TIMER_BEACON_DURATION, 148 | TIMER_BEACON_DURATION_STEP as u32, 149 | None, 150 | ); 151 | // SetForegroundWindow(BEACON_WND); // 这个会发生焦点切换 152 | SetWindowPos( 153 | BEACON_WND, 154 | HWND_TOPMOST, 155 | BEACON_ARG_X as c_int, 156 | BEACON_ARG_Y as c_int, 157 | BEACON_WIDTH as c_int, 158 | BEACON_HEIGHT as c_int, 159 | SWP_SHOWWINDOW, 160 | ); 161 | on_beacon_timer(BEACON_DURATION_LEFT_COUNT); 162 | } 163 | } 164 | 165 | fn create_beacon_wnd() { 166 | unsafe { 167 | if !BEACON_WND.is_null() { 168 | return; 169 | } 170 | let cls_name = to_wstring("rust_window_class"); 171 | let wc = WNDCLASSEXW { 172 | cbSize: std::mem::size_of::() as UINT, 173 | style: CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, 174 | lpfnWndProc: Some(window_proc), 175 | cbClsExtra: 0, 176 | cbWndExtra: 0, 177 | hInstance: GetModuleHandleW(std::ptr::null_mut()) as HINSTANCE, 178 | hIcon: std::ptr::null_mut(), 179 | hCursor: LoadCursorW(std::ptr::null_mut(), IDC_ARROW), 180 | hbrBackground: GetStockObject(WHITE_BRUSH as i32) as HBRUSH, 181 | lpszMenuName: std::ptr::null_mut(), 182 | lpszClassName: cls_name.as_ptr(), 183 | hIconSm: std::ptr::null_mut(), 184 | }; 185 | if RegisterClassExW(&wc) == 0 { 186 | #[cfg(debug_assertions)] 187 | { 188 | OutputDebugStringA(b"RegisterClassEx failed\0".as_ptr() as *const _); 189 | } 190 | } 191 | 192 | let wnd_name = to_wstring("Rust Window"); 193 | // 透明要带WS_EX_LAYERED 194 | let hwnd = CreateWindowExW( 195 | WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE | WS_EX_LAYERED, // 任务栏无标题,显示时无焦点切换 196 | wc.lpszClassName, 197 | wnd_name.as_ptr(), 198 | WS_POPUP, // 调试用| WS_BORDER 199 | 0, 200 | 0, 201 | BEACON_WIDTH as i32, 202 | BEACON_HEIGHT as i32, 203 | std::ptr::null_mut(), 204 | std::ptr::null_mut(), 205 | wc.hInstance, 206 | std::ptr::null_mut(), 207 | ); 208 | if hwnd == std::ptr::null_mut() { 209 | #[cfg(debug_assertions)] 210 | { 211 | let err = GetLastError(); 212 | let msg = format!("err:{}", err); 213 | OutputDebugStringW(to_wstring(&msg).as_slice().as_ptr()); 214 | } 215 | return; 216 | } 217 | // 指定透明色 218 | SetLayeredWindowAttributes( 219 | hwnd, 220 | RGB(0, 0, 0), 221 | 200, // LWA_COLORKEY这个就没效果,是全透明的 222 | LWA_COLORKEY, // 223 | // LWA_ALPHA, 224 | ); 225 | BEACON_WND = hwnd; 226 | } 227 | } 228 | 229 | fn create_animation_wnd() { 230 | unsafe { 231 | if !ANIMATION_WND.is_null() { 232 | return; 233 | } 234 | let cls_name = to_wstring("rust_animation_window_class"); 235 | let wc = WNDCLASSEXW { 236 | cbSize: std::mem::size_of::() as UINT, 237 | style: CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, 238 | lpfnWndProc: Some(window_proc), 239 | cbClsExtra: 0, 240 | cbWndExtra: 0, 241 | hInstance: GetModuleHandleW(std::ptr::null_mut()) as HINSTANCE, 242 | hIcon: std::ptr::null_mut(), 243 | hCursor: LoadCursorW(std::ptr::null_mut(), IDC_ARROW), 244 | hbrBackground: GetStockObject(BLACK_BRUSH as i32) as HBRUSH, 245 | lpszMenuName: std::ptr::null_mut(), 246 | lpszClassName: cls_name.as_ptr(), 247 | hIconSm: std::ptr::null_mut(), 248 | }; 249 | if RegisterClassExW(&wc) == 0 { 250 | #[cfg(debug_assertions)] 251 | { 252 | OutputDebugStringA(b"RegisterClassEx failed\0".as_ptr() as *const _); 253 | } 254 | } 255 | 256 | let wnd_name = to_wstring("Rust Window2"); 257 | // 透明要带WS_EX_LAYERED 258 | let hwnd = CreateWindowExW( 259 | WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE | WS_EX_LAYERED, // 任务栏无标题,显示时无焦点切换 260 | wc.lpszClassName, 261 | wnd_name.as_ptr(), 262 | WS_POPUP, // 调试用| WS_BORDER 263 | 0, 264 | 0, 265 | BEACON_WIDTH as i32, 266 | BEACON_HEIGHT as i32, 267 | std::ptr::null_mut(), 268 | std::ptr::null_mut(), 269 | wc.hInstance, 270 | std::ptr::null_mut(), 271 | ); 272 | if hwnd == std::ptr::null_mut() { 273 | #[cfg(debug_assertions)] 274 | { 275 | let err = GetLastError(); 276 | let msg = format!("err:{}", err); 277 | OutputDebugStringW(to_wstring(&msg).as_slice().as_ptr()); 278 | } 279 | return; 280 | } 281 | 282 | // 指定透明色 283 | SetLayeredWindowAttributes( 284 | hwnd, 285 | RGB(0, 0, 0), 286 | 200, // LWA_COLORKEY这个就没效果,是全透明的 287 | LWA_COLORKEY, // 288 | // LWA_ALPHA, 289 | ); 290 | ANIMATION_WND = hwnd; 291 | 292 | // 直接覆盖整个屏幕 293 | let width = GetSystemMetrics(SM_CXSCREEN); 294 | let height = GetSystemMetrics(SM_CYSCREEN); 295 | SetWindowPos( 296 | ANIMATION_WND, 297 | HWND_TOPMOST, 298 | 0, 299 | 0, 300 | width, 301 | height, 302 | SWP_SHOWWINDOW, 303 | ); 304 | } 305 | } 306 | fn show_animation_wnd() { 307 | unsafe { 308 | if ANIMATION_WND.is_null() { 309 | return; 310 | } 311 | #[cfg(debug_assertions)] 312 | { 313 | OutputDebugStringA(b"WM_SHOW_ANIMATION!\0" as *const _ as *const _); 314 | } 315 | ShowWindow(ANIMATION_WND, SW_SHOW); 316 | 317 | ANIMATION_DURATION_LEFT_COUNT = 318 | (ANIMATION_ARG_DURATION as f64 / ANIMATION_ARG_DURATION_STEP as f64) as isize; 319 | SetTimer( 320 | ANIMATION_WND, 321 | TIMER_ANIMATION_DURATION, 322 | TIMER_BEACON_DURATION_STEP as u32, 323 | None, 324 | ); 325 | 326 | if cursor_color.is_null() 327 | || cursor_color_r_old != cursor_color_r 328 | || cursor_color_g_old != cursor_color_g 329 | || cursor_color_b_old != cursor_color_b 330 | { 331 | if !cursor_color.is_null() { 332 | DeleteObject(cursor_color as *mut _); 333 | cursor_color = std::ptr::null_mut(); 334 | } 335 | cursor_color = CreateSolidBrush(RGB(cursor_color_r, cursor_color_g, cursor_color_b)); 336 | cursor_color_r_old = cursor_color_r; 337 | cursor_color_g_old = cursor_color_g; 338 | cursor_color_b_old = cursor_color_b; 339 | } 340 | 341 | // SetForegroundWindow(ANIMATION_WND); // 这个会发生焦点切换 342 | on_animation_timer(ANIMATION_DURATION_LEFT_COUNT); 343 | } 344 | } 345 | 346 | // from https://docs.rs/qttypes/0.2.9/src/qttypes/lib.rs.html#923 347 | #[repr(C)] 348 | #[derive(Default, Clone, Copy, PartialEq, Debug)] 349 | pub struct QPointF { 350 | pub x: f32, 351 | pub y: f32, 352 | } 353 | impl QPointF { 354 | fn new(x: f32, y: f32) -> QPointF { 355 | QPointF { x, y } 356 | } 357 | } 358 | impl std::ops::Add for QPointF { 359 | type Output = QPointF; 360 | fn add(self, other: QPointF) -> QPointF { 361 | QPointF { 362 | x: self.x + other.x, 363 | y: self.y + other.y, 364 | } 365 | } 366 | } 367 | impl std::ops::Sub for QPointF { 368 | type Output = QPointF; 369 | fn sub(self, other: QPointF) -> QPointF { 370 | QPointF { 371 | x: self.x - other.x, 372 | y: self.y - other.y, 373 | } 374 | } 375 | } 376 | impl std::ops::Mul for QPointF { 377 | type Output = QPointF; 378 | fn mul(self, other: f32) -> Self::Output { 379 | QPointF { 380 | x: self.x * other, 381 | y: self.y * other, 382 | } 383 | } 384 | } 385 | 386 | fn cursor_animation_draw_jelly_cursor( 387 | cs: QPointF, 388 | ce: QPointF, 389 | w: f32, 390 | h: f32, 391 | p: f32, 392 | points: &mut [QPointF; 4], 393 | ) { 394 | let diff = cs - ce; 395 | let diff_x = diff.x; 396 | let diff_y = diff.y; 397 | let w_point = QPointF::new(w, 0.0); 398 | let h_point = QPointF::new(0.0, h); 399 | let wh_point = QPointF::new(w, h); 400 | if diff_x * diff_y > 0.0 { 401 | points[0] = cs; 402 | points[1] = cs + wh_point; 403 | points[2] = ce + wh_point; 404 | points[3] = ce; 405 | } else if diff_x * diff_y < 0.0 { 406 | points[0] = cs + h_point; 407 | points[1] = cs + w_point; 408 | points[2] = ce + w_point; 409 | points[3] = ce + h_point; 410 | } else if diff_x == 0.0 { 411 | if diff_y >= 0.0 { 412 | points[0] = cs + h_point; 413 | points[1] = cs + wh_point; 414 | points[2] = ce + w_point; 415 | points[3] = ce; 416 | } else { 417 | points[0] = cs; 418 | points[1] = cs + w_point; 419 | points[2] = ce + wh_point; 420 | points[3] = ce + h_point; 421 | } 422 | } else if diff_y == 0.0 { 423 | if diff_x >= 0.0 { 424 | points[0] = cs + w_point; 425 | points[1] = cs + wh_point; 426 | points[2] = ce + h_point; 427 | points[3] = ce; 428 | } else { 429 | points[0] = cs; 430 | points[1] = cs + h_point; 431 | points[2] = ce + wh_point; 432 | points[3] = ce + w_point; 433 | } 434 | } 435 | 436 | if p < 0.5 { 437 | points[2] = points[2] * p * 2.0 + points[1] * (1.0 - p * 2.0); 438 | points[3] = points[3] * p * 2.0 + points[0] * (1.0 - p * 2.0); 439 | } else { 440 | points[0] = points[3] * (p - 0.5) * 2.0 + points[0] * (1.0 - (p - 0.5) * 2.0); 441 | points[1] = points[2] * (p - 0.5) * 2.0 + points[1] * (1.0 - (p - 0.5) * 2.0); 442 | } 443 | } 444 | 445 | fn on_animation_timer(left: isize) { 446 | unsafe { 447 | cursor_animation_percent = 1.0 448 | - (left as f32 / (ANIMATION_ARG_DURATION as f32 / ANIMATION_ARG_DURATION_STEP as f32)); 449 | 450 | #[cfg(debug_assertions)] 451 | { 452 | let msg = format!("cursor_animation_percent:{}", cursor_animation_percent); 453 | OutputDebugStringW(to_wstring(&msg).as_slice().as_ptr()); 454 | } 455 | 456 | // https://vc.zhizuobiao.com/vc-18112700241/ 457 | let mut polygon: [QPointF; 4] = std::mem::zeroed(); 458 | let p = cursor_animation_percent; 459 | let cs = QPointF::new(cursor_start_x as f32, cursor_start_y as f32); 460 | let ce = QPointF::new(cursor_end_x as f32, cursor_end_y as f32); 461 | cursor_animation_draw_jelly_cursor( 462 | cs, 463 | ce, 464 | cursor_info_w as f32, 465 | cursor_info_h as f32, 466 | p, 467 | &mut polygon, 468 | ); 469 | 470 | #[cfg(debug_assertions)] 471 | { 472 | let msg = format!( 473 | "0:{:?}, 1:{:?}, 2:{:?}, 3: {:?}", 474 | polygon[0], polygon[1], polygon[2], polygon[3] 475 | ); 476 | OutputDebugStringW(to_wstring(&msg).as_slice().as_ptr()); 477 | } 478 | 479 | let hdc = GetDC(ANIMATION_WND); 480 | 481 | if !hdc.is_null() { 482 | // 强制刷新,不然会有拖影 483 | let bb = GetStockObject(BLACK_BRUSH as i32); 484 | let mut rc: RECT = std::mem::zeroed(); 485 | GetClientRect(ANIMATION_WND, &mut rc); 486 | FillRect(hdc, &rc, bb as *mut _); 487 | 488 | // let bb = GetStockObject(WHITE_BRUSH as i32); 489 | let mut poly: [POINT; 4] = std::mem::zeroed(); 490 | poly[0].x = polygon[0].x as i32; 491 | poly[0].y = polygon[0].y as i32; 492 | poly[1].x = polygon[1].x as i32; 493 | poly[1].y = polygon[1].y as i32; 494 | poly[2].x = polygon[2].x as i32; 495 | poly[2].y = polygon[2].y as i32; 496 | poly[3].x = polygon[3].x as i32; 497 | poly[3].y = polygon[3].y as i32; 498 | 499 | let rgn = CreatePolygonRgn(&poly as *const _, 4, WINDING); 500 | if !rgn.is_null() { 501 | FillRgn(hdc, rgn, cursor_color as *mut _); 502 | DeleteObject(rgn as *mut _); 503 | } else { 504 | // OutputDebugStringA(b"failed rng!\0".as_ptr() as *const _); 505 | } 506 | ReleaseDC(ANIMATION_WND, hdc); 507 | } 508 | } 509 | } 510 | 511 | pub fn becaon_init() { 512 | std::thread::spawn(move || unsafe { 513 | UI_THREAD_ID = GetCurrentThreadId(); 514 | let mut msg = MSG { 515 | hwnd: std::ptr::null_mut(), 516 | message: 0, 517 | wParam: 0, 518 | lParam: 0, 519 | time: 0, 520 | pt: POINT { x: 0, y: 0 }, 521 | }; 522 | loop { 523 | let res = GetMessageW(&mut msg, std::ptr::null_mut(), 0, 0); 524 | if res == 0 || res == -1 { 525 | break; 526 | } 527 | TranslateMessage(&msg); 528 | // 消息不能去window_proc里处理 529 | if msg.message == WM_SHOW_BEACON { 530 | create_beacon_wnd(); 531 | if !BEACON_WND.is_null() { 532 | KillTimer(BEACON_WND, TIMER_BEACON_DURATION); 533 | SetTimer(BEACON_WND, TIMER_BEACON_DELAY, msg.wParam as u32, None); 534 | } else { 535 | #[cfg(debug_assertions)] 536 | { 537 | OutputDebugStringA(b"window is null!\0".as_ptr() as *const _); 538 | } 539 | } 540 | } else if msg.message == WM_SHOW_ANIMATION { 541 | create_animation_wnd(); 542 | if !ANIMATION_WND.is_null() { 543 | KillTimer(ANIMATION_WND, TIMER_ANIMATION_DURATION); 544 | show_animation_wnd(); 545 | } else { 546 | #[cfg(debug_assertions)] 547 | { 548 | OutputDebugStringA(b"animation window is null!\0".as_ptr() as *const _); 549 | } 550 | } 551 | } else if msg.message == WM_BEACON_SET_SIZE { 552 | BEACON_WIDTH = msg.wParam as DWORD; 553 | BEACON_HEIGHT = msg.lParam as DWORD; 554 | if !BEACON_WND.is_null() { 555 | DestroyWindow(BEACON_WND); 556 | BEACON_WND = std::ptr::null_mut(); 557 | } 558 | } else if msg.message == WM_TIMER { 559 | if msg.wParam == TIMER_BEACON_DURATION { 560 | BEACON_DURATION_LEFT_COUNT = BEACON_DURATION_LEFT_COUNT - 1; 561 | if BEACON_DURATION_LEFT_COUNT < 0 { 562 | #[cfg(debug_assertions)] 563 | { 564 | OutputDebugStringA(b"kill timer!\0" as *const _ as *const _); 565 | } 566 | ShowWindow(BEACON_WND, SW_HIDE); 567 | KillTimer(BEACON_WND, TIMER_BEACON_DURATION); 568 | } else { 569 | on_beacon_timer(BEACON_DURATION_LEFT_COUNT); 570 | } 571 | } else if msg.wParam == TIMER_BEACON_DELAY { 572 | KillTimer(BEACON_WND, TIMER_BEACON_DELAY); 573 | show_beacon_wnd(); 574 | } else if msg.wParam == TIMER_ANIMATION_DURATION { 575 | ANIMATION_DURATION_LEFT_COUNT = ANIMATION_DURATION_LEFT_COUNT - 1; 576 | if ANIMATION_DURATION_LEFT_COUNT < 0 { 577 | #[cfg(debug_assertions)] 578 | { 579 | OutputDebugStringA(b"kill timer!\0" as *const _ as *const _); 580 | } 581 | ShowWindow(ANIMATION_WND, SW_HIDE); 582 | KillTimer(ANIMATION_WND, TIMER_ANIMATION_DURATION); 583 | } else { 584 | on_animation_timer(ANIMATION_DURATION_LEFT_COUNT); 585 | } 586 | } 587 | } 588 | DispatchMessageW(&msg); 589 | } 590 | }); 591 | } 592 | 593 | #[defun] 594 | fn set_parameters( 595 | width: usize, 596 | height: usize, 597 | r: u8, 598 | g: u8, 599 | b: u8, 600 | duration_step: usize, 601 | ) -> Result<()> { 602 | unsafe { 603 | PostThreadMessageW( 604 | UI_THREAD_ID, 605 | WM_BEACON_SET_SIZE, 606 | width as WPARAM, 607 | height as LPARAM, 608 | ); 609 | BEACON_R = r; 610 | BEACON_G = g; 611 | BEACON_B = b; 612 | TIMER_BEACON_DURATION_STEP = duration_step; 613 | } 614 | Ok(()) 615 | } 616 | 617 | #[defun] 618 | fn blink(x: usize, y: usize, timer: usize, delay: usize) -> Result<()> { 619 | unsafe { 620 | BEACON_ARG_X = x; 621 | BEACON_ARG_Y = y; 622 | BEACON_ARG_DURATION = timer; 623 | PostThreadMessageW(UI_THREAD_ID, WM_SHOW_BEACON, delay as WPARAM, 0 as LPARAM); 624 | } 625 | Ok(()) 626 | } 627 | 628 | #[defun] 629 | fn animation( 630 | x: usize, 631 | y: usize, 632 | w: usize, 633 | h: usize, 634 | timer: usize, 635 | step: usize, 636 | r: u8, 637 | g: u8, 638 | b: u8, 639 | diff_min: usize, 640 | ) -> Result<()> { 641 | unsafe { 642 | cursor_info_x = x; 643 | cursor_info_y = y; 644 | cursor_info_w = w; 645 | cursor_info_h = h; 646 | // 之前位置初始化,不用创建动画 647 | if cursor_info_prev_x == 0 && cursor_info_prev_y == 0 { 648 | cursor_info_prev_x = x; 649 | cursor_info_prev_y = y; 650 | return Ok(()); 651 | } 652 | 653 | if cursor_info_x != cursor_info_prev_x || cursor_info_y != cursor_info_prev_y { 654 | if cursor_info_x.abs_diff(cursor_info_prev_x) >= diff_min 655 | || cursor_info_y.abs_diff(cursor_info_prev_y) >= diff_min 656 | { 657 | cursor_start_x = cursor_info_prev_x; 658 | cursor_start_y = cursor_info_prev_y; 659 | cursor_end_x = cursor_info_x; 660 | cursor_end_y = cursor_info_y; 661 | cursor_color_r = r; 662 | cursor_color_g = g; 663 | cursor_color_b = b; 664 | ANIMATION_ARG_DURATION = timer; 665 | ANIMATION_ARG_DURATION_STEP = step; 666 | PostThreadMessageW(UI_THREAD_ID, WM_SHOW_ANIMATION, 0 as WPARAM, 0 as LPARAM); 667 | } 668 | cursor_info_prev_x = x; 669 | cursor_info_prev_y = y; 670 | cursor_info_prev_w = w; 671 | cursor_info_prev_h = h; 672 | } 673 | } 674 | Ok(()) 675 | } 676 | 677 | #[defun] 678 | fn animation_update_pos(x: usize, y: usize, w: usize, h: usize) -> Result<()> { 679 | unsafe { 680 | cursor_info_prev_x = x; 681 | cursor_info_prev_y = y; 682 | cursor_info_prev_w = w; 683 | cursor_info_prev_h = h; 684 | } 685 | Ok(()) 686 | } 687 | -------------------------------------------------------------------------------- /src/cmake_build.bat: -------------------------------------------------------------------------------- 1 | cd .. 2 | cd pop-select_build 3 | ninja 4 | -------------------------------------------------------------------------------- /src/gui.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_snake_case)] 3 | 4 | extern crate native_windows_gui as nwg; 5 | use crate::to_wstring; 6 | use crate::transparent::debug_output; 7 | use std::ffi; 8 | use std::os::raw::c_int; 9 | use std::rc::Rc; 10 | use winapi::shared::basetsd::LONG_PTR; 11 | use winapi::shared::minwindef::BOOL; 12 | use winapi::shared::minwindef::*; 13 | use winapi::shared::ntdef::LPCSTR; 14 | use winapi::shared::windef::*; 15 | use winapi::um::libloaderapi::*; 16 | use winapi::um::processthreadsapi::*; 17 | use winapi::um::sysinfoapi::GetVersionExW; 18 | use winapi::um::winbase::lstrcmpiA; 19 | use winapi::um::winnt::LPCWSTR; 20 | use winapi::um::winuser::*; 21 | use winapi::{shared::ntdef::HRESULT, shared::windef::HWND}; 22 | 23 | // hotkey参考 https://blog.csdn.net/x356982611/article/details/16341797 24 | static mut HACCEL: usize = 0; 25 | 26 | pub fn popup( 27 | h_mod: winapi::shared::minwindef::HINSTANCE, 28 | pop_list: Vec, 29 | to_sel: usize, 30 | ) -> Option { 31 | // 确认不是emacs gui线程,所以头次运行经常会失去焦点 32 | // debug_output!(format!("popup tid: {}", winapi::um::processthreadsapi::GetCurrentThreadId())); 33 | if unsafe { HACCEL == 0 } { 34 | unsafe { 35 | HACCEL = 36 | winapi::um::winuser::LoadAcceleratorsA(h_mod, b"IDR_ACCELERATOR1\0".as_ptr() as _) 37 | as usize; 38 | } 39 | 40 | nwg::init().ok()?; 41 | nwg::Font::set_global_family("Segoe UI").ok()?; 42 | } 43 | 44 | let mut window = Default::default(); 45 | let mut list = Default::default(); 46 | let layout = Default::default(); 47 | const LIST_ITEM_HEIGHT: u16 = 23; 48 | nwg::Window::builder() 49 | .size(( 50 | 300, 51 | std::cmp::min( 52 | nwg::Monitor::height(), 53 | LIST_ITEM_HEIGHT as i32 * (pop_list.len() + 1) as i32, // 需要预留一行,不然会有滚动条 54 | ), 55 | )) 56 | .ex_flags(WS_EX_TOOLWINDOW) // 无任务栏窗口 57 | .flags(nwg::WindowFlags::POPUP | nwg::WindowFlags::VISIBLE) 58 | .position((300, 300)) 59 | // .title("Basic example") 60 | .topmost(true) 61 | .center(true) 62 | .build(&mut window) 63 | .ok()?; 64 | 65 | nwg::ListBox::builder() 66 | .collection(pop_list) 67 | .flags(nwg::ListBoxFlags::VISIBLE | nwg::ListBoxFlags::TAB_STOP) 68 | // .focus(true) // 不能设置focus,否则抓不到快捷键 69 | .selected_index(Some(0)) 70 | .parent(&window) 71 | .build(&mut list) 72 | .ok()?; 73 | 74 | nwg::GridLayout::builder() 75 | .parent(&window) 76 | .margin([1, 1, 1, 1]) 77 | .spacing(0) 78 | .child(0, 0, &list) 79 | // .child_item(nwg::GridLayoutItem::new(&hello_button, 0, 1, 1, 2)) 80 | .build(&layout) 81 | .ok()?; 82 | 83 | let window = Rc::new(window); 84 | let list = Rc::new(list); 85 | let list1 = list.clone(); 86 | list.set_selection(Some(to_sel)); 87 | unsafe { 88 | SendMessageW( 89 | list.handle.hwnd().unwrap(), 90 | LB_SETITEMHEIGHT, 91 | 0, 92 | MAKELONG(LIST_ITEM_HEIGHT, 0) as isize, 93 | ); 94 | } 95 | 96 | // 总是遇到弹出的窗口失去焦点的情况,参考Findwindow那样去做 97 | unsafe { 98 | let hwnd = window.handle.hwnd().unwrap(); 99 | let h_fore_wnd = GetForegroundWindow(); 100 | let dw_cur_id = GetCurrentThreadId(); 101 | let dw_fore_id = GetWindowThreadProcessId(h_fore_wnd, std::ptr::null_mut()); 102 | 103 | AttachThreadInput(dw_cur_id, dw_fore_id, TRUE); 104 | ShowWindow(hwnd, SW_SHOWNORMAL); 105 | SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); 106 | SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); 107 | SetForegroundWindow(hwnd); 108 | BringWindowToTop(hwnd); 109 | AttachThreadInput(dw_cur_id, dw_fore_id, FALSE); 110 | } 111 | // hook list也可以,但是头次ctrl+shift+tab没反应 112 | let handler = nwg::bind_raw_event_handler(&window.handle, 0x10000, move |_hwnd, msg, w, _l| { 113 | if msg == WM_COMMAND { 114 | let cmd = winapi::shared::minwindef::LOWORD(w as u32); 115 | const ID_ALT_1: u16 = 40000; 116 | const ID_CTRLTAB: u16 = 40001; 117 | const ID_CTRLTAB_SHIFT: u16 = 40002; 118 | const ID_CTRL_P: u16 = 40003; 119 | const ID_CTRL_N: u16 = 40004; 120 | let mut to_next = false; 121 | let mut to_prev = false; 122 | if cmd == ID_CTRLTAB || cmd == ID_CTRL_N || cmd == ID_ALT_1 { 123 | to_next = true; 124 | } else if ID_CTRLTAB_SHIFT == cmd || ID_CTRL_P == cmd { 125 | to_prev = true; 126 | } 127 | if to_next || to_prev { 128 | let to_sel; 129 | let len = list1.len(); 130 | let cur_sel = list1.selection(); 131 | if len != 0 { 132 | if to_prev { 133 | if let Some(cur) = cur_sel { 134 | let mut cur = cur as isize; 135 | cur -= 1; 136 | if cur < 0 { 137 | cur = len as isize - 1; 138 | } 139 | to_sel = Some(cur as usize); 140 | } else { 141 | to_sel = Some(len - 1); 142 | } 143 | } else { 144 | if let Some(mut cur) = cur_sel { 145 | cur += 1; 146 | if cur >= len { 147 | cur = 0; 148 | } 149 | to_sel = Some(cur); 150 | } else { 151 | to_sel = Some(0); 152 | } 153 | } 154 | list1.set_selection(to_sel); 155 | } 156 | } 157 | } else if msg == WM_KEYUP { 158 | let ctrl = unsafe { GetAsyncKeyState(VK_CONTROL) as i32 & 0x8000 }; 159 | if ctrl == 0 { 160 | nwg::stop_thread_dispatch(); 161 | } 162 | } else if msg == WM_KILLFOCUS { 163 | nwg::stop_thread_dispatch(); 164 | } 165 | None 166 | }) 167 | .ok()?; 168 | 169 | // 拷贝nwg::dispatch_thread_events()代码,添加TranslateAcceleratorW 170 | unsafe { 171 | let mut msg: MSG = std::mem::zeroed(); 172 | while GetMessageW(&mut msg, std::ptr::null_mut(), 0, 0) != 0 { 173 | if TranslateAcceleratorW(msg.hwnd, HACCEL as _, &mut msg) != 0 { 174 | continue; 175 | } 176 | if IsDialogMessageW(GetAncestor(msg.hwnd, GA_ROOT), &mut msg) == 0 { 177 | TranslateMessage(&msg); 178 | DispatchMessageW(&msg); 179 | } 180 | } 181 | } 182 | 183 | nwg::unbind_raw_event_handler(&handler).ok(); 184 | // dbg!(&selection_string); 185 | // *selection_string 186 | list.selection() 187 | } 188 | 189 | pub fn gui_init() { 190 | // 不知道有没有效果,保留吧 191 | unsafe { 192 | let mut dwtimeout: DWORD = u32::MAX as DWORD; 193 | SystemParametersInfoA( 194 | SPI_GETFOREGROUNDLOCKTIMEOUT, 195 | 0, 196 | &mut dwtimeout as *mut _ as *mut _, 197 | 0, 198 | ); 199 | if dwtimeout >= 100 { 200 | SystemParametersInfoA( 201 | SPI_SETFOREGROUNDLOCKTIMEOUT, 202 | 0, 203 | std::ptr::null_mut(), 204 | SPIF_SENDCHANGE | SPIF_UPDATEINIFILE, 205 | ); 206 | } 207 | } 208 | } 209 | 210 | ///////////// 实现win10 dark mode 211 | type DwmSetWindowAttribute = unsafe extern "system" fn( 212 | h_wnd: HWND, 213 | dw_attribute: DWORD, 214 | pv_attribute: LPCVOID, 215 | cb_attribute: DWORD, 216 | ) -> HRESULT; 217 | type SetWindowTheme = 218 | unsafe extern "system" fn(hwnd: HWND, pszSubAppName: LPCWSTR, pszSubIdList: LPCWSTR) -> HRESULT; 219 | 220 | static mut DwmSetWindowAttribute_fn: usize = 0; 221 | static mut SetWindowTheme_fn: usize = 0; 222 | static mut w32_darkmode: Option = None; 223 | static mut w32_build_number: DWORD = 0; 224 | fn w32_applytheme(hwnd: HWND) { 225 | unsafe { 226 | if let Some(wd) = &w32_darkmode { 227 | if SetWindowTheme_fn != 0 { 228 | let pfnSetWindowTheme: SetWindowTheme = std::mem::transmute(SetWindowTheme_fn); 229 | let DARK_MODE_APP_NAME = to_wstring("DarkMode_Explorer"); 230 | pfnSetWindowTheme(hwnd, DARK_MODE_APP_NAME.as_ptr(), std::ptr::null_mut()); 231 | } 232 | if DwmSetWindowAttribute_fn != 0 { 233 | let pfnDwmSetWindowAttribute: DwmSetWindowAttribute = 234 | std::mem::transmute(DwmSetWindowAttribute_fn); 235 | let mut attr = 20; // DWMWA_USE_IMMERSIVE_DARK_MODE; 236 | if w32_build_number < 19041 { 237 | attr = 19; // DWMWA_USE_IMMERSIVE_DARK_MODE_OLD 238 | } 239 | pfnDwmSetWindowAttribute(hwnd, attr, &wd as *const _ as *const _, 4 as u32); 240 | debug_output!(format!("hwnd:{:#X}", hwnd as usize)); 241 | } 242 | } 243 | } 244 | } 245 | 246 | struct EnumData { 247 | pid: DWORD, 248 | ret: Vec, 249 | } 250 | fn enum_windows_item(data: &mut EnumData, hwnd: HWND) -> BOOL { 251 | let mut p: DWORD = 0; 252 | if unsafe { GetWindowThreadProcessId(hwnd, &mut p as *mut _) != 0 } && p == data.pid { 253 | unsafe { 254 | let mut buf: [u8; MAX_PATH] = std::mem::zeroed(); 255 | let len = GetClassNameA(hwnd, &mut buf as *mut _ as *mut _, MAX_PATH as i32); 256 | if (len == 5 && &buf[0..5] == b"Emacs") || (len == 9 && &buf[0..9] == b"ScrollBar") 257 | // 实际也就这两种需要,https://github.com/emacs-mirror/emacs/blob/master/src/w32fns.c 搜索 w32_applytheme 258 | { 259 | data.ret.push(hwnd); 260 | } 261 | } 262 | } 263 | 1 264 | } 265 | unsafe extern "system" fn enum_callback(win_hwnd: HWND, arg: LONG_PTR) -> BOOL { 266 | let pthis = arg as *mut EnumData; 267 | enum_windows_item(&mut *pthis, win_hwnd) 268 | } 269 | fn get_current_process_wnd() -> Option> { 270 | let mut data = EnumData { 271 | pid: unsafe { winapi::um::processthreadsapi::GetCurrentProcessId() }, 272 | ret: Vec::new(), 273 | }; 274 | unsafe { 275 | EnumWindows(Some(enum_callback), &mut data as *mut _ as LONG_PTR); 276 | } 277 | 278 | // scrollbar属于子窗口,还要枚举字窗口 279 | let wd = data.ret.clone(); 280 | for w in wd { 281 | unsafe { 282 | EnumChildWindows(w, Some(enum_callback), &mut data as *mut _ as LONG_PTR); 283 | } 284 | } 285 | if !data.ret.is_empty() { 286 | Some(data.ret) 287 | } else { 288 | None 289 | } 290 | } 291 | 292 | pub fn ensure_all_window_dark_mode() { 293 | unsafe { 294 | if w32_darkmode.is_none() { 295 | use winapi::um::winnt::OSVERSIONINFOW; 296 | let mut osi: OSVERSIONINFOW = std::mem::zeroed(); 297 | osi.dwOSVersionInfoSize = std::mem::size_of::() as u32; 298 | GetVersionExW(&mut osi as *mut _ as *mut _); 299 | w32_build_number = osi.dwBuildNumber; // maj获取win10可能有问题,但buildnumber一般不会错 300 | 301 | let dwmapi_lib = LoadLibraryA(b"dwmapi.dll\0".as_ptr() as *const _); 302 | if !dwmapi_lib.is_null() { 303 | DwmSetWindowAttribute_fn = 304 | GetProcAddress(dwmapi_lib, b"DwmSetWindowAttribute\0".as_ptr() as *const _) 305 | as usize; 306 | } 307 | let uxtheme_lib = LoadLibraryA(b"uxtheme.dll\0".as_ptr() as *const _); 308 | if !uxtheme_lib.is_null() { 309 | SetWindowTheme_fn = 310 | GetProcAddress(uxtheme_lib, b"SetWindowTheme\0".as_ptr() as *const _) as usize; 311 | } 312 | 313 | if SetWindowTheme_fn == 0 || DwmSetWindowAttribute_fn == 0 { 314 | w32_darkmode = Some(0); 315 | } else { 316 | w32_darkmode = Some(1); // TODO; 参考emacs源码可以读注册表判断是否开启dark mode 317 | } 318 | debug_output!(format!("current maj:{}, build:{}, DwmSetWindowAttribute_fn: {:#X}, SetWindowTheme_fn: {:#X}", osi.dwMajorVersion, osi.dwBuildNumber, DwmSetWindowAttribute_fn, SetWindowTheme_fn)); 319 | 320 | // hook emacs,让后面创建的窗口也有效果 321 | hook_CreateWindowExA(); 322 | } 323 | if *w32_darkmode.as_ref().unwrap_or(&0) == 0 { 324 | return; 325 | } 326 | } 327 | 328 | if let Some(windows) = get_current_process_wnd() { 329 | for w in windows { 330 | w32_applytheme(w); 331 | // TODO: 目前主窗口不会即时生效,试了很多方法不能让它重绘,只能elisp里放大重绘了 332 | } 333 | } 334 | } 335 | 336 | // emacs调用的是CreateWindow,但x64dbg里没找到?CreateWindowExA是调用了的。rust的hook库有个跨平台的detour只支持nightly 337 | // 定义在这里找https://github.com/microsoft/Detours/blob/24357c6a5a6bb9025a71050e50b38dbe9c02713a/src/detours.h 338 | // demo https://github.com/DianaNites/detours/blob/master/detours-sys/src/lib.rs 339 | type FnCreateWindowExA = unsafe extern "system" fn( 340 | dwExStyle: DWORD, 341 | lpClassName: LPCSTR, 342 | lpWindowName: LPCSTR, 343 | dwStyle: DWORD, 344 | x: c_int, 345 | y: c_int, 346 | nWidth: c_int, 347 | nHeight: c_int, 348 | hWndParent: HWND, 349 | hMenu: HMENU, 350 | hInstance: HINSTANCE, 351 | lpParam: LPVOID, 352 | ) -> HWND; 353 | 354 | unsafe extern "system" fn CreateWindowExA_detour( 355 | dwExStyle: DWORD, 356 | lpClassName: LPCSTR, 357 | lpWindowName: LPCSTR, 358 | dwStyle: DWORD, 359 | x: c_int, 360 | y: c_int, 361 | nWidth: c_int, 362 | nHeight: c_int, 363 | hWndParent: HWND, 364 | hMenu: HMENU, 365 | hInstance: HINSTANCE, 366 | lpParam: LPVOID, 367 | ) -> HWND { 368 | let org: FnCreateWindowExA = std::mem::transmute(CreateWindowExAOrg); 369 | let ret = org( 370 | dwExStyle, 371 | lpClassName, 372 | lpWindowName, 373 | dwStyle, 374 | x, 375 | y, 376 | nWidth, 377 | nHeight, 378 | hWndParent, 379 | hMenu, 380 | hInstance, 381 | lpParam, 382 | ); 383 | if !ret.is_null() { 384 | // lpClassName可能是atom 385 | if std::mem::size_of::() == 8 { 386 | // 64位,判断高位DWORD是否是0,是的话再判断低DWORD的HIWORD是否是0,是0那就是atom 387 | if (lpClassName as u64 >> 32) & 0xffffffff == 0 { 388 | if winapi::shared::minwindef::HIWORD(lpClassName as DWORD) == 0 { 389 | return ret; 390 | } 391 | } 392 | } else { 393 | // 32位判断HIWORD是否是0,是0那就是atom 394 | if winapi::shared::minwindef::HIWORD(lpClassName as DWORD) == 0 { 395 | return ret; 396 | } 397 | } 398 | 399 | if 0 == lstrcmpiA(lpClassName, b"emacs\0".as_ptr() as _) 400 | || 0 == lstrcmpiA(lpClassName, b"ScrollBar\0".as_ptr() as _) 401 | { 402 | //debug_output!(format!("hook {:#x}", ret as usize)); 403 | w32_applytheme(ret); 404 | } 405 | } 406 | 407 | ret 408 | } 409 | 410 | fn get_module_symbol_address(module: &str, symbol: &str) -> Option { 411 | let module = module 412 | .encode_utf16() 413 | .chain(std::iter::once(0)) 414 | .collect::>(); 415 | let symbol = std::ffi::CString::new(symbol).unwrap(); 416 | unsafe { 417 | let handle = GetModuleHandleW(module.as_ptr()); 418 | match GetProcAddress(handle, symbol.as_ptr()) as usize { 419 | 0 => None, 420 | n => Some(n), 421 | } 422 | } 423 | } 424 | 425 | static mut CreateWindowExAOrg: usize = 0; 426 | 427 | fn hook_CreateWindowExA() { 428 | if let Some(address) = get_module_symbol_address("user32.dll", "CreateWindowExA") { 429 | //let target: FnCreateWindowExA = ; 430 | unsafe { 431 | CreateWindowExAOrg = std::mem::transmute(address); 432 | let tru = &mut CreateWindowExAOrg as *mut _ as *mut *mut ffi::c_void; 433 | let new = CreateWindowExA_detour as *mut ffi::c_void; 434 | 435 | use detours_sys::{ 436 | DetourAttach, DetourTransactionBegin, DetourTransactionCommit, DetourUpdateThread, 437 | }; 438 | DetourTransactionBegin(); 439 | DetourUpdateThread(GetCurrentThread() as _); 440 | DetourAttach(tru, new); 441 | DetourTransactionCommit(); 442 | // Initialize AND enable the detour (the 2nd parameter can also be a closure) 443 | } 444 | } else { 445 | debug_output!(format!("can't get CreateWindowExA!")); 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod beacon; 2 | mod gui; 3 | mod shellmenu; 4 | mod transparent; 5 | 6 | // (module-load "pop_select") 7 | // (message (pop-select/select (vector "aa" "bb" "aa" "bb""aa" "bb""aa" "bb""aa" "bb""aa" "bb""aa" "中文"))) 8 | // (global-set-key (kbd "") (lambda () (interactive)(pop-select/select (vector "aa" "bb" "aa" "bb""aa" "bb""aa" "bb""aa" "bb""aa" "bb""aa" "中文")))) 9 | 10 | // 需要设置环境变量LIBCLANG_PATH E:\Program Files\LLVM\bin\ 11 | use emacs::{ 12 | defun, 13 | Env, 14 | Result, 15 | Vector, // Value 16 | }; 17 | use std::ffi::OsStr; 18 | use std::os::windows::ffi::OsStrExt; 19 | use winapi::um::debugapi::OutputDebugStringW; 20 | 21 | // Emacs won't load the module without this. 22 | emacs::plugin_is_GPL_compatible!(); 23 | 24 | #[emacs::module(separator = "/")] 25 | fn init(_: &Env) -> Result<()> { 26 | gui::gui_init(); 27 | beacon::becaon_init(); 28 | transparent::transparent_init(); 29 | shellmenu::shellmenu_init(); 30 | Ok(()) 31 | } 32 | 33 | #[defun] 34 | fn pop_select(name: Vector, to_sel: usize) -> Result { 35 | let mut v = Vec::new(); 36 | if let Ok(s) = name.size() { 37 | for i in 0..s { 38 | if let Ok(ss) = name.get(i) { 39 | v.push(ss); 40 | } 41 | } 42 | if let Some(s) = gui::popup(unsafe { DLL_MOD }, v, to_sel) { 43 | return Ok(s); 44 | } 45 | } 46 | return Ok(0); 47 | } 48 | static mut DLL_MOD: winapi::shared::minwindef::HINSTANCE = std::ptr::null_mut(); 49 | 50 | #[no_mangle] 51 | #[allow(non_snake_case)] 52 | extern "system" fn DllMain( 53 | hinstAcc: winapi::shared::minwindef::HINSTANCE, 54 | reason: u32, 55 | _: winapi::shared::minwindef::LPVOID, 56 | ) -> i32 { 57 | match reason { 58 | 1 => unsafe { 59 | DLL_MOD = hinstAcc; 60 | }, 61 | 0 => (), 62 | _ => (), 63 | } 64 | 1 65 | } 66 | 67 | fn to_wstring(s: &str) -> Vec { 68 | OsStr::new(s) 69 | .encode_wide() 70 | .chain(std::iter::once(0)) 71 | .collect() 72 | } 73 | 74 | // 让标题和滚动条支持in10的dark mode,emacs28.1版本不支持,最新版本支持,但最新版本有点卡 75 | // https://github.com/emacs-mirror/emacs/blob/1a9175a0de98676ac9aa1dec8f3c5c585ce2a9cd/src/w32fns.c#L11209-L11238 76 | // https://github.com/godotengine/godot-proposals/issues/1868 77 | #[defun] 78 | fn ensure_all_window_dark_mode() -> Result { 79 | crate::gui::ensure_all_window_dark_mode(); 80 | Ok(0) 81 | } 82 | 83 | #[defun] 84 | fn popup_shell_menu(paths: Vector, x: i32, y: i32, show_extra_head: i32) -> Result { 85 | let mut v = Vec::new(); 86 | if let Ok(s) = paths.size() { 87 | for i in 0..s { 88 | if let Ok(ss) = paths.get(i) { 89 | v.push(ss); 90 | } 91 | } 92 | if let Err(e) = crate::shellmenu::pop_shell_menu(v, x, y, show_extra_head) { 93 | let es = format!("popup_shell_menu error: {}", e); 94 | let esw = to_wstring(&es); 95 | unsafe { 96 | OutputDebugStringW(esw.as_ptr()); 97 | } 98 | } 99 | } 100 | Ok(0) 101 | } 102 | 103 | #[defun] 104 | fn shell_copyfiles(paths: Vector) -> Result { 105 | let mut v = Vec::new(); 106 | if let Ok(s) = paths.size() { 107 | for i in 0..s { 108 | if let Ok(ss) = paths.get(i) { 109 | v.push(ss); 110 | } 111 | } 112 | crate::shellmenu::shell_copyfiles(v); 113 | } 114 | Ok(0) 115 | } 116 | 117 | #[defun] 118 | fn shell_cutfiles(paths: Vector) -> Result { 119 | let mut v = Vec::new(); 120 | if let Ok(s) = paths.size() { 121 | for i in 0..s { 122 | if let Ok(ss) = paths.get(i) { 123 | v.push(ss); 124 | } 125 | } 126 | crate::shellmenu::shell_cutfiles(v); 127 | } 128 | Ok(0) 129 | } 130 | 131 | #[defun] 132 | fn shell_pastefiles(path: String) -> Result { 133 | crate::shellmenu::shell_pastefiles(path); 134 | Ok(0) 135 | } 136 | -------------------------------------------------------------------------------- /src/res.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynnux/pop-select/b2ffbef7a905480bdaed71e8aa3ae1c0a9c12e2d/src/res.rc -------------------------------------------------------------------------------- /src/resource.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynnux/pop-select/b2ffbef7a905480bdaed71e8aa3ae1c0a9c12e2d/src/resource.h -------------------------------------------------------------------------------- /src/shellmenu.rs: -------------------------------------------------------------------------------- 1 | extern crate native_windows_gui as nwg; 2 | use nwg::NwgError; 3 | use winapi::shared::minwindef::{TRUE, FALSE}; 4 | use winapi::um::processthreadsapi::GetCurrentThreadId; 5 | use std::cell::RefCell; 6 | use winapi::um::ole2::OleInitialize; 7 | use winapi::{shared::ntdef::LPCWSTR, um::winuser::*}; 8 | // 弹shell窗口,测试发现SetWindowSubclass跨线程不会成功,因为emacs module的运行线程不是gui线程。所以需要创建一个父窗口。 9 | // 再者,按MSDN说明TrackPopupMenuEx的父窗口需要前置,否则点空白处menu不会退出,所以这里是窗口最大化,并且设置成透明1(测试完全透明不行) 10 | 11 | extern "C" { 12 | fn PopupShellMenu( 13 | h: winapi::shared::windef::HWND, 14 | path: *const LPCWSTR, 15 | x: i32, 16 | y: i32, 17 | showExtraHead: i32, 18 | ); 19 | fn CopyPathsToClipboard(path: *const LPCWSTR); 20 | fn CutPathsToClipboard(path: *const LPCWSTR); 21 | fn PasteToPathFromClipboard(path: LPCWSTR); 22 | } 23 | 24 | thread_local! { 25 | // 测试menu的透明父窗口是可以重用的 26 | static SHELL_PARENT_WND: RefCell> = RefCell::new(None); 27 | } 28 | 29 | pub fn shellmenu_init() { 30 | // 复制到进程外需要调用OleInitialize 31 | unsafe { 32 | OleInitialize(std::ptr::null_mut()); 33 | } 34 | } 35 | 36 | pub fn pop_shell_menu( 37 | paths: Vec, 38 | x: i32, 39 | y: i32, 40 | show_extra_head: i32, 41 | ) -> Result<(), NwgError> { 42 | SHELL_PARENT_WND.with(|wnd| { 43 | if (*wnd.borrow()).is_none() { 44 | nwg::init().ok(); // 必须,会注册ngw的类 45 | 46 | let mut window = Default::default(); 47 | if nwg::Window::builder() 48 | .ex_flags(WS_EX_TOOLWINDOW) // 无任务栏窗口 49 | // .flags(nwg::WindowFlags::POPUP | nwg::WindowFlags::VISIBLE) // | 50 | .maximized(true) 51 | .build(&mut window) 52 | .is_ok() 53 | { 54 | crate::transparent::set_transparent_one_frame(1, window.handle.hwnd().unwrap()); 55 | *wnd.borrow_mut() = Some(window); 56 | } 57 | } 58 | 59 | if let Some(window) = &*wnd.borrow() { 60 | unsafe { 61 | ShowWindow(window.handle.hwnd().unwrap(), SW_SHOW); 62 | } 63 | 64 | // 解决首次弹出后点击空白处无效的问题(之前测试发现用了CTRL+TAB后就没这问题,说明仍然是后台弹窗焦点问题) 65 | unsafe { 66 | let hwnd = window.handle.hwnd().unwrap(); 67 | let h_fore_wnd = GetForegroundWindow(); 68 | let dw_cur_id = GetCurrentThreadId(); 69 | let dw_fore_id = GetWindowThreadProcessId(h_fore_wnd, std::ptr::null_mut()); 70 | 71 | AttachThreadInput(dw_cur_id, dw_fore_id, TRUE); 72 | ShowWindow(hwnd, SW_SHOWNORMAL); 73 | SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); 74 | SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); 75 | SetForegroundWindow(hwnd); 76 | BringWindowToTop(hwnd); 77 | AttachThreadInput(dw_cur_id, dw_fore_id, FALSE); 78 | } 79 | 80 | if let Ok(handler) = 81 | nwg::bind_raw_event_handler(&window.handle, 0x10000, move |hwnd, msg, _w, _l| { 82 | if msg == (WM_USER + 1) { 83 | unsafe { 84 | let mut vp = Vec::new(); 85 | for p in &paths { 86 | vp.push(crate::to_wstring(&p)); 87 | } 88 | let mut vpr = Vec::new(); 89 | for p in &vp { 90 | vpr.push(p.as_ptr()); 91 | } 92 | vpr.push(std::ptr::null()); // 以0结尾 93 | PopupShellMenu(hwnd, vpr.as_ptr(), x, y, show_extra_head); 94 | ShowWindow(hwnd, SW_HIDE); 95 | } 96 | nwg::stop_thread_dispatch(); 97 | } 98 | None 99 | }) 100 | { 101 | unsafe { 102 | winapi::um::winuser::PostMessageW( 103 | window.handle.hwnd().unwrap(), 104 | WM_USER + 1, 105 | 0, 106 | 0, 107 | ); 108 | } 109 | nwg::dispatch_thread_events(); 110 | nwg::unbind_raw_event_handler(&handler).ok(); 111 | } 112 | } 113 | }); 114 | Ok(()) 115 | } 116 | 117 | pub fn shell_copyfiles(paths: Vec) { 118 | let mut vp = Vec::new(); 119 | for p in &paths { 120 | vp.push(crate::to_wstring(&p)); 121 | } 122 | let mut vpr = Vec::new(); 123 | for p in &vp { 124 | vpr.push(p.as_ptr()); 125 | } 126 | vpr.push(std::ptr::null()); // 以0结尾 127 | unsafe { 128 | CopyPathsToClipboard(vpr.as_ptr()); 129 | } 130 | } 131 | 132 | pub fn shell_cutfiles(paths: Vec) { 133 | let mut vp = Vec::new(); 134 | for p in &paths { 135 | vp.push(crate::to_wstring(&p)); 136 | } 137 | let mut vpr = Vec::new(); 138 | for p in &vp { 139 | vpr.push(p.as_ptr()); 140 | } 141 | vpr.push(std::ptr::null()); // 以0结尾 142 | unsafe { 143 | CutPathsToClipboard(vpr.as_ptr()); 144 | } 145 | } 146 | 147 | pub fn shell_pastefiles(path: String) { 148 | let p = crate::to_wstring(&path); 149 | unsafe { 150 | PasteToPathFromClipboard(p.as_ptr()); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/shellmenu_wrap.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynnux/pop-select/b2ffbef7a905480bdaed71e8aa3ae1c0a9c12e2d/src/shellmenu_wrap.cpp -------------------------------------------------------------------------------- /src/transparent.rs: -------------------------------------------------------------------------------- 1 | use crate::to_wstring; 2 | use emacs::{defun, Result}; 3 | use std::sync::atomic::AtomicU16; 4 | use std::sync::Arc; 5 | use std::sync::Mutex; 6 | use winapi::shared::ntdef::NULL; 7 | use winapi::shared::winerror::ERROR_SUCCESS; 8 | use winapi::um::errhandlingapi::GetLastError; 9 | use winapi::um::errhandlingapi::SetLastError; 10 | use winapi::um::libloaderapi::*; 11 | use winapi::um::wingdi::*; 12 | extern crate native_windows_gui as nwg; 13 | use std::collections::HashMap; 14 | use std::sync::atomic::Ordering; 15 | use winapi::shared::basetsd::LONG_PTR; 16 | use winapi::shared::minwindef::*; 17 | use winapi::shared::windef::*; 18 | use winapi::um::wingdi::RGB; 19 | use winapi::um::winuser::*; 20 | 21 | // 查看过release,确实把format!给优化了,没有内存分配 22 | macro_rules! debug_output { 23 | ($str:expr) => { 24 | // #[cfg(debug_assertions)] 25 | #[allow(unused_unsafe)] 26 | unsafe { 27 | use winapi::um::debugapi::*; 28 | use winapi::um::processthreadsapi::*; 29 | $str.insert_str( 30 | 0, 31 | &format!( 32 | "pid:{}, tid:{} ", 33 | GetCurrentProcessId(), 34 | GetCurrentThreadId() 35 | ), 36 | ); 37 | let s = to_wstring(&$str); 38 | OutputDebugStringW(s.as_ptr() as *const _); 39 | } 40 | }; 41 | } 42 | pub(crate) use debug_output; // 这样其它mod才能用 43 | 44 | // https://github.com/duilib/duilib/blob/bbc817e0a134cda1dc5be6a38864257649273095/DuiLib/Utils/WndShadow.cpp#L197-L307 45 | // msdn: https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features 46 | pub fn transparent_init() { 47 | unsafe { 48 | let cls_name = to_wstring("EmacsShadowWindow"); 49 | let wc = WNDCLASSEXW { 50 | cbSize: std::mem::size_of::() as UINT, 51 | style: CS_HREDRAW | CS_VREDRAW, 52 | lpfnWndProc: Some(cwndshadow_window_proc), 53 | cbClsExtra: 0, 54 | cbWndExtra: 0, 55 | hInstance: GetModuleHandleW(std::ptr::null_mut()) as HINSTANCE, 56 | hIcon: std::ptr::null_mut(), 57 | hCursor: LoadCursorW(std::ptr::null_mut(), IDC_ARROW), 58 | hbrBackground: GetStockObject(WHITE_BRUSH as i32) as HBRUSH, 59 | lpszMenuName: std::ptr::null_mut(), 60 | lpszClassName: cls_name.as_ptr(), 61 | hIconSm: std::ptr::null_mut(), 62 | }; 63 | if RegisterClassExW(&wc) == 0 { 64 | #[cfg(debug_assertions)] 65 | { 66 | //OutputDebugStringA(b"RegisterClassEx failed\0".as_ptr() as *const _); 67 | } 68 | } 69 | } 70 | } 71 | 72 | struct EnumData { 73 | pid: DWORD, 74 | ret: Vec, 75 | } 76 | fn enum_windows_item(data: &mut EnumData, hwnd: HWND) -> BOOL { 77 | let mut p: DWORD = 0; 78 | if unsafe { GetWindowThreadProcessId(hwnd, &mut p as *mut _) != 0 } && p == data.pid { 79 | unsafe { 80 | let mut buf: [u8; MAX_PATH] = std::mem::zeroed(); 81 | let len = GetClassNameA(hwnd, &mut buf as *mut _ as *mut _, MAX_PATH as i32); 82 | if len == 5 && &buf[0..5] == b"Emacs" { 83 | data.ret.push(hwnd); 84 | } 85 | } 86 | } 87 | 1 88 | } 89 | unsafe extern "system" fn enum_callback(win_hwnd: HWND, arg: LONG_PTR) -> BOOL { 90 | let pthis = arg as *mut EnumData; 91 | enum_windows_item(&mut *pthis, win_hwnd) 92 | } 93 | 94 | struct ShadowData { 95 | hwnd: usize, 96 | width: AtomicU16, // WORD, 97 | height: AtomicU16, 98 | xpos: AtomicU16, 99 | ypos: AtomicU16, 100 | org_winproc: usize, //WNDPROC, 101 | } 102 | type ShadowDataPtr = Arc; 103 | 104 | // emacs命令调用跟gui处理并一定是同一线程,要按多线程来处理! 105 | use lazy_static::lazy_static; 106 | lazy_static! { 107 | static ref SHADOW_WNDOWS: Mutex> = Mutex::new(HashMap::new()); 108 | } 109 | static mut SHADOW_BRUSH: HBRUSH = std::ptr::null_mut(); 110 | static mut SHADOW_DISABLED: bool = false; 111 | 112 | fn set_window_pos(pthis: &ShadowData, parent: Option) { 113 | let x = pthis.xpos.load(Ordering::SeqCst).into(); 114 | let y = pthis.ypos.load(Ordering::SeqCst).into(); 115 | let w = pthis.width.load(Ordering::SeqCst).into(); 116 | let h = pthis.height.load(Ordering::SeqCst).into(); 117 | 118 | unsafe { 119 | if SHADOW_DISABLED { 120 | // 非透明情况下仍然需要更新位置,不然重新透明时窗口会挫位 121 | SetWindowPos( 122 | pthis.hwnd as HWND, 123 | std::ptr::null_mut(), 124 | x, 125 | y, 126 | w, 127 | h, 128 | SWP_HIDEWINDOW | SWP_NOACTIVATE, 129 | ); 130 | } else { 131 | SetWindowPos( 132 | pthis.hwnd as HWND, 133 | HWND_TOPMOST, 134 | x, 135 | y, 136 | w, 137 | h, 138 | SWP_SHOWWINDOW | SWP_NOACTIVATE, // SWP_NOSIZE SWP_NOZORDER 139 | ); 140 | if let Some(hp) = parent { 141 | SetWindowPos( 142 | hp, 143 | HWND_TOPMOST, 144 | 0, 145 | 0, 146 | 0, 147 | 0, 148 | SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE, 149 | ); 150 | } 151 | } 152 | } 153 | } 154 | 155 | fn create_shadow_window(hwd_parent: HWND) -> Option { 156 | unsafe { 157 | debug_output!("create_shadow_window".to_owned()); 158 | let cls_name = to_wstring("EmacsShadowWindow"); 159 | let wnd_name = to_wstring("EmacsShadowWindowName"); 160 | let m_h_wnd = CreateWindowExW( 161 | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_LAYERED, // WS_EX_TOPMOST WS_EX_NOACTIVATE 162 | cls_name.as_ptr(), 163 | wnd_name.as_ptr(), 164 | WS_POPUPWINDOW, // WS_CHILD,//,//WS_VISIBLE, 165 | CW_USEDEFAULT, 166 | 0, 167 | 0, 168 | 0, 169 | // hwd_parent,//; child不行,老是在emacs窗口上面 170 | std::ptr::null_mut(), 171 | std::ptr::null_mut(), 172 | GetModuleHandleW(std::ptr::null_mut()) as HINSTANCE, 173 | NULL, 174 | ); 175 | 176 | if m_h_wnd.is_null() { 177 | debug_output!(format!( 178 | "create_shadow_window CreateWindowExA failed! LastError:{}", 179 | GetLastError() 180 | )); 181 | return None; 182 | } 183 | 184 | let mut rc: RECT = std::mem::zeroed(); 185 | GetWindowRect(hwd_parent, &mut rc as *mut _); // 最大化shadow初始化最下面有些挫位,影响不大 186 | // MapWindowPoints(HWND_DESKTOP, hwd_parent, &mut rc as *mut _ as *mut _, 2); 187 | let x = rc.left; 188 | let y = rc.top; 189 | let w = rc.right - rc.left; 190 | let h = rc.bottom - rc.top; 191 | debug_output!(format!("init rect: x:{}, y: {}, w:{}, h:{}", x, y, w, h)); 192 | let sd = ShadowData { 193 | hwnd: m_h_wnd as usize, 194 | width: AtomicU16::new((w).try_into().unwrap_or(0)), 195 | height: AtomicU16::new((h).try_into().unwrap_or(0)), 196 | xpos: AtomicU16::new(x.try_into().unwrap_or(0)), 197 | ypos: AtomicU16::new(y.try_into().unwrap_or(0)), 198 | org_winproc: 0, 199 | }; 200 | set_window_pos(&sd, Some(hwd_parent)); 201 | 202 | return Some(sd); 203 | } 204 | } 205 | 206 | unsafe extern "system" fn cwndshadow_window_proc( 207 | hwnd: HWND, 208 | msg: u32, 209 | wparam: WPARAM, 210 | lparam: LPARAM, 211 | ) -> LRESULT { 212 | if msg == WM_MOUSEACTIVATE { 213 | return MA_NOACTIVATE as LRESULT; 214 | }; 215 | if msg == WM_PAINT { 216 | debug_output!("child WM_PAINT".to_owned()); 217 | if !SHADOW_BRUSH.is_null() { 218 | let mut ps: PAINTSTRUCT = std::mem::zeroed(); 219 | let hdc = BeginPaint(hwnd, &mut ps as *mut _); 220 | FillRect(hdc, &ps.rcPaint, SHADOW_BRUSH as *mut _); 221 | EndPaint(hwnd, &ps); 222 | } 223 | } 224 | return DefWindowProcA(hwnd, msg, wparam, lparam); 225 | } 226 | 227 | fn show_window(hwnd: HWND, show: bool) { 228 | unsafe { 229 | if show { 230 | if !SHADOW_DISABLED { 231 | ShowWindow(hwnd as HWND, SW_SHOWNA); 232 | } 233 | } else { 234 | ShowWindow(hwnd as HWND, SW_HIDE); 235 | } 236 | } 237 | } 238 | extern "system" fn window_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { 239 | unsafe { 240 | let data; 241 | { 242 | let mut sw = SHADOW_WNDOWS.lock().unwrap(); 243 | if let Some(shadow) = sw.get_mut(&(hwnd as usize)) { 244 | data = shadow.clone(); 245 | } else { 246 | return DefWindowProcW(hwnd, msg, wparam, lparam); 247 | } 248 | } 249 | let pthis = data; 250 | if msg == WM_MOVE { 251 | let x = LOWORD(lparam as DWORD); 252 | let y = HIWORD(lparam as DWORD); 253 | debug_output!(format!("WM_MOVE, x:{}, y:{}", x, y)); 254 | pthis.xpos.store(x, Ordering::SeqCst); 255 | pthis.ypos.store(y, Ordering::SeqCst); 256 | set_window_pos(&pthis, Some(hwnd)); 257 | } 258 | if msg == WM_ERASEBKGND { 259 | // debug_output!("WM_ERASEBKGND".to_owned()); 260 | } 261 | if msg == WM_SIZE { 262 | let w = LOWORD(lparam as DWORD); 263 | let h = HIWORD(lparam as DWORD); 264 | let mut out = format!("WM_SIZE w:{}, h:{}", w, h); 265 | if SIZE_MINIMIZED != wparam { 266 | // 缩小时长宽都成0了,不能保存,否则restore时得不到长宽 267 | pthis.width.store(w, Ordering::SeqCst); 268 | pthis.height.store(h, Ordering::SeqCst); 269 | set_window_pos(&pthis, Some(hwnd)); 270 | } 271 | if SIZE_MAXIMIZED == wparam || SIZE_MINIMIZED == wparam { 272 | if SIZE_MINIMIZED == wparam { 273 | show_window(pthis.hwnd as HWND, false); 274 | out += " SIZE_MINIMIZED"; 275 | } else { 276 | out += " SIZE_MINIMIZED"; 277 | } 278 | } 279 | debug_output!(out); 280 | } 281 | if msg == WM_SHOWWINDOW { 282 | if wparam == 0 { 283 | debug_output!("WM_SHOWWINDOW SW_HIDE".to_owned()); 284 | show_window(pthis.hwnd as HWND, false); 285 | } else { 286 | debug_output!("WM_SHOWWINDOW SHOW".to_owned()); 287 | show_window(pthis.hwnd as HWND, true); 288 | } 289 | } 290 | if msg == WM_PAINT { 291 | debug_output!("parent WM_PAINT".to_owned()); 292 | } 293 | if msg == WM_SETFOCUS { 294 | debug_output!("WM_SETFOCUS".to_owned()); 295 | } 296 | if msg == WM_DESTROY { 297 | debug_output!("WM_DESTROY".to_owned()); 298 | show_window(pthis.hwnd as HWND, false); 299 | DestroyWindow(pthis.hwnd as HWND); 300 | } 301 | if msg == WM_NCDESTROY { 302 | debug_output!("WM_NCDESTROY".to_owned()); 303 | let mut sw = SHADOW_WNDOWS.lock().unwrap(); 304 | sw.remove(&(hwnd as usize)); 305 | } 306 | 307 | return CallWindowProcW( 308 | Some(std::mem::transmute(pthis.org_winproc)), 309 | hwnd, 310 | msg, 311 | wparam, 312 | lparam, 313 | ); 314 | } 315 | } 316 | 317 | fn hook_emacs_windproc(h: HWND) { 318 | unsafe { 319 | { 320 | let sw = SHADOW_WNDOWS.lock().unwrap(); 321 | if sw.contains_key(&(h as usize)) { 322 | return; 323 | } 324 | } 325 | 326 | if let Some(mut sd) = create_shadow_window(h) { 327 | SetLastError(ERROR_SUCCESS); 328 | let result = SetWindowLongPtrW(h, GWLP_WNDPROC, window_proc as isize); 329 | if result == 0 && GetLastError() != ERROR_SUCCESS { 330 | debug_output!("SetWindowLongPtrW failed!".to_owned()); 331 | return; 332 | } 333 | sd.org_winproc = result as usize; 334 | let mut sw = SHADOW_WNDOWS.lock().unwrap(); 335 | if sw.contains_key(&(h as usize)) { 336 | return; 337 | } 338 | sw.insert(h as usize, Arc::new(sd)); 339 | } 340 | } 341 | } 342 | 343 | fn get_current_process_wnd() -> Option> { 344 | let mut data = EnumData { 345 | pid: unsafe { winapi::um::processthreadsapi::GetCurrentProcessId() }, 346 | ret: Vec::new(), 347 | }; 348 | unsafe { 349 | EnumWindows(Some(enum_callback), &mut data as *mut _ as LONG_PTR); 350 | } 351 | if !data.ret.is_empty() { 352 | Some(data.ret) 353 | } else { 354 | None 355 | } 356 | } 357 | 358 | pub fn set_transparent_one_frame(alpha: u8, hwnd: HWND) { 359 | unsafe { 360 | let old_style = GetWindowLongPtrW(hwnd, GWL_EXSTYLE); 361 | if 0 == old_style & WS_EX_LAYERED as LONG_PTR { 362 | SetWindowLongPtrW(hwnd, GWL_EXSTYLE, old_style | WS_EX_LAYERED as LONG_PTR); 363 | } 364 | SetLayeredWindowAttributes( 365 | hwnd, 366 | RGB(40, 44, 52), //RGB(0, 0, 0), 367 | alpha, 368 | LWA_ALPHA, //LWA_COLORKEY,// LWA_ALPHA, 369 | ); 370 | } 371 | } 372 | 373 | fn set_background_transparent_one_frame(hwnd: HWND, r: u8, g: u8, b: u8) { 374 | unsafe { 375 | let old_style = GetWindowLongPtrW(hwnd, GWL_EXSTYLE); 376 | if 0 == old_style & WS_EX_LAYERED as LONG_PTR { 377 | SetWindowLongPtrW(hwnd, GWL_EXSTYLE, old_style | WS_EX_LAYERED as LONG_PTR); 378 | } 379 | SetLayeredWindowAttributes(hwnd, RGB(r, g, b), 0, LWA_COLORKEY); 380 | } 381 | } 382 | 383 | fn enable_shadow_windows(enable: bool, alpha: u8) { 384 | let mut temp = Vec::new(); 385 | { 386 | let sw = SHADOW_WNDOWS.lock().unwrap(); 387 | for (_, s) in sw.iter() { 388 | temp.push(s.hwnd); 389 | } 390 | } 391 | for hwnd in temp { 392 | if enable { 393 | set_transparent_one_frame(alpha, hwnd as HWND); 394 | show_window(hwnd as HWND, true); 395 | } else { 396 | show_window(hwnd as HWND, false); 397 | } 398 | } 399 | } 400 | 401 | // static mut 402 | #[defun] 403 | pub fn set_current_frame(alpha: u8) -> Result<()> { 404 | enable_shadow_windows(false, alpha); 405 | if let Some(hwnd) = get_current_process_wnd() { 406 | set_transparent_one_frame(alpha, hwnd[0]); // 第1个就是当前窗口 407 | } 408 | Ok(()) 409 | } 410 | 411 | #[defun] 412 | pub fn set_all_frame(alpha: u8) -> Result<()> { 413 | enable_shadow_windows(false, alpha); 414 | if let Some(hwnd) = get_current_process_wnd() { 415 | for h in hwnd { 416 | set_transparent_one_frame(alpha, h); 417 | } 418 | } 419 | Ok(()) 420 | } 421 | 422 | static mut ORG_R: u8 = 0; 423 | static mut ORG_G: u8 = 0; 424 | static mut ORG_B: u8 = 0; 425 | 426 | #[defun] 427 | pub fn set_background(alpha: u8, r: u8, g: u8, b: u8) -> Result<()> { 428 | unsafe { 429 | if !SHADOW_BRUSH.is_null() { 430 | if ORG_R != r || ORG_G != g || ORG_B != b { 431 | DeleteObject(SHADOW_BRUSH as *mut _); 432 | SHADOW_BRUSH = CreateSolidBrush(RGB(r, g, b)); 433 | if !SHADOW_BRUSH.is_null() { 434 | ORG_R = r; 435 | ORG_G = g; 436 | ORG_B = b; 437 | } 438 | } 439 | } else { 440 | SHADOW_BRUSH = CreateSolidBrush(RGB(r, g, b)); 441 | } 442 | 443 | if SHADOW_BRUSH.is_null() { 444 | debug_output!("SHADOW_BRUSH.is_null !".to_owned()); 445 | } 446 | let mut enable = true; 447 | let emacs_windows = get_current_process_wnd(); 448 | SHADOW_DISABLED = alpha == 255; 449 | // 先给所有Emacs窗口设置透明颜色 450 | if let Some(hwnd) = &emacs_windows { 451 | for hp in hwnd { 452 | let h = *hp; 453 | if !SHADOW_DISABLED { 454 | hook_emacs_windproc(h); 455 | set_background_transparent_one_frame(h, r, g, b); 456 | } else { 457 | // 目前是TOPMOST实现的文字不透明,背景透明,因此在255不透明时去掉TOPMOST 458 | remove_topmost(h); 459 | set_transparent_one_frame(255, h); 460 | enable = false; 461 | } 462 | } 463 | } 464 | enable_shadow_windows(enable, alpha); 465 | 466 | // 再把emacs置顶一下 467 | if !SHADOW_DISABLED { 468 | if let Some(hwnd) = &emacs_windows { 469 | for hp in hwnd { 470 | SetWindowPos( 471 | *hp, 472 | HWND_TOPMOST, //HWND_TOP 473 | 0, 474 | 0, 475 | 0, 476 | 0, 477 | SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE, 478 | ); 479 | } 480 | } 481 | } 482 | } 483 | Ok(()) 484 | } 485 | 486 | fn remove_topmost(h: HWND) { 487 | unsafe { 488 | SetWindowPos( 489 | h, 490 | HWND_NOTOPMOST, //HWND_TOP 491 | 0, 492 | 0, 493 | 0, 494 | 0, 495 | SWP_NOSIZE | SWP_NOMOVE, 496 | ); 497 | } 498 | } 499 | --------------------------------------------------------------------------------