├── .gitignore ├── APLSource ├── ahk_shifts │ ├── Alt.apla │ ├── Ctrl.apla │ ├── LAlt.apla │ ├── LCtrl.apla │ ├── RAlt.apla │ ├── RCtrl.apla │ ├── LWin.apla │ ├── RWin.apla │ └── CapsLock.apla ├── version.apla ├── OutFile.aplf ├── GUI │ ├── IsClassicInterpreter.aplf │ ├── Send.aplf │ ├── Shifts.aplf │ ├── Suspend.aplf │ ├── Run.aplf │ ├── HandleHTTPRequest.aplf │ └── HandleWSReceive.aplf ├── DevSetup.aplf ├── SaveScript.aplf ├── ReplaceForBoth.aplo ├── AddGroups.aplf ├── LoadSnippets.aplf ├── LoadTSV.aplf ├── LoadKeymaps.aplf ├── Run.aplf ├── MakeScript.aplf ├── LoadGUI.aplf ├── OutPath.aplf ├── Suspend.aplf ├── Prefix.aplf ├── HotKey.aplf ├── LocateClassicExes.aplf ├── StartupFolder.aplf ├── Make.aplf ├── GenerateScript.aplf └── RunTests.aplf ├── packages ├── apl-dependencies.txt └── apl-buildlist.json ├── CI ├── Build.aplf ├── publish_release.ps1 └── Inject-Version.sh ├── scripts ├── prefix.ahk ├── tmp.ahk ├── DE-Alt.ahk ├── APL-de_DE-Ctrl.ahk └── CapsLock-en_UK.ahk ├── docs └── index.md ├── APLAutoHotKey.dyalogbuild ├── snippets └── suspend.ahk ├── GUI ├── classic.html ├── app.css ├── app.js └── app.html ├── keymaps ├── da_DK.tsv ├── de_DE.tsv ├── en_GB.tsv ├── en_US.tsv ├── es_ES.tsv ├── fi_FI.tsv ├── fr_FR.tsv ├── it_IT.tsv ├── sv_SE.tsv └── keymaps.md ├── LICENSE ├── cider.config ├── README.md └── apl-packages ├── APLTreeUtils2.aplc └── WinReg.aplc /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | packages/*/ -------------------------------------------------------------------------------- /APLSource/ahk_shifts/Alt.apla: -------------------------------------------------------------------------------- 1 | ('!'⋄) 2 | -------------------------------------------------------------------------------- /APLSource/ahk_shifts/Ctrl.apla: -------------------------------------------------------------------------------- 1 | ('^'⋄) 2 | -------------------------------------------------------------------------------- /APLSource/ahk_shifts/LAlt.apla: -------------------------------------------------------------------------------- 1 | '!' 2 | -------------------------------------------------------------------------------- /APLSource/ahk_shifts/RCtrl.apla: -------------------------------------------------------------------------------- 1 | '>^' 2 | -------------------------------------------------------------------------------- /APLSource/ahk_shifts/LWin.apla: -------------------------------------------------------------------------------- 1 | 'LWin & ' 2 | -------------------------------------------------------------------------------- /APLSource/ahk_shifts/RWin.apla: -------------------------------------------------------------------------------- 1 | 'RWin & ' 2 | -------------------------------------------------------------------------------- /APLSource/version.apla: -------------------------------------------------------------------------------- 1 | '1.0.0/DEVELOPMENT' 2 | -------------------------------------------------------------------------------- /APLSource/ahk_shifts/CapsLock.apla: -------------------------------------------------------------------------------- 1 | 'CapsLock & ' 2 | -------------------------------------------------------------------------------- /packages/apl-dependencies.txt: -------------------------------------------------------------------------------- 1 | aplteam-WinReg-5.0.4 2 | -------------------------------------------------------------------------------- /APLSource/OutFile.aplf: -------------------------------------------------------------------------------- 1 | OutFile←{ 'APL-',⍵.locale,'-',(⊃,/⍵.shift),'.ahk' } 2 | -------------------------------------------------------------------------------- /APLSource/GUI/IsClassicInterpreter.aplf: -------------------------------------------------------------------------------- 1 | r←IsClassicInterpreter 2 | r←82≡⎕DR'' 3 | -------------------------------------------------------------------------------- /CI/Build.aplf: -------------------------------------------------------------------------------- 1 | Build 2 | ⎕SE.UCMD'DBuild APLAutoHotKey.dyalogbuild -save' 3 | ⎕OFF 4 | -------------------------------------------------------------------------------- /APLSource/DevSetup.aplf: -------------------------------------------------------------------------------- 1 | DevSetup dir 2 | LoadGUI dir 3 | LoadKeymaps dir 4 | LoadSnippets dir 5 | -------------------------------------------------------------------------------- /APLSource/SaveScript.aplf: -------------------------------------------------------------------------------- 1 | size←script SaveScript path;overwrite 2 | overwrite←1 3 | size←script ⎕NPUT path overwrite 4 | -------------------------------------------------------------------------------- /APLSource/ReplaceForBoth.aplo: -------------------------------------------------------------------------------- 1 | ReplaceForBoth←{ 2 | ⍝ Replace ⍵⍵ in ⍵ with ⍺⍺ if both of ⍵⍵ found in ⍵ 3 | opts←⍵⍵ ⋄ Rmv←~∘opts ⋄ Add←⍺⍺∘(,⍥⊆) ⋄ both←∧/⍵⍵∊⍵ 4 | (Add⍤Rmv⍣both)⍵ 5 | } 6 | -------------------------------------------------------------------------------- /APLSource/AddGroups.aplf: -------------------------------------------------------------------------------- 1 | AddGroups←{ 2 | ⍝ ⍵: vtv paths to dyalog exes 3 | ⍝ ←: char vec GroupAdd lines 4 | newline←⎕UCS 10 5 | ⊃,/'GroupAdd "Classic", "ahk_exe '∘,¨⍵,¨⊂'"',⎕UCS 10 6 | } 7 | -------------------------------------------------------------------------------- /APLSource/GUI/Send.aplf: -------------------------------------------------------------------------------- 1 | Send←{ 2 | ⍝ Send WebSocket message ⍵ to HTMLRenderer with ID ⍺ 3 | _←hr.WebSocketSend ⍺ ⍵ 1 1 ⍝ Shy result to prevent the workspace application from opening the APL session 4 | } 5 | -------------------------------------------------------------------------------- /packages/apl-buildlist.json: -------------------------------------------------------------------------------- 1 | { packageID: [ "aplteam-WinReg-5.0.4", "aplteam-APLTreeUtils2-1.1.3", ], principal: [ 1, 0, ], url: [ "https://tatin.dev/", "https://tatin.dev/", ], } 2 | -------------------------------------------------------------------------------- /APLSource/GUI/Shifts.aplf: -------------------------------------------------------------------------------- 1 | Shifts←{ 2 | ⍝ ⍵: options namespace 3 | ⍝ ←: options namespace with API-ready shifts 4 | 0=⍵.⎕NC'shift':'Please select at least 1 shifting key'⎕SIGNAL 400 5 | ⍵.shift←⍵.shift.⎕NL ¯2 6 | ⍵ 7 | } 8 | -------------------------------------------------------------------------------- /APLSource/LoadSnippets.aplf: -------------------------------------------------------------------------------- 1 | LoadSnippets dir;file;name;root;sp 2 | 'snippets'⎕NS ⍬ 3 | root←dir,'/snippets/' 4 | sp←⊃(⎕NINFO ⎕OPT'Wildcard' 1)root,'/*' 5 | (name file)←↓⍉↑{(2⊃⌽⎕NPARTS ⍵)(⍵)}¨sp 6 | ⍎¨name{'snippets.',⍺,'←⊃⎕NGET ''',⍵,''''}¨file 7 | -------------------------------------------------------------------------------- /APLSource/LoadTSV.aplf: -------------------------------------------------------------------------------- 1 | LoadTSV←{ 2 | 82≡⎕DR'':0 2⍴'' ⍝ Cannot use Unicode characters with Classic interpreter, but still need to build on it 3 | opts←⊂'Separator'(⎕UCS 9) 4 | opts,←⊂'EscapeChar' '\' 5 | (⎕CSV ⎕OPT opts)⍵ 6 | } 7 | -------------------------------------------------------------------------------- /APLSource/LoadKeymaps.aplf: -------------------------------------------------------------------------------- 1 | LoadKeymaps dir;km;root 2 | ⍝ ⍵: project root directory 3 | 'keymaps'⎕NS ⍬ 4 | root←dir,'/keymaps/' 5 | km←'en_GB' 'en_US' 'da_DK' 'de_DE' 'es_ES' 'fr_FR' 'it_IT' 'fi_FI' 'sv_SE' 6 | ⍎¨{'keymaps.',⍵,'←LoadTSV ''',root,⍵,'.tsv'''}¨km 7 | -------------------------------------------------------------------------------- /scripts/prefix.ahk: -------------------------------------------------------------------------------- 1 | `:: 2 | { 3 | ih := InputHook("L1 M") 4 | ih.Start() 5 | ih.Wait() 6 | Switch ih.Input { 7 | Case "a": 8 | Send "á" 9 | Case "e": 10 | Send "ŕe" 11 | Case "A": 12 | Send "Á" 13 | } 14 | } 15 | <^>!a::Send "ayyy" 16 | -------------------------------------------------------------------------------- /APLSource/Run.aplf: -------------------------------------------------------------------------------- 1 | Run dev 2 | ⍝ dev 0=production, 1=trap expected events, 2=do not trap events 3 | :If dev>0 4 | DevSetup CiderConfig.HOME 5 | GUI.dev←2-dev 6 | GUI.Run 7 | :Else 8 | GUI.dev←0 9 | GUI.Run 10 | ⎕OFF 11 | :EndIf 12 | -------------------------------------------------------------------------------- /APLSource/GUI/Suspend.aplf: -------------------------------------------------------------------------------- 1 | Suspend←{ 2 | ⍝ ⍵: options namespace 3 | ⍝ ←: options namespace with API-ready suspend option 4 | 0=⍵.⎕NC'suspend':⍵ 5 | 2<≢sus←⍵.suspend.⎕NL ¯2:'Maximum of 2 keys allowed in suspend shortcut'⎕SIGNAL 400 6 | ⍵.suspend←sus 7 | ⍵ 8 | } 9 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## Escape special characters 2 | Some characters have meaning in AutoHotKey scripts and must be treated specially: 3 | 4 | **TODO:** different when denoting output (Send "") vs input? 5 | 6 | |Key|Literal| 7 | |---|---| 8 | |`\``|`VKDC`| 9 | |`!`|`{!}`| 10 | |`;`|`\`;`| 11 | -------------------------------------------------------------------------------- /APLSource/MakeScript.aplf: -------------------------------------------------------------------------------- 1 | MakeScript←{ 2 | ⍝ ⍵ ←→ Options JSON Object 3 | ⍝ ← ←→ (Error number)(Error message) 4 | 5 | script←GenerateScript ⍵ 6 | b←script∘SaveScript¨⊆⍵.outfile 7 | Saved←{'Saved: ',⍵,' (',(⍕⍺),' bytes)
'} 8 | 0(⊃(⊣,'
',⊢)/b Saved¨⊆⍵.outfile) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /APLSource/LoadGUI.aplf: -------------------------------------------------------------------------------- 1 | LoadGUI dir 2 | span←'' 3 | vspan←'v',version,'' 4 | GUI.app_html←(span ⎕R vspan)⊃⎕NGET dir,'/GUI/app.html' 5 | GUI.app_css ←⊃⎕NGET dir,'/GUI/app.css' 6 | GUI.app_js ←⊃⎕NGET dir,'/GUI/app.js' 7 | GUI.classic ←⊃⎕NGET dir,'/GUI/classic.html' 8 | -------------------------------------------------------------------------------- /APLSource/GUI/Run.aplf: -------------------------------------------------------------------------------- 1 | Run;hr;html 2 | hr←⎕NEW⊂'HTMLRenderer' 3 | hr.onWebSocketReceive←'HandleWSReceive' 4 | hr.onHTTPRequest←'HandleHTTPRequest' 5 | hr.InterceptedURLs⍪←↑('/app.css' 1)('/app.js' 1) 6 | html←(1+IsClassicInterpreter)⊃(app_html classic) 7 | hr.HTML←html 8 | hr.(Posn Size)←(1 10)(95 45) 9 | ⎕DQ'hr' 10 | -------------------------------------------------------------------------------- /APLAutoHotKey.dyalogbuild: -------------------------------------------------------------------------------- 1 | DYALOGBUILD : 1.80.0 2 | EXEC : db←1⊃⎕RSI ⍝ DBuild's home 3 | EXEC : ⎕SE.Link.Import 'APLAutoHotKey' (db.path,'APLSource') 4 | EXEC : ⎕SE.Link.Import 'APLAutoHotKey' (db.path,'apl-packages') 5 | EXEC : APLAutoHotKey.DevSetup db.path 6 | EXEC : ⎕EX'db' 7 | LX : APLAutoHotKey.Run 0 8 | TARGET : build/APLAutoHotKey.dws 9 | -------------------------------------------------------------------------------- /APLSource/OutPath.aplf: -------------------------------------------------------------------------------- 1 | OutPath←{ 2 | ⍝ ⍺: char vec :: directory path 3 | ⍝ ⍵: options namespace 4 | ⍝ ←: options namespace with .ahk outfile fully qualified file path 5 | dir←'\',⍨⍣(~'\/'∊⍨⊃⌽⍺)⊢⍺ 6 | of←('/'⎕R'\\') dir,OutFile ⍵ 7 | 0=⍵.⎕NC'outfile':of{⍵.outfile←⊂⍺ ⋄ ⍵}⍵ ⍝ Create outfile if not exists 8 | ⍵.outfile,←⊂of ⍝ Otherwise append outfile 9 | ⍵ 10 | } 11 | -------------------------------------------------------------------------------- /APLSource/GUI/HandleHTTPRequest.aplf: -------------------------------------------------------------------------------- 1 | res ← HandleHTTPRequest (hr evt pr update code msg mime url headers body method);root;http_code;http_msg;mime_type 2 | root ← 'http://dyalog_root/' 3 | body ← ⍎⊃(⎕NL ¯2)∩⊂('_'@('.'∘=))(≢root)↓url 4 | http_code ← 200 ⋄ http_msg ← 'OK' 5 | mime_type ← 'text/javascript' 'text/css' 'text/plain'⊃⍨'.js' '.css'⍳⊢/⎕nparts url 6 | update ← 1 7 | res ← hr evt pr update http_code http_msg mime_type url headers body method 8 | -------------------------------------------------------------------------------- /APLSource/Suspend.aplf: -------------------------------------------------------------------------------- 1 | Suspend←{ 2 | ⍝ ⍵: options namespace 3 | ⍝ ←: snippet to suspend hotkeys with user's hotkey or when classic interpreter window in focus 4 | 0=⍵.⎕NC'suspend':snippets.suspend 5 | txt←snippets.suspend 6 | Esc←('&'⎕R'\\&') ⋄ Join←{⊃(⊣,⍺⍺,⊢)/⍵} 7 | sus←Esc' & 'Join ⍵.suspend 8 | Replace←'%SUSPENDKEYS%'⎕R sus 9 | UnComment←~∘(';'/⍨''≢⍵.suspend) 10 | UnComment Replace snippets.suspend 11 | } 12 | -------------------------------------------------------------------------------- /CI/publish_release.ps1: -------------------------------------------------------------------------------- 1 | # Inject patch number into version 2 | $majmin=cat "APLSource/version.apla" 3 | $patch=git rev-list --count HEAD 4 | $version=$majmin.replace('DEVELOPMENT',$patch) 5 | echo $version | Out-File "APLSource/version.apla" 6 | echo "Building workspace for APLAutoHotKey v$version" 7 | # Build workspace 8 | $env:LOAD = 'CI/Build.aplf'; &"C:\Program Files\Dyalog\Dyalog APL-64 18.2 Unicode\dyalog.exe" 9 | # Publish draft release 10 | -------------------------------------------------------------------------------- /APLSource/Prefix.aplf: -------------------------------------------------------------------------------- 1 | Prefix←{ 2 | map←⍵ 3 | pk←⍺ 4 | nl←⎕UCS 10 5 | r←pk,'::',nl 6 | r,←'{',nl 7 | r,←' ih := InputHook("L1 M")',nl 8 | r,←' uh.Start()',nl 9 | r,←' ih.Wait()',nl 10 | r,←' Switch ih.Input {',nl 11 | 12 | Compose←{ 13 | (k a s)←⍵ 14 | r←' Case "',k,'":',nl 15 | r,←' Send "',a,'":',nl 16 | } 17 | 18 | r,←,(Compose⍤1)map 19 | 20 | } 21 | -------------------------------------------------------------------------------- /snippets/suspend.ahk: -------------------------------------------------------------------------------- 1 | 2 | Check() { 3 | if WinActive("ahk_group Classic") || user_suspend { 4 | Suspend True 5 | } else { 6 | Suspend False 7 | } 8 | } 9 | ToggleSuspend() { 10 | global user_suspend 11 | if user_suspend { 12 | user_suspend := False 13 | } else { 14 | user_suspend := True 15 | } 16 | } 17 | 18 | SetTimer Check, 500 19 | 20 | ;#SuspendExempt 21 | ;%SUSPENDKEYS%::ToggleSuspend 22 | ;#SuspendExempt False 23 | -------------------------------------------------------------------------------- /APLSource/HotKey.aplf: -------------------------------------------------------------------------------- 1 | HotKey←{ 2 | ⍝ ⍵ ←→ kas matrix with columns (Key)(APL Glyph)(Shifted APL Glyph) 3 | ⍝ ← ←→ AutoHotKey script text 4 | nl←⎕UCS 10 5 | Compose←{ 6 | (k a s)←⍵ 7 | r←⍺,' & ',k,'::',nl 8 | r,←'{ if GetKeyState("Shift","P") {',nl 9 | r,←' Send "',s,'"',nl 10 | r,←'} else {',nl 11 | r,←' Send "',a,'"',nl 12 | r,←'}}',nl 13 | r 14 | } 15 | ,⍺(Compose⍤1)⍵ 16 | } 17 | -------------------------------------------------------------------------------- /GUI/classic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

APLAutoHotKey

8 |

9 | APLAutoHotKey is not compatible with Dyalog Classic interpreters. 10 |

11 |

12 | A standalone executable (.exe) and Unicode-compatible workspace (.dws) are available from https://github.com/Dyalog/APLAutoHotKey/releases. 13 |

14 | 15 | 16 | -------------------------------------------------------------------------------- /APLSource/LocateClassicExes.aplf: -------------------------------------------------------------------------------- 1 | exe_path←LocateClassicExes;classic;dyalog;installed;reg_path 2 | ⍝ Uses APLTeam WinReg utility https://tatin.dev/v1/packages/major_versions/aplteam-WinReg 3 | dyalog←'SOFTWARE\Dyalog' 4 | :If ~WinReg.DoesKeyExist dyalog 5 | exe_path←'' 6 | :Else 7 | installed←WinReg.GetAllSubKeyNames dyalog 8 | classic←>⌿'Dyalog APL/W' 'Unicode'∘.(∨/⍷)installed ⍝ Is Dyalog APL/W but NOT Unicode 9 | reg_path←dyalog∘,¨'\',¨classic⌿installed 10 | exe_path←0~⍨WinReg.GetValue¨reg_path,¨⊂'\dyalog' 11 | exe_path,¨←⊂'\dyalog.exe' 12 | :EndIf 13 | -------------------------------------------------------------------------------- /APLSource/StartupFolder.aplf: -------------------------------------------------------------------------------- 1 | StartupFolder←{ 2 | ⍝ ⍵: options namespace 3 | ⍝ ←: options namespace with startup folder (or nothing) appended to outfile 4 | ⍝ Uses APLTeam WinReg utility https://tatin.dev/v1/packages/major_versions/aplteam-WinReg 5 | 0=⍵.⎕NC'startup':⍵ 6 | 0∊⍴⍵.startup:⍵ 7 | GetStartupDir←{ 8 | 'user'≡⍵:WinReg.GetValue'HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders\Startup' 9 | 'all'≡⍵:WinReg.GetValue'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Common Startup' 10 | '' 11 | } 12 | startup←GetStartupDir ⍵.startup 13 | startup OutPath ⍵ 14 | } 15 | -------------------------------------------------------------------------------- /keymaps/da_DK.tsv: -------------------------------------------------------------------------------- 1 | SC029 ⋄ ⌺ 2 | SC002 ¨ ⌶ 3 | SC003 ¯ ⍫ 4 | SC004 < ⍒ 5 | SC005 ≤ ⍋ 6 | SC006 = ⌽ 7 | SC007 ≥ ⍉ 8 | SC008 > ⊖ 9 | SC009 ≠ ⍟ 10 | SC00A ∨ ⍱ 11 | SC00B ∧ ⍲ 12 | SC00C × {!} 13 | SC00D ÷ ⌹ 14 | SC010 ? ⍰ 15 | SC011 ⍵ 16 | SC012 ∊ ⍷ 17 | SC013 ⍴ 18 | SC014 ~ ⍨ 19 | SC015 ↑ 20 | SC016 ↓ 21 | SC017 ⍳ ⍸ 22 | SC018 ○ ⍥ 23 | SC019 * ⍣ 24 | SC01A ← ⍞ 25 | SC01B → ⍬ 26 | SC01E ⍺ 27 | SC01F ⌈ 28 | SC020 ⌊ 29 | SC021 _ ⍛ 30 | SC022 ∇ ⍢ 31 | SC023 ∆ 32 | SC024 ∘ ⍤ 33 | SC025 ' ⌸ 34 | SC026 ⎕ ⌷ 35 | SC027 ⍎ ≡ 36 | SC028 ⍕ ≢ 37 | SC056 ⊢ ⊣ 38 | SC02C ⊂ ⊆ 39 | SC02D ⊃ ⊇ 40 | SC02E ∩ 41 | SC02F ∪ 42 | SC030 ⊥ 43 | SC031 ⊤ ¤ 44 | SC032 | ∥ 45 | SC033 ⍝ ⍪ 46 | SC034 ⍀ ⍙ 47 | SC035 ⌿ ⍠ 48 | -------------------------------------------------------------------------------- /keymaps/de_DE.tsv: -------------------------------------------------------------------------------- 1 | SC029 ⋄ ⌺ 2 | SC002 ¨ ⌶ 3 | SC003 ¯ ⍫ 4 | SC004 < ⍒ 5 | SC005 ≤ ⍋ 6 | SC006 = ⌽ 7 | SC007 ≥ ⍉ 8 | SC008 > ⊖ 9 | SC009 ≠ ⍟ 10 | SC00A ∨ ⍱ 11 | SC00B ∧ ⍲ 12 | SC00C × {!} 13 | SC00D ÷ ⌹ 14 | SC010 ? ⍰ 15 | SC011 ⍵ 16 | SC012 ∊ ⍷ 17 | SC013 ⍴ 18 | SC014 ~ ⍨ 19 | SC015 ↑ 20 | SC016 ↓ 21 | SC017 ⍳ ⍸ 22 | SC018 ○ ⍥ 23 | SC019 * ⍣ 24 | SC01A ← ⍞ 25 | SC01B → ⍬ 26 | SC01E ⍺ 27 | SC01F ⌈ 28 | SC020 ⌊ 29 | SC021 _ ⍛ 30 | SC022 ∇ ⍢ 31 | SC023 ∆ 32 | SC024 ∘ ⍤ 33 | SC025 ' ⌸ 34 | SC026 ⎕ ⌷ 35 | SC027 ⍎ ≡ 36 | SC028 ⍕ ≢ 37 | SC056 ⊢ ⊣ 38 | SC02C ⊂ ⊆ 39 | SC02D ⊃ ⊇ 40 | SC02E ∩ 41 | SC02F ∪ 42 | SC030 ⊥ 43 | SC031 ⊤ ¤ 44 | SC032 | ∥ 45 | SC033 ⍝ ⍪ 46 | SC034 ⍀ ⍙ 47 | SC035 ⌿ ⍠ 48 | -------------------------------------------------------------------------------- /APLSource/GUI/HandleWSReceive.aplf: -------------------------------------------------------------------------------- 1 | req←HandleWSReceive req;gui;evt;data;en;msg;opts;os;as;script;nl;number;message;filename;dir;id;FileName 2 | (gui evt id data en msg)←req 3 | :Trap ∊dev↓0(400 19) 4 | opts←0 ⎕JSON data 5 | as←##.ahk_shifts.⎕NL ¯2 ⍝ Available keys for shift and suspend 6 | opts←as Shifts opts 7 | opts←as Suspend opts 8 | opts←opts.outpath ##.OutPath opts 9 | opts←##.StartupFolder opts 10 | (number message)←##.MakeScript opts 11 | :Case 19 12 | (number message)←1('You do not have permission to write to this directory

',⎕DMX.Message) 13 | :Else 14 | (number message)←⎕DMX.(EN EM) 15 | :EndTrap 16 | id∘Send 1 ⎕JSON(number message) 17 | -------------------------------------------------------------------------------- /keymaps/en_GB.tsv: -------------------------------------------------------------------------------- 1 | SC029 ⋄ ⌺ 2 | SC002 ¨ ⌶ 3 | SC003 ¯ ⍫ 4 | SC004 < ⍒ 5 | SC005 ≤ ⍋ 6 | SC006 = ⌽ 7 | SC007 ≥ ⍉ 8 | SC008 > ⊖ 9 | SC009 ≠ ⍟ 10 | SC00A ∨ ⍱ 11 | SC00B ∧ ⍲ 12 | SC00C × {!} 13 | SC00D ÷ ⌹ 14 | SC010 ? ⍰ 15 | SC011 ⍵ 16 | SC012 ∊ ⍷ 17 | SC013 ⍴ 18 | SC014 ~ ⍨ 19 | SC015 ↑ 20 | SC016 ↓ 21 | SC017 ⍳ ⍸ 22 | SC018 ○ ⍥ 23 | SC019 * ⍣ 24 | SC01A ← ⍞ 25 | SC01B → ⍬ 26 | SC01E ⍺ 27 | SC01F ⌈ 28 | SC020 ⌊ 29 | SC021 _ ⍛ 30 | SC022 ∇ ⍢ 31 | SC023 ∆ 32 | SC024 ∘ ⍤ 33 | SC025 ' ⌸ 34 | SC026 ⎕ ⌷ 35 | SC027 ⍎ ≡ 36 | SC028 ⍕ ≢ 37 | SC02B ⊢ ⊣ 38 | SC056 ⊢ ⊣ 39 | SC02C ⊂ ⊆ 40 | SC02D ⊃ ⊇ 41 | SC02E ∩ 42 | SC02F ∪ 43 | SC030 ⊥ 44 | SC031 ⊤ ¤ 45 | SC032 | ∥ 46 | SC033 ⍝ ⍪ 47 | SC034 ⍀ ⍙ 48 | SC035 ⌿ ⍠ 49 | -------------------------------------------------------------------------------- /keymaps/en_US.tsv: -------------------------------------------------------------------------------- 1 | SC029 ⋄ ⌺ 2 | SC002 ¨ ⌶ 3 | SC003 ¯ ⍫ 4 | SC004 < ⍒ 5 | SC005 ≤ ⍋ 6 | SC006 = ⌽ 7 | SC007 ≥ ⍉ 8 | SC008 > ⊖ 9 | SC009 ≠ ⍟ 10 | SC00A ∨ ⍱ 11 | SC00B ∧ ⍲ 12 | SC00C × {!} 13 | SC00D ÷ ⌹ 14 | SC010 ? ⍰ 15 | SC011 ⍵ 16 | SC012 ∊ ⍷ 17 | SC013 ⍴ 18 | SC014 ~ ⍨ 19 | SC015 ↑ 20 | SC016 ↓ 21 | SC017 ⍳ ⍸ 22 | SC018 ○ ⍥ 23 | SC019 * ⍣ 24 | SC01A ← ⍞ 25 | SC01B → ⍬ 26 | SC01E ⍺ 27 | SC01F ⌈ 28 | SC020 ⌊ 29 | SC021 _ ⍛ 30 | SC022 ∇ ⍢ 31 | SC023 ∆ 32 | SC024 ∘ ⍤ 33 | SC025 ' ⌸ 34 | SC026 ⎕ ⌷ 35 | SC027 ⍎ ≡ 36 | SC028 ⍕ ≢ 37 | SC02B ⊢ ⊣ 38 | SC056 ⊢ ⊣ 39 | SC02C ⊂ ⊆ 40 | SC02D ⊃ ⊇ 41 | SC02E ∩ 42 | SC02F ∪ 43 | SC030 ⊥ 44 | SC031 ⊤ ¤ 45 | SC032 | ∥ 46 | SC033 ⍝ ⍪ 47 | SC034 ⍀ ⍙ 48 | SC035 ⌿ ⍠ 49 | -------------------------------------------------------------------------------- /keymaps/es_ES.tsv: -------------------------------------------------------------------------------- 1 | SC029 ⋄ ⌺ 2 | SC002 ¨ ⌶ 3 | SC003 ¯ ⍫ 4 | SC004 < ⍒ 5 | SC005 ≤ ⍋ 6 | SC006 = ⌽ 7 | SC007 ≥ ⍉ 8 | SC008 > ⊖ 9 | SC009 ≠ ⍟ 10 | SC00A ∨ ⍱ 11 | SC00B ∧ ⍲ 12 | SC00C × {!} 13 | SC00D ÷ ⌹ 14 | SC010 ? ⍰ 15 | SC011 ⍵ 16 | SC012 ∊ ⍷ 17 | SC013 ⍴ 18 | SC014 ~ ⍨ 19 | SC015 ↑ 20 | SC016 ↓ 21 | SC017 ⍳ ⍸ 22 | SC018 ○ ⍥ 23 | SC019 * ⍣ 24 | SC01A ← ⍞ 25 | SC01B → ⍬ 26 | SC01E ⍺ 27 | SC01F ⌈ 28 | SC020 ⌊ 29 | SC021 _ ⍛ 30 | SC022 ∇ ⍢ 31 | SC023 ∆ 32 | SC024 ∘ ⍤ 33 | SC025 ' ⌸ 34 | SC026 ⎕ ⌷ 35 | SC027 ⍎ ≡ 36 | SC028 ⍕ ≢ 37 | SC02B ⊢ ⊣ 38 | SC056 ⊢ ⊣ 39 | SC02C ⊂ ⊆ 40 | SC02D ⊃ ⊇ 41 | SC02E ∩ 42 | SC02F ∪ 43 | SC030 ⊥ 44 | SC031 ⊤ ¤ 45 | SC032 | ∥ 46 | SC033 ⍝ ⍪ 47 | SC034 ⍀ ⍙ 48 | SC035 ⌿ ⍠ 49 | -------------------------------------------------------------------------------- /keymaps/fi_FI.tsv: -------------------------------------------------------------------------------- 1 | SC029 ⋄ ⌺ 2 | SC002 ¨ ⌶ 3 | SC003 ¯ ⍫ 4 | SC004 < ⍒ 5 | SC005 ≤ ⍋ 6 | SC006 = ⌽ 7 | SC007 ≥ ⍉ 8 | SC008 > ⊖ 9 | SC009 ≠ ⍟ 10 | SC00A ∨ ⍱ 11 | SC00B ∧ ⍲ 12 | SC00C × {!} 13 | SC00D ÷ ⌹ 14 | SC010 ? ⍰ 15 | SC011 ⍵ 16 | SC012 ∊ ⍷ 17 | SC013 ⍴ 18 | SC014 ~ ⍨ 19 | SC015 ↑ 20 | SC016 ↓ 21 | SC017 ⍳ ⍸ 22 | SC018 ○ ⍥ 23 | SC019 * ⍣ 24 | SC01A ← ⍞ 25 | SC01B → ⍬ 26 | SC01E ⍺ 27 | SC01F ⌈ 28 | SC020 ⌊ 29 | SC021 _ ⍛ 30 | SC022 ∇ ⍢ 31 | SC023 ∆ ⍙ 32 | SC024 ∘ ⍤ 33 | SC025 ' ⌸ 34 | SC026 ⎕ ⌷ 35 | SC027 ⍎ [ 36 | SC028 ⍕ ] 37 | SC02B ≡ ⍷ 38 | SC056 ⊢ ⊣ 39 | SC02C ⊂ ⊆ 40 | SC02D ⊃ ⊇ 41 | SC02E ∩ 42 | SC02F ∪ 43 | SC030 ⊥ 44 | SC031 ⊤ ¤ 45 | SC032 | ∥ 46 | SC033 ⍝ ⍪ 47 | SC034 ⍀ ≢ 48 | SC035 ⌿ ⍠ 49 | -------------------------------------------------------------------------------- /keymaps/fr_FR.tsv: -------------------------------------------------------------------------------- 1 | SC029 ⋄ ⌺ 2 | SC002 ¨ ⌶ 3 | SC003 ¯ ⍫ 4 | SC004 < ⍒ 5 | SC005 ≤ ⍋ 6 | SC006 = ⌽ 7 | SC007 ≥ ⍉ 8 | SC008 > ⊖ 9 | SC009 ≠ ⍟ 10 | SC00A ∨ ⍱ 11 | SC00B ∧ ⍲ 12 | SC00C × {!} 13 | SC00D ÷ ⌹ 14 | SC010 ? ⍰ 15 | SC011 ⍵ 16 | SC012 ∊ ⍷ 17 | SC013 ⍴ 18 | SC014 ~ ⍨ 19 | SC015 ↑ 20 | SC016 ↓ 21 | SC017 ⍳ ⍸ 22 | SC018 ○ ⍥ 23 | SC019 * ⍣ 24 | SC01A ← ⍞ 25 | SC01B → ⍬ 26 | SC01E ⍺ 27 | SC01F ⌈ 28 | SC020 ⌊ 29 | SC021 _ ⍛ 30 | SC022 ∇ ⍢ 31 | SC023 ∆ ⍙ 32 | SC024 ∘ ⍤ 33 | SC025 ' ⌸ 34 | SC026 ⎕ ⌷ 35 | SC027 ⍎ ≡ 36 | SC028 ⍕ ≢ 37 | SC02B # 38 | SC056 ⊢ ⊣ 39 | SC02C ⊂ ⊆ 40 | SC02D ⊃ ⊇ 41 | SC02E ∩ 42 | SC02F ∪ 43 | SC030 ⊥ 44 | SC031 ⊤ ¤ 45 | SC032 | ∥ 46 | SC033 ⍝ ⍪ 47 | SC034 ⍀ ⍙ 48 | SC035 ⌿ ⍠ 49 | -------------------------------------------------------------------------------- /keymaps/it_IT.tsv: -------------------------------------------------------------------------------- 1 | SC029 ⋄ ⌺ 2 | SC002 ¨ ⌶ 3 | SC003 ¯ ⍫ 4 | SC004 < ⍒ 5 | SC005 ≤ ⍋ 6 | SC006 = ⌽ 7 | SC007 ≥ ⍉ 8 | SC008 > ⊖ 9 | SC009 ≠ ⍟ 10 | SC00A ∨ ⍱ 11 | SC00B ∧ ⍲ 12 | SC00C × {!} 13 | SC00D ÷ ⌹ 14 | SC010 ? ⍰ 15 | SC011 ⍵ 16 | SC012 ∊ ⍷ 17 | SC013 ⍴ 18 | SC014 ~ ⍨ 19 | SC015 ↑ 20 | SC016 ↓ 21 | SC017 ⍳ ⍸ 22 | SC018 ○ ⍥ 23 | SC019 * ⍣ 24 | SC01A ← ⍞ 25 | SC01B → ⍬ 26 | SC01E ⍺ 27 | SC01F ⌈ 28 | SC020 ⌊ 29 | SC021 _ ⍛ 30 | SC022 ∇ ⍢ 31 | SC023 ∆ ⍙ 32 | SC024 ∘ ⍤ 33 | SC025 ' ⌸ 34 | SC026 ⎕ ⌷ 35 | SC027 ⍎ ≡ 36 | SC028 ⍕ ≢ 37 | SC02B # 38 | SC056 ⊢ ⊣ 39 | SC02C ⊂ ⊆ 40 | SC02D ⊃ ⊇ 41 | SC02E ∩ 42 | SC02F ∪ 43 | SC030 ⊥ 44 | SC031 ⊤ ¤ 45 | SC032 | ∥ 46 | SC033 ⍝ ⍪ 47 | SC034 ⍀ ⍙ 48 | SC035 ⌿ ⍠ 49 | -------------------------------------------------------------------------------- /keymaps/sv_SE.tsv: -------------------------------------------------------------------------------- 1 | SC029 ⋄ ⌺ 2 | SC002 ¨ ⌶ 3 | SC003 ¯ ⍫ 4 | SC004 < ⍒ 5 | SC005 ≤ ⍋ 6 | SC006 = ⌽ 7 | SC007 ≥ ⍉ 8 | SC008 > ⊖ 9 | SC009 ≠ ⍟ 10 | SC00A ∨ ⍱ 11 | SC00B ∧ ⍲ 12 | SC00C × {!} 13 | SC00D ÷ ⌹ 14 | SC010 ? ⍰ 15 | SC011 ⍵ 16 | SC012 ∊ ⍷ 17 | SC013 ⍴ 18 | SC014 ~ ⍨ 19 | SC015 ↑ 20 | SC016 ↓ 21 | SC017 ⍳ ⍸ 22 | SC018 ○ ⍥ 23 | SC019 * ⍣ 24 | SC01A ← ⍞ 25 | SC01B → ⍬ 26 | SC01E ⍺ 27 | SC01F ⌈ 28 | SC020 ⌊ 29 | SC021 _ ⍛ 30 | SC022 ∇ ⍢ 31 | SC023 ∆ ⍙ 32 | SC024 ∘ ⍤ 33 | SC025 ' ⌸ 34 | SC026 ⎕ ⌷ 35 | SC027 ⍎ [ 36 | SC028 ⍕ ] 37 | SC02B ≡ ⍷ 38 | SC056 ⊢ ⊣ 39 | SC02C ⊂ ⊆ 40 | SC02D ⊃ ⊇ 41 | SC02E ∩ 42 | SC02F ∪ 43 | SC030 ⊥ 44 | SC031 ⊤ ¤ 45 | SC032 | ∥ 46 | SC033 ⍝ ⍪ 47 | SC034 ⍀ ≢ 48 | SC035 ⌿ ⍠ 49 | -------------------------------------------------------------------------------- /APLSource/Make.aplf: -------------------------------------------------------------------------------- 1 | Make;Copy;apl_minor;apl_version;build;create;dir;dyalog;exe;filename;files;flags;folders;from;to;type 2 | 3 | ⍝ Create build folders 4 | 5 | build←CiderConfig.HOME,'/build' 6 | exe←build,'/exe' 7 | create←0=⎕NEXISTS build exe 8 | ⎕MKDIR create/build exe 9 | 10 | ⍝ Save application as .dws workspace 11 | 12 | apl_version←2⊃'.'⎕WG'APLVersion' 13 | apl_minor←apl_version/⍨2>+\'.'=apl_version 14 | :If apl_minor≢CiderConfig.USER.APLVersion 15 | ('Please build APLAutoHotKey with Dyalog version ',CiderConfig.USER.APLVersion)⎕SIGNAL 10 16 | :EndIf 17 | 18 | DevSetup CiderConfig.HOME 19 | ⎕LX←'#.APLAutoHotKey.Run 0' 20 | ⎕WSID←build,'/APLAutoHotKey.dws' 21 | ⎕TKILL ⎕TNUMS 22 | ⎕SAVE ⎕WSID 23 | ⎕←'Application workspace saved to: ',⎕WSID 24 | -------------------------------------------------------------------------------- /APLSource/GenerateScript.aplf: -------------------------------------------------------------------------------- 1 | GenerateScript←{ 2 | ⍝ ⍵ ←→ options JSON namespace. All members (1≥≢⍴) 3 | ⍝ ⍵.layout ←→ (1=≡)(char) :: name of .tsv file which maps ASCII glyphs to APL glyphs 4 | ⍝ ⍵.shifts ←→ (2=≡)(char) :: shifting key(s) 5 | 6 | map←keymaps⍎⍵.locale 7 | nl ← ⎕UCS 10 8 | script ←'#Requires AutoHotKey v2.0-',nl 9 | script,←';Created with APLAutoHotKey v',version,nl 10 | script,←'user_suspend := False',nl 11 | 12 | Alt←('Alt'ReplaceForBoth'LAlt' 'RAlt') 13 | Ctrl←('Ctrl'ReplaceForBoth'LCtrl' 'RCtrl') 14 | AltGr←('AltGr'⎕R'RAlt') 15 | 16 | shifts←Alt Ctrl AltGr ⍵.shift 17 | script,←⊃,/HotKey∘map¨⊆shifts 18 | 19 | script,←AddGroups LocateClassicExes 20 | script,←Suspend ⍵ 21 | 22 | script 23 | 24 | } 25 | -------------------------------------------------------------------------------- /GUI/app.css: -------------------------------------------------------------------------------- 1 | body { text-align: center; } 2 | #version { font-size: 0.6em; } 3 | form { 4 | text-align: left; 5 | display: flex; 6 | flex-direction: column; 7 | padding: 1em 5em; 8 | } 9 | form > * { padding: 0.5em; } 10 | label, fieldset { margin-top: 1.5em; } 11 | .error { 12 | color: red; 13 | font-family: monospace; 14 | white-space: pre; 15 | text-align: left; 16 | } 17 | #save { width: 50%; } 18 | .modal { 19 | position: fixed; 20 | z-index: 1; 21 | left: 0; top: 0; 22 | width: 100%; height: 100%; 23 | overflow: auto; 24 | background-color: rgb(0,0,0); /* Fallback colour */ 25 | background-color: rgba(0,0,0,0.4); 26 | } 27 | .modal-content { 28 | background-color: #fefefe; 29 | margin: 15% auto; 30 | padding: 20px; 31 | border: 1px solid #888; 32 | border-radius: 8px; 33 | width: 70%; 34 | } 35 | -------------------------------------------------------------------------------- /scripts/tmp.ahk: -------------------------------------------------------------------------------- 1 | user_suspend := False 2 | 3 | CapsLock & SC029:: 4 | { if GetKeyState("Shift","P") { 5 | Send "⌺" 6 | } else { 7 | Send "⋄" 8 | }} 9 | 10 | 11 | 12 | RAlt & SC029:: 13 | { if GetKeyState("Shift","P") { 14 | Send "⌺" 15 | } else { 16 | Send "⋄" 17 | }} 18 | 19 | 20 | RControl & i:: 21 | { if GetKeyState("Shift","P") { 22 | Send "⍸" 23 | } else { 24 | Send "⍳" 25 | }} 26 | 27 | GroupAdd "Classic", "ahk_exe C:\Program Files\Dyalog\Dyalog APL-64 17.1 Classic\dyalog.exe" 28 | 29 | Check() { 30 | if WinActive("ahk_group Classic") || user_suspend { 31 | Suspend True 32 | } else { 33 | Suspend False 34 | } 35 | } 36 | ToggleSuspend() { 37 | global user_suspend 38 | if user_suspend { 39 | user_suspend := False 40 | } else { 41 | user_suspend := True 42 | } 43 | } 44 | 45 | SetTimer Check, 500 46 | 47 | #SuspendExempt 48 | LControl & Space::ToggleSuspend 49 | #SuspendExempt False 50 | 51 | -------------------------------------------------------------------------------- /CI/Inject-Version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # APLAutoHotKey inject_version 3 | raw_version=`cat APLSource/version.apla | grep "'[0-9]\+\.[0-9]\+\.0-\?\w\+\?" | grep -o "[0-9]\+\.[0-9]\+\.0-\?\w\+\?"` 4 | major_minor=`echo ${raw_version} | grep -o "[0-9]\+\.[0-9]\+"` 5 | special=`echo ${raw_version} | grep -o "\-\w\+$"` 6 | 7 | # We have checked out some specific tag (a release we need to build) or the HEAD of the trunk (about to publish a new release) 8 | # Get the last tag before this commit (HEAD^). Our patch number (this tagged release or the new release we are about to publish) is previous_patch+1 9 | previous_release=`git describe --tags --abbrev=0 HEAD^` 10 | previous_patch=`echo $last_release | sed -r 's/v[0-9]+.[0-9]+.([0-9]+).*/\1/p' | head -1` 11 | patch=$(($previous_patch + 1)) 12 | 13 | full_version=${major_minor}.${patch}${special} 14 | echo ${full_version} 15 | 16 | sed -i -r "s/[0-9]+.[0-9]+.[0-9]+-?\w+?\/DEVELOPMENT/${full_version}/g" APLSource/version.apla 17 | exit 0 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Rich Park 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /APLSource/RunTests.aplf: -------------------------------------------------------------------------------- 1 | r←RunTests all 2 | project_dir←⎕SE.Cider.GetProjectPath'' 3 | OutFile←{project_dir,'/build/scripts/','APL-',⍵.locale,'-',(⊃,/⍵.shift),'.ahk'} 4 | LoadKeymaps project_dir 5 | LoadSnippets project_dir 6 | 7 | :If ~all 8 | opts←⎕NS ⍬ 9 | opts.locale←'en_GB' 10 | opts.shift←'CapsLock' 'AltGr' 11 | opts.suspend←'LControl' 'Space' 12 | opts.outfile←OutFile opts 13 | 14 | r←MakeScript opts 15 | 16 | :Else 17 | 18 | Assert ← {⍺←'assertion failure' ⋄ 0∊⍵:⍺ ⎕SIGNAL 8 ⋄ shy←0} 19 | 20 | ⍝ Check no duplicate HotKeys 21 | Assert ∧/{∧/≠⊣/⍵}¨keymaps∘⍎¨keymaps.⎕NL ¯2 22 | 23 | ⍝ Generate a bunch of scripts 24 | build ← project_dir,'/build' 25 | scripts ← build,'/scripts' 26 | mkdir←0=⎕nexists¨build scripts 27 | ⎕mkdir mkdir/build scripts 28 | 29 | shifts←'CapsLock' 'LCtrl' 'RCtrl' 'LAlt' 'RAlt' 30 | combos←∪∪¨,∘.(,⍥⊆)⍣2⍨shifts ⍝ Up to 3 shifting keys 31 | layouts←'en_GB' 'en_US' 'da_DK' 'de_DE' 'es_ES' 'fr_FR' 32 | 33 | MakeOpts←{ 34 | o←⎕NS ⍬ 35 | o.locale←⍺ 36 | o.shift←⍵ 37 | o.suspend←'LCtrl' 'Space' 38 | o.outfile←OutFile o 39 | o 40 | } 41 | 42 | r←⍪MakeScript¨,layouts∘.MakeOpts combos 43 | 44 | :EndIf 45 | -------------------------------------------------------------------------------- /GUI/app.js: -------------------------------------------------------------------------------- 1 | $ = s => document.querySelector(s) 2 | const ws = new WebSocket("ws://dyalog_root/") 3 | const send = data => { ws.send(JSON.stringify(data)) } 4 | ws.onmessage = function(data) { 5 | // data: (error number)(message) 6 | result = JSON.parse(data.data) 7 | errorNumber = result[0] 8 | let p = document.createElement("p") 9 | p.innerHTML = result[1] 10 | if ( 0 !== errorNumber ) { p.classList.add("error") } 11 | showModal(p) 12 | } 13 | 14 | function saveScript () { 15 | fd = new FormData($("#userOptions")) 16 | opts = objectFromForm(fd) 17 | send(opts) 18 | } 19 | 20 | function objectFromForm (formData) { 21 | // Iterate through form data and place values from elements with names like "key[subKey]" into a nested object structure 22 | r = {} 23 | fa = [...formData] 24 | fa.forEach(e=>{ 25 | [fk ,v] = e; // Form Key, Value 26 | if ( fk.includes("[") ){ 27 | k = fk.slice(0,fk.indexOf("[")) 28 | sk = fk.slice(fk.indexOf("[")+1,-1) 29 | if (!r[k]) { r[k] = {} } 30 | r[k][sk] = v 31 | } else { 32 | r[fk] = v 33 | } 34 | }) 35 | return r 36 | } 37 | 38 | function showModal(el) { 39 | modal = document.createElement("div") 40 | modal.classList.add("modal") 41 | content = document.createElement("div") 42 | content.classList.add("modal-content") 43 | content.appendChild(el) 44 | modal.appendChild(content) 45 | button = document.createElement("button") 46 | button.innerHTML = "close" 47 | button.onclick = function() {modal.style.display = "none"} 48 | content.appendChild(button) 49 | document.body.appendChild(modal) 50 | window.onclick = function(event) { 51 | if (event.target == modal) { modal.style.display = "none" } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scripts/DE-Alt.ahk: -------------------------------------------------------------------------------- 1 | !^::Send "⋄" 2 | !+^::Send "⌺" 3 | !1::Send "¨" 4 | !+1::Send "⌶" 5 | !2::Send "¯" 6 | !+2::Send "⍫" 7 | !3::Send "<" 8 | !+3::Send "⍒" 9 | !4::Send "≤" 10 | !+4::Send "⍋" 11 | !´::Send "÷" 12 | !+´::Send "⌹" 13 | !5::Send "=" 14 | !+5::Send "⌽" 15 | !6::Send "≥" 16 | !+6::Send "⍉" 17 | !7::Send ">" 18 | !+7::Send "⊖" 19 | !8::Send "≠" 20 | !+8::Send "⍟" 21 | !9::Send "∨" 22 | !+9::Send "⍱" 23 | !0::Send "∧" 24 | !+0::Send "⍲" 25 | !ß::Send "×" 26 | !+ß::Send "{!}" 27 | !q::Send "?" 28 | !+q::Send "⍰" 29 | !w::Send "⍵" 30 | !+w::Send "" 31 | !e::Send "∊" 32 | !+e::Send "⍷" 33 | !r::Send "⍴" 34 | !+r::Send "" 35 | !t::Send "~" 36 | !+t::Send "⍨" 37 | !z::Send "↑" 38 | !+z::Send "" 39 | !u::Send "↓" 40 | !+u::Send "" 41 | !i::Send "⍳" 42 | !+i::Send "⍸" 43 | !o::Send "○" 44 | !+o::Send "⍥" 45 | !p::Send "*" 46 | !+p::Send "⍣" 47 | !ü::Send "←" 48 | !+ü::Send "⍞" 49 | !+::Send "→" 50 | !++::Send "⍬" 51 | !a::Send "⍺" 52 | !+a::Send "" 53 | !s::Send "⌈" 54 | !+s::Send "" 55 | !d::Send "⌊" 56 | !+d::Send "" 57 | !f::Send "_" 58 | !+f::Send "⍛" 59 | !g::Send "∇" 60 | !+g::Send "⍢" 61 | !h::Send "∆" 62 | !+h::Send "" 63 | !j::Send "∘" 64 | !+j::Send "⍤" 65 | !k::Send "'" 66 | !+k::Send "⌸" 67 | !l::Send "⎕" 68 | !+l::Send "⌷" 69 | !ö::Send "⍎" 70 | !+ö::Send "≡" 71 | !ä::Send "⍕" 72 | !+ä::Send "≢" 73 | !<::Send "⊢" 74 | !+<::Send "⊣" 75 | !y::Send "⊂" 76 | !+y::Send "⊆" 77 | !x::Send "⊃" 78 | !+x::Send "⊇" 79 | !c::Send "∩" 80 | !+c::Send "" 81 | !v::Send "∪" 82 | !+v::Send "" 83 | !b::Send "⊥" 84 | !+b::Send "" 85 | !n::Send "⊤" 86 | !+n::Send "¤" 87 | !m::Send "|" 88 | !+m::Send "∥" 89 | !,::Send "⍝" 90 | !+,::Send "⍪" 91 | !.::Send "⍀" 92 | !+.::Send "⍙" 93 | !-::Send "⌿" 94 | !+-::Send "⍠" 95 | -------------------------------------------------------------------------------- /cider.config: -------------------------------------------------------------------------------- 1 | { 2 | CIDER: { 3 | dependencies: { 4 | tatin: "packages", 5 | }, 6 | dependencies_dev: { 7 | tatin: "", 8 | }, 9 | distributionFolder: "", 10 | init: "", 11 | make: "Make", 12 | parent: "#", 13 | projectSpace: "APLAutoHotKey", 14 | project_url: "", 15 | source: "APLSource", 16 | tests: "RunTests 1", 17 | }, 18 | LINK: { 19 | arrays: 0, 20 | beforeRead: "", 21 | beforeWrite: "", 22 | caseCode: 0, 23 | codeExtensions: [ 24 | "aplf", 25 | "aplo", 26 | "apln", 27 | "aplc", 28 | "apli", 29 | "dyalog", 30 | "apl", 31 | "mipage", 32 | ], 33 | fastLoad: 0, 34 | flatten: 0, 35 | forceExtensions: 0, 36 | forceFilenames: 1, 37 | getFilename: "", 38 | typeExtensions: [ 39 | [ 40 | 2, 41 | "apla", 42 | ], 43 | [ 44 | 3, 45 | "aplf", 46 | ], 47 | [ 48 | 4, 49 | "aplo", 50 | ], 51 | [ 52 | 9.1, 53 | "apln", 54 | ], 55 | [ 56 | 9.4, 57 | "aplc", 58 | ], 59 | [ 60 | 9.5, 61 | "apli", 62 | ], 63 | ], 64 | watch: "both", 65 | }, 66 | SYSVARS: { 67 | io: 1, 68 | ml: 1, 69 | }, 70 | USER: { 71 | APLVersion: "18.2", 72 | HTMLRenderer: { 73 | files: [ 74 | "chrome_100_percent.pak", 75 | "chrome_200_percent.pak", 76 | "chrome_elf.dll", 77 | "d3dcompiler_47.dll", 78 | "htmlrenderer.dll", 79 | "icudtl.dat", 80 | "libcef.dll", 81 | "libEGL.dll", 82 | "libGLESv2.dll", 83 | "resources.pak", 84 | "snapshot_blob.bin", 85 | "v8_context_snapshot.bin", 86 | "dyalognet.dll", 87 | "bridge182-64_unicode.dll", 88 | ], 89 | folders: [ 90 | "locales", 91 | "swiftshader", 92 | ], 93 | }, 94 | }, 95 | } 96 | -------------------------------------------------------------------------------- /scripts/APL-de_DE-Ctrl.ahk: -------------------------------------------------------------------------------- 1 | ^SC029::Send "⋄" 2 | ^+SC029::Send "⌺" 3 | ^1::Send "¨" 4 | ^+1::Send "⌶" 5 | ^2::Send "¯" 6 | ^+2::Send "⍫" 7 | ^3::Send "<" 8 | ^+3::Send "⍒" 9 | ^4::Send "≤" 10 | ^+4::Send "⍋" 11 | ^´::Send "÷" 12 | ^+´::Send "⌹" 13 | ^5::Send "=" 14 | ^+5::Send "⌽" 15 | ^6::Send "≥" 16 | ^+6::Send "⍉" 17 | ^7::Send ">" 18 | ^+7::Send "⊖" 19 | ^8::Send "≠" 20 | ^+8::Send "⍟" 21 | ^9::Send "∨" 22 | ^+9::Send "⍱" 23 | ^0::Send "∧" 24 | ^+0::Send "⍲" 25 | ^ß::Send "×" 26 | ^+ß::Send "{!}" 27 | ^q::Send "?" 28 | ^+q::Send "⍰" 29 | ^w::Send "⍵" 30 | ^+w::Send "" 31 | ^e::Send "∊" 32 | ^+e::Send "⍷" 33 | ^r::Send "⍴" 34 | ^+r::Send "" 35 | ^t::Send "~" 36 | ^+t::Send "⍨" 37 | ^z::Send "↑" 38 | ^+z::Send "" 39 | ^u::Send "↓" 40 | ^+u::Send "" 41 | ^i::Send "⍳" 42 | ^+i::Send "⍸" 43 | ^o::Send "○" 44 | ^+o::Send "⍥" 45 | ^p::Send "*" 46 | ^+p::Send "⍣" 47 | ^ü::Send "←" 48 | ^+ü::Send "⍞" 49 | ^+::Send "→" 50 | ^++::Send "⍬" 51 | ^a::Send "⍺" 52 | ^+a::Send "" 53 | ^s::Send "⌈" 54 | ^+s::Send "" 55 | ^d::Send "⌊" 56 | ^+d::Send "" 57 | ^f::Send "_" 58 | ^+f::Send "⍛" 59 | ^g::Send "∇" 60 | ^+g::Send "⍢" 61 | ^h::Send "∆" 62 | ^+h::Send "" 63 | ^j::Send "∘" 64 | ^+j::Send "⍤" 65 | ^k::Send "'" 66 | ^+k::Send "⌸" 67 | ^l::Send "⎕" 68 | ^+l::Send "⌷" 69 | ^ö::Send "⍎" 70 | ^+ö::Send "≡" 71 | ^ä::Send "⍕" 72 | ^+ä::Send "≢" 73 | ^<::Send "⊢" 74 | ^+<::Send "⊣" 75 | ^y::Send "⊂" 76 | ^+y::Send "⊆" 77 | ^x::Send "⊃" 78 | ^+x::Send "⊇" 79 | ^c::Send "∩" 80 | ^+c::Send "" 81 | ^v::Send "∪" 82 | ^+v::Send "" 83 | ^b::Send "⊥" 84 | ^+b::Send "" 85 | ^n::Send "⊤" 86 | ^+n::Send "¤" 87 | ^m::Send "|" 88 | ^+m::Send "∥" 89 | ^,::Send "⍝" 90 | ^+,::Send "⍪" 91 | ^.::Send "⍀" 92 | ^+.::Send "⍙" 93 | ^-::Send "⌿" 94 | ^+-::Send "⍠" 95 | 96 | ;#SuspendExempt 97 | ;LControl & Space::Suspend 98 | ;#SuspendExempt False 99 | 100 | GroupAdd "Classic", "ahk_exe C:\Program Files (x86)\Dyalog\Dyalog APL 12.1 Classic\dyalog.exe" 101 | GroupAdd "Classic", "ahk_exe C:\Program Files\Dyalog\Dyalog APL-64 17.1 Classic\dyalog.exe" 102 | 103 | Check() { 104 | if WinActive("ahk_group Classic") { 105 | Suspend True 106 | } else { 107 | Suspend False 108 | } 109 | } 110 | 111 | SetTimer Check, 500 112 | -------------------------------------------------------------------------------- /GUI/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

APLAutoHotKey

9 |

Generate AutoHotKey scripts for APL glyph keyboard input

10 |
11 | 12 | 22 |
23 | Choose your shifting key(s) 24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 |
37 | 38 | 39 |
40 |
41 | 42 | 43 |
44 |
45 | 46 | 47 |
48 |
49 | 50 | 51 |
52 |
53 |
54 | Set a shortcut to toggle whether AutoHotKey is active (max 2 keys): 55 |
56 | 57 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 | 66 |
67 |
68 | 69 | 70 |
71 |
72 | 73 | 74 |
75 |
76 | 77 | 78 |
79 |
80 | 81 | 82 |
83 |

The selected keys, when pressed together at the same time, will toggle whether AutoHotKey is active.

84 |
85 | 86 | 87 |
88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /scripts/CapsLock-en_UK.ahk: -------------------------------------------------------------------------------- 1 | #SuspendExempt 2 | LControl & Space::Suspend 3 | #SuspendExempt False 4 | *CapsLock::return 5 | CapsLock & Space::Send " " 6 | CapsLock & `:: 7 | { if GetKeyState("Shift","P") { 8 | Send "⌺" 9 | } else { 10 | Send "⋄" 11 | }} 12 | CapsLock & 1:: 13 | { if GetKeyState("Shift","P") { 14 | Send "⌶" 15 | } else { 16 | Send "¨" 17 | }} 18 | CapsLock & 2:: 19 | { if GetKeyState("Shift","P") { 20 | Send "⍫" 21 | } else { 22 | Send "¯" 23 | }} 24 | CapsLock & 3:: 25 | { if GetKeyState("Shift","P") { 26 | Send "⍒" 27 | } else { 28 | Send "<" 29 | }} 30 | CapsLock & 4:: 31 | { if GetKeyState("Shift","P") { 32 | Send "⍋" 33 | } else { 34 | Send "≤" 35 | }} 36 | CapsLock & =:: 37 | { if GetKeyState("Shift","P") { 38 | Send "⌹" 39 | } else { 40 | Send "÷" 41 | }} 42 | CapsLock & 5:: 43 | { if GetKeyState("Shift","P") { 44 | Send "⌽" 45 | } else { 46 | Send "=" 47 | }} 48 | CapsLock & 6:: 49 | { if GetKeyState("Shift","P") { 50 | Send "⍉" 51 | } else { 52 | Send "≥" 53 | }} 54 | CapsLock & 7:: 55 | { if GetKeyState("Shift","P") { 56 | Send "⊖" 57 | } else { 58 | Send ">" 59 | }} 60 | CapsLock & 8:: 61 | { if GetKeyState("Shift","P") { 62 | Send "⍟" 63 | } else { 64 | Send "≠" 65 | }} 66 | CapsLock & 9:: 67 | { if GetKeyState("Shift","P") { 68 | Send "⍱" 69 | } else { 70 | Send "∨" 71 | }} 72 | CapsLock & 0:: 73 | { if GetKeyState("Shift","P") { 74 | Send "⍲" 75 | } else { 76 | Send "∧" 77 | }} 78 | CapsLock & -:: 79 | { if GetKeyState("Shift","P") { 80 | Send "{!}" 81 | } else { 82 | Send "×" 83 | }} 84 | CapsLock & q:: 85 | { if GetKeyState("Shift","P") { 86 | Send "⍰" 87 | } else { 88 | Send "?" 89 | }} 90 | CapsLock & w:: 91 | { if GetKeyState("Shift","P") { 92 | Send "⍹" 93 | } else { 94 | Send "⍵" 95 | }} 96 | CapsLock & e:: 97 | { if GetKeyState("Shift","P") { 98 | Send "⍷" 99 | } else { 100 | Send "∊" 101 | }} 102 | CapsLock & r:: 103 | { if GetKeyState("Shift","P") { 104 | Send " " 105 | } else { 106 | Send "⍴" 107 | }} 108 | CapsLock & t:: 109 | { if GetKeyState("Shift","P") { 110 | Send "⍨" 111 | } else { 112 | Send "~" 113 | }} 114 | CapsLock & y:: 115 | { if GetKeyState("Shift","P") { 116 | Send " " 117 | } else { 118 | Send "↑" 119 | }} 120 | CapsLock & u:: 121 | { if GetKeyState("Shift","P") { 122 | Send " " 123 | } else { 124 | Send "↓" 125 | }} 126 | CapsLock & i:: 127 | { if GetKeyState("Shift","P") { 128 | Send "⍸" 129 | } else { 130 | Send "⍳" 131 | }} 132 | CapsLock & o:: 133 | { if GetKeyState("Shift","P") { 134 | Send "⍥" 135 | } else { 136 | Send "○" 137 | }} 138 | CapsLock & p:: 139 | { if GetKeyState("Shift","P") { 140 | Send "⍣" 141 | } else { 142 | Send "*" 143 | }} 144 | CapsLock & [:: 145 | { if GetKeyState("Shift","P") { 146 | Send "⍞" 147 | } else { 148 | Send "←" 149 | }} 150 | CapsLock & ]:: 151 | { if GetKeyState("Shift","P") { 152 | Send "⍬" 153 | } else { 154 | Send "→" 155 | }} 156 | CapsLock & a:: 157 | { if GetKeyState("Shift","P") { 158 | Send "⍶" 159 | } else { 160 | Send "⍺" 161 | }} 162 | CapsLock & s:: 163 | { if GetKeyState("Shift","P") { 164 | Send " " 165 | } else { 166 | Send "⌈" 167 | }} 168 | CapsLock & d:: 169 | { if GetKeyState("Shift","P") { 170 | Send " " 171 | } else { 172 | Send "⌊" 173 | }} 174 | CapsLock & f:: 175 | { if GetKeyState("Shift","P") { 176 | Send "⍛" 177 | } else { 178 | Send "_" 179 | }} 180 | CapsLock & g:: 181 | { if GetKeyState("Shift","P") { 182 | Send "⍢" 183 | } else { 184 | Send "∇" 185 | }} 186 | CapsLock & h:: 187 | { if GetKeyState("Shift","P") { 188 | Send " " 189 | } else { 190 | Send "∆" 191 | }} 192 | CapsLock & j:: 193 | { if GetKeyState("Shift","P") { 194 | Send "⍤" 195 | } else { 196 | Send "∘" 197 | }} 198 | CapsLock & k:: 199 | { if GetKeyState("Shift","P") { 200 | Send "⌸" 201 | } else { 202 | Send "'" 203 | }} 204 | CapsLock & l:: 205 | { if GetKeyState("Shift","P") { 206 | Send "⌷" 207 | } else { 208 | Send "⎕" 209 | }} 210 | CapsLock & `;:: 211 | { if GetKeyState("Shift","P") { 212 | Send "≡" 213 | } else { 214 | Send "⍎" 215 | }} 216 | CapsLock & ':: 217 | { if GetKeyState("Shift","P") { 218 | Send "≢" 219 | } else { 220 | Send "⍕" 221 | }} 222 | CapsLock & #:: 223 | { if GetKeyState("Shift","P") { 224 | Send "⊣" 225 | } else { 226 | Send "⊢" 227 | }} 228 | CapsLock & \:: 229 | { if GetKeyState("Shift","P") { 230 | Send "⊣" 231 | } else { 232 | Send "⊢" 233 | }} 234 | CapsLock & z:: 235 | { if GetKeyState("Shift","P") { 236 | Send "⊆" 237 | } else { 238 | Send "⊂" 239 | }} 240 | CapsLock & x:: 241 | { if GetKeyState("Shift","P") { 242 | Send "⊇" 243 | } else { 244 | Send "⊃" 245 | }} 246 | CapsLock & c:: 247 | { if GetKeyState("Shift","P") { 248 | Send " " 249 | } else { 250 | Send "∩" 251 | }} 252 | CapsLock & v:: 253 | { if GetKeyState("Shift","P") { 254 | Send " " 255 | } else { 256 | Send "∪" 257 | }} 258 | CapsLock & b:: 259 | { if GetKeyState("Shift","P") { 260 | Send " " 261 | } else { 262 | Send "⊥" 263 | }} 264 | CapsLock & n:: 265 | { if GetKeyState("Shift","P") { 266 | Send "¤" 267 | } else { 268 | Send "⊤" 269 | }} 270 | CapsLock & m:: 271 | { if GetKeyState("Shift","P") { 272 | Send "∥" 273 | } else { 274 | Send "|" 275 | }} 276 | CapsLock & ,:: 277 | { if GetKeyState("Shift","P") { 278 | Send "⍪" 279 | } else { 280 | Send "⍝" 281 | }} 282 | CapsLock & .:: 283 | { if GetKeyState("Shift","P") { 284 | Send "⍙" 285 | } else { 286 | Send "⍀" 287 | }} 288 | CapsLock & /:: 289 | { if GetKeyState("Shift","P") { 290 | Send "⍠" 291 | } else { 292 | Send "⌿" 293 | }} 294 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # APLAutoHotKey 2 | This is an application to generate AutoHotKey scripts to enable keyboard input of APL glyphs on Microsoft Windows. 3 | 4 | [AutoHotKey](https://www.autohotkey.com/) is an automation scripting software for Microsoft Windows. You can map keyboard shortcuts (or "hotkeys") to actions, including the output of characters. This program presents a form of options and generates an AutoHotKey script based on the user's choices. 5 | 6 | > AutoHotKey version 2 must be installed in order to use the scripts generated by APLAutoHotKey. 7 | 8 | - [Usage](#usage) 9 | - [GUI](#gui) 10 | - [API](#api) 11 | - [Options](#options) 12 | - [Layouts](#layouts) 13 | - [Shifting key](#shifting-key) 14 | - [Suspend shortcut](#suspend-shortcut) 15 | - [Launch at startup](#launch-at-startup) 16 | - [Dyalog Classic Edition](#dyalog-classic-edition) 17 | - [Development](#development) 18 | - [Mapping dead keys](#mapping-dead-keys) 19 | 20 | ## Usage 21 | 22 | ### GUI (normal usage) 23 | 1. Download and install [AutoHotKey v2](https://www.autohotkey.com/) 24 | 25 | 2. Download the [latest release workspace](https://github.com/Dyalog/APLAutoHotKey/releases), **APLAutoHotKey.dws**, from the releases page. `)LOAD` or double-click on the workspace file to launch the GUI application. 26 | 27 | 3. Choose your locale/layout, shifting key, optional suspend shortcut and enter the path to the folder in which to save the resulting script. Then click the **Generate Script** button. 28 | 29 | > The script name will be based on the options provided and it will have a file extension of **.ahk**. For example, `APL-en_GB-CapsLockRCtrl.ahk`. 30 | 31 | 4. Double click on or otherwise run the **.ahk** script file to start the script and enable APL keyboard input using your chosen options. 32 | 33 | ### API 34 | The following demonstrates how to use the API to generate scripts programmatically. 35 | 36 | 1. Import APLAutoHotKey: 37 | 38 | ``` 39 | ]Get -u https://github.com/rikedyp/APLAutoHotKey/releases/download/v0.1.0/APLAutoHotKey.dws 40 | ``` 41 | 42 | > `]Get` can accept a local file path or the URL of a released .dws workspace 43 | 44 | 2. Set options 45 | 46 | ``` 47 | opt←⎕NS'' 48 | opt.shift←'CapsLock' 'RAlt' 49 | opt.locale←'en_GB' 50 | opt.outfile←'/tmp/APL-en_GB-CapsLockAlt.ahk' 51 | ``` 52 | 53 | 3. Generate and save the script 54 | 55 | ``` 56 | APLAutoHotKey.MakeScript opt 57 | ┌─┬──────────────────────────────────────────────────┐ 58 | │0│Saved: /tmp/APL-en_GB-CapsLockAlt.ahk (9404 bytes)│ 59 | └─┴──────────────────────────────────────────────────┘ 60 | ``` 61 | 62 | 4. Double click on or otherwise run the **.ahk** script file to start the script and enable APL keyboard input using your chosen options. 63 | 64 | ## Options 65 | 66 | ### Layouts 67 | Each script is based on a single locale. 68 | 69 | |locale|code| 70 | |---|---| 71 | |English (UK)|en_GB| 72 | |English(US)|en_US| 73 | |Danish|da_DK| 74 | |Finnish (Finland)|fi_FI| 75 | |French (France)|fr_FR| 76 | |German (Germany)|de_DE| 77 | |Italian (Italy)|it_IT| 78 | |Spanish (Spain)|es_ES| 79 | |Swedish (Sweden)|sv_SE| 80 | 81 | > Other layouts can be supported on request, or generated by the user if they create the appropriate key map. See the `keymaps` folder for existing keymap tab separated values (.tsv) files. 82 | 83 | ### Shifting key 84 | This is a key which, while pressed, enables the input of APL glyphs. For example, Ctrl + e produces `∊`. 85 | 86 | Available shifts are listed in the `ahk_shifts` namespace. 87 | 88 | ``` 89 | APLAutoHotKey.ahk_shifts.⎕NL¯2 90 | ┌───┬────────┬────┬────┬─────┬────┬────┬─────┬────┐ 91 | │Alt│CapsLock│Ctrl│LAlt│LCtrl│LWin│RAlt│RCtrl│RWin│ 92 | └───┴────────┴────┴────┴─────┴────┴────┴─────┴────┘ 93 | ``` 94 | 95 | > The AltGr key sends a Left Control + Right Alt signal. Therefore, using the **Alt** (both), **RAlt**, **Ctrl** (both) or **LCtrl** shifting key options can affect the behaviour of the AltGr key. 96 | 97 | ### Suspend shortcut 98 | The user may specify a key combination to toggle suspension of hotkeys, which may be useful if an application uses keyboard shortcuts with which the hotkeys interfere. 99 | 100 | ### Launch at startup 101 | The script can be made to launch at startup by placing it into the user's startup folder or the common startup folder. 102 | 103 | The startup folders on Microsoft Windows can be found by entering `shell:startup` (this user) or `shell:common startup` (all users) into the **Run** app (Win+R). 104 | 105 | ## Dyalog Classic Edition 106 | When generating a script, APLAutoHotKey checks the Microsoft Windows registry for installations of Dyalog Classic interpreters, and adds the paths to their executable files to the "Classic" group. When a Classic window is active, hotkeys suspend to allow the Classic application to handle keyboard input. 107 | 108 | If a user installs a Dyalog Classic interpreter after generating a script with APLAutoHotKey, then they can either rebuild their script using APLAutoHotKey, or add the path to the executable file in an additional `GroupAdd` line in their script. 109 | 110 | ## Development 111 | This application is developed as a [Cider](https://github.com/aplteam/Cider) project. 112 | 113 | Open the project: 114 | 115 | ``` 116 | ]CIDER.OpenProject /path/to/APLAutoHotKey 117 | ``` 118 | 119 | Build a new release workspace: 120 | 121 | 1. In a bash shell in the folder of this cloned repository: 122 | 123 | ``` 124 | ./CI/Inject-Version.sh 125 | ``` 126 | 127 | 2. In Dyalog 128 | 129 | ``` 130 | ]DBuild APLAutoHotKey.dyalogbuild 131 | ``` 132 | 133 | ### Mapping dead keys 134 | Some keys do not immediately produce output when pressed, but will affect the next key press usually by adding an accent to a letter. These are known as "dead keys". 135 | 136 | In AutoHotKey **.ahk** scripts, you may use a Unicode character directly to indicate it as a HotKey meaning that some action is taken when that character is output by the keyboard. For dead keys, you should use the *scan code* corresponding to that key. 137 | 138 | To find the scan code for a particular key, start AutoHotKey by double-clicking any script file. A green "H" icon will appear in the system tray. **Right click** the system tray icon → click **Open** → click the **View** menu item and then click **Key history and script info**. Press the key and then press F5. The 2nd most recent key in the list should be the key you just pressed, and the 3-digit alphanumeric code in the 2nd column is the scan code. 139 | -------------------------------------------------------------------------------- /keymaps/keymaps.md: -------------------------------------------------------------------------------- 1 | # Keymap diagrams 2 | 3 | ## fi_FI 4 | 5 | ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬─────────┐ 6 | │ ⌺ │! ⌶ │" ⍫ │# ⍒ │⊢ ⍋ │% ⌽ │& ⍉ │/ ⊖ │( ⍟ │) ⍱ │= ⍲ │? ! │` ⌹ │Backspace│ 7 | │§ ⋄ │1 ¨ │2 ¯ │3 < │4 ≤ │5 = │6 ≥ │7 > │8 ≠ │9 ∨ │0 ∧ │+ × │' ÷ │ │ 8 | ├────┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬──────┤ 9 | │Tab │Q ⍰ │W │E ⍷ │R │T ⍨ │Y │U │I ⍸ │O ⍥ │P ⍣ │Å ⍞ │^ ⍬ │Enter │ 10 | │ │q ? │w ⍵ │e ∊ │r ⍴ │t ~ │y ↑ │u ↓ │i ⍳ │o ○ │p * │å ← │¨ → │ │ 11 | ├───────┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┐ │ 12 | │Caps │A │S │D │F ⍛ │G ⍢ │H ⍙ │J ⍤ │K ⌸ │L ⌷ │Ö [ │Ä ] │* ⍷ │ │ 13 | │Lock │a ⍺ │s ⌈ │d ⌊ │f _ │g ∇ │h ∆ │j ∘ │k ' │l ⎕ │ö ⍎ │ä ⍕ │' ≡ │ │ 14 | ├──────┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴────┴─────┤ 15 | │Shift │> ⊣ │Z ⊆ │X ⊇ │C │V │B │N ¤ │M ∥ │; ⍪ │: ≢ │_ ⍠ │Shift │ 16 | │ │< ⊢ │z ⊂ │x ⊃ │c ∩ │v ∪ │b ⊥ │n ⊤ │m | │, ⍝ │. ⍀ │- ⌿ │ │ 17 | ├──────┴┬───┴─┬──┴───┬┴────┴────┴────┴────┴────┴┬───┴──┬─┴────┼─────┬──────┤ 18 | │Ctrl │Win │Alt │ │Alt Gr│Win │Menu │Ctrl │ 19 | │ │ │ │ │ │ │ │ │ 20 | └───────┴─────┴──────┴──────────────────────────┴──────┴──────┴─────┴──────┘ 21 | Additional characters are accessed using Alt Gr: 22 | ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬─────────┐ 23 | │ │! │" │# │⊢ │% │& │/ │( │) │= │? │ │Backspace│ 24 | │§ │1 │2 @ │3 £ │4 $ │5 € │6 │7 { │8 [ │9 ] │0 } │+ \ │' ´ │ │ 25 | ├────┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬──────┤ 26 | │Tab │Q │W │E │R │T │Y │U │I │O │P │Å │^ │Enter │ 27 | │ │q │w │e € │r │t │y │u │i │o │p │å │¨ ~ │ │ 28 | ├───────┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┐ │ 29 | │Caps │A │S │D │F │G │H │J │K │L │Ö │Ä Æ │* ≢ │ │ 30 | │Lock │a │s │d │f │g │h │j │k │l │ö │ä │' ⍣ │ │ 31 | ├──────┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴────┴─────┤ 32 | │Shift │> │Z │X │C │V │B │N │M │; │: │_ │Shift │ 33 | │ │< | │z │x │c │v │b │n │m µ │, │. │- │ │ 34 | ├──────┴┬───┴─┬──┴───┬┴────┴────┴────┴────┴────┴┬───┴──┬─┴────┼─────┬──────┤ 35 | │Ctrl │Win │Alt │ │Alt Gr│Win │Menu │Ctrl │ 36 | │ │ │ │ │ │ │ │ │ 37 | └───────┴─────┴──────┴──────────────────────────┴──────┴──────┴─────┴──────┘ 38 | More additional characters are available using Alt Gr on the Finnish with Sami keyboard: 39 | ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬─────────┐ 40 | │ │! │" │# │⊢ │% │& │/ │( │) │= │? │ │Backspace│ 41 | │§ │1 │2 @ │3 £ │4 $ │5 € │6 │7 { │8 [ │9 ] │0 } │+ \ │' ´ │ │ 42 | ├────┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬──────┤ 43 | │Tab │Q  │W │E │R │T Ŧ │Y │U │I Ï │O Õ │P │Å │^ │Enter │ 44 | │ │q â │w │e € │r │t ŧ │y │u │i ï │o õ │p │å │¨ ~ │ │ 45 | ├───────┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┐ │ 46 | │Caps │A Á │S Š │D Đ │F Ǥ │G Ǧ │H Ȟ │J │K Ǩ │L │Ö Ø │Ä Æ │* │ │ 47 | │Lock │a á │s š │d đ │f ǥ │g ǧ │h ȟ │j │k ǩ │l │ö ø │ä æ │' │ │ 48 | ├──────┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴────┴─────┤ 49 | │Shift │> │Z Ž │X │C Č │V Ǯ │B Ʒ │N Ŋ │M │; │: │_ │Shift │ 50 | │ │< | │z ž │x │c č │v ǯ │b ʒ │n ŋ │m µ │, │. │- │ │ 51 | ├──────┴┬───┴─┬──┴───┬┴────┴────┴────┴────┴────┴┬───┴──┬─┴────┼─────┬──────┤ 52 | │Ctrl │Win │Alt │ │Alt Gr│Win │Menu │Ctrl │ 53 | │ │ │ │ │ │ │ │ │ 54 | └───────┴─────┴──────┴──────────────────────────┴──────┴──────┴─────┴──────┘ 55 | 56 | ## fr_FR 57 | ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬─────────┐ 58 | │ ⌺ │1 ⌶ │2 ⍫ │3 ⍒ │4 ⍋ │5 ⌽ │6 ⍉ │7 ⊖ │8 ⍟ │9 ⍱ │0 ⍲ │° ! │+ ⌹ │Backspace│ 59 | │² ⋄ │& ¨ │é ¯ │" < │' ≤ │( = │- ≥ │è > │_ ≠ │ç ∨ │à ∧ │) × │= ÷ │ │ 60 | ├────┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬──────┤ 61 | │Tab │A ⍰ │Z │E ⍷ │R │T ⍨ │Y │U │I ⍸ │O ⍥ │P ⍣ │¨ ⍞ │£ ⍬ │Enter │ 62 | │ │a ? │z ⍵ │e ∊ │r ⍴ │t ~ │y ↑ │u ↓ │i ⍳ │o ○ │p * │^ ← │$ → │ │ 63 | ├───────┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┐ │ 64 | │Caps │Q │S │D │F ⍛ │G ⍢ │H ⍙ │J ⍤ │K ⌸ │L ⌷ │M ≡ │% ≢ │μ │ │ 65 | │Lock │q ⍺ │s ⌈ │d ⌊ │f _ │g ∇ │h ∆ │j ∘ │k ' │l ⎕ │m ⍎ │ù ⍕ │* # │ │ 66 | ├──────┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴────┴─────┤ 67 | │Shift │> ⊣ │W ⊆ │X ⊇ │C │V │B │N ¤ │? ∥ │. ⍪ │/ ⍙ │§ ⍠ │Shift │ 68 | │ │< ⊢ │w ⊂ │x ⊃ │c ∩ │v ∪ │b ⊥ │n ⊤ │, | │; ⍝ │: ⍀ │! ⌿ │ │ 69 | ├──────┴┬───┴─┬──┴───┬┴────┴────┴────┴────┴────┴┬───┴──┬─┴────┼─────┬──────┤ 70 | │Ctrl │Win │Alt │ │Alt Gr│Win │Menu │Ctrl │ 71 | │ │ │ │ │ │ │ │ │ 72 | └───────┴─────┴──────┴──────────────────────────┴──────┴──────┴─────┴──────┘ 73 | Additional characters on the front of keycaps are accessed using Alt Gr: 74 | ┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐ 75 | │2 ⍫ ││3 ⍒ ││4 ⍋ ││5 ⌽ ││6 ⍉ ││7 ⊖ ││8 ⍟ ││9 ⍱ ││0 ⍲ ││° ││+ ⌹ ││E ⍷ │ 76 | │é ¯ ││" < ││' ≤ ││( = ││- ≥ ││è > ││_ ≠ ││ç ∨ ││à ∧ ││) × ││= ÷ ││e ∊ │ 77 | ├────┤├────┤├────┤├────┤├────┤├────┤├────┤├────┤├────┤├────┤├────┤├────┤ 78 | │ ~ ││ # ││ { ││ [ ││ ¦ ││ ` ││ \ ││ ^ ││ @ ││ ] ││ } ││ € │ 79 | └────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘ 80 | 81 | 82 | 83 | ## de_DE 84 | This version is based on the layout of the Dyalog hardware keyboards 85 | ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬─────────┐ 86 | │° ⌺ │! ⌶ │" ⍫ │§ ⍒ │$ ⍋ │% ⌽ │& ⍉ │/ ⊖ │( ⍟ │) ⍱ │= ⍲ │? ! │` ⌹ │Backspace│ 87 | │^ ⋄ │1 ¨ │2 ¯ │3 < │4 ≤ │5 = │6 ≥ │7 > │8 ≠ │9 ∨ │0 ∧ │ß × │´ ÷ │ │ 88 | ├────┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬──────┤ 89 | │Tab │Q ⍰ │W │E ⍷ │R │T ⍨ │Z │U │I ⍸ │O ⍥ │P ⍣ │Ü ⍞ │* ⍬ │Enter │ 90 | │ │q ? │w ⍵ │e ∊ │r ⍴ │t ~ │z ↑ │u ↓ │i ⍳ │o ○ │p * │ü ← │+ → │ │ 91 | ├───────┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┐ │ 92 | │Caps │A │S │D │F ⍛ │G ⍢ │H │J ⍤ │K ⌸ │L ⌷ │Ö ≡ │Ä ≢ │' │ │ 93 | │Lock │a ⍺ │s ⌈ │d ⌊ │f _ │g ∇ │h ∆ │j ∘ │k ' │l ⎕ │ö ⍎ │ä ⍕ │# │ │ 94 | ├──────┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴────┴─────┤ 95 | │Shift │> ⊣ │Y ⊆ │X ⊇ │C │V │B │N ¤ │M ∥ │; ⍪ │: ⍙ │_ ⍠ │Shift │ 96 | │ │< ⊢ │y ⊂ │x ⊃ │c ∩ │v ∪ │b ⊥ │n ⊤ │m | │, ⍝ │. ⍀ │- ⌿ │ │ 97 | ├──────┴┬───┴─┬──┴───┬┴────┴────┴────┴────┴────┴┬───┴──┬─┴────┼─────┬──────┤ 98 | │Ctrl │Win │Alt │ │Alt Gr│Win │Menu │Ctrl │ 99 | │ │ │ │ │ │ │ │ │ 100 | └───────┴─────┴──────┴──────────────────────────┴──────┴──────┴─────┴──────┘ 101 | Additional characters on the front of keycaps are accessed using Alt Gr: 102 | ┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐ 103 | │" ⍫ ││§ ⍒ ││/ ⊖ ││( ⍟ ││) ⍱ ││= ⍲ ││? ! ││Q ││E ⍷ ││* ⍬ ││> ⊣ ││M │ 104 | │2 ¯ ││3 < ││7 > ││8 ≠ ││9 ∨ ││0 ∧ ││ß × ││q ? ││e ∊ ││+ → ││< ⊢ ││m | │ 105 | ├────┤├────┤├────┤├────┤├────┤├────┤├────┤├────┤├────┤├────┤├────┤├────┤ 106 | │² ││³ ││{ ││[ ││] ││} ││\ ││@ ││€ ││~ ││| ││μ │ 107 | └────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘ 108 | 109 | ## it_IT 110 | ``` 111 | ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬─────────┐ 112 | │| ⌺ │! ⌶ │" ⍫ │£ ⍒ │$ ⍋ │% ⌽ │& ⍉ │/ ⊖ │( ⍟ │) ⍱ │= ⍲ │? ! │^ ⌹ │Backspace│ 113 | │\ ⋄ │1 ¨ │2 ¯ │3 < │4 ≤ │5 = │6 ≥ │7 > │8 ≠ │9 ∨ │0 ∧ │' × │ì ÷ │ │ 114 | ├────┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬──────┤ 115 | │Tab │Q ⍰ │W │E ⍷ │R │T ⍨ │Y │U │I ⍸ │O ⍥ │P ⍣ │é ⍞ │* ⍬ │Enter │ 116 | │ │q ? │w ⍵ │e ∊ │r ⍴ │t ~ │y ↑ │u ↓ │i ⍳ │o ○ │p * │è ← │+ → │ │ 117 | ├───────┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┐ │ 118 | │Caps │A │S │D │F │G │H ⍙ │J ⍤ │K ⌸ │L ⌷ │ç ≡ │° ≢ │§ │ │ 119 | │Lock │a ⍺ │s ⌈ │d ⌊ │f _ │g ∇ │h ∆ │j ∘ │k ' │l ⎕ │ò ⍎ │à ⍕ │ù │ │ 120 | ├──────┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴────┴─────┤ 121 | │Shift │> ⊣ │Z ⊆ │X ⊇ │C │V │B │N ¤ │M ∥ │; ⍪ │: ⍙ │_ ⍠ │Shift │ 122 | │ │< ⊢ │z ⊂ │x ⊃ │c ∩ │v ∪ │b ⊥ │n ⊤ │m | │, ⍝ │. ⍀ │- ⌿ │ │ 123 | ├──────┴┬───┴─┬──┴───┬┴────┴────┴────┴────┴────┴┬───┴──┬─┴────┼─────┬──────┤ 124 | │Ctrl │Win │Alt │ │Alt Gr│Win │Menu │Ctrl │ 125 | │ │ │ │ │ │ │ │ │ 126 | └───────┴─────┴──────┴──────────────────────────┴──────┴──────┴─────┴──────┘ 127 | Additional characters on the front of keycaps are accessed using Alt Gr and Alt Gr+Shift 128 | ┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐ 129 | │% ⌽ ││E ⍷ ││é ⍞ ││* ⍬ ││ç ≡ ││° ≢ │ 130 | │5 = ││e ∊ ││è ← ││+ → ││ò ⍎ ││à ⍕ │ 131 | ├────┤├────┤├────┤├────┤├────┤├────┤ 132 | │€ ││€ ││[ { ││] } ││@ ││# │ 133 | └────┘└────┘└────┘└────┘└────┘└────┘ 134 | ``` 135 | 136 | ## sv_SE 137 | ``` 138 | ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬─────────┐ 139 | │½ ⌺ │! ⌶ │" ⍫ │# ⍒ │¤ ⍋ │% ⌽ │& ⍉ │/ ⊖ │( ⍟ │) ⍱ │= ⍲ │? ! │` ⌹ │Backspace│ 140 | │§ ⋄ │1 ¨ │2 ¯ │3 < │4 ≤ │5 = │6 ≥ │7 > │8 ≠ │9 ∨ │0 ∧ │+ × │' ÷ │ │ 141 | ├────┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬──────┤ 142 | │Tab │Q ⍰ │W │E ⍷ │R │T ⍨ │Y │U │I ⍸ │O ⍥ │P ⍣ │Å ⍞ │^ ⍬ │Enter │ 143 | │ │q ? │w ⍵ │e ∊ │r ⍴ │t ~ │y ↑ │u ↓ │i ⍳ │o ○ │p * │å ← │¨ → │ │ 144 | ├───────┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┬───┴┐ │ 145 | │Caps │A │S │D │F ⍛ │G ⍢ │H ⍙ │J ⍤ │K ⌸ │L ⌷ │Ö [ │Ä ] │* ⍷ │ │ 146 | │Lock │a ⍺ │s ⌈ │d ⌊ │f _ │g ∇ │h ∆ │j ∘ │k ' │l ⎕ │ö ⍎ │ä ⍕ │' ≡ │ │ 147 | ├──────┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴──┬─┴────┴─────┤ 148 | │Shift │> ⊣ │Z ⊆ │X ⊇ │C │V │B │N ¤ │M ∥ │; ⍪ │: ≢ │_ ⍠ │Shift │ 149 | │ │< ⊢ │z ⊂ │x ⊃ │c ∩ │v ∪ │b ⊥ │n ⊤ │m | │, ⍝ │. ⍀ │- ⌿ │ │ 150 | ├──────┴┬───┴─┬──┴───┬┴────┴────┴────┴────┴────┴┬───┴──┬─┴────┼─────┬──────┤ 151 | │Ctrl │Win │Alt │ │Alt Gr│Win │Menu │Ctrl │ 152 | │ │ │ │ │ │ │ │ │ 153 | └───────┴─────┴──────┴──────────────────────────┴──────┴──────┴─────┴──────┘ 154 | Additional characters are accessed using Alt Gr: 155 | ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐ 156 | │" │# │¤ │% │/ │( │) │= │? │E │> │M │ 157 | │2 @ │3 £ │4 $ │5 € │7 { │8 [ │9 ] │0 } │+ \ │e € │< | │m µ │ 158 | └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘ 159 | ``` 160 | -------------------------------------------------------------------------------- /apl-packages/APLTreeUtils2.aplc: -------------------------------------------------------------------------------- 1 | :Class APLTreeUtils2 2 | ⍝ This is the predecessor of the namespace `APLTreeUtils`.\\ 3 | ⍝ While `APLTreeUtils` was a namespace scipt designed to be included into pretty much every member of the 4 | ⍝ APLTree library, `APLTreeUtils2` is a class with shared methods. You are supposed to call those methods. 5 | ⍝ This has some major advantageous over the old approach: 6 | ⍝ * It's possible to add new functins to `APLTreeUtils`. With the old approach there was always the possibility 7 | ⍝ of a name clash, so adding new function was practically impossible. 8 | ⍝ * The sequence of fixing does not matter (though with lazy fixing that should not be an issue anymore anyway, 9 | ⍝ but at the time of writing it still is). 10 | ⍝ * Over the years we have seen rare `⎕IO` and `⎕ML` issues with `:Include`. We just avoid the possibility now.\\ 11 | ⍝ For a list with the precise differences between `APLTreeUtils` and `APLTreeUtils2` see the project ReadMe on 12 | ⍝ GitHub. Note that there are many. Most importantly, `APLTreeUtils2` requires Dyalog 18.0. 13 | ⍝ Kai Jaeger - APL Team Ltd.\\ 14 | ⍝ Homepage: 15 | 16 | ⎕ML←⎕IO←1 17 | 18 | ∇ r←Version 19 | :Access Public Shared 20 | r←'APLTreeUtils2' '1.1.3+53' '2021-04-07' 21 | ∇ 22 | 23 | ∇ History 24 | ⍝ * 1.1.3 from 2021-04-07 25 | ⍝ * `AtLeastVersion` was NOT buggy, its test case was 26 | ⍝ * 1.1.2 from 2021-04-06 27 | ⍝ * `AtLeastVersion` was buggy 28 | ⍝ * 1.1.1 from 2021-03-01 29 | ⍝ * Package config file corrected (new format) 30 | ⍝ * 1.1.0 from 2020-09-26 31 | ⍝ * Method `IsRunningAsAdmin` added 32 | ⍝ * 1.0.0 from 2020-09-06 33 | ⍝ * This is the predecessor of `APLTreeUtils` 34 | ∇ 35 | 36 | ∇ r←{x}Lowercase txt 37 | ⍝ `txt` is transformed into lowercase.\\ 38 | ⍝ This function is kept for compatability reasons. 39 | :Access Public Shared 40 | x←{0<⎕NC ⍵:⍎⍵ ⋄ ¯3}'x' 41 | r←x ⎕C txt 42 | ∇ 43 | 44 | ∇ r←Uppercase txt 45 | ⍝ `txt` is transformed into uppercase.\\ 46 | ⍝ This function is kept for compatability reasons. 47 | :Access Public Shared 48 | r←1 ⎕C txt 49 | ∇ 50 | 51 | ∇ r←IsChar y 52 | ⍝ Checks `y` for being text 53 | :Access Public Shared 54 | r←0 2∊⍨10|⎕DR y 55 | ∇ 56 | 57 | ∇ r←ToNum y 58 | ⍝ Transforms `y` into number(s) 59 | :Access Public Shared 60 | r←⊃(//)⎕VFI y 61 | ∇ 62 | 63 | ∇ r←IsScripted y 64 | ⍝ Returns a 1 for classes, interfaces and scripted namespaces and 0 otherwise. 65 | :Access Public Shared 66 | r←{11 16::0 ⋄ 1⊣⎕SRC ⍵}y 67 | ∇ 68 | 69 | ∇ {r}←{msg}Assert y;EN;success 70 | ⍝ Use this to ensure certain conditions. If the condition is not met an error is signalled, otherwise a 1 is returned.\\ 71 | ⍝ The optional left argument would become the message that is signalled.\\ 72 | ⍝ `y` might be a scalar or a vector of length one: that must be a Boolean. 1 means "success".\\ 73 | ⍝ `y` can also be a two-item vector: 74 | ⍝ 1. A Boolean as before 75 | ⍝ 2. An error number to be signalle; defaults to 11. 76 | :Access Public Shared 77 | r←1 78 | msg←{0<⎕NC ⍵:⍎⍵ ⋄ ''}'msg' 79 | (success EN)←2↑y,11 80 | :If (,1)≢,success 81 | msg ⎕SIGNAL EN 82 | :EndIf 83 | ∇ 84 | 85 | ∇ r←IsDevelopment 86 | :Access Public Shared 87 | ⍝ Returns 1 in case the function is running under a Dyalog development (EXE or DLL). 88 | r←'Development'≡4⊃'#'⎕WG'APLVersion' 89 | r∨←'DLL'≡4⊃'#'⎕WG'APLVersion' ⍝ May be DLLRT instead! 90 | ∇ 91 | 92 | ∇ r←{sep}SplitPath y;l 93 | ⍝ Separates the path from the filename and returns both.\\ 94 | ⍝ Default for the optional left argument (the separator) are `\/`.\\ 95 | ⍝ `'C:\Buffer\' 'my.txt' ←→ SplitPath 'C:\Buffer\my.txt'` 96 | ⍝ `(,¨'1.2.3.4.') (,'5') ←→ '.' SplitPath '1.2.3.4.5'` 97 | :Access Public Shared 98 | sep←,{0<⎕NC ⍵:⍎⍵ ⋄ '/\'}'sep' 99 | :If 0=≢y 100 | r←2⍴⊂y 101 | :Else 102 | l←1+-⌊/sep⍳⍨⌽y 103 | r←(l↓y)(l↑y) 104 | :EndIf 105 | ∇ 106 | 107 | ∇ r←{sep}Split y;b 108 | :Access Public Shared 109 | ⍝ `'First' 'Second' ←→ Split 'First',(⎕UCS 13 10),'Second'`\\ 110 | ⍝ `(,¨'1' '2' '3' '' '4') ←→ '.' Split '1.2.3..4'`\\ 111 | ⍝ That's _different_ from what `(≠⊆⊢)` is doing:\\ 112 | ⍝ `(,¨'1' '2' '3' '4') ←→ '.' (≠⊆⊢) '1.2.3..4'`\\ 113 | ⍝ Default for the optional left argument (the separator) is CR+LF. 114 | sep←{0<⎕NC ⍵:⍎⍵ ⋄ ⎕UCS 13 10}'sep' 115 | b←(1↑⍨≢sep),sep⍷y ⍝ This is more efficient in terms of memory then doing it one one line 116 | r←(≢sep)↓¨b⊂sep,y 117 | ∇ 118 | 119 | ∇ r←{sep}Last y;where 120 | :Access Public Shared 121 | ⍝ Returns the last part in `y`.\\ 122 | ⍝ Default separator is the dot (`.`), so for a filename it would return the extension:\\ 123 | ⍝ `BAT ←→ Last '/path/to/file.BAT'`\\ 124 | ⍝ You can also use this to get the filename from a path:\\ 125 | ⍝ `file.BAT ←→ '/\' Last '/path/to/file.BAT'`\\ 126 | ⍝ Returns always a vector:\\ 127 | ⍝ `(,¨'3') ←→ Last '1.2.3'` 128 | sep←{0<⎕NC ⍵:⍎⍵ ⋄ '.'}'sep' 129 | :If (≢y)=where←¯1+⌊/sep⍳⍨⌽,y 130 | r←0⍴y 131 | :ElseIf ~0 1∊⍨≡r←(-where)↑y 132 | r←⍬⍴r 133 | :EndIf 134 | ∇ 135 | 136 | ∇ r←{x}DMB y;w 137 | :Access Public Shared 138 | ⍝ Delete leading, trailing and multiple blanks by default. Accepts scalar, vector and matrix as argument. 139 | x←{0<⎕NC ⍵:⍎⍵ ⋄ ' '}'x' 140 | r←x{~0 1∊⍨≡⍵:⍺ ∇¨⍵ 141 | 2=⍴⍴⍵:↑⍺ ∇¨↓⍵ 142 | (,⍺)≡,⍵:'' 143 | w←1↓¯1↓⍺{⍵/⍨~(2⍴⍺)⍷⍵}⍺,⍵,⍺ 144 | (0=⍴⍴⍵)∧1=≢w:⍬⍴⍵ 145 | w 146 | }y 147 | ∇ 148 | 149 | ∇ r←DLB y 150 | :Access Public Shared 151 | ⍝ Delete leading blanks. Accepts scalar, vector and matrix as argument. 152 | :If 2=|≡y 153 | r←DLB¨y 154 | :ElseIf 1=⍴⍴y 155 | r←(+/∧\' '=y)↓y ⍝ Vectors (main application) 156 | :ElseIf 2=⍴⍴y 157 | r←(+/∧\' '=y)⌽y ⍝ Matrix 158 | :ElseIf 0=⍴⍴y 159 | r←(1+' '≡y)⊃y'' ⍝ Scalar 160 | :EndIf 161 | ∇ 162 | 163 | ∇ r←DTB y 164 | :Access Public Shared 165 | ⍝ Delete trailing blanks. Accepts scalar, vector and matrix as argument. 166 | :If 2=|≡y 167 | r←DTB¨y 168 | :ElseIf 1=⍴⍴y 169 | r←⌽{(+/∧\' '=⍵)↓⍵}⌽y ⍝ Vectors (main application) 170 | :ElseIf 2=⍴⍴y 171 | r←(-+/∧⌿∧\' '=⌽y)↓[2]y ⍝ Matrix 172 | :ElseIf 0=⍴⍴y 173 | r←(1+' '≡y)⊃y'' ⍝ Scalar 174 | :EndIf 175 | ∇ 176 | 177 | ∇ {success}←{browser}GoToWebPage Url;wsh;url;html;title;EncodeBlanksForNix;⎕WX 178 | :Access Public Shared 179 | ⍝ Fires up the default browser and displays "Url". Exception is when the current 180 | ⍝ session is connected to Ride: then "Url" is displayed in the Ride browser.\\ 181 | ⍝ Instead of using the default browser you can specify an EXE (Windows) or command 182 | ⍝ as the left argument. That will then be used to show "Url". Note that the left 183 | ⍝ argument is ignored in case the current session is connected to Ride.\\ 184 | ⍝ For displaying a local file rather then a url add "file:///".\\ 185 | ⍝ However, note that "file:///" does not work on some systems / with some browsers.\\ 186 | ⍝ Returns 1 for success and 0 for failure. That can happen for example when the 187 | ⍝ Windows Scripting Host throws a DOMAIN ERROR.\\ 188 | ⍝ Examples: 189 | ⍝ ~~~ 190 | ⍝ GoToWebPage 'file:///c:/my.html' 191 | ⍝ GoToWebPage 'file://localhost/c:/my.html' 192 | ⍝ GoToWebPage 'http://aplwiki.com' 193 | ⍝ GoToWebPage 'https://en.wikipedia.org/wiki/Main_Page' 194 | ⍝ ~~~ 195 | ⎕WX←1 196 | success←1 197 | url←Lowercase Url 198 | :If ∨/'file:'{⍺≡(≢⍺)↑⍵}url 199 | :If 'Win'≡GetOperatingSystem ⍬ 200 | Url←'"',('"'~⍨'file://'{⍺,(≢⍺)↓⍵}Url),'"' 201 | :EndIf 202 | :ElseIf ∨/'https:'{⍺≡(≢⍺)↑⍵}url 203 | Url←'https://'{⍺,(≢⍺)↓⍵}Url 204 | :ElseIf ∨/'http:'{⍺≡(≢⍺)↑⍵}url 205 | Url←'http://'{⍺,(≢⍺)↓⍵}Url 206 | :Else 207 | :If ∨/'//:'∊Url ⍝ Any protocol at all?! 208 | ⍝ ? No idea what that protocol is, so we just allow it going through. 209 | :Else 210 | Url←'https://',Url ⍝ That's the default 211 | :EndIf 212 | :EndIf 213 | (('\'=Url)/Url)←'/' 214 | Url←{l←3+1⍳⍨'://'⍷⍵ ⋄ (l↑⍵),{(~'//'⍷⍵)/⍵}l↓Url}Url 215 | :If 3501⌶⍬ ⍝ Connected to Ride? 216 | :If 'file://'{⍺≡(≢⍺)↑⍵}Url 217 | ⍝Url←∊(⊂'\ ')@(⍸' '=Url)⊣Url 218 | Url←(≢'file://')↓Url 219 | html←⊃⎕NGET Url 1 220 | :If 0=≢title←'' '' 226 | html,←⊂'' 227 | html,←'' '' 228 | success←0=Url(3500⌶)∊html 229 | :EndIf 230 | :Else 231 | browser←{0<⎕NC ⍵:⍎⍵ ⋄ ''}'browser' 232 | browser←{'"',(⍵~'"'),'" '}⍣(0<≢browser)⊢browser 233 | :Select GetOperatingSystem ⍬ 234 | :Case 'Win' 235 | 'wsh'⎕WC'OLEClient' 'WScript.Shell' 236 | :Trap 11 237 | :If 0=≢browser 238 | {}wsh.Run Url 239 | :Else 240 | {}wsh.Run browser,Url 241 | :EndIf 242 | :Else 243 | success←0 244 | :EndTrap 245 | :Case 'Lin' 246 | :Trap 11 247 | :If 0=≢browser 248 | {}⎕SH'xdg-open ',Url,' /dev/null 2>&1 &' 249 | :Else 250 | {}⎕SH browser,' ',Url,' /dev/null 2>&1 &' 251 | :EndIf 252 | :Else 253 | success←0 254 | :EndTrap 255 | :Case 'Mac' 256 | :Trap 11 257 | {}⎕SH'open Safari ',Url 258 | :Else 259 | success←0 260 | :EndTrap 261 | :EndSelect 262 | :EndIf 263 | ∇ 264 | 265 | ∇ r←IsRunningAsAdmin;IsUserAnAdmin 266 | ⍝ Tells whether this process is being "Run as Administrator" (Windows) or has sudo rights (others) 267 | :Access Public Shared 268 | :If 'Win'≡APLTreeUtils2.GetOperatingSystem'' 269 | :Trap r←0 270 | r←⍎⎕NA'I Shell32|IsUserAnAdmin' 271 | :EndTrap 272 | :Else 273 | r←{∨/' sudo '⍷' ',⍵,' '}⊃⎕SH'groups ',⎕AN 274 | :EndIf 275 | ∇ 276 | 277 | ∇ r←GetOperatingSystem dummy 278 | :Access Public Shared 279 | ⍝ Returns one of: "Win", "Mac", "AIX", "Lin".\\ 280 | ⍝ Note that under Linux you might need more information. 281 | r←3↑⊃'.'⎕WG'APLVersion' 282 | ∇ 283 | 284 | ∇ r←Create_UUID;⎕RL 285 | ⍝ Produces a UUID 286 | :Access Public Shared 287 | ⎕RL←+/⎕TS 288 | r←'-'@(+\9,3⍴5)⊢(⎕D,⎕C ⎕A)[?36⍴16] 289 | ∇ 290 | 291 | ∇ r←{type}Base64 txt;charset 292 | :Access Public Shared 293 | ⍝ Base64 encoding. `txt` must be a simple character vector.\\ 294 | ⍝ `type` defaults to 1 which specifies the default alphabet: `[a-zA-z0-9+/=]`.\\ 295 | ⍝ If you want a file and URL-save alphabet specify `type` as 2. Then `- is used instead of `+`, and `_` instead of `/`.\\ 296 | ⍝ See [RFC 4648](http://www.rfc-editor.org/rfc/rfc4648.txt) for details.\\ 297 | ⍝ Whitespace in `txt` is ignored on decode. 298 | type←{0<⎕NC ⍵:⍎⍵ ⋄ 1}'type' 299 | :If (,1)≡,type 300 | charset←'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' 301 | :ElseIf (,2)≡,type 302 | charset←'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' 303 | :Else 304 | 'Invalid left argument: "type" must be either 1 or 2'⎕SIGNAL 11 305 | :EndIf 306 | :If ∧/('='~⍨∪txt)∊charset 307 | r←charset base64 txt 308 | :Else 309 | r←charset base64'UTF-8'⎕UCS txt 310 | :EndIf 311 | ∇ 312 | 313 | ∇ r←{x}AtLeastVersion min 314 | :Access Public Shared 315 | ⍝ Returns 1 if the currently running version is at least `min`.\\ 316 | ⍝ If the current version is 17.1 then:\\ 317 | ⍝ `1 1 1 0 ←→ AtLeastVersion¨16 17 17.1 18`\\ 318 | ⍝ You may specify a version different from the currently running one via `⍺`:\\ 319 | ⍝ `1 1 0 0 ←→ 17 AtLeastVersion¨16 17 17.1 18` 320 | x←{0<⎕NC ⍵:⍎⍵ ⋄ {⊃⊃(//)⎕VFI ⍵/⍨2>+\⍵='.'}2⊃'#'⎕WG'APLVersion'}'x' 321 | 'Right argument must be length 1'⎕SIGNAL 11/⍨1≠≢min 322 | r←⊃min≤x 323 | ∇ 324 | 325 | ∇ r←{length}FormatDateTime ts;ts2;formatstring;bool;buffer 326 | :Access Public Shared 327 | ⍝ Formats the right argument (defaults to `⎕TS` if empty) as a string with 'YYYY-MM-DD hh:mm:ss by default\\ 328 | ⍝ The right argument can be one of: 329 | ⍝ * A single float (like 20120102030405) representing date and time. 330 | ⍝ * A simple vector of length 6 or 7 representing a timestamp (`⎕TS`). 331 | ⍝ * A matrix of either floats or vectors (length 6 or 7) representing a DateTime. 332 | ⍝ Note that you cannot mix floats and time stamps.\\ 333 | ⍝ If the right argument has not 7 but 6 or 3 elements, formatting is done accordingly.\\ 334 | ⍝ Via the left argument the length can be set to 335 | ⍝ ⍬, 3, 6 or 7; default is 6; ⍬ Accepts any length of the right argument which is 3, 6 or 7.\\ 336 | ⍝ If the right argument is a... 337 | ⍝ * simple vector, a string is returned. 338 | ⍝ * matrix, a matrix is returned. 339 | :If 645≡⎕DR ts 340 | :If (⍴⍴ts)∊0 1 341 | :If 1=≢ts 342 | ts←{0=≢,⍵:⍬ ⋄ ⎕ML←3 ⋄ ⍎¨(4 2 2 2 2 2/⍳6)⊂14 0⍕⍵}ts 343 | :EndIf 344 | :Else 345 | ts←↑,{0=⍵:6⍴0 ⋄ v←14 0⍕⍵ ⋄ ⎕ML←3 ⋄ ⍎¨(4 2 2 2 2 2/⍳6)⊂v}¨ts 346 | :EndIf 347 | :EndIf 348 | :If ⍬≡length←{2=⎕NC ⍵:⍎⍵ ⋄ 6}'length' 349 | length←''⍴¯1↑⍴ts 350 | :EndIf 351 | :If ~0=≢ts 352 | :If 2=⍴⍴ts 353 | buffer←{⍵/⍨0<≢¨⍵~¨⊂' ' 0}↓ts 354 | 'Invalid right argument: must be integer'⎕SIGNAL 11/⍨~326 163∊⍨∪⎕DR¨buffer 355 | 'Invalid right argument: must not be negative'⎕SIGNAL 11/⍨∨/¯1∊¨×¨buffer 356 | 'Invalid right argument: must be simple'⎕SIGNAL 11/⍨1≠∪≡¨buffer 357 | :Else 358 | 'Invalid right argument: must be integer'⎕SIGNAL 11/⍨~326 163∊⍨⎕DR ts 359 | 'Invalid right argument: must not be negative'⎕SIGNAL 11/⍨¯1∊×,ts 360 | 'Invalid right argument: must be simple'⎕SIGNAL 11/⍨1≠≡ts 361 | :EndIf 362 | :EndIf 363 | :If 2=⍴⍴ts 364 | ts2←length↑[2]ts 365 | :Else 366 | ts2←,[0.5]length↑{0=≢⍵:⎕TS ⋄ ts}ts 367 | :EndIf 368 | :Select ⊃length 369 | :Case 3 370 | formatstring←'ZI4,<->,ZI2,<->,ZI2' 371 | :CaseList 6 7 372 | formatstring←'ZI4,<->,ZI2,<->,ZI2,< >,ZI2,<:>,ZI2,<:>,ZI2' 373 | :Else 374 | 'Invalid left argument'⎕SIGNAL 11 375 | :EndSelect 376 | bool←(ts2∨.≠' ')∧ts2∨.≠0 377 | r←bool⍀formatstring ⎕FMT(6⌊length)↑[2]bool⌿ts2 378 | :If 7=2⊃⍴ts2 379 | r←⊃(↓r),¨{0=⍵:'' ⋄ 0=≢⍵~' ':'' ⋄ '.',⍕⍵}¨ts2[;7] 380 | :EndIf 381 | :If 2≠⍴⍴ts 382 | r←,r 383 | :EndIf 384 | ∇ 385 | 386 | base64←{ 387 | ⎕IO←0 388 | ⍺←'' 389 | chars←⍺ 390 | bits←{,⍉(⍺⍴2)⊤⍵} ⍝ encode each element of ⍵ in ⍺ bits, 391 | ⍝ and catenate them all together 392 | part←{((⍴⍵)⍴⍺↑1)⊂⍵} ⍝ partition ⍵ into chunks of length ⍺ 393 | 394 | 0=2|⎕DR ⍵:'UTF-8'⎕UCS 2∘⊥∘(8∘↑)¨8 part{(-8|≢⍵)↓⍵}6 bits{(⍵≠64)/⍵}chars⍳⍵ 395 | ⍝ decode a string into octets 396 | 397 | four←{ ⍝ use 4 characters to encode either 398 | 8=≢⍵:'=='∇ ⍵,0 0 0 0 ⍝ 1, 399 | 16=≢⍵:'='∇ ⍵,0 0 ⍝ 2 400 | chars[2∘⊥¨6 part ⍵],⍺ ⍝ or 3 octets of input 401 | } 402 | cats←⊃∘(,/)∘((⊂'')∘,) ⍝ catenate zero or more strings 403 | cats''∘four¨24 part 8 bits ⍵ 404 | } 405 | 406 | :EndClass 407 | -------------------------------------------------------------------------------- /apl-packages/WinReg.aplc: -------------------------------------------------------------------------------- 1 | :Class WinReg 2 | ⍝ Offers shared methods useful to deal with the Windows Registry.\\ 3 | ⍝ Note that the Window Registry contains data saved under a kind 4 | ⍝ of "path". Such a path consists of: 5 | ⍝ * A so-called "Main Key" like HKEY_CURRENT_USER (or short HKCU) 6 | ⍝ * A so called sub key (which is displayed by RegEdt32 as a folder) 7 | ⍝ like "HKEY_CURRENT_USER\Software\Dyalog\Dyalog APL/W 12.0" 8 | ⍝ * A so-called value like "maxws"\\ 9 | ⍝ These terms might look strange but they are used by this class for 10 | ⍝ the sake of consistency with the Microsoft documentation.\\ 11 | ⍝ `WinReg` is also able to read and write default values of a 12 | ⍝ sub-key. For that you have to add a `\` to the end of the sub-key.\\ 13 | ⍝ This class as such does not come with any limitations 14 | ⍝ regarding the amount of data (REG_SZ, REG_MULTI, REG_EXPAND_SZ, 15 | ⍝ REG_BINARY) you can write. However, Microsoft suggests not to save 16 | ⍝ anything bigger than 2048 bytes (Unicode 4096?!) to avoid performance 17 | ⍝ penalties. One should save larger amounts of data in a file and 18 | ⍝ save the filename rather then the data in the Registry.\\ 19 | ⍝ `WinReg` supports only a limited number of data types for writing: 20 | ⍝ * REG_SZ (Strings) 21 | ⍝ * REG_EXPAND_SZ (STRINGS where variables between % get expanded on read) 22 | ⍝ * REG_MULTI_SZ (Vectors of strings) 23 | ⍝ * REG_BINARY (Binary data) 24 | ⍝ * REG_DWORD (32-bit signed integer)\\ 25 | ⍝ Author: Kai Jaeger 26 | ⍝ Homepage: 27 | 28 | ⎕ML←1 ⋄ ⎕IO←1 29 | 30 | ∇ R←Version 31 | :Access Public Shared 32 | R←'WinReg' '5.0.4+22' '2021-08-05' 33 | ∇ 34 | 35 | ∇ History 36 | :Access Public Shared 37 | ⍝ * 5.0.4 ⋄ 2022-08-05 38 | ⍝ * Bug fix in `GetValue` 39 | ⍝ * 5.0.3 ⋄ 2022-08-04 40 | ⍝ * "Author" information updated 41 | ⍝ * New "Make" 42 | ⍝ * Packages udated 43 | ⍝ * 5.0.2 ⋄ 2021-03-02 44 | ⍝ * Package config corrected 45 | ⍝ * 5.0.1 46 | ⍝ * Naming problem fixed caused by move to `APLTreeUtils2` 47 | ⍝ * 5.0.0 48 | ⍝ * BREAKIN CHANGES: 49 | ⍝ * Requires the class `APLTreeUtils2` 50 | ⍝ * Requires Dyalog 18.0 or better 51 | ⍝ * Is delivered as a Tatin package 52 | ⍝ * Internal changes 53 | ⍝ * Does not `:Include APLTreeUtils` anymore 54 | ⍝ * Uses `⎕ML←1` 55 | ⍝\\ 56 | ⍝ For information regarding older versions see 57 | ∇ 58 | 59 | ⍝ All data types, including those not supported yet 60 | :Field Public Shared ReadOnly REG_NONE←0 ⍝ None 61 | :Field Public Shared ReadOnly REG_SZ←1 ⍝ String 62 | :Field Public Shared ReadOnly REG_EXPAND_SZ←2 ⍝ String, but somethign like "%WinDir%" is expanded 63 | :Field Public Shared ReadOnly REG_BINARY←3 ⍝ Free form binary 64 | :Field Public Shared ReadOnly REG_DWORD←4 ⍝ 32-bit number 65 | :Field Public Shared ReadOnly REG_DWORD_LITTLE_ENDIAN←4 ⍝ 32-bit number (same as REG_DWORD) 66 | :Field Public Shared ReadOnly REG_DWORD_BIG_ENDIAN←5 ⍝ 32-bit number 67 | :Field Public Shared ReadOnly REG_LINK←6 ⍝ Symbolic Link (unicode) 68 | :Field Public Shared ReadOnly REG_MULTI_SZ←7 ⍝ Multiple Unicode strings 69 | :Field Public Shared ReadOnly REG_RESOURCE_LIST←8 ⍝ Resource list in the resource map 70 | :Field Public Shared ReadOnly REG_FULL_RESOURCE_DESCRIPTOR←9 ⍝ Resource list in the hardware description 71 | :Field Public Shared ReadOnly REG_RESOURCE_REQUIREMENTS_LIST←10 72 | :Field Public Shared ReadOnly REG_QWORD←11 ⍝ 64-bit number 73 | :Field Public Shared ReadOnly REG_QWORD_LITTLE_ENDIAN←11 ⍝ 64-bit number (same as REG_QWORD) 74 | ⍝ Error codes 75 | :Field Public Shared ReadOnly ERROR_SUCCESS←0 ⍝ Return code for success 76 | :Field Public Shared ReadOnly ERROR_FILE_NOT_FOUND←2 ⍝ Registry path does not exist 77 | :Field Public Shared ReadOnly ERROR_PATH_NOT_FOUND←3 ⍝ Key not found 78 | :Field Public Shared ReadOnly ERROR_ACCESS_DENIED←5 ⍝ Requested permissions not available 79 | :Field Public Shared ReadOnly ERROR_INVALID_HANDLE←6 ⍝ Invalid handle or top-level key 80 | :Field Public Shared ReadOnly ERROR_NOT_ENOUGH_MEMORY←8 ⍝ Not enough memory 81 | :Field Public Shared ReadOnly ERROR_BAD_NETPATH←53 ⍝ Network path not found 82 | :Field Public Shared ReadOnly ERROR_INVALID_PARAMETER←87 ⍝ Bad parameter to a Win32 API function 83 | :Field Public Shared ReadOnly ERROR_CALL_NOT_IMPLEMENTED←120 ⍝ Function valid only in WinNT/2000?XP 84 | :Field Public Shared ReadOnly ERROR_INSUFFICIENT_BUFFER←122 ⍝ Buffer too small to hold data 85 | :Field Public Shared ReadOnly ERROR_BAD_PATHNAME←161 ⍝ Registry path does not exist 86 | :Field Public Shared ReadOnly ERROR_MORE_DATA←234 ⍝ Buffer was too small 87 | :Field Public Shared ReadOnly ERROR_NO_MORE_ITEMS←259 ⍝ Invalid enumerated value 88 | :Field Public Shared ReadOnly ERROR_BADDB←1009 ⍝ Corrupted registry 89 | :Field Public Shared ReadOnly ERROR_BADKEY←1010 ⍝ Invalid registry key 90 | :Field Public Shared ReadOnly ERROR_CANTOPEN←1011 ⍝ Cannot open registry key 91 | :Field Public Shared ReadOnly ERROR_CANTREAD←1012 ⍝ Cannot read from registry key 92 | :Field Public Shared ReadOnly ERROR_CANTWRITE←1013 ⍝ Cannot write to registry key 93 | :Field Public Shared ReadOnly ERROR_REGISTRY_RECOVERED←1014 ⍝ Recovery of part of registry successful 94 | :Field Public Shared ReadOnly ERROR_REGISTRY_CORRUPT←1015 ⍝ Corrupted registry 95 | :Field Public Shared ReadOnly ERROR_REGISTRY_IO_FAILED←1016 ⍝ Input/output operation failed 96 | :Field Public Shared ReadOnly ERROR_NOT_REGISTRY_FILE←1017 ⍝ Input file not in registry file format 97 | :Field Public Shared ReadOnly ERROR_KEY_DELETED←1018 ⍝ Key already deleted 98 | :Field Public Shared ReadOnly ERROR_KEY_HAS_CHILDREN←1020 ⍝ Key has subkeys & cannot be deleted 99 | :Field Public Shared ReadOnly ERROR_UNSUPPORTED_TYPE←1630 ⍝ Caution: this is sometimes returned when everything is just fine! 100 | ⍝ Defines the Access Rights constants 101 | :Field Public Shared ReadOnly KEY_READ←25 102 | :Field Public Shared ReadOnly KEY_ALL_ACCESS←983103 103 | :Field Public Shared ReadOnly KEY_KEY_WRITE←131078 104 | :Field Public Shared ReadOnly KEY_CREATE_LINK←32 105 | :Field Public Shared ReadOnly KEY_CREATE_SUB_KEY←4 106 | :Field Public Shared ReadOnly KEY_ENUMERATE_SUB_KEYS←8 107 | :Field Public Shared ReadOnly KEY_EXECUTE←131097 108 | :Field Public Shared ReadOnly KEY_NOTIFE←16 109 | :Field Public Shared ReadOnly KEY_QUERY_VALUE←1 110 | :Field Public Shared ReadOnly KEY_SET_VALUE←2 111 | ⍝ Other stuff 112 | :Field Public Shared ReadOnly NULL←⎕UCS 0 113 | 114 | ∇ r←{default}GetString y;yIsHandle;handle;value;subKey;path;bufSize;∆RegQueryValueEx;rc;type;data;errMsg 115 | :Access Public Shared 116 | ⍝ Use this function in order to read a value of type REG_SZ or REG_EXPAND_SZ or REG_MULTI_SZ. 117 | ⍝ 118 | ⍝ `⍵` can be one of: 119 | ⍝ * A simple string which is supposed to be a full path then (sub-key plus value name). 120 | ⍝ * A vector of length 2 with a handle to the sub-key in [1] and a value name in [2]. 121 | default←{0<⎕NC ⍵:⍎⍵ ⋄ ''}'default' 122 | r←default 123 | 'WinReg error: invalid right argument'⎕SIGNAL 11/⍨(~0 1∊⍨≡y)∧2≠≢y 124 | 'WinReg error: right argument must not be empty'⎕SIGNAL 11/⍨0=≢y 125 | 'WinReg error: invalid right argument'⎕SIGNAL 11/⍨(~(⎕DR y)∊80 82)∧2≠≢y 126 | :If (2=≢y)∧(0=1↑0⍴1⊃,y)∧(' '=1↑0⍴2⊃,y) 127 | yIsHandle←1 128 | (handle value)←y 129 | :ElseIf 1=|≡y 130 | yIsHandle←0 131 | path←y 132 | (subKey value)←{⍵{((-⍵)↓⍺)((-⍵-1)↑⍺)}'\'⍳⍨⌽⍵}path 133 | subKey←CheckPath subKey 134 | :If '\'≠¯1↑path 135 | :AndIf ~DoesKeyExist subKey 136 | r←default 137 | :Return 138 | :EndIf 139 | handle←OpenKey subKey 140 | :Else 141 | 'WinReg error: invalid right argument'⎕SIGNAL 11 142 | :EndIf 143 | :If 0=handle 144 | r←default 145 | :Return 146 | :EndIf 147 | value←((,'\')≢,value)/value ⍝ For access to the default value 148 | '∆RegQueryValueEx'⎕NA'I ADVAPI32.dll.C32|RegQueryValueEx* U <0T I =I >0T =I4' 149 | bufSize←1024 150 | :Repeat 151 | (rc type data bufSize)←∆RegQueryValueEx handle value 0 REG_SZ,bufSize bufSize 152 | :If type=REG_MULTI_SZ ⋄ :Leave ⋄ :EndIf 153 | :Until rc≠ERROR_MORE_DATA 154 | :If type=REG_EXPAND_SZ 155 | data←ExpandEnv data 156 | :ElseIf type=REG_MULTI_SZ 157 | '∆RegQueryValueEx'⎕NA'I ADVAPI32.dll.C32|RegQueryValueEx* U <0T I =I >T[] =I4' 158 | :Repeat 159 | (rc type data bufSize)←∆RegQueryValueEx handle value 0 REG_SZ,bufSize bufSize 160 | :If type=REG_MULTI_SZ ⋄ :Leave ⋄ :EndIf 161 | :Until rc≠ERROR_MORE_DATA 162 | data←Partition(bufSize÷2)↑data 163 | :EndIf 164 | errMsg←'' 165 | :If rc=ERROR_FILE_NOT_FOUND 166 | r←default 167 | :ElseIf rc=0 168 | r←data 169 | :Else 170 | errMsg←'WinReg error: ',ConvertErrorCode rc 171 | :EndIf 172 | Close(~yIsHandle)/handle 173 | errMsg ⎕SIGNAL 11/⍨0≠≢errMsg 174 | ∇ 175 | 176 | ∇ r←ListError 177 | :Access Public Shared 178 | ⍝ List all vars starting with "ERROR" 179 | r←List'ERROR_' 180 | r←r,[1.5]⍎¨r 181 | ∇ 182 | 183 | ∇ r←ListReg 184 | :Access Public Shared 185 | ⍝ List all vars starting with "REG\_" 186 | r←List'REG_' 187 | r←r,[1.5]⍎¨r 188 | ∇ 189 | 190 | ∇ r←{default}GetValue y;yIsHandle;handle;value;subKey;∆RegQueryValueEx;type;rc;errMsg;bufSize;length;data 191 | :Access Public Shared 192 | ⍝ `⍵` can be either a vector of length one or two: 193 | ⍝ 194 | ⍝ * If length 1: 195 | ⍝ * [1] path (subkey + value name) 196 | ⍝ * If length 2: 197 | ⍝ * [1] handle to the sub key 198 | ⍝ * [2] value name 199 | ⍝ 200 | ⍝ Returns the data saved as "path" in the Registry, or "Default". The data type is 201 | ⍝ determined from the Registry. 202 | default←{0<⎕NC ⍵:⍎⍵ ⋄ 0}'default' 203 | r←default 204 | yIsHandle←0 205 | 'WinReg error: invalid right argument'⎕SIGNAL 11/⍨(~0 1∊⍨≡y)∧2≠≢y 206 | 'WinReg error: right argument must not be empty'⎕SIGNAL 11/⍨0=≢y 207 | 'WinReg error: invalid right argument'⎕SIGNAL 11/⍨(~(⎕DR y)∊80 82)∧2≠≢y 208 | :If 2=≢y 209 | :AndIf yIsHandle←0=1↑0⍴1⊃y ⍝ Is the first item possibly a handle? 210 | (handle value)←y 211 | :Else 212 | (subKey value)←##.APLTreeUtils2.SplitPath y 213 | :If ~DoesKeyExist subKey ⋄ :Return ⋄ :EndIf 214 | handle←OpenKey subKey 215 | :EndIf 216 | '∆RegQueryValueEx'⎕NA'I ADVAPI32.dll.C32|RegQueryValueEx* U <0T I >I >I4 =I4' 217 | :If (,'\')≡,value 218 | value←'' ⍝ Default value has no value name 219 | :EndIf 220 | (rc type)←2↑∆RegQueryValueEx handle value 0 0 0 0 221 | :If rc=ERROR_FILE_NOT_FOUND 222 | r←default 223 | :Return 224 | :ElseIf ~rc∊ERROR_SUCCESS,ERROR_MORE_DATA,ERROR_UNSUPPORTED_TYPE ⍝ Yes, I know - but this sometimes happens although everything is fine!! 225 | errMsg←'Error, rc=',⍕rc 226 | :EndIf 227 | errMsg←'' 228 | bufSize←1024 229 | :Select type 230 | :Case REG_BINARY 231 | '∆RegQueryValueEx'⎕NA'I ADVAPI32.dll.C32|RegQueryValueEx* U <0T I >I4 >I1[] =I4 ' 232 | :Case REG_DWORD 233 | '∆RegQueryValueEx'⎕NA'I ADVAPI32.dll.C32|RegQueryValueEx* U <0T I >I4 >I4 =I4' 234 | :Case REG_QWORD 235 | '∆RegQueryValueEx'⎕NA'I ADVAPI32.dll.C32|RegQueryValueEx* U <0T I >U4 >I8 =U4' 236 | :CaseList REG_SZ,REG_EXPAND_SZ,REG_MULTI_SZ 237 | '∆RegQueryValueEx'⎕NA'I ADVAPI32.dll.C32|RegQueryValueEx* U <0T I =I >T[] =I4' 238 | :Else 239 | ('WinReg error: unsupported data type: ',GetTypeAsStringFrom type)⎕SIGNAL 11 240 | :EndSelect 241 | :Repeat 242 | (rc type data length)←∆RegQueryValueEx handle value 0 bufSize bufSize bufSize 243 | bufSize+←1024 244 | :Until (ERROR_MORE_DATA≠rc) 245 | errMsg←'' 246 | :If rc=ERROR_FILE_NOT_FOUND 247 | r←default 248 | :ElseIf ~rc∊ERROR_SUCCESS,ERROR_UNSUPPORTED_TYPE ⍝ Yes, I know - but this sometimes happens although everything is fine!! 249 | errMsg←'WinReg error: ',ConvertErrorCode rc 250 | :Else 251 | r←HandleDataType type length data 252 | :EndIf 253 | Close(~yIsHandle)/handle 254 | errMsg ⎕SIGNAL 11/⍨0≠≢errMsg 255 | ∇ 256 | 257 | ∇ {r}←{type}PutString y;yIsHandle;path;data;value;subKey;handle;rc 258 | :Access Public Shared 259 | ⍝ `⍵` can be either a vector of length two or three: 260 | ⍝ If length 2: 261 | ⍝ [1] path (subkey + value name) 262 | ⍝ [2] data to be saved 263 | ⍝ If length 3: 264 | ⍝ [1] handle to the sub key 265 | ⍝ [2] value name 266 | ⍝ [3] data to be saved 267 | ⍝ Stores "data" under `¯1↓y`. If "path" ends with a "\\" char, "data" 268 | ⍝ is saved as the default value of "path"; data type is always "REG_SZ" then. 269 | ⍝ 270 | ⍝ Note that type defaults to "REG_SZ" except when "data" is nested, then 271 | ⍝ the default is "REG_MULTI_SZ. 272 | ⍝ 273 | ⍝ You can set "type" to one of: REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ. 274 | :Select ⊃≢y 275 | :Case 2 276 | yIsHandle←0 277 | (path data)←y 278 | (subKey value)←{⍵{((-⍵)↓⍺)((-⍵-1)↑⍺)}'\'⍳⍨⌽⍵}path 279 | subKey←CheckPath subKey 280 | handle←OpenAndCreateKey subKey 281 | :Case 3 282 | yIsHandle←1 283 | (handle value data)←y 284 | 'WinReg error: invalid right argument'⎕SIGNAL 11/⍨0≠1↑0⍴handle 285 | :Else 286 | 'WinReg error: invalid right argument'⎕SIGNAL 11 287 | :EndSelect 288 | data←,data 289 | 'WinReg error: invalid "value"'⎕SIGNAL 11/⍨' '≠1↑0⍴value 290 | type←data{2=⎕NC ⍵:⍎⍵ ⋄ (1+0 1∊⍨≡⍺)⊃REG_MULTI_SZ REG_SZ}'type' 291 | :If type=REG_MULTI_SZ 292 | 'WinReg error: invalid "data"'⎕SIGNAL 11/⍨0∊{⊃' '=1↑0⍴⍵}¨data 293 | :Else 294 | 'WinReg error: invalid "data"'⎕SIGNAL 11/⍨' '≠1↑0⍴data 295 | :EndIf 296 | :If type=REG_MULTI_SZ 297 | data←{0 1∊⍨≡⍵:⍵ ⋄ 0=≢⍵:'' ⋄ (⊃,/⍵,¨NULL),NULL}data 298 | :Else 299 | data←data,{NULL=⍵:'' ⋄ NULL}¯1↑data 300 | :EndIf 301 | 'WinReg error: invalid data type - must be one of REG_SZ,REG_MULTI_SZ,REG_EXPAND_SZ'⎕SIGNAL 11/⍨~type∊REG_SZ,REG_MULTI_SZ,REG_EXPAND_SZ 302 | '∆RegSetValueEx'⎕NA'I ADVAPI32.dll.C32|RegSetValueEx* I <0T I I biggest 351 | Close(~yIsHandle)/handle 352 | 'WinReg error: data too large'⎕SIGNAL 11 353 | :EndIf 354 | :If dataT[] =I I >I >I >I >I >I >I >I >{U U}' 434 | buffer←∆RegQueryInfoKey handle 0 0 0 1 1 1 1 0 0 0 0 435 | rc←1⊃buffer 436 | :If 0=rc 437 | noofValues←7⊃buffer 438 | noofSubKeys←4⊃buffer 439 | maxNameLength←8⊃buffer 440 | maxValueLength←9⊃buffer 441 | r←noofValues noofSubKeys maxNameLength maxValueLength 442 | :Else 443 | Close(~yIsHandle)/handle 444 | ('WinReg error: ',ConvertErrorCode rc)⎕SIGNAL 11 445 | :EndIf 446 | Close(~yIsHandle)/handle 447 | ∇ 448 | 449 | ∇ r←GetAllValues y 450 | :Access Public Shared 451 | ⍝⍝ ⍝TODO⍝ : delete in the next major version 452 | ⍝ DEPRECATED 453 | ⍝ This was a misnomer from the start. 454 | ⍝ 455 | ⍝ This method will disappear with version 3.0 456 | r←GetAllNamesAndValues y 457 | ∇ 458 | 459 | ∇ r←GetAllNamesAndValues y;names;handle;noof;i;yIsHandle;qdmx 460 | ⍝ This method gets all values for a given subkey. 461 | ⍝ 462 | ⍝ `r` is a matrix with: 463 | ⍝ |[;1]| Value name 464 | ⍝ |[;2]| The data 465 | ⍝ |[;3]| Error message (for example unspported data type) 466 | ⍝ 467 | ⍝ `⍵` can be one of: 468 | ⍝ 469 | ⍝ * A string representing a path (sub key) 470 | ⍝ * A handle 471 | :Access Public Shared 472 | :If (0=1↑0⍴y)∧1=≢y ⍝ Is it a handle? 473 | yIsHandle←1 474 | handle←y 475 | :ElseIf (' '=1↑0⍴y)∧1=≡y ⍝ Is it a path 476 | handle←OpenKey y 477 | yIsHandle←0 478 | :Else 479 | 'WinReg error: invalid right argument'⎕SIGNAL 11 480 | :EndIf 481 | :If 0=handle 482 | r←0 3⍴'' 483 | :Return 484 | :EndIf 485 | noof←≢names←GetAllValueNames handle 486 | r←(noof,3)⍴'' 487 | :If 0≠≢r 488 | r[;1]←names 489 | :For i :In ⍳≢names 490 | :Trap 11 491 | r[i;2]←⊂GetValue handle(i⊃names) 492 | :Else 493 | qdmx←⎕DMX 494 | :If ∨/'WinReg error: unsupported data type'⍷qdmx.EM 495 | r[i;3]←⊂'Failed due to unsupported data type: ',{##.APLTreeUtils2.DMB ⍵↓⍨1++/2>+\⍵=':'}qdmx.EM 496 | :Else 497 | r[i;3]←⊂qdmx.EM,'; rc=',⍕qdmx.EN 498 | :EndIf 499 | :EndTrap 500 | :EndFor 501 | :EndIf 502 | Close(~yIsHandle)/handle 503 | ∇ 504 | 505 | ∇ r←{verbose}GetAllValueNames y;yIsHandle;handle;noofValues;noofSubkeys;For;∆RegEnumValue;i;rc;data;length;type;keyLength;dataLength 506 | ⍝ This method gets all value names for a given subkey. 507 | ⍝ 508 | ⍝ `r` is a vector with value names or, if the left argument is the string 509 | ⍝ "verbose" (default is `''`), a matrix with 2 columns: 510 | ⍝ 511 | ⍝ |[;1]| names 512 | ⍝ |[;2]| data types 513 | ⍝ `⍵` can be one of: 514 | ⍝ 515 | ⍝ * A string which is treated as a path (sub key) 516 | ⍝ * An integer which is treated as a handle 517 | ⍝ 518 | ⍝ Note that for a default value a "\\" is returned as value name. 519 | :Access Public Shared 520 | verbose←{2=⎕NC ⍵:'verbose'≡⍎⍵ ⋄ 0}'verbose' 521 | :If (0=1↑0⍴y)∧1=≢y ⍝ Is it a handle? 522 | yIsHandle←1 523 | handle←y 524 | :ElseIf (' '=1↑0⍴y)∧1=≡y ⍝ Is it a path 525 | handle←OpenKey y 526 | 'Could not access Windows Registry key'⎕SIGNAL 11/⍨0=handle 527 | yIsHandle←0 528 | :Else 529 | 'WinReg error: invalid right argument'⎕SIGNAL 11 530 | :EndIf 531 | '∆RegEnumValue'⎕NA'I ADVAPI32|RegEnumValue* I I >T[] =I I >I >T[] =I' 532 | (noofValues noofSubkeys keyLength dataLength)←4↑KeyInfo handle ⍝ No. of values, no. of SubKeys, max name length, data length 533 | r←(noofValues,2)⍴' ' 534 | :For i :In (⍳noofValues)-1 535 | (rc data length type)←4↑∆RegEnumValue handle,i,(keyLength+1),(keyLength+1),0 1,(dataLength+1),(dataLength+1) 536 | :If rc∊ERROR_SUCCESS,ERROR_MORE_DATA 537 | :If 0=length ⍝ Then it is the default value 538 | r[i+1;]←'\' 539 | :Else 540 | r[i+1;]←(⊃↑/length data)type 541 | :EndIf 542 | :Else 543 | Close(~yIsHandle)/handle 544 | ('WinReg error! ',ConvertErrorCode rc)⎕SIGNAL 11 545 | :EndIf 546 | :EndFor 547 | Close(~yIsHandle)/handle 548 | :If ~verbose 549 | r←r[;1] 550 | :EndIf 551 | ∇ 552 | 553 | ∇ r←GetAllSubKeyNames y;yIsHandle;handle;∆RegEnumKey;flag;rc;i;bufSize;name;length 554 | ⍝ This method returns a vector of strings with the namesof all sub keys for a given key. 555 | :Access Public Shared 556 | :If (0=1↑0⍴y)∧1=≢y ⍝ Is it a handle? 557 | yIsHandle←1 558 | handle←y 559 | :ElseIf (' '=1↑0⍴y)∧1=≡y ⍝ Is it a path 560 | handle←OpenKey y 561 | yIsHandle←0 562 | :Else 563 | 'WinReg error: invalid right argument'⎕SIGNAL 11 564 | :EndIf 565 | '∆RegEnumKey'⎕NA'I ADVAPI32|RegEnumKeyEx* I4 I4 >T[] =P P P P P' 566 | flag←i←0 567 | bufSize←1024×1+80=⎕DR'' 568 | r←'' 569 | :Repeat 570 | (rc name length)←∆RegEnumKey handle,i,bufSize,bufSize,0 0 0 0 571 | :If rc=ERROR_SUCCESS 572 | r,←⊂length↑name 573 | i+←1 574 | :ElseIf rc∊ERROR_NO_MORE_ITEMS ERROR_INVALID_HANDLE 575 | flag←1 576 | :Else 577 | Close(~yIsHandle)/handle 578 | ('WinReg error! ',ConvertErrorCode rc)⎕SIGNAL 11 579 | :EndIf 580 | :Until flag 581 | Close(~yIsHandle)/handle 582 | ∇ 583 | 584 | ∇ r←{depth}GetTree key;handle;depth;allValues;AllSubKeys;thisSubKey;buffer;allSubKeys 585 | ⍝ Takes the name of a key (but no handle!) and returns a (possibly empty) matrix with: 586 | ⍝ |[;1]| depth 587 | ⍝ |[;2]| fully qualified name 588 | ⍝ Note that sub-keys end with a backslash. 589 | ⍝ 590 | ⍝ See `GetTreeWithValues` if you need the data of the values, too. 591 | :Access Public Shared 592 | depth←{0=⎕NC ⍵:0 ⋄ ⍎⍵}'depth' 593 | :If (0=1↑0⍴key)∧1=≢key ⍝ Is it a valid handle? 594 | 11 ⎕SIGNAL⍨'WinReg error: right argument is not a key name' 595 | :ElseIf (' '=1↑0⍴key)∧1=≡key ⍝ Is it a path? 596 | handle←OpenKey key 597 | :Else 598 | 'WinReg error: invalid right argument'⎕SIGNAL 11 599 | :EndIf 600 | :If handle=0 601 | 11 ⎕SIGNAL⍨'WinReg error: key does not exist' 602 | :Else 603 | r←1 2⍴depth(key,('\'≠¯1↑key)/'\') 604 | :If 0≠≢allValues←GetAllValueNames handle 605 | r⍪←(1+depth),[1.5](⊂key,('\'≠¯1↑key)/'\'),¨allValues 606 | :EndIf 607 | :If 0≠≢allSubKeys←GetAllSubKeyNames key 608 | :For thisSubKey :In allSubKeys 609 | :If 0≠≢buffer←(depth+1)GetTree key,(('\'≠¯1↑key)/'\'),thisSubKey 610 | r⍪←buffer 611 | :EndIf 612 | :EndFor 613 | :EndIf 614 | Close handle 615 | :EndIf 616 | ∇ 617 | 618 | ∇ r←{depth}GetTreeWithValues key;handle;depth;allValues;AllSubKeys;thisSubKey;buffer;allSubKeys 619 | ⍝ Takes the name of a key (but no handle!) and returns a (possibly empty) matrix with: 620 | ⍝ |[;1]| depth 621 | ⍝ |[;2]| fully qualified name 622 | ⍝ |[;3]| value data (empty for sub-keys) 623 | ⍝ |[;4]| error messages, for example in case of unsupported data types. 624 | ⍝ Note that sub-keys end with a backslash. 625 | ⍝ 626 | ⍝ See `GetTree` if you don't need the data of the values. 627 | :Access Public Shared 628 | depth←{0=⎕NC ⍵:0 ⋄ ⍎⍵}'depth' 629 | :If (0=1↑0⍴key)∧1=≢key ⍝ Is it a valid handle? 630 | 11/⍨⎕SIGNAL'WinReg error: invalid right argument' 631 | :ElseIf (' '=1↑0⍴key)∧1=≡key ⍝ Is it a path? 632 | handle←OpenKey key 633 | :Else 634 | 'WinReg error: invalid right argument'⎕SIGNAL 11 635 | :EndIf 636 | :If handle=0 637 | 11 ⎕SIGNAL⍨'WinReg error: key does not exist' 638 | :Else 639 | r←1 4⍴depth(key,('\'≠¯1↑key)/'\')'' '' 640 | :If 0≠≢allValues←GetAllNamesAndValues handle 641 | allValues[;1]←(⊂key,('\'≠¯1↑key)/'\'),¨allValues[;1] 642 | r⍪←(1+depth),allValues 643 | :EndIf 644 | :If 0≠≢allSubKeys←GetAllSubKeyNames key 645 | :For thisSubKey :In allSubKeys 646 | :If 0≠≢buffer←(depth+1)GetTreeWithValues key,(('\'≠¯1↑key)/'\'),thisSubKey 647 | r⍪←buffer 648 | :EndIf 649 | :EndFor 650 | :EndIf 651 | Close handle 652 | :EndIf 653 | ∇ 654 | 655 | ∇ {r}←CopyTree(source destination);sourceHandle;destinationHandle;wv;sourceIsHandle;destinationIsHandle;∆RegCopyTree 656 | :Access Public Shared 657 | ⍝ Use this to copy a Registry Key from `source` to `destination`. 658 | ⍝ 659 | ⍝ Note that this method needs at least Vista. 660 | ⍝ 661 | ⍝ Both, `source` as well as `destination` can be one of: 662 | ⍝ * A path (sub key) 663 | ⍝ * A handle 664 | :If (0=1↑0⍴source)∧1=≢source ⍝ Is it a handle? 665 | sourceIsHandle←1 666 | sourceHandle←source 667 | :ElseIf (' '=1↑0⍴source)∧1=≡source ⍝ Is it a path 668 | sourceHandle←OpenKey source 669 | sourceIsHandle←0 670 | :Else 671 | 'WinReg error: invalid right argument ("source")'⎕SIGNAL 11 672 | :EndIf 673 | :If (0=1↑0⍴destination)∧1=≢destination ⍝ Is it a handle? 674 | destinationIsHandle←1 675 | destinationHandle←destination 676 | :ElseIf (' '=1↑0⍴destination)∧1=≡destination ⍝ Is it a path 677 | destinationHandle←OpenAndCreateKey destination 678 | destinationIsHandle←0 679 | :Else 680 | 'WinReg error: invalid right argument ("source")'⎕SIGNAL 11 681 | :EndIf 682 | :If ~destinationIsHandle 683 | 'WinReg error: destination must not be a Registry value'⎕SIGNAL 11/⍨0≠##.WinReg.DoesValueExist destination 684 | :EndIf 685 | wv←GetVersion ⍝ Get the Windows version 686 | 'WinReg error: "CopyTree" is not supported in this version of Windows'⎕SIGNAL 11/⍨6>1⊃wv 687 | 'WinReg error: recursive copy failed'⎕SIGNAL 11/⍨source≡destination 688 | '∆RegCopyTree'⎕NA'I ADVAPI32.dll.C32|RegCopyTree* U <0T[] U' 689 | r←∆RegCopyTree sourceHandle''destinationHandle 690 | Close(~sourceIsHandle)/sourceHandle 691 | Close(~destinationIsHandle)/destinationHandle 692 | ∇ 693 | 694 | ∇ {r}←DeleteSubKey y;handle;HKEY;subKey;∆RegDeleteKey;wv;yIsHandle;path 695 | :Access Public Shared 696 | ⍝ Deletes a subkey "path", even if this subkeys holds values. 697 | ⍝ 698 | ⍝ The subkey to be deleted must not have subkeys. (You can achieve 699 | ⍝ this with the `DeleteSubKeyTree` function, see there) 700 | ⍝ 701 | ⍝ `⍵` can be one of: 702 | ⍝ * A string which is treated as a path (sub key) 703 | ⍝ * An integer which is treated as a handle 704 | ⍝ 705 | ⍝ Note that for a default value a "\\" is returned as value name. 706 | :Access Public Shared 707 | :If (0=1↑0⍴y)∧1=≢y ⍝ Is it a handle? 708 | handle←y 709 | subKey←'' 710 | yIsHandle←1 711 | :ElseIf (' '=1↑0⍴y)∧1=≡y ⍝ Is it a path 712 | y←CheckPath y 713 | (path subKey)←##.APLTreeUtils2.SplitPath y 714 | handle←OpenKey path 715 | yIsHandle←0 716 | :Else 717 | 'WinReg error: invalid right argument'⎕SIGNAL 11 718 | :EndIf 719 | :If 0=handle ⋄ r←0 ⋄ :Return ⋄ :EndIf ⍝ handle is 0? Nothing to delete then. 720 | wv←GetVersion ⍝ Get the Windows version 721 | 'WinReg error: "DeleteSubKey" is not supported in this version of Windows'⎕SIGNAL 11/⍨6>1⊃wv 722 | '∆RegDeleteKey'⎕NA'I ADVAPI32.dll.C32|RegDeleteKey* U <0T[]' 723 | r←∆RegDeleteKey handle subKey 724 | Close(~yIsHandle)/handle 725 | ∇ 726 | 727 | ∇ {r}←DeleteSubKeyTree y;handle;∆RegDeleteTree;yIsHandle;subKey;path 728 | :Access Public Shared 729 | ⍝ Deletes a subkey "path", even if this subkeys holds values. 730 | ⍝ 731 | ⍝ Any subkeys in "path" will be deleted as well. 732 | ⍝ 733 | ⍝ Note that this methods needs at least Vista. 734 | ⍝ 735 | ⍝ `⍵` can be one of: 736 | ⍝ * A path (sub key) 737 | ⍝ * A handle to a sub key 738 | 'WinReg error: right argument must not be empty'⎕SIGNAL 11/⍨0=≢y 739 | :If 1≠≡y 740 | :AndIf (0=1↑0⍴1⊃,y)∧1=≢1⊃,y ⍝ Is it a handle? 741 | yIsHandle←1 742 | handle←y 743 | subKey←'' 744 | :ElseIf (' '=1↑0⍴y)∧1=≡y ⍝ Is it a path? 745 | y←CheckPath y 746 | (path subKey)←##.APLTreeUtils2.SplitPath{⍵↓⍨-'\'=¯1↑⍵}y 747 | handle←OpenKey path 748 | yIsHandle←0 749 | :Else 750 | 'WinReg error: invalid right argument'⎕SIGNAL 11 751 | :EndIf 752 | '∆RegDeleteTree'⎕NA'I ADVAPI32.dll.C32|RegDeleteTree* U <0T[]' 753 | r←∆RegDeleteTree handle subKey 754 | Close(~yIsHandle)/handle 755 | ∇ 756 | 757 | ∇ {r}←DeleteValue y;path;RegDeleteValueA;handle;∆RegDeleteValue;value;subKey;yIsHandle 758 | :Access Public Shared 759 | ⍝ Delete a value from the Windows Registry. 760 | ⍝ 761 | ⍝ `⍵` can be one of: 762 | ⍝ |[1]| A full path (sub key + value name) 763 | ⍝ |[2]| A vector of length 2 with a handle in [1] and a value name in [2] 764 | ⍝ This method normally returns either ERROR_SUCCESS or ERROR_FILE_NOT_FOUND 765 | ⍝ in case the value did not exist from the start. 766 | :If 1≠≡y 767 | :AndIf (0=1↑0⍴1⊃,y)∧1=≢1⊃,y ⍝ Is it a handle? 768 | yIsHandle←1 769 | (handle value)←y 770 | :ElseIf (' '=1↑0⍴y)∧1=≡y ⍝ Is it a path 771 | (subKey value)←##.APLTreeUtils2.SplitPath y 772 | subKey←CheckPath subKey 773 | handle←OpenKey subKey 774 | yIsHandle←0 775 | :Else 776 | 'WinReg error: invalid right argument'⎕SIGNAL 11 777 | :EndIf 778 | '∆RegDeleteValue'⎕NA'I ADVAPI32.dll.C32|RegDeleteValue* U <0T[]' 779 | r←∆RegDeleteValue handle value 780 | Close(~yIsHandle)/handle 781 | ∇ 782 | 783 | ∇ bool←DoesKeyExist path;handle 784 | :Access Public Shared 785 | ⍝ Checks if a given Registry key exists. 786 | ⍝ 787 | ⍝ Note that you cannot pass a handle as right argument because 788 | ⍝ that makes no sense: if there is a handle the sub key **must** exist. 789 | 'WinReg error: right argument must not be empty'⎕SIGNAL 11/⍨0=≢path 790 | handle←OpenKey path 791 | bool←handle≠0 792 | Close handle 793 | ∇ 794 | 795 | ∇ r←GetTypeAsStringFrom value;l;values;mask 796 | ⍝ Use this to convert a value like 3 to REG_BINARY or 'REG_BINARY' 797 | ⍝ into 3. Specify an empty argument to get a matrix with all possible 798 | ⍝ names and their values. 799 | :Access Public Shared 800 | 'WinReg error: invalid right argument'⎕SIGNAL 11/⍨~0 1∊⍨≡value 801 | l←GetAllTypes ⍬ ⍝ Get list with all REG fields 802 | r←l GetAsString value 803 | ∇ 804 | 805 | ∇ r←GetErrorAsStringFrom value;l;values;mask 806 | ⍝ Use this to convert a value like 8 to "ERROR_NOT_ENOUGH_MEMORY". 807 | ⍝ 808 | ⍝ Specify an empty argument to get a matrix with all possible 809 | ⍝ names and their values. 810 | :Access Public Shared 811 | 'WinReg error: invalid right argument'⎕SIGNAL 11/⍨~0 1∊⍨≡value 812 | l←GetAll'ERROR' ⍝ Get list with all errors 813 | r←l GetAsString value 814 | ∇ 815 | 816 | ∇ bool←DoesValueExist y;names;yIsHandle;value;handle;subKey 817 | :Access Public Shared 818 | ⍝ Checks if a value exists in the Registry. 819 | ⍝ 820 | ⍝ `⍵` can be one of: 821 | ⍝ * Path (sub key) 822 | ⍝ * A handle to a sub key 823 | :If 1≠≡y 824 | :AndIf (0=1↑0⍴1⊃,y)∧1=≢1⊃,y ⍝ Is it a handle? 825 | yIsHandle←1 826 | (handle value)←y 827 | :ElseIf (' '=1↑0⍴y)∧1=≡y ⍝ Is it a path 828 | (subKey value)←##.APLTreeUtils2.SplitPath y 829 | subKey←CheckPath subKey 830 | handle←OpenKey subKey 831 | yIsHandle←0 832 | :Else 833 | 'WinReg error: invalid right argument'⎕SIGNAL 11 834 | :EndIf 835 | :If 0=handle ⍝ Then the sub key does not exist, let alone the value 836 | bool←0 837 | :Else 838 | names←GetAllValueNames handle 839 | bool←(⊂⎕C value)∊⎕C names 840 | Close(~yIsHandle)/handle 841 | :EndIf 842 | ∇ 843 | 844 | ∇ {r}←Close handle;RegCloseKey;_ 845 | :Access Public Shared 846 | r←⍬ 847 | :If 0≠≢handle 848 | ⎕NA'U ADVAPI32.dll.C32|RegCloseKey U' 849 | _←RegCloseKey¨handle 850 | :EndIf 851 | ∇ 852 | 853 | ∇ handle←{accessRights}OpenKey path;HKEY;subKey;∆RegCreateKeyEx;rc 854 | ⍝ Opens a key. This will fail if the key does not already exist. 855 | ⍝ 856 | ⍝ See also: [`OpenKeyAndCreate`](#). 857 | ⍝ 858 | ⍝ The optional left argument defaults to KEY_ALL_ACCESS which includes "Create"). 859 | ⍝ Instead you can specify KEY_READ in case of lacking the rights to create anything. 860 | :Access Public Shared 861 | accessRights←{2=⎕NC ⍵:⍎⍵ ⋄ KEY_ALL_ACCESS}'accessRights' 862 | path←CheckPath path 863 | :If 'HKEY_'≡5↑path 864 | (HKEY subKey)←{a←⍵⍳'\' ⋄ ((a-1)↑⍵)(a↓⍵)}path 865 | HKEY←Get_HKEY_From HKEY 866 | :Else 867 | HKEY←Get_HKEY_From'HKEY_CURRENT_USER' ⍝ Default 868 | :EndIf 869 | '∆RegCreateKeyEx'⎕NA'I ADVAPI32.dll.C32|RegOpenKeyEx* U <0T I I >I' 870 | (rc handle)←∆RegCreateKeyEx HKEY subKey 0 accessRights 1 871 | ('WinReg error: opening Registry key failed with ',ConvertErrorCode rc)⎕SIGNAL 11/⍨~rc∊ERROR_SUCCESS,ERROR_FILE_NOT_FOUND 872 | ∇ 873 | 874 | ∇ handle←OpenAndCreateKey path;HKEY;subKey;∆RegCreateKeyEx;rc;newFlag 875 | ⍝ Opens a key. If the key does not already exist it is going to be created. 876 | ⍝ 877 | ⍝ See also: [`OpenKey`](#). 878 | ⍝ 879 | ⍝ Note that this method needs KEY_ALL_ACCESS otherwise it cannot work properly. 880 | :Access Public Shared 881 | path←CheckPath path 882 | :If 'HKEY_'≡5↑path 883 | (HKEY subKey)←{a←⍵⍳'\' ⋄ ((a-1)↑⍵)(a↓⍵)}path 884 | HKEY←Get_HKEY_From HKEY 885 | :Else 886 | HKEY←Get_HKEY_From'HKEY_CURRENT_USER' ⍝ Default 887 | :EndIf 888 | '∆RegCreateKeyEx'⎕NA'I ADVAPI32.dll.C32|RegCreateKeyEx* U <0T I <0T I I I >U >U' 889 | (rc handle newFlag)←∆RegCreateKeyEx HKEY subKey 0 '' 0 KEY_ALL_ACCESS 0 1 1 890 | ('WinReg error: opening/creating Registry key failed with ',ConvertErrorCode rc)⎕SIGNAL 11/⍨ERROR_SUCCESS≠rc 891 | ∇ 892 | 893 | ∇ r←GetDyalogRegPath aplVersion;v 894 | ⍝ Returns the full Registry key for `aplVersion` which defaults to `'#' ⎕WG 'APLVersion'` when empty. 895 | :Access Public Shared 896 | r←'HKEY_CURRENT_USER\Software\Dyalog\Dyalog APL/W' 897 | v←{0=≢⍵:'#'⎕WG'APLVersion' ⋄ ⍵}aplVersion 898 | r,←(0<+/'-64'⍷⊃v)/'-64' 899 | r,←' ',⊃{⍺,'.',⍵}/2↑'.'##.APLTreeUtils2.Split 2⊃v 900 | r,←(80=⎕DR' ')/' Unicode' 901 | ⍝Done 902 | ∇ 903 | 904 | ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝ Private stuff ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝, 905 | ∇ HKEY←Get_HKEY_From Type 906 | Type←{0=≢⍵:'HKEY_CURRENT_USER' ⋄ ⍵}Type 907 | :If ' '=1↑0⍴Type 908 | :Select Type 909 | :Case 'HKEY_CLASSES_ROOT' 910 | HKEY←2147483648 ⍝ HEX 0x80000000 911 | :Case 'HKEY_CURRENT_USER' 912 | HKEY←2147483649 ⍝ HEX 0x80000001 913 | :Case 'HKEY_LOCAL_MACHINE' 914 | HKEY←2147483650 ⍝ HEX 0x80000002 915 | :Case 'HKEY_USERS' 916 | HKEY←2147483651 ⍝ HEX 0x80000003 917 | :Case 'HKEY_PERFORMANCE_DATA' 918 | HKEY←2147483652 ⍝ HEX 0x80000004 919 | :Case 'HKEY_CURRENT_CONFIG' 920 | HKEY←2147483653 ⍝ HEX 0x80000050 921 | :Case 'HKEY_DYN_DATA' 922 | HKEY←2147483654 ⍝ HEX 0x80000060 923 | :Else 924 | 'WinReg error: invalid Keyword'⎕SIGNAL 11 925 | :EndSelect 926 | :Else 927 | HKEY←Type 928 | :EndIf 929 | ∇ 930 | 931 | ∇ path←CheckPath path;buffer;path2;HKEY 932 | ⍝ Check the path, replace shortcuts by proper names and establish default if needed 933 | :If 'HK'≡2↑path 934 | (HKEY path2)←{⍵{((¯1+⍵)↑⍺)(⍵↓⍺)}⍵⍳'\'}path 935 | :If 'HKEY_'{⍺≢⍵↑⍨⍴⍺}HKEY 936 | :Select HKEY 937 | :Case 'HKCU' 938 | path←'HKEY_CURRENT_USER\',path2 939 | :Case 'HKCR' 940 | path←'HKEY_CLASSES_ROOT\',path2 941 | :Case 'HKLM' 942 | path←'HKEY_LOCAL_MACHINE\',path2 943 | :Case 'HKU' 944 | path←'HKEY_USERS\',path2 945 | :Else 946 | 11 ⎕SIGNAL⍨'WinReg error: invalid Registry key' 947 | :EndSelect 948 | :EndIf 949 | :Else 950 | path←'HKEY_CURRENT_USER\',path 951 | :EndIf 952 | ∇ 953 | 954 | Make←{ 955 | 0=⎕NC ⍵:⍺ 956 | ⍎⍵} 957 | 958 | GetAll←{ 959 | ⍝ Examples: 960 | ⍝ GetAll 'REG' 961 | ⍝ GetAll 'ERROR' 962 | l←⎕NL 2 ⍝ All variables 963 | l←(l[;⍳≢⍵]∧.=⍵)⌿l ⍝ Only those which start with ⍵ 964 | (↓l)~¨' ' ⍝ Transform into vectors of vectors 965 | } 966 | 967 | Partition←{⎕ML←3 ⋄ ⍵⊂⍨⍵≠NULL} 968 | 969 | ∇ r←GetVersion;rc;OSVERSIONINFO 970 | ⍝ Gets the OS version. 971 | ⍝ r[0] = Major version 972 | ⍝ r[1] = Minor version 973 | ⍝ r[2] = BuildNumber 974 | '∆GetVersion'⎕NA'I KERNEL32|GetVersionExA ={I I I I I T[128]}' ⍝ Don't convert this to Unicode - no point! 975 | OSVERSIONINFO←148 0 0 0 0(128⍴NULL) 976 | (rc r)←∆GetVersion⊂OSVERSIONINFO 977 | r←3↑1↓r 978 | ∇ 979 | 980 | ∇ R←ExpandEnv Y;ExpandEnvironmentStrings 981 | ⍝ If Y does not contain any "%", Y is passed untouched. 982 | ⍝ In case Y is empty R is empty as well. 983 | ⍝ Example: 984 | ⍝
'C:\Windows\MyDir' ←→ ExpandEnv '%WinDir%\MyDir'
985 | :If '%'∊R←Y 986 | 'ExpandEnvironmentStrings'⎕NA'I4 KERNEL32.C32|ExpandEnvironmentStrings* <0T >0T I4' 987 | R←2⊃ExpandEnvironmentStrings(Y 2048 2048) 988 | :EndIf 989 | ∇ 990 | 991 | ∇ r←HandleDataType(type length data) 992 | :Select type 993 | :Case REG_SZ 994 | r←(¯1+length÷2)↑data 995 | :CaseList REG_DWORD,REG_QWORD 996 | r←data 997 | :Case REG_BINARY 998 | r←length↑data 999 | :Case REG_EXPAND_SZ 1000 | r←ExpandEnv(¯1+length÷2)↑data 1001 | :Case REG_MULTI_SZ 1002 | r←Partition(¯1+length÷2)↑data 1003 | :Else 1004 | ('Data type is not supported: ',⍕type)⎕SIGNAL 11 1005 | :EndSelect 1006 | ∇ 1007 | 1008 | ∇ r←mat GetAsString value;values 1009 | ⍝ Sub fns of GetErrorAsStringFrom and GetTypeAsStringFrom 1010 | :If 0=≢value 1011 | r←⍉mat,[0.5]⍎¨mat ⍝ Build matrix with cols "Name" and "Value" 1012 | :Else 1013 | values←⍎¨l ⍝ Get the values 1014 | r←{1=≢⍵:⊃⍵ ⋄ ⍵}(mat,⊂'?')[values⍳value] 1015 | :EndIf 1016 | ∇ 1017 | 1018 | ∇ r←ConvertErrorCode rc;all 1019 | ⍝ rc is a number representing an error code. 1020 | ⍝ Returns either the name of the constant and/or the number as text. 1021 | ⍝ Examples: 1022 | ⍝ 'ERROR_ACCESS_DENIED (RC=5)' ←→ 'E' ConvertErrorCode 5 1023 | ⍝ '(RC=123)' ←→ 'E' ConvertErrorCode ¯123 1024 | :If 0=rc 1025 | r←'' 1026 | :Else 1027 | all←ListError 1028 | :If rc∊all[;2] 1029 | r←1⊃all[all[;2]⍳rc;] 1030 | :Else 1031 | r←'RC=',⍕rc 1032 | :EndIf 1033 | :EndIf 1034 | ∇ 1035 | 1036 | ∇ r←List string 1037 | ⍝ List all vars starting with "string" 1038 | r←(⊃string)⎕NL-2 1039 | r←string{⍵⌿⍨((≢⍺)↑[2]↑⍵)∧.=⍺}r 1040 | ∇ 1041 | 1042 | GetAllTypes←{ 1043 | 'REG_'{⍵/⍨⍺∘≡¨(≢⍺)↑¨⍵}⎕NL-2 1044 | } 1045 | 1046 | :EndClass 1047 | --------------------------------------------------------------------------------