├── LICENSE.txt ├── README.md ├── iswitchw.ahk └── using-iswitchw.gif /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 John Guidry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iswitchw 2 | ## fast keyboard-driven window switching via AutoHotKey 3 | 4 | When iswitchw is triggered, the titles of all visible windows are shown in a 5 | popup. Start typing to narrow possible matches -- hit enter at any point to 6 | activate the top match. Matches are narrowed using an approximate/'fuzzy' 7 | filtering method similar to tools like [Ido][ido] and [CtrlP][ctrlp]. 8 | 9 | Built and tested using AutoHotkeyL v1.1.11.01 on Windows 7 (x64). 10 | 11 | ### Usage 12 | 13 | iswitchw strives to be as intuitive as possible. 14 | 15 | * `Win + Space` activates iswitchw 16 | * `Esc` cancels at any time 17 | * `Tab` to advance the selected window match 18 | * `Up/Down` arrow keys move through the matches 19 | * `Left/Right` arrow keys move the insert cursor in the search box 20 | * `Home/End` move the insert cursor or match selection depending on context 21 | * `Ctrl + Delete/Backspace` removes a word from the search 22 | * `Ctrl + Left/Right` arrow keys skip forward/backward by the word 23 | * Click a title to activate a window with the mouse 24 | * Any other typing should be passed through to the search 25 | 26 | By default, iswitchw is restricted to a single instance and hides itself from 27 | the tray. Run the script again at any time to replace the running instance. If 28 | you want to quit the script entirely, activate iswitchw with `Win + Space` and 29 | then press `Alt + F4`. 30 | 31 | If you want iswitchw to run when Windows starts, make a shortcut to the 32 | iswitchw.ahk file in the folder `%APPDATA%\Microsoft\Windows\Start 33 | Menu\Programs\Startup`. See [here][start-on-boot] also. 34 | 35 | ### Options 36 | 37 | User configurable options are presented at the top of the ahk script. 38 | 39 | ### Example 40 | 41 | ![iswitchw in action](./using-iswitchw.gif?raw=true) 42 | 43 | ### Todo 44 | 45 | * [ ] Tweak scoring algorithm to work better with long window titles 46 | * [ ] Add better explanations/examples for configuration options 47 | * [ ] Move hotkey binding and options into ini file 48 | 49 | ### History 50 | 51 | Original inspiration provided by the creators of the [iswitchb][iswitchb] 52 | package for the Emacs editor. 53 | 54 | 2004/10/10, CREATED by keyboardfreak [[link][hist1]] 55 | 2008/07/03, MODIFIED by ezuk [[link][hist2]] 56 | 2011/06/11, MODIFIED by jixiuf [[link][hist3]] 57 | 2013/08/23, MODIFIED by dirtyrottenscoundrel [[link][hist4]] 58 | 59 | My thanks to the previous contributors to this script. Without your work to 60 | lean on and learn from, I would have never started. My primary goals were to 61 | remove accumulated cruft and publish a version that could be easily forked. 62 | Previous implementations depended heavily on subroutines storing shared program 63 | state in undesignated globals. 64 | 65 | Gui and global variables (including user configurable options) were trimmed to 66 | a more reasonable minimum. Window icon display code was fixed for 64-bit 67 | versions of Windows. Approximate search matching a la ido/CtrlP was added. Code 68 | length was reduced significantly. When possible, I attempted to avoid old style 69 | AHK idioms (e.g. EnvAdd, a%index%) in favor of code that would be more 70 | readable to the majority of programmers. 71 | 72 | [ido]: http://www.emacswiki.org/emacs/InteractivelyDoThings 73 | [ctrlp]: http://kien.github.io/ctrlp.vim/ 74 | [start-on-boot]: http://windows.microsoft.com/en-us/windows-vista/run-a-program-automatically-when-windows-starts 75 | [iswitchb]: http://www.gnu.org/software/emacs/manual/html_node/emacs/Iswitchb.html 76 | [hist1]: http://www.autohotkey.com/forum/viewtopic.php?t=1040 77 | [hist2]: http://www.autohotkey.com/forum/viewtopic.php?t=33353 78 | [hist3]: https://github.com/jixiuf/my_autohotkey_scripts/blob/master/ahk_scripts/iswitchw-plus.ahk 79 | [hist4]: https://github.com/dirtyrottenscoundrel/iswitchw 80 | -------------------------------------------------------------------------------- /iswitchw.ahk: -------------------------------------------------------------------------------- 1 | ;---------------------------------------------------------------------- 2 | ; 3 | ; User configuration 4 | ; 5 | #SingleInstance force 6 | #NoTrayIcon 7 | 8 | ; Window titles containing any of the listed substrings are filtered out from 9 | ; the initial list of windows presented when iswitchw is activated. Can be 10 | ; useful for things like hiding improperly configured tool windows or screen 11 | ; capture software during demos. 12 | filters := [] 13 | 14 | ; Set this to true to update the list of windows every time the search is 15 | ; updated. This is usually not necessary and creates additional overhead, so 16 | ; it is disabled by default. 17 | refreshEveryKeystroke := false 18 | 19 | ; Only re-filter the possible window matches this often (in ms) at maximum. 20 | ; When typing is rapid, no sense in running the search on every keypress. 21 | debounceDuration = 250 22 | 23 | ; When true, filtered matches are scored and the best matches are presented 24 | ; first. This helps account for simple spelling mistakes such as transposed 25 | ; letters e.g. googel, vritualbox. When false, title matches are filtered and 26 | ; presented in the order given by Windows. 27 | scoreMatches := true 28 | 29 | ; Split search string on spaces and use each term as an additional 30 | ; filter expression. 31 | ; 32 | ; For example, you are working on an AHK script: 33 | ; - There are two Explorer windows open to ~/scripts and ~/scripts-old. 34 | ; - Two Vim instances editing scripts in each one of those folders. 35 | ; - A browser window open that mentions scripts in the title 36 | ; 37 | ; This is amongst all the other stuff going on. You bring up iswitchw and 38 | ; begin typing 'scrip'. Now, we have several best matches filtered. But I 39 | ; want the Vim windows only. Now I might be able to make a more unique match by 40 | ; adding the extension of the file open in Vim: 'scripahk'. Pretty good, but 41 | ; really the first thought was process name -- Vim. By breaking on space, we 42 | ; can first filter the list for matches on 'scrip' for 'script' and then, 43 | ; 'vim' in order to match by Vim amongst the remaining windows. 44 | useMultipleTerms := true 45 | 46 | ;---------------------------------------------------------------------- 47 | ; 48 | ; Global variables 49 | ; 50 | ; allwindows - windows on desktop 51 | ; windows - windows in listbox 52 | ; search - the current search string 53 | ; lastSearch - previous search string 54 | ; switcher_id - the window ID of the switcher window 55 | ; debounced - true when its ok to re-filter 56 | ; 57 | ;---------------------------------------------------------------------- 58 | 59 | #NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases. 60 | ; #Warn ; Enable warnings to assist with detecting common errors. 61 | SendMode Input ; Recommended for new scripts due to its superior speed and reliability. 62 | SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory. 63 | 64 | AutoTrim, off 65 | 66 | Gui, +LastFound +AlwaysOnTop -Caption +ToolWindow 67 | Gui, Color, black,black 68 | WinSet, Transparent, 225 69 | Gui, Font, s16 cEEE8D5 bold, Consolas 70 | Gui, Margin, 4, 4 71 | Gui, Add, Text, w100 h30 x6 y8, Search`: 72 | Gui, Add, Edit, w500 h30 x110 y4 gSearchChange vsearch, 73 | Gui, Add, ListView, w854 h510 x4 y40 -VScroll -HScroll -Hdr -Multi Count10 AltSubmit gListViewClick, index|title|proc 74 | 75 | ;---------------------------------------------------------------------- 76 | ; 77 | ; Win+space to activate. 78 | ; 79 | #space:: 80 | 81 | search = 82 | lastSearch = 83 | debounced := true 84 | allwindows := Object() 85 | 86 | GuiControl, , Edit1 87 | Gui, Show, Center, Window Switcher 88 | WinGet, switcher_id, ID, A 89 | WinSet, AlwaysOnTop, On, ahk_id %switcher_id% 90 | ControlFocus, Edit1, ahk_id %switcher_id% 91 | 92 | Loop 93 | { 94 | Input, input, L1, {enter}{esc}{tab}{backspace}{delete}{up}{down}{left}{right}{home}{end}{F4} 95 | 96 | if ErrorLevel = EndKey:enter 97 | { 98 | ActivateWindow() 99 | break 100 | } 101 | else if ErrorLevel = EndKey:escape 102 | { 103 | Gui Cancel 104 | break 105 | } 106 | else if ErrorLevel = EndKey:tab 107 | { 108 | ControlFocus, SysListView321, ahk_id %switcher_id% 109 | 110 | ; When on last row, wrap tab next to top of list. 111 | if LV_GetNext(0) = LV_GetCount() 112 | { 113 | LV_Modify(1, "Select") 114 | LV_Modify(1, "Focus") 115 | } else { 116 | ControlSend, SysListView321, {down}, ahk_id %switcher_id% 117 | } 118 | 119 | continue 120 | } 121 | else if ErrorLevel = EndKey:backspace 122 | { 123 | ControlFocus, Edit1, ahk_id %switcher_id% 124 | 125 | if GetKeyState("Ctrl","P") 126 | chars = {blind}^{Left}{Del} ; courtesy of VxE: http://www.autohotkey.com/board/topic/35458-backward-search-delete-a-word-to-the-left/#entry223378 127 | else 128 | chars = {backspace} 129 | 130 | ControlSend, Edit1, %chars%, ahk_id %switcher_id% 131 | 132 | continue 133 | } 134 | else if ErrorLevel = EndKey:delete 135 | { 136 | ControlFocus, Edit1, ahk_id %switcher_id% 137 | keys := AddModifierKeys("{del}") 138 | ControlSend, Edit1, %keys%, ahk_id %switcher_id% 139 | continue 140 | } 141 | else if ErrorLevel = EndKey:up 142 | { 143 | ControlFocus, SysListView321, ahk_id %switcher_id% 144 | ControlSend, SysListView321, {up}, ahk_id %switcher_id% 145 | continue 146 | } 147 | else if ErrorLevel = EndKey:down 148 | { 149 | ControlFocus, SysListView321, ahk_id %switcher_id% 150 | ControlSend, SysListView321, {down}, ahk_id %switcher_id% 151 | continue 152 | } 153 | else if ErrorLevel = EndKey:left 154 | { 155 | ControlFocus, Edit1, ahk_id %switcher_id% 156 | keys := AddModifierKeys("{left}") 157 | ControlSend, Edit1, %keys%, ahk_id %switcher_id% 158 | continue 159 | } 160 | else if ErrorLevel = EndKey:right 161 | { 162 | ControlFocus, Edit1, ahk_id %switcher_id% 163 | keys := AddModifierKeys("{right}") 164 | ControlSend, Edit1, %keys%, ahk_id %switcher_id% 165 | continue 166 | } 167 | else if ErrorLevel = EndKey:home 168 | { 169 | send % AddModifierKeys("{home}") 170 | continue 171 | } 172 | else if ErrorLevel = EndKey:end 173 | { 174 | send % AddModifierKeys("{end}") 175 | continue 176 | } 177 | else if ErrorLevel = EndKey:F4 178 | { 179 | if GetKeyState("Alt","P") 180 | ExitApp 181 | } 182 | 183 | ControlFocus, Edit1, ahk_id %switcher_id% 184 | Control, EditPaste, %input%, Edit1, ahk_id %switcher_id% 185 | } 186 | 187 | exit 188 | 189 | ;---------------------------------------------------------------------- 190 | ; 191 | ; Runs whenever Edit control is updated 192 | SearchChange: 193 | global debounced, debounceDuration 194 | if (!debounced) { 195 | return 196 | } 197 | debounced := false 198 | SetTimer, Debounce, -%debounceDuration% 199 | 200 | Gui, Submit, NoHide 201 | RefreshWindowList() 202 | return 203 | 204 | ;---------------------------------------------------------------------- 205 | ; 206 | ; Clear debounce check 207 | Debounce: 208 | global debounced := true 209 | 210 | Gui, Submit, NoHide 211 | RefreshWindowList() 212 | return 213 | 214 | ;---------------------------------------------------------------------- 215 | ; 216 | ; Handle mouse click events on the listview 217 | ; 218 | ListViewClick: 219 | if (A_GuiControlEvent = "Normal") { 220 | SendEvent {enter} 221 | } 222 | return 223 | 224 | ;---------------------------------------------------------------------- 225 | ; 226 | ; Checks if user is holding Ctrl and/or Shift, then adds the 227 | ; appropriate modifiers to the key parameter before returning the 228 | ; result. 229 | ; 230 | AddModifierKeys(key) 231 | { 232 | if GetKeyState("Ctrl","P") 233 | key := "^" . key 234 | 235 | if GetKeyState("Shift","P") 236 | key := "+" . key 237 | 238 | return key 239 | } 240 | 241 | ;---------------------------------------------------------------------- 242 | ; 243 | ; Unoptimized array search, returns index of first occurrence or -1 244 | ; 245 | IncludedIn(haystack,needle) 246 | { 247 | Loop % haystack.MaxIndex() 248 | { 249 | item := haystack[a_index] 250 | StringTrimRight, item, item, 0 251 | if item = 252 | continue 253 | 254 | IfInString, needle, %item% 255 | return %a_index% 256 | } 257 | 258 | return -1 259 | } 260 | 261 | ;---------------------------------------------------------------------- 262 | ; 263 | ; Fetch info on all active windows 264 | ; 265 | GetAllWindows() 266 | { 267 | global switcher_id, filters 268 | windows := Object() 269 | 270 | WinGet, id, list, , , Program Manager 271 | Loop, %id% 272 | { 273 | StringTrimRight, wid, id%a_index%, 0 274 | WinGetTitle, title, ahk_id %wid% 275 | StringTrimRight, title, title, 0 276 | 277 | ; FIXME: windows with empty titles? 278 | if title = 279 | continue 280 | 281 | ; don't add the switcher window 282 | if switcher_id = %wid% 283 | continue 284 | 285 | ; don't add titles which match any of the filters 286 | if IncludedIn(filters, title) > -1 287 | continue 288 | 289 | ; replace pipe (|) characters in the window title, 290 | ; because Gui Add uses it for separating listbox items 291 | StringReplace, title, title, |, -, all 292 | 293 | procName := GetProcessName(wid) 294 | windows.Insert({ "id": wid, "title": title, "procName": procName }) 295 | } 296 | 297 | return windows 298 | } 299 | 300 | ;---------------------------------------------------------------------- 301 | ; 302 | ; Refresh the list of windows according to the search criteria 303 | ; 304 | RefreshWindowList() 305 | { 306 | global allwindows, windows 307 | global search, lastSearch, refreshEveryKeystroke 308 | uninitialized := (allwindows.MinIndex() = "") 309 | 310 | if (uninitialized || refreshEveryKeystroke) 311 | allwindows := GetAllWindows() 312 | 313 | currentSearch := Trim(search) 314 | if ((currentSearch == lastSearch) && !uninitialized) { 315 | return 316 | } 317 | 318 | ; When adding to criteria (ie typing, not erasing), refilter 319 | ; the existing filtered list. This should be sane since the even if we enter 320 | ; a new letter at the beginning of the search term, all shown matches should 321 | ; still contain the previous search term as a 'substring'. 322 | useExisting := (StrLen(currentSearch) > StrLen(lastSearch)) 323 | lastSearch := currentSearch 324 | 325 | windows := FilterWindowList(useExisting ? windows : allwindows, currentSearch) 326 | 327 | DrawListView(windows) 328 | } 329 | 330 | ;---------------------------------------------------------------------- 331 | ; 332 | ; Filter window list with given search criteria 333 | ; 334 | FilterWindowList(list, criteria) 335 | { 336 | global scoreMatches, useMultipleTerms 337 | filteredList := Object(), expressions := Object() 338 | lastTermInSearch := criteria, doScore := scoreMatches 339 | 340 | ; If useMultipleTerms, do multiple passes with filter expressions 341 | if (useMultipleTerms) { 342 | StringSplit, searchTerms, criteria, %A_space% 343 | 344 | Loop, %searchTerms0% 345 | { 346 | term := searchTerms%A_index% 347 | lastTermInSearch := term 348 | 349 | expr := BuildFilterExpression(term) 350 | expressions.Insert(expr) 351 | } 352 | } else if (criteria <> "") { 353 | expr := BuildFilterExpression(criteria) 354 | expressions[0] := expr 355 | } 356 | 357 | atNextWindow: 358 | For idx, window in list 359 | { 360 | ; if there is a search string 361 | if criteria <> 362 | { 363 | title := window.title 364 | procName := window.procName 365 | 366 | ; don't add the windows not matching the search string 367 | titleAndProcName = %procName% %title% 368 | 369 | For idx, expr in expressions 370 | { 371 | if RegExMatch(titleAndProcName, expr) = 0 372 | continue atNextWindow 373 | } 374 | } 375 | 376 | doScore := scoreMatches && (criteria <> "") && (lastTermInSearch <> "") 377 | window["score"] := doScore ? StrDiff(lastTermInSearch, titleAndProcName) : 0 378 | 379 | filteredList.Insert(window) 380 | } 381 | 382 | return (doScore ? SortByScore(filteredList) : filteredList) 383 | } 384 | 385 | ;---------------------------------------------------------------------- 386 | ; 387 | ; http://stackoverflow.com/questions/2891514/algorithms-for-fuzzy-matching-strings 388 | ; 389 | ; Matching in the style of Ido/CtrlP 390 | ; 391 | ; Returns: 392 | ; Regex for provided search term 393 | ; 394 | ; Example: 395 | ; explr builds the regex /[^e]*e[^x]*x[^p]*p[^l]*l[^r]*r/i 396 | ; which would match explorer 397 | ; or likewise 398 | ; explr ahk 399 | ; which would match Explorer - ~/autohotkey, but not Explorer - Documents 400 | ; 401 | ; Rules: 402 | ; It is expected that all the letters of the input be in the keyword 403 | ; It is expected that the letters in the input be in the same order in the keyword 404 | ; The list of keywords returned should be presented in a consistent (reproductible) order 405 | ; The algorithm should be case insensitive 406 | ; 407 | BuildFilterExpression(term) 408 | { 409 | expr := "i)" 410 | Loop, parse, term 411 | { 412 | expr .= "[^" . A_LoopField . "]*" . A_LoopField 413 | } 414 | 415 | return expr 416 | } 417 | 418 | ;------------------------------------------------------------------------ 419 | ; 420 | ; Perform insertion sort on list, comparing on each item's score property 421 | ; 422 | SortByScore(list) 423 | { 424 | Loop % list.MaxIndex() - 1 425 | { 426 | i := A_Index+1 427 | window := list[i] 428 | j := i-1 429 | 430 | While j >= 0 and list[j].score > window.score 431 | { 432 | list[j+1] := list[j] 433 | j-- 434 | } 435 | 436 | list[j+1] := window 437 | } 438 | 439 | return list 440 | } 441 | 442 | ;---------------------------------------------------------------------- 443 | ; 444 | ; Activate selected window 445 | ; 446 | ActivateWindow() 447 | { 448 | global windows 449 | 450 | Gui Submit 451 | rowNum:= LV_GetNext(0) 452 | wid := windows[rowNum].id 453 | 454 | ; In some cases, calling WinMinimize minimizes the window, but it retains its 455 | ; focus preventing WinActivate from raising window. 456 | IfWinActive, ahk_id %wid% 457 | { 458 | WinGet, state, MinMax, ahk_id %wid% 459 | if (state = -1) 460 | { 461 | WinRestore, ahk_id %wid% 462 | } 463 | } else { 464 | WinActivate, ahk_id %wid% 465 | } 466 | 467 | LV_Delete() 468 | } 469 | 470 | ;---------------------------------------------------------------------- 471 | ; 472 | ; Add window list to listview 473 | ; 474 | DrawListView(windows) 475 | { 476 | windowCount := windows.MaxIndex() 477 | imageListID := IL_Create(windowCount, 1, 1) 478 | 479 | ; Attach the ImageLists to the ListView so that it can later display the icons: 480 | LV_SetImageList(imageListID, 1) 481 | LV_Delete() 482 | 483 | iconCount = 0 484 | removedRows := Array() 485 | 486 | For idx, window in windows 487 | { 488 | wid := window.id 489 | title := window.title 490 | 491 | ; Retrieves an 8-digit hexadecimal number representing extended style of a window. 492 | WinGet, style, ExStyle, ahk_id %wid% 493 | 494 | ; http://msdn.microsoft.com/en-us/library/windows/desktop/ff700543(v=vs.85).aspx 495 | ; Forces a top-level window onto the taskbar when the window is visible. 496 | WS_EX_APPWINDOW = 0x40000 497 | ; A tool window does not appear in the taskbar or in the dialog that appears when the user presses ALT+TAB. 498 | WS_EX_TOOLWINDOW = 0x80 499 | 500 | isAppWindow := (style & WS_EX_APPWINDOW) 501 | isToolWindow := (style & WS_EX_TOOLWINDOW) 502 | 503 | ; http://msdn.microsoft.com/en-us/library/windows/desktop/ms632599(v=vs.85).aspx#owned_windows 504 | ; An application can use the GetWindow function with the GW_OWNER flag to retrieve a handle to a window's owner. 505 | GW_OWNER = 4 506 | ownerHwnd := DllCall("GetWindow", "uint", wid, "uint", GW_OWNER) 507 | 508 | iconNumber = 509 | 510 | if (isAppWindow or ( !ownerHwnd and !isToolWindow )) 511 | { 512 | ; http://www.autohotkey.com/docs/misc/SendMessageList.htm 513 | WM_GETICON := 0x7F 514 | 515 | ; http://msdn.microsoft.com/en-us/library/windows/desktop/ms632625(v=vs.85).aspx 516 | ICON_BIG := 1 517 | ICON_SMALL2 := 2 518 | ICON_SMALL := 0 519 | 520 | SendMessage, WM_GETICON, ICON_BIG, 0, , ahk_id %wid% 521 | iconHandle := ErrorLevel 522 | 523 | if (iconHandle = 0) 524 | { 525 | SendMessage, WM_GETICON, ICON_SMALL2, 0, , ahk_id %wid% 526 | iconHandle := ErrorLevel 527 | 528 | if (iconHandle = 0) 529 | { 530 | SendMessage, WM_GETICON, ICON_SMALL, 0, , ahk_id %wid% 531 | iconHandle := ErrorLevel 532 | 533 | if (iconHandle = 0) 534 | { 535 | ; http://msdn.microsoft.com/en-us/library/windows/desktop/ms633581(v=vs.85).aspx 536 | ; To write code that is compatible with both 32-bit and 64-bit 537 | ; versions of Windows, use GetClassLongPtr. When compiling for 32-bit 538 | ; Windows, GetClassLongPtr is defined as a call to the GetClassLong 539 | ; function. 540 | iconHandle := DllCall("GetClassLongPtr", "uint", wid, "int", -14) ; GCL_HICON is -14 541 | 542 | if (iconHandle = 0) 543 | { 544 | iconHandle := DllCall("GetClassLongPtr", "uint", wid, "int", -34) ; GCL_HICONSM is -34 545 | 546 | if (iconHandle = 0) { 547 | iconHandle := DllCall("LoadIcon", "uint", 0, "uint", 32512) ; IDI_APPLICATION is 32512 548 | } 549 | } 550 | } 551 | } 552 | } 553 | 554 | if (iconHandle <> 0) 555 | iconNumber := DllCall("ImageList_ReplaceIcon", UInt, imageListID, Int, -1, UInt, iconHandle) + 1 556 | 557 | } else { 558 | WinGetClass, Win_Class, ahk_id %wid% 559 | if Win_Class = #32770 ; fix for displaying control panel related windows (dialog class) that aren't on taskbar 560 | iconNumber := IL_Add(imageListID, "C:\WINDOWS\system32\shell32.dll", 217) ; generic control panel icon 561 | } 562 | 563 | if iconNumber > 0 564 | { 565 | iconCount+=1 566 | LV_Add("Icon" . iconNumber, iconCount, window.procName, title) 567 | } else { 568 | removedRows.Insert(idx) 569 | } 570 | } 571 | 572 | ; Don't draw rows without icons. 573 | windowCount-=removedRows.MaxIndex() 574 | For key,rowNum in removedRows 575 | { 576 | windows.Remove(rowNum) 577 | } 578 | 579 | LV_Modify(1, "Select") 580 | LV_Modify(1, "Focus") 581 | 582 | LV_ModifyCol(1,70) 583 | LV_ModifyCol(2,140) 584 | LV_ModifyCol(3,640) 585 | } 586 | 587 | ;---------------------------------------------------------------------- 588 | ; 589 | ; Get process name for given window id 590 | ; 591 | GetProcessName(wid) 592 | { 593 | WinGet, name, ProcessName, ahk_id %wid% 594 | StringGetPos, pos, name, . 595 | if ErrorLevel <> 1 596 | { 597 | StringLeft, name, name, %pos% 598 | } 599 | 600 | return name 601 | } 602 | 603 | /* 604 | https://gist.github.com/grey-code/5286786 605 | 606 | By Toralf: 607 | Forum thread: http://www.autohotkey.com/board/topic/54987-sift3-super-fast-and-accurate-string-distance-algorithm/#entry345400 608 | 609 | Basic idea for SIFT3 code by Siderite Zackwehdex 610 | http://siderite.blogspot.com/2007/04/super-fast-and-accurate-string-distance.html 611 | 612 | Took idea to normalize it to longest string from Brad Wood 613 | http://www.bradwood.com/string_compare/ 614 | 615 | Own work: 616 | - when character only differ in case, LSC is a 0.8 match for this character 617 | - modified code for speed, might lead to different results compared to original code 618 | - optimized for speed (30% faster then original SIFT3 and 13.3 times faster than basic Levenshtein distance) 619 | */ 620 | 621 | ;---------------------------------------------------------------------- 622 | ; 623 | ; returns a float: between "0.0 = identical" and "1.0 = nothing in common" 624 | ; 625 | StrDiff(str1, str2, maxOffset:=5) { 626 | if (str1 = str2) 627 | return (str1 == str2 ? 0/1 : 0.2/StrLen(str1)) 628 | 629 | if (str1 = "" || str2 = "") 630 | return (str1 = str2 ? 0/1 : 1/1) 631 | 632 | StringSplit, n, str1 633 | StringSplit, m, str2 634 | 635 | ni := 1, mi := 1, lcs := 0 636 | while ((ni <= n0) && (mi <= m0)) { 637 | if (n%ni% == m%mi%) 638 | lcs++ 639 | else if (n%ni% = m%mi%) 640 | lcs += 0.8 641 | else { 642 | Loop, % maxOffset { 643 | oi := ni + A_Index, pi := mi + A_Index 644 | if ((n%oi% = m%mi%) && (oi <= n0)) { 645 | ni := oi, lcs += (n%oi% == m%mi% ? 1 : 0.8) 646 | break 647 | } 648 | if ((n%ni% = m%pi%) && (pi <= m0)) { 649 | mi := pi, lcs += (n%ni% == m%pi% ? 1 : 0.8) 650 | break 651 | } 652 | } 653 | } 654 | 655 | ni++, mi++ 656 | } 657 | 658 | return ((n0 + m0)/2 - lcs) / (n0 > m0 ? n0 : m0) 659 | } 660 | -------------------------------------------------------------------------------- /using-iswitchw.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tvjg/iswitchw/c4fdf27176949d946c11754e78e097ec2012f262/using-iswitchw.gif --------------------------------------------------------------------------------