├── mac-app ├── demo.gif ├── .swiftlint.yml ├── src │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── New Piskel-1.png.png │ │ │ └── Contents.json │ ├── BridgingHeader.h │ ├── ReadStdin.h │ ├── Notification+Name.swift │ ├── CommandArguments.swift │ ├── ListProvider.swift │ ├── ReadStdin.h.m │ ├── Info.plist │ ├── PipeListProvider.swift │ ├── InputField.swift │ ├── SearchWindow.swift │ ├── SettingsViewController.swift │ ├── VerticalAlignedTextFieldCell.swift │ ├── ResultsView.swift │ ├── Base.lproj │ │ ├── GeneralSettingsViewController.xib │ │ └── Main.storyboard │ ├── AppListProvider.swift │ ├── FuzzyMatcher.swift │ ├── SearchViewController.swift │ └── AppDelegate.swift ├── unmenu.entitlements ├── unmenu.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcuserdata │ │ │ └── ivan.xcuserdatad │ │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ ├── xcuserdata │ │ └── ivan.xcuserdatad │ │ │ ├── xcschemes │ │ │ └── xcschememanagement.plist │ │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── unmenu.xcscheme │ └── project.pbxproj ├── scripts │ └── unmenu ├── mac-application-archive.plist ├── libfuzzylib.h ├── .github │ └── workflows │ │ └── main.yml ├── Config.swift ├── keys.hpp ├── default_config.toml └── Keys.swift ├── fuzzylib ├── README.md ├── src │ ├── main.rs │ └── lib.rs ├── Cargo.toml └── Cargo.lock ├── .swiftlint.yml ├── .gitignore ├── Makefile ├── .github └── workflows │ └── main.yml └── README.md /mac-app/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unmanbearpig/unmenu/HEAD/mac-app/demo.gif -------------------------------------------------------------------------------- /fuzzylib/README.md: -------------------------------------------------------------------------------- 1 | # fuzzylib 2 | 3 | C bindings for https://crates.io/crates/fuzzy-matcher 4 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | identifier_name: 2 | excluded: # excluded via string array 3 | - id 4 | - i 5 | -------------------------------------------------------------------------------- /mac-app/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | identifier_name: 2 | excluded: # excluded via string array 3 | - id 4 | - i 5 | -------------------------------------------------------------------------------- /mac-app/src/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /mac-app/src/Assets.xcassets/AppIcon.appiconset/New Piskel-1.png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unmanbearpig/unmenu/HEAD/mac-app/src/Assets.xcassets/AppIcon.appiconset/New Piskel-1.png.png -------------------------------------------------------------------------------- /mac-app/unmenu.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mac-app/unmenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /mac-app/unmenu.xcodeproj/project.xcworkspace/xcuserdata/ivan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unmanbearpig/unmenu/HEAD/mac-app/unmenu.xcodeproj/project.xcworkspace/xcuserdata/ivan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /mac-app/scripts/unmenu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | function realpath() { python3 -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$0"; } 4 | CONTENTS="$(dirname "$(dirname "$(realpath "$0")")")" 5 | UNMENU="$CONTENTS/MacOS/dmenu-mac" 6 | "$UNMENU" "$@" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /mac-app/xcuserdata/ 2 | /mac-app//*.app 3 | /mac-app//*.xcarchive 4 | /mac-app//*.zip 5 | /mac-app/build 6 | /mac-app/DerivedData 7 | /mac-app/libfuzzylib.dylib 8 | /mac-app/libfuzzylib.a 9 | /fuzzylib/target 10 | .DS_Store 11 | /mac-app/.DS_Store 12 | -------------------------------------------------------------------------------- /mac-app/unmenu.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mac-app/mac-application-archive.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | method 6 | mac-application 7 | 8 | -------------------------------------------------------------------------------- /mac-app/unmenu.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /mac-app/unmenu.xcodeproj/xcuserdata/ivan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | unmenu.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean fuzzylib mac-app 2 | 3 | all: mac-app 4 | 5 | fuzzylib: 6 | cargo build --release --manifest-path=fuzzylib/Cargo.toml 7 | cp fuzzylib/target/release/libfuzzylib.a mac-app/ 8 | 9 | mac-app: fuzzylib 10 | xcodebuild -project mac-app/unmenu.xcodeproj -scheme unmenu -derivedDataPath build -configuration Release build 11 | 12 | install: mac-app 13 | cp -r mac-app/Build/Products/Release/unmenu.app /Applications/ 14 | 15 | clean: 16 | cd fuzzylib && cargo clean 17 | cd mac-app && rm -rf clean 18 | -------------------------------------------------------------------------------- /fuzzylib/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::BufRead; 2 | 3 | mod lib; 4 | 5 | fn main() { 6 | let mut matcher = lib::Matcher::new().expect("couldn't create matcher"); 7 | matcher.rescan(); 8 | for item in matcher.items.iter() { 9 | println!("item = {item:?}"); 10 | } 11 | 12 | let stdin = std::io::stdin(); 13 | let input_lines = stdin.lock().lines().map(Result::unwrap); 14 | 15 | for line in input_lines { 16 | println!("Received input: {}", line); 17 | let results = matcher.search(&line); 18 | println!("results = {results:?}"); 19 | 20 | if line == "exit" { 21 | break; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /fuzzylib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzzylib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | appkit-nsworkspace-bindings = "0.1.0" 10 | cocoa = "0.25.0" 11 | core-foundation = "0.9.3" 12 | core-foundation-sys = "0.8.4" 13 | core-services = "0.2.0" 14 | fuzzy-matcher = "0.3.7" 15 | libc = "0.2.147" 16 | objc = "0.2.7" 17 | objc-foundation = "0.1.1" 18 | regex = "1.8.4" 19 | serde = "1.0.164" 20 | serde_derive = "1.0.164" 21 | toml = "0.7.5" 22 | 23 | [lib] 24 | crate-type = ["staticlib"] 25 | 26 | [[bin]] 27 | name = "unmenu" 28 | path = "src/main.rs" 29 | 30 | -------------------------------------------------------------------------------- /mac-app/libfuzzylib.h: -------------------------------------------------------------------------------- 1 | // 2 | // libfuzzylib.h 3 | // unmenu 4 | // 5 | // Copyright © 2024 Ivan . All rights reserved. 6 | // Copyright © 2023 Jose Pereira. All rights reserved. 7 | // 8 | 9 | #ifndef libfuzzylib_h 10 | #define libfuzzylib_h 11 | 12 | // typedef struct Matcher Matcher; 13 | // typedef struct Item Item; 14 | typedef struct SearchResults SearchResults; 15 | 16 | void* matcher_new(); 17 | void matcher_rescan(void* matcher); 18 | SearchResults* matcher_search(void* matcher, const char* pattern); 19 | void search_results_free(SearchResults* results); 20 | // char* get_item_name(const Item* item); 21 | char* get_item_name(const void *item); 22 | 23 | void item_open(const void *item); 24 | 25 | struct SearchResults { 26 | uint64_t num_items; 27 | const void* items; 28 | }; 29 | 30 | 31 | #endif /* libfuzzylib_h */ 32 | -------------------------------------------------------------------------------- /mac-app/src/BridgingHeader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef BridgingHeader_h 19 | #define BridgingHeader_h 20 | 21 | #import "ReadStdin.h" 22 | #import "libfuzzylib.h" 23 | 24 | #endif /* BridgingHeader_h */ 25 | -------------------------------------------------------------------------------- /mac-app/src/ReadStdin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #import 19 | 20 | NS_ASSUME_NONNULL_BEGIN 21 | 22 | @interface ReadStdin : NSObject 23 | 24 | +(NSString *)read; 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /mac-app/src/Notification+Name.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import Foundation 19 | 20 | extension Notification.Name { 21 | static let AppleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification") 22 | } 23 | -------------------------------------------------------------------------------- /mac-app/src/CommandArguments.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import ArgumentParser 19 | 20 | struct DmenuMac: ParsableArguments { 21 | @Option(name: .shortAndLong, help: "Show a prompt instead of the search input.") 22 | var prompt: String? 23 | } 24 | -------------------------------------------------------------------------------- /mac-app/unmenu.xcodeproj/project.xcworkspace/xcuserdata/ivan.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | CustomLocation 7 | CustomBuildIntermediatesPath 8 | Build/Intermediates.noindex 9 | CustomBuildLocationType 10 | RelativeToWorkspace 11 | CustomBuildProductsPath 12 | Build/Products 13 | DerivedDataCustomLocation 14 | DerivedData 15 | DerivedDataLocationStyle 16 | WorkspaceRelativePath 17 | IssueFilterStyle 18 | ShowActiveSchemeOnly 19 | LiveSourceIssuesEnabled 20 | 21 | ShowSharedSchemesAutomaticallyEnabled 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: macos-12 8 | 9 | steps: 10 | - uses: actions/checkout@v1 11 | 12 | - name: Lint 13 | run: swiftlint --strict 14 | 15 | - name: Build 16 | run: xcodebuild 17 | -scheme dmenu-mac 18 | -archivePath dmenu-mac.xcarchive archive 19 | 20 | - name: Package 21 | run: xcodebuild 22 | -exportArchive 23 | -archivePath dmenu-mac.xcarchive 24 | -exportOptionsPlist mac-application-archive.plist 25 | -exportPath . 26 | 27 | - name: Compress 28 | run: zip -r dmenu-mac.zip dmenu-mac.app 29 | 30 | - uses: actions/upload-artifact@v1 31 | with: 32 | name: dmenu-mac.zip 33 | path: dmenu-mac.zip 34 | 35 | - name: Release 36 | if: startsWith(github.ref, 'refs/tags/') 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | GITHUB_USER: "user" 40 | run: hub release edit ${GITHUB_REF//refs\/tags\//} -a dmenu-mac.zip -m '' 41 | -------------------------------------------------------------------------------- /mac-app/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: macos-12 8 | 9 | steps: 10 | - uses: actions/checkout@v1 11 | 12 | - name: Lint 13 | run: swiftlint --strict 14 | 15 | - name: Build 16 | run: xcodebuild 17 | -scheme dmenu-mac 18 | -archivePath dmenu-mac.xcarchive archive 19 | 20 | - name: Package 21 | run: xcodebuild 22 | -exportArchive 23 | -archivePath dmenu-mac.xcarchive 24 | -exportOptionsPlist mac-application-archive.plist 25 | -exportPath . 26 | 27 | - name: Compress 28 | run: zip -r dmenu-mac.zip dmenu-mac.app 29 | 30 | - uses: actions/upload-artifact@v1 31 | with: 32 | name: dmenu-mac.zip 33 | path: dmenu-mac.zip 34 | 35 | - name: Release 36 | if: startsWith(github.ref, 'refs/tags/') 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | GITHUB_USER: "user" 40 | run: hub release edit ${GITHUB_REF//refs\/tags\//} -a dmenu-mac.zip -m '' 41 | -------------------------------------------------------------------------------- /mac-app/src/ListProvider.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import Foundation 19 | 20 | protocol ListProvider { 21 | // Returns list of items 22 | func get() -> [ListItem] 23 | 24 | // Performs action on a selected item 25 | func doAction(item: ListItem) 26 | } 27 | 28 | struct ListItem { 29 | var name: String 30 | var data: Any? 31 | } 32 | -------------------------------------------------------------------------------- /mac-app/src/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "New Piskel-1.png.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "idiom" : "mac", 11 | "scale" : "2x", 12 | "size" : "16x16" 13 | }, 14 | { 15 | "idiom" : "mac", 16 | "scale" : "1x", 17 | "size" : "32x32" 18 | }, 19 | { 20 | "idiom" : "mac", 21 | "scale" : "2x", 22 | "size" : "32x32" 23 | }, 24 | { 25 | "idiom" : "mac", 26 | "scale" : "1x", 27 | "size" : "128x128" 28 | }, 29 | { 30 | "idiom" : "mac", 31 | "scale" : "2x", 32 | "size" : "128x128" 33 | }, 34 | { 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "256x256" 38 | }, 39 | { 40 | "idiom" : "mac", 41 | "scale" : "2x", 42 | "size" : "256x256" 43 | }, 44 | { 45 | "idiom" : "mac", 46 | "scale" : "1x", 47 | "size" : "512x512" 48 | }, 49 | { 50 | "idiom" : "mac", 51 | "scale" : "2x", 52 | "size" : "512x512" 53 | } 54 | ], 55 | "info" : { 56 | "author" : "xcode", 57 | "version" : 1 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /mac-app/src/ReadStdin.h.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #import "ReadStdin.h" 19 | 20 | #import 21 | 22 | @implementation ReadStdin 23 | 24 | +(NSString *)read { 25 | char buf[BUFSIZ]; 26 | 27 | // prevent fgets from being blocked 28 | int flags; 29 | flags = fcntl(STDIN_FILENO, F_GETFL, 0); 30 | flags |= O_NONBLOCK; 31 | fcntl(STDIN_FILENO, F_SETFL, flags); 32 | 33 | NSMutableString *str = [NSMutableString string]; 34 | while (fgets(buf, sizeof(BUFSIZ), stdin) != 0) { 35 | [str appendString:[NSString stringWithUTF8String:buf]]; 36 | } 37 | 38 | return str; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /mac-app/unmenu.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "filewatcher", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/eonist/FileWatcher.git", 7 | "state" : { 8 | "revision" : "e67c2a99502eade343fecabeca8c57e749a55b59", 9 | "version" : "0.2.3" 10 | } 11 | }, 12 | { 13 | "identity" : "launchatlogin", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/sindresorhus/LaunchAtLogin", 16 | "state" : { 17 | "branch" : "main", 18 | "revision" : "7ad6331f9c38953eb1ce8737758e18f7607e984a" 19 | } 20 | }, 21 | { 22 | "identity" : "swift-argument-parser", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/apple/swift-argument-parser", 25 | "state" : { 26 | "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", 27 | "version" : "0.5.0" 28 | } 29 | }, 30 | { 31 | "identity" : "tomldecoder", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/dduan/TOMLDecoder", 34 | "state" : { 35 | "revision" : "a70a127cab92ddcb562a548190fa3ad2a2e5f9bc", 36 | "version" : "0.2.2" 37 | } 38 | } 39 | ], 40 | "version" : 2 41 | } 42 | -------------------------------------------------------------------------------- /mac-app/src/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.7.2 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 0.7.2 25 | LSApplicationCategoryType 26 | public.app-category.utilities 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | LSUIElement 30 | 31 | NSHumanReadableCopyright 32 | Copyright © 2016 Jose Pereira. All rights reserved. 33 | NSMainStoryboardFile 34 | Main 35 | NSPrincipalClass 36 | NSApplication 37 | 38 | 39 | -------------------------------------------------------------------------------- /mac-app/src/PipeListProvider.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import Foundation 19 | import Cocoa 20 | 21 | /** 22 | * Provide a list from a terminal pipe. When action is performed, quit app since we act like a prompt 23 | */ 24 | class PipeListProvider: ListProvider { 25 | var choices = [String]() 26 | 27 | init(str: String) { 28 | choices = str.trimmingCharacters(in: .whitespacesAndNewlines) 29 | .components(separatedBy: "\n") 30 | } 31 | 32 | func get() -> [ListItem] { 33 | return choices.map({ListItem(name: $0, data: nil)}) 34 | } 35 | 36 | func doAction(item: ListItem) { 37 | print(item.name) 38 | NSApplication.shared.terminate(self) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mac-app/src/InputField.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import Foundation 19 | import Cocoa 20 | 21 | class InputField: NSTextField { 22 | override func becomeFirstResponder() -> Bool { 23 | let responderStatus = super.becomeFirstResponder() 24 | 25 | if let fieldEditor = self.window?.fieldEditor(true, for: self) as? NSTextView { 26 | fieldEditor.selectedTextAttributes = [ 27 | // Make selection transparent 28 | NSAttributedString.Key.backgroundColor: NSColor.clear 29 | ] 30 | // Make blinking cursos transparent 31 | fieldEditor.insertionPointColor = NSColor.clear 32 | } 33 | 34 | return responderStatus 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mac-app/src/SearchWindow.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import Cocoa 19 | 20 | class SearchWindow: NSWindow { 21 | 22 | override func awakeFromNib() { 23 | self.hasShadow = false 24 | self.collectionBehavior = NSWindow.CollectionBehavior.canJoinAllSpaces 25 | updatePosition() 26 | } 27 | 28 | /** 29 | * Updates search window position. 30 | */ 31 | func updatePosition() { 32 | guard let screen = NSScreen.main else { return } 33 | 34 | let frame = NSRect( 35 | x: screen.frame.minX, 36 | y: screen.frame.minY + screen.frame.height - self.frame.height, 37 | width: screen.frame.width, 38 | height: self.frame.height) 39 | 40 | setFrame(frame, display: false) 41 | } 42 | 43 | override var canBecomeKey: Bool { 44 | return true 45 | } 46 | 47 | override var canBecomeMain: Bool { 48 | return true 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /mac-app/src/SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import Cocoa 19 | 20 | public protocol SettingsViewControllerDelegate: AnyObject { 21 | func onSettingsCanceled() 22 | func onSettingsApplied() 23 | } 24 | 25 | class SettingsViewController: NSViewController { 26 | 27 | @IBOutlet var hotkeyTextField: DDHotKeyTextField! 28 | weak var delegate: SettingsViewControllerDelegate? 29 | 30 | override func viewDidLoad() { 31 | let keycode = UserDefaults.standard 32 | .integer(forKey: kDefaultsGlobalShortcutKeycode) 33 | let modifierFlags = UserDefaults.standard 34 | .integer(forKey: kDefaultsGlobalShortcutModifiedFlags) 35 | 36 | hotkeyTextField.hotKey = DDHotKey( 37 | keyCode: UInt16(keycode), 38 | modifierFlags: UInt(modifierFlags), 39 | task: nil) 40 | } 41 | 42 | @IBAction func applySettings(_ sender: AnyObject) { 43 | UserDefaults.standard.set( 44 | Int(hotkeyTextField.hotKey.keyCode), forKey: kDefaultsGlobalShortcutKeycode) 45 | UserDefaults.standard.set( 46 | Int(hotkeyTextField.hotKey.modifierFlags), forKey: kDefaultsGlobalShortcutModifiedFlags) 47 | 48 | delegate?.onSettingsApplied() 49 | } 50 | 51 | @IBAction func cancelSettings(_ sender: AnyObject) { 52 | delegate?.onSettingsCanceled() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mac-app/src/VerticalAlignedTextFieldCell.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import Cocoa 19 | 20 | class VerticalAlignedTextFieldCell: NSTextFieldCell { 21 | var editingOrSelecting: Bool = false 22 | 23 | override func drawingRect(forBounds theRect: NSRect) -> NSRect { 24 | var newRect = super.drawingRect(forBounds: theRect) 25 | if !editingOrSelecting { 26 | let textSize = self.cellSize(forBounds: theRect) 27 | let heightDelta = newRect.size.height - textSize.height 28 | if heightDelta > 0 { 29 | newRect.size.height -= heightDelta 30 | newRect.origin.y += (heightDelta / 2) 31 | } 32 | } 33 | return newRect 34 | } 35 | 36 | override func select(withFrame aRect: NSRect, in controlView: NSView, 37 | editor textObj: NSText, delegate anObject: Any?, 38 | start selStart: Int, length selLength: Int) { 39 | let aRect = self.drawingRect(forBounds: aRect) 40 | 41 | editingOrSelecting = true 42 | super.select(withFrame: aRect, 43 | in: controlView, 44 | editor: textObj, 45 | delegate: anObject, 46 | start: selStart, 47 | length: selLength) 48 | 49 | editingOrSelecting = false 50 | } 51 | 52 | override func edit(withFrame aRect: NSRect, in controlView: NSView, 53 | editor textObj: NSText, delegate anObject: Any?, 54 | event theEvent: NSEvent?) { 55 | let aRect = self.drawingRect(forBounds: aRect) 56 | editingOrSelecting = true 57 | self.edit(withFrame: aRect, 58 | in: controlView, 59 | editor: textObj, 60 | delegate: anObject, 61 | event: theEvent) 62 | editingOrSelecting = false 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unmenu 2 | 3 | This repository is a fork of dmenu-mac (https://github.com/oNaiPs/dmenu-mac), enhancing its functionality and addressing certain issues. 4 | 5 | Update: as of 2025-10-08 I'm still using it and it works perfectly for me, even though there might be half implemented features. Turns out I need just the basic stuff and I can't be bothered to clean it up. 6 | 7 | ### Changes and New Features 8 | 9 | - Fixed a longstanding issue https://github.com/oNaiPs/dmenu-mac/issues/41 10 | - Switched to Accessibility API for handling hotkeys because it seems to work well 11 | 12 | - Implemented what I believe to be a superior fuzzy matching algorithm, leveraging https://crates.io/crates/fuzzy-matcher 13 | 14 | - Introduced a configuration file located at ~/.config/unmenu/config, enabling users to customize search directories, filter out applications and integrate scripts and aliases 15 | 16 | # Building 17 | 18 | ```sh 19 | make 20 | ``` 21 | 22 | Or `make install` to copy the app into /Applications 23 | 24 | 1. Build fuzzylib 25 | This step requires Rust installed 26 | 27 | ``` 28 | cargo build --release --manifest-path=fuzzylib/Cargo.toml 29 | cp fuzzylib/target/release/libfuzzylib.a mac-app/ 30 | ``` 31 | 32 | 2. Build macOS application: 33 | Requires Xcode 34 | 35 | CLI: 36 | ``` 37 | xcodebuild -project mac-app/unmenu.xcodeproj -scheme unmenu -derivedDataPath build -configuration Release build 38 | cp -r mac-app/Build/Products/Release/unmenu.app /Applications/ 39 | ``` 40 | 41 | OR 42 | 43 | GUI: 44 | - open mac-app/unmenu.xcodeproj 45 | - Click the menu item Product -> Archive 46 | - Right click on the archive called `unmenu` and `Show in Finder` 47 | - Right click on it in Finder and `Show Package`, Products -> Applications 48 | - Copy unmenu.app to /Applications 49 | 50 | # Getting started 51 | 52 | 1. Launch the app `open -a unmenu` 53 | 2. Follow instructions to enable permissions 54 | 3. Hit Ctrl-Cmd-b by deafult 55 | 4. Customize settings by editing ~/.config/unmenu/config.toml according to your preferences 56 | 57 | # Scripts / Aliases 58 | 59 | Users can incorporate various scripts and aliases into unmenu for extended functionality. 60 | 61 | 1. Set up a directory to store your scripts 62 | 2. Add your scripts with appropriate shebangs, and ensure they are executable (chmod +x). 63 | 3. Include the script directory in the `dirs` variable within the configuration. 64 | 4. Ensure that `find_executables` is set to true in the config file 65 | 66 | After these steps, you can execute scripts directly from unmenu by invoking their names via unmenu. This allows for the addition of aliases or any desired custom functionality. 67 | 68 | # Authors 69 | 70 | [@onaips - Original Author](https://twitter.com/onaips) 71 | [@unmanbearpig - Author of this fork](https://unmb.pw) 72 | -------------------------------------------------------------------------------- /mac-app/unmenu.xcodeproj/xcshareddata/xcschemes/unmenu.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 53 | 55 | 61 | 62 | 63 | 64 | 72 | 75 | 76 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /mac-app/src/ResultsView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import Cocoa 19 | 20 | class ResultsView: NSView { 21 | @IBOutlet fileprivate var scrollView: NSScrollView! 22 | 23 | let rectFillPadding: CGFloat = 5 24 | var resultsList: [ListItem] = [] 25 | 26 | var dirtyWidth: Bool = false 27 | var selectedRect = NSRect() 28 | 29 | var selectedIndexValue: Int = 0 30 | var selectedIndex: Int { 31 | get { 32 | return selectedIndexValue 33 | } 34 | set { 35 | if newValue < 0 || newValue >= resultsList.count { 36 | return 37 | } 38 | 39 | selectedIndexValue = newValue 40 | needsDisplay = true 41 | } 42 | } 43 | 44 | var list: [ListItem] { 45 | get { 46 | return resultsList 47 | } 48 | set { 49 | selectedIndexValue = 0 50 | resultsList = newValue 51 | needsDisplay = true 52 | } 53 | } 54 | 55 | func selectedItem() -> ListItem? { 56 | if selectedIndexValue < 0 || selectedIndexValue >= resultsList.count { 57 | return nil 58 | } else { 59 | return resultsList[selectedIndexValue] 60 | } 61 | } 62 | 63 | func clear() { 64 | resultsList.removeAll() 65 | needsDisplay = true 66 | } 67 | 68 | override func draw(_ dirtyRect: NSRect) { 69 | var textX = CGFloat(rectFillPadding) 70 | let drawList = list.count > 0 ? list : [ListItem(name: "No results", data: nil)] 71 | 72 | for i in 0 ..< drawList.count { 73 | let item = (drawList[i].name) as NSString 74 | let size = item.size(withAttributes: [NSAttributedString.Key: Any]()) 75 | let textY = (frame.height - size.height) / 2 76 | 77 | if selectedIndexValue == i { 78 | selectedRect = NSRect( 79 | x: textX - rectFillPadding, 80 | y: textY - rectFillPadding, 81 | width: size.width + rectFillPadding * 2, 82 | height: size.height + rectFillPadding * 2) 83 | NSColor.selectedTextBackgroundColor.setFill() 84 | __NSRectFill(selectedRect) 85 | } 86 | 87 | item.draw(in: NSRect( 88 | x: textX, 89 | y: textY, 90 | width: size.width, 91 | height: size.height), withAttributes: [ 92 | NSAttributedString.Key.foregroundColor: NSColor.textColor 93 | ]) 94 | 95 | textX += 10 + size.width 96 | } 97 | if dirtyWidth { 98 | frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: textX, height: frame.height) 99 | dirtyWidth = false 100 | scrollView.contentView.scrollToVisible(selectedRect) 101 | } 102 | } 103 | 104 | func updateWidth() { 105 | dirtyWidth = true 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /mac-app/src/Base.lproj/GeneralSettingsViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 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 | 55 | -------------------------------------------------------------------------------- /mac-app/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // unmenu 4 | // 5 | // Copyright © 2024 Ivan . All rights reserved. 6 | // Copyright © 2024 Jose Pereira. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import TOMLDecoder 11 | import Cocoa 12 | 13 | class Config { 14 | var hotkeyCode: UInt16? 15 | var hotkeyModifierFlags: UInt? 16 | 17 | // Define the structure of your TOML config file, making each property optional. 18 | struct Conf: Codable { 19 | var hotkey: Hotkey? 20 | 21 | struct Hotkey: Codable { 22 | // Meaning it's the same physical key as on qwerty layout, regardless of current layout 23 | // So it's not gonna match dvorak keys for example 24 | var qwerty_hotkey: String? 25 | var key_code: UInt16? 26 | var modifier_flags: UInt16? 27 | } 28 | } 29 | 30 | // The initializer that takes the path of the TOML file. 31 | init?(tomlFilePath: String) { 32 | do { 33 | // let content = try String(contentsOfFile: tomlFilePath) 34 | let tomlData = try Data(contentsOf: URL(fileURLWithPath: tomlFilePath)) 35 | 36 | parseConfig(tomlData: tomlData) 37 | } catch { 38 | print("Error reading TOML file: \(error)") 39 | return nil 40 | } 41 | } 42 | 43 | // A simple parser for the given TOML content. 44 | private func parseConfig(tomlData: Data) { 45 | do { 46 | // Read the content into data. 47 | // let tomlData = try Data(contentsOf: URL(fileURLWithPath: tomlFilePath)) 48 | 49 | // Create an instance of the TOMLDecoder and decode the file. 50 | let decoder = TOMLDecoder() 51 | let config = try decoder.decode(Conf.self, from: tomlData) 52 | 53 | // Access the optional properties safely with optional binding. 54 | if let hotkeyConfig = config.hotkey { 55 | if let keyCode = hotkeyConfig.key_code, let modifierFlags = hotkeyConfig.modifier_flags { 56 | print("Key Code: \(keyCode), Modifier Flags: \(modifierFlags)") 57 | } else { 58 | if let hotkey = hotkeyConfig.qwerty_hotkey { 59 | if let (keyCode, modifierFlags) = Keys.convertToKeyCodeAndModifierFlags(shortcut: hotkey) { 60 | 61 | self.hotkeyCode = keyCode 62 | self.hotkeyModifierFlags = modifierFlags 63 | } 64 | 65 | } else { 66 | print("Hotkey properties are not fully defined in TOML file.") 67 | } 68 | } 69 | } else { 70 | print("No hotkey configuration found in TOML file.") 71 | } 72 | 73 | } catch { 74 | print("Failed to read or decode the TOML file: \(error)") 75 | } 76 | 77 | } 78 | 79 | static func createDefaultConfig() { 80 | log("creating default config") 81 | let maybeDefaultConfigURL = Bundle.main.url(forResource: "default_config", withExtension: "toml") 82 | if maybeDefaultConfigURL == nil { 83 | log("couldn't find default_config.toml in the bundle") 84 | exit(1) 85 | return 86 | } 87 | 88 | let defaultConfigURL = maybeDefaultConfigURL! 89 | log("default config bundle url = \(defaultConfigURL)") 90 | let directoryURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".config/unmenu") 91 | let destinationURL = directoryURL.appendingPathComponent("config.toml") 92 | 93 | if !FileManager.default.fileExists(atPath: directoryURL.path) { 94 | log("creating config directory") 95 | try! FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true) 96 | } 97 | 98 | if !FileManager.default.fileExists(atPath: destinationURL.path) { 99 | log("file doesn't exist") 100 | 101 | do { 102 | try FileManager.default.copyItem(at: defaultConfigURL, to: destinationURL) 103 | print("Copied default config to ~/.config/unmenu/config.toml") 104 | } catch { 105 | print("Failed to copy config file: \(error)") 106 | } 107 | log("created default config") 108 | } 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /mac-app/src/AppListProvider.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import Foundation 19 | import Cocoa 20 | 21 | 22 | /** 23 | * Provide a list of launcheable apps for the OS 24 | */ 25 | class AppListProvider: ListProvider { 26 | 27 | var appDirDict = [String: Bool]() 28 | 29 | var appList = [URL]() 30 | 31 | init() { 32 | log("-> init") 33 | // let applicationDir = NSSearchPathForDirectoriesInDomains( 34 | // .applicationDirectory, .localDomainMask, true)[0] 35 | // 36 | // // Catalina moved default applications under a different mask. 37 | // let systemApplicationDir = NSSearchPathForDirectoriesInDomains( 38 | // .applicationDirectory, .systemDomainMask, true)[0] 39 | // 40 | // log("systemApplicationDir = \(systemApplicationDir)") 41 | // 42 | // // appName to dir recursivity key/value dict 43 | // appDirDict[applicationDir] = true 44 | // appDirDict[systemApplicationDir] = true 45 | // appDirDict["/System/Applications/Utilities/"] = true 46 | // appDirDict["/System/Library/CoreServices/"] = false 47 | // let customDir = (FileManager.default.homeDirectoryForCurrentUser).appendingPathComponent(".unmenu-bin/").path 48 | // log("customDir = \(customDir)") 49 | // appDirDict[customDir] = true 50 | // 51 | // initFileWatch(Array(appDirDict.keys)) 52 | // updateAppList() 53 | } 54 | 55 | func get() -> [ListItem] { 56 | return appList.map({ListItem(name: $0.deletingPathExtension().lastPathComponent, data: $0)}) 57 | } 58 | 59 | func isDir(path: String) -> Bool { 60 | var isDirectory: ObjCBool = false 61 | FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) 62 | return isDirectory.boolValue 63 | } 64 | 65 | func getAppList(_ appDir: URL, recursive: Bool = true) -> [URL] { 66 | var list = [URL]() 67 | let fileManager = FileManager.default 68 | 69 | do { 70 | let subs = try fileManager.contentsOfDirectory(atPath: appDir.path) 71 | // TODO why can't we find Terminal.app in /System/Applications/Utilities ? 72 | for sub in subs { 73 | let dir = appDir.appendingPathComponent(sub) 74 | let isDirectory = isDir(path: dir.path) 75 | if isDirectory { 76 | if dir.pathExtension == "app" { 77 | list.append(dir) 78 | } 79 | } else if FileManager.default.isExecutableFile(atPath: dir.path) { 80 | list.append(dir) 81 | } else if dir.hasDirectoryPath && recursive { 82 | list.append(contentsOf: self.getAppList(dir)) 83 | } else { 84 | log("found non-executable non-app \(dir)") 85 | } 86 | } 87 | } catch { 88 | log("Error on getAppList appDir = \(appDir), recursive = \(recursive)") 89 | NSLog("Error on getAppList: %@", error.localizedDescription) 90 | } 91 | return list 92 | } 93 | 94 | // TODO add support for arguments? 95 | func runExecutable(path: String) { 96 | let task = Process() 97 | task.launchPath = path // Path to your executable 98 | task.arguments = [] // Optional: Specify any arguments for your executable 99 | 100 | let outputPipe = Pipe() 101 | task.standardOutput = outputPipe 102 | task.launch() 103 | } 104 | 105 | func doAction(item: ListItem) { 106 | log("-> doAction \(item)") 107 | let app: URL = URL.init(fileURLWithPath: (item.data as? String)!) 108 | DispatchQueue.main.async { 109 | log("opening \(app)") 110 | 111 | if self.isDir(path: app.path) { 112 | log("running app") 113 | let conf = NSWorkspace.OpenConfiguration.init() 114 | conf.activates = true 115 | NSWorkspace.shared.openApplication(at: app, configuration: conf) 116 | } else { 117 | log("running executable file") 118 | self.runExecutable(path: app.path) 119 | } 120 | 121 | log("done\n") 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /mac-app/keys.hpp: -------------------------------------------------------------------------------- 1 | constexpr value_t keyboard_a(0x0); 2 | constexpr value_t keyboard_b(0xb); 3 | constexpr value_t keyboard_c(0x8); 4 | constexpr value_t keyboard_d(0x2); 5 | constexpr value_t keyboard_e(0xe); 6 | constexpr value_t keyboard_f(0x3); 7 | constexpr value_t keyboard_g(0x5); 8 | constexpr value_t keyboard_h(0x4); 9 | constexpr value_t keyboard_i(0x22); 10 | constexpr value_t keyboard_j(0x26); 11 | constexpr value_t keyboard_k(0x28); 12 | constexpr value_t keyboard_l(0x25); 13 | constexpr value_t keyboard_m(0x2e); 14 | constexpr value_t keyboard_n(0x2d); 15 | constexpr value_t keyboard_o(0x1f); 16 | constexpr value_t keyboard_p(0x23); 17 | constexpr value_t keyboard_q(0xc); 18 | constexpr value_t keyboard_r(0xf); 19 | constexpr value_t keyboard_s(0x1); 20 | constexpr value_t keyboard_t(0x11); 21 | constexpr value_t keyboard_u(0x20); 22 | constexpr value_t keyboard_v(0x9); 23 | constexpr value_t keyboard_w(0xd); 24 | constexpr value_t keyboard_x(0x7); 25 | constexpr value_t keyboard_y(0x10); 26 | constexpr value_t keyboard_z(0x6); 27 | constexpr value_t keyboard_1(0x12); 28 | constexpr value_t keyboard_2(0x13); 29 | constexpr value_t keyboard_3(0x14); 30 | constexpr value_t keyboard_4(0x15); 31 | constexpr value_t keyboard_5(0x17); 32 | constexpr value_t keyboard_6(0x16); 33 | constexpr value_t keyboard_7(0x1a); 34 | constexpr value_t keyboard_8(0x1c); 35 | constexpr value_t keyboard_9(0x19); 36 | constexpr value_t keyboard_0(0x1d); 37 | constexpr value_t keyboard_return_or_enter(0x24); 38 | constexpr value_t keyboard_escape(0x35); 39 | constexpr value_t keyboard_delete_or_backspace(0x33); 40 | constexpr value_t keyboard_tab(0x30); 41 | constexpr value_t keyboard_spacebar(0x31); 42 | constexpr value_t keyboard_hyphen(0x1b); 43 | constexpr value_t keyboard_equal_sign(0x18); 44 | constexpr value_t keyboard_open_bracket(0x21); 45 | constexpr value_t keyboard_close_bracket(0x1e); 46 | constexpr value_t keyboard_backslash(0x2a); 47 | constexpr value_t keyboard_non_us_pound(0x2a); // non_us_pound == backslash 48 | constexpr value_t keyboard_semicolon(0x29); 49 | constexpr value_t keyboard_quote(0x27); 50 | constexpr value_t keyboard_grave_accent_and_tilde(0x32); 51 | constexpr value_t keyboard_comma(0x2b); 52 | constexpr value_t keyboard_period(0x2f); 53 | constexpr value_t keyboard_slash(0x2c); 54 | constexpr value_t keyboard_caps_lock(0x39); 55 | constexpr value_t keyboard_f1(0x7a); 56 | constexpr value_t keyboard_f2(0x78); 57 | constexpr value_t keyboard_f3(0x63); 58 | constexpr value_t keyboard_f4(0x76); 59 | constexpr value_t keyboard_f5(0x60); 60 | constexpr value_t keyboard_f6(0x61); 61 | constexpr value_t keyboard_f7(0x62); 62 | constexpr value_t keyboard_f8(0x64); 63 | constexpr value_t keyboard_f9(0x65); 64 | constexpr value_t keyboard_f10(0x6d); 65 | constexpr value_t keyboard_f11(0x67); 66 | constexpr value_t keyboard_f12(0x6f); 67 | constexpr value_t keyboard_print_screen(0x69); 68 | constexpr value_t keyboard_scroll_lock(0x6b); 69 | constexpr value_t keyboard_pause(0x71); 70 | constexpr value_t keyboard_insert(0x72); 71 | constexpr value_t keyboard_home(0x73); 72 | constexpr value_t keyboard_page_up(0x74); 73 | constexpr value_t keyboard_delete_forward(0x75); 74 | constexpr value_t keyboard_end(0x77); 75 | constexpr value_t keyboard_page_down(0x79); 76 | constexpr value_t keyboard_right_arrow(0x7c); 77 | constexpr value_t keyboard_left_arrow(0x7b); 78 | constexpr value_t keyboard_down_arrow(0x7d); 79 | constexpr value_t keyboard_up_arrow(0x7e); 80 | constexpr value_t keypad_num_lock(0x47); 81 | constexpr value_t keypad_slash(0x4b); 82 | constexpr value_t keypad_asterisk(0x43); 83 | constexpr value_t keypad_hyphen(0x4e); 84 | constexpr value_t keypad_plus(0x45); 85 | constexpr value_t keypad_enter(0x4c); 86 | constexpr value_t keypad_1(0x53); 87 | constexpr value_t keypad_2(0x54); 88 | constexpr value_t keypad_3(0x55); 89 | constexpr value_t keypad_4(0x56); 90 | constexpr value_t keypad_5(0x57); 91 | constexpr value_t keypad_6(0x58); 92 | constexpr value_t keypad_7(0x59); 93 | constexpr value_t keypad_8(0x5b); 94 | constexpr value_t keypad_9(0x5c); 95 | constexpr value_t keypad_0(0x52); 96 | constexpr value_t keypad_period(0x41); 97 | constexpr value_t keyboard_non_us_backslash(0xa); 98 | constexpr value_t keyboard_application(0x6e); 99 | constexpr value_t keypad_equal_sign(0x51); 100 | constexpr value_t keyboard_f13(0x69); 101 | constexpr value_t keyboard_f14(0x6b); 102 | constexpr value_t keyboard_f15(0x71); 103 | constexpr value_t keyboard_f16(0x6a); 104 | constexpr value_t keyboard_f17(0x40); 105 | constexpr value_t keyboard_f18(0x4f); 106 | constexpr value_t keyboard_f19(0x50); 107 | constexpr value_t keyboard_f20(0x5a); 108 | constexpr value_t keyboard_help(0x72); 109 | constexpr value_t keypad_comma(0x5f); 110 | constexpr value_t keyboard_international1(0x5e); 111 | constexpr value_t keyboard_international3(0x5d); 112 | constexpr value_t keyboard_lang1(0x68); 113 | constexpr value_t keyboard_lang2(0x66); 114 | constexpr value_t keyboard_left_control(0x3b); 115 | constexpr value_t keyboard_left_shift(0x38); 116 | constexpr value_t keyboard_left_alt(0x3a); 117 | constexpr value_t keyboard_left_gui(0x37); 118 | constexpr value_t keyboard_right_control(0x3e); 119 | constexpr value_t keyboard_right_shift(0x3c); 120 | constexpr value_t keyboard_right_alt(0x3d); 121 | constexpr value_t keyboard_right_gui(0x36); 122 | constexpr value_t apple_vendor_keyboard_dashboard(0x82); 123 | constexpr value_t apple_vendor_keyboard_function(0x3f); 124 | constexpr value_t apple_vendor_keyboard_launchpad(0x83); 125 | constexpr value_t apple_vendor_keyboard_expose_all(0xa0); 126 | constexpr value_t apple_vendor_top_case_keyboard_fn(0x3f); // apple_vendor_top_case_keyboard_fn == apple_vendor_keyboard_function 127 | 128 | -------------------------------------------------------------------------------- /mac-app/default_config.toml: -------------------------------------------------------------------------------- 1 | 2 | hotkey.qwerty_hotkey = "ctrl-cmd-space" 3 | 4 | find_apps = true 5 | find_executables = true 6 | 7 | dirs = ["/System/Applications/", "/Applications/", "/System/Applications/Utilities/", "/System/Library/CoreServices/", "~/.unmenu-bin"] 8 | 9 | ignore_names = [ 10 | "unmenu.app", # we don't want to launch ourselves, right? 11 | 12 | ".Karabiner-VirtualHIDDevice-Manager.app", 13 | 14 | # Mostly not very useful things from /System/Library/CoreServices 15 | "Install Command Line Developer Tools.app", 16 | "Install in Progress.app", 17 | "Migration Assistant.app", 18 | "TextInputMenuAgent.app", 19 | "TextInputSwitcher.app", 20 | "AOSUIPrefPaneLauncher.app", 21 | "Automator Application Stub.app", 22 | "NetAuthAgent.app", 23 | "Captive Network Assistant.app", 24 | "screencaptureui.app", 25 | "ScreenSaverEngine.app", 26 | "SystemUIServer.app", 27 | "UIKitSystem.app", 28 | "NowPlayingTouchUI.app", 29 | "WindowManager.app", 30 | "APFSUserAgent", 31 | "AVB Audio Configuration.app", 32 | "AddPrinter.app", 33 | "AddressBookUrlForwarder.app", 34 | "AirPlayUIAgent.app", 35 | "AirPort Base Station Agent.app", 36 | "AppleScript Utility.app", 37 | "Automator Application Stub.app", 38 | "Automator Installer.app", 39 | "BluetoothSetupAssistant.app", 40 | "BluetoothUIServer.app", 41 | "BluetoothUIService.app", 42 | "CSUserAgent", 43 | "CalendarFileHandler.app", 44 | "Captive Network Assistant.app", 45 | "Certificate Assistant.app", 46 | "CloudSettingsSyncAgent", 47 | "ControlCenter.app", 48 | "ControlStrip.app", 49 | "CoreLocationAgent.app", 50 | "CoreServicesUIAgent.app", 51 | "Coverage Details.app", 52 | "CrashReporterSupportHelper", 53 | "DMProxy", 54 | "Database Events.app", 55 | "DefaultBackground.jpg", 56 | "DefaultDesktop.heic", 57 | "Diagnostics Reporter.app", 58 | "DiscHelper.app", 59 | "DiskImageMounter.app", 60 | "Dock.app", 61 | "Dwell Control.app", 62 | "Erase Assistant.app", 63 | "EscrowSecurityAlert.app", 64 | "ExpansionSlotNotification", 65 | "FolderActionsDispatcher.app", 66 | "HelpViewer.app", 67 | "IOUIAgent.app", 68 | "Image Events.app", 69 | "Install Command Line Developer Tools.app", 70 | "Install in Progress.app", 71 | "Installer Progress.app", 72 | "Installer.app", 73 | "JavaLauncher.app", 74 | "KeyboardAccessAgent.app", 75 | "KeyboardSetupAssistant.app", 76 | "Keychain Circle Notification.app", 77 | "Language Chooser.app", 78 | "MTLReplayer.app", 79 | "ManagedClient.app", 80 | "MapsSuggestionsTransportModePrediction.mlmodelc", 81 | "MemorySlotNotification", 82 | "Menu Extras", 83 | "NetAuthAgent.app", 84 | "NowPlayingTouchUI.app", 85 | "OBEXAgent.app", 86 | "ODSAgent.app", 87 | "OSDUIHelper.app", 88 | "PIPAgent.app", 89 | "PodcastsAuthAgent.app", 90 | "PowerChime.app", 91 | "Pro Display Calibrator.app", 92 | "Problem Reporter.app", 93 | "ProfileHelper.app", 94 | "RapportUIAgent.app", 95 | "RegisterPluginIMApp.app", 96 | "RemoteManagement", 97 | "RemotePairTool", 98 | "ReportCrash", 99 | "Resources", 100 | "RestoreVersion.plist", 101 | "Rosetta 2 Updater.app", 102 | "ScopedBookmarkAgent", 103 | "ScreenSaverEngine.app", 104 | "Script Menu.app", 105 | "ScriptMonitor.app", 106 | "SecurityAgentPlugins", 107 | "ServicesUIAgent", 108 | "Setup Assistant.app", 109 | "SetupAssistantPlugins", 110 | "ShortcutDroplet.app", 111 | "Shortcuts Events.app", 112 | "SpacesTouchBarAgent.app", 113 | "StageManagerEducation.app", 114 | "SubmitDiagInfo", 115 | "SystemFolderLocalizations", 116 | "SystemUIServer.app", 117 | "SystemVersion.plist", 118 | "SystemVersionCompat.plist", 119 | "TextInputMenuAgent.app", 120 | "TextInputSwitcher.app", 121 | "ThermalTrap.app", 122 | "Tips.app", 123 | "UAUPlugins", 124 | "UIKitSystem.app", 125 | "UniversalAccessControl.app", 126 | "UniversalControl.app", 127 | "UnmountAssistantAgent.app", 128 | "UserAccountUpdater", 129 | "UserNotificationCenter.app", 130 | "UserPictureSyncAgent", 131 | "VoiceOver.app", 132 | "WatchFaceAlert.app", 133 | "WiFiAgent.app", 134 | "WidgetKit Simulator.app", 135 | "WindowManager.app", 136 | "Xcode Previews.app", 137 | "appleeventsd", 138 | "boot.efi", 139 | "cacheTimeZones", 140 | "cloudpaird", 141 | "com.apple.NSServicesRestrictions.plist", 142 | "coreservicesd", 143 | "destinationd", 144 | "diagnostics_agent", 145 | "iCloud+.app", 146 | "iCloud.app", 147 | "iOSSystemVersion.plist", 148 | "iTunesStoreURLPatterns.plist", 149 | "iconservicesagent", 150 | "iconservicesd", 151 | "ionodecache", 152 | "launchservicesd", 153 | "lockoutagent", 154 | "logind", 155 | "loginwindow.app", 156 | "mapspushd", 157 | "navd", 158 | "osanalyticshelper", 159 | "pbs", 160 | "rc.trampoline", 161 | "rcd.app", 162 | "screencaptureui.app", 163 | "sessionlogoutd", 164 | "sharedfilelistd", 165 | "talagent", 166 | "uncd", 167 | 168 | "NotificationCenter.app", 169 | ] 170 | ignore_patterns = [ 171 | "^Install.*", 172 | ".*Installer\\.app$", 173 | "\\.bundle$" 174 | ] 175 | 176 | -------------------------------------------------------------------------------- /mac-app/src/FuzzyMatcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FuzzyMatcher.swift 3 | // unmenu 4 | // 5 | // Copyright © 2024 Ivan . All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import Darwin 10 | 11 | func printHexDump(of pointer: UnsafeRawPointer, byteCount: Int) { 12 | print("hex dump of addr \(pointer):") 13 | 14 | pointer.withMemoryRebound(to: UInt8.self, capacity: byteCount) { bytePointer in 15 | let buffer = UnsafeBufferPointer(start: bytePointer, count: byteCount) 16 | 17 | for (index, byte) in buffer.enumerated() { 18 | print(String(format: " %02x", byte), terminator: "") 19 | 20 | // if (index + 1) % 8 == 0 { 21 | // print(" ", terminator: "") 22 | // } 23 | 24 | if (index + 1) % 8 == 0 { 25 | print("") 26 | } 27 | } 28 | 29 | if buffer.count % 16 != 0 { 30 | print("") 31 | } 32 | } 33 | } 34 | 35 | 36 | 37 | 38 | // Matcher 39 | class FuzzyMatcher { 40 | private var matcher: UnsafeMutableRawPointer? 41 | private var searchResultsPtr: Optional> 42 | 43 | init() { 44 | self.matcher = matcher_new() 45 | self.searchResultsPtr = nil 46 | } 47 | 48 | deinit { 49 | // if let matcher = self.matcher { 50 | // search_results_free(matcher) 51 | // } 52 | } 53 | 54 | func rescan() { 55 | matcher_rescan(matcher) 56 | } 57 | 58 | func search(pattern: String) -> [Item] { 59 | if let resultsPtr = searchResultsPtr { 60 | search_results_free(resultsPtr) 61 | self.searchResultsPtr = nil 62 | } 63 | 64 | let patternCString = pattern.cString(using: .utf8) 65 | let resultsPtr = matcher_search(matcher, patternCString) 66 | // printHexDump(of: resultsPtr!, byteCount: 128) 67 | // print("resultsPtr = \(resultsPtr)") 68 | let numItems = resultsPtr?.pointee.num_items ?? 0 69 | var items = [Item]() 70 | let pointee = resultsPtr?.pointee 71 | 72 | guard let pointee = pointee else { return [] } 73 | let items_ptr = pointee.items 74 | 75 | // print("items_ptr = \(items_ptr)") 76 | guard let items_ptr = items_ptr else { return [] } 77 | // printHexDump(of: items_ptr, byteCount: 128) // here it's correct 78 | 79 | // let as_size = malloc_size(items_ptr) 80 | // print("alloc_size of \(items_ptr) = \(as_size)") 81 | // let ptr_items = items_ptr.pointee 82 | // print("ptr_items = \(ptr_items)") 83 | 84 | let itemPointers = items_ptr 85 | // print("swift pointers: ") 86 | // print("hexdump2") 87 | // printHexDump(of: items_ptr, byteCount: 128) 88 | 89 | var citems: [UnsafeRawPointer] = [] // Array([]) 90 | // citems.reserveCapacity(512) // breaks just as well as adding 64 items 91 | 92 | 93 | for i in 0.. 1 { 95 | // let i_ptr = withUnsafePointer(to: citems[0]) { $0 } 96 | // 97 | // let address = UInt(bitPattern: i_ptr) 98 | // print("Memory address of citems: 0x\(String(address, radix: 16))") 99 | // } 100 | let itemPointer = itemPointers + (8 * i) 101 | // let citem = itemPointer.pointee 102 | 103 | //print("\(i) itemPointer = \(itemPointer) citem = \(citem)") 104 | //citems.append(citem) 105 | if i == 64 { 106 | print("64") 107 | } 108 | 109 | citems.append(itemPointer) 110 | // if i == 0 || i == 63 || i == 64 { 111 | // print("hexdump3: \(i)") // memory is rewritten at i= 64 // WTF? 112 | // printHexDump(of: items_ptr, byteCount: 128) 113 | // } 114 | } 115 | // memory is rewritten between hexdumps 3 and 4 116 | // print("hexdump last") 117 | // printHexDump(of: items_ptr, byteCount: 128) 118 | // 119 | // print("------------------------------") 120 | // print("------------------------------") 121 | // print("------------------------------") 122 | 123 | //for i in 0.. name item = \(item)") 170 | let itemName = get_item_name(item) 171 | let itemNameString = String(cString: itemName!) 172 | return itemNameString 173 | } 174 | 175 | func open() { 176 | // guard let item = self.item else { 177 | // return 178 | // } 179 | 180 | item_open(item) 181 | 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /mac-app/src/SearchViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import Cocoa 19 | 20 | class SearchViewController: NSViewController, NSTextFieldDelegate, NSWindowDelegate { 21 | 22 | @IBOutlet fileprivate var searchText: InputField! 23 | @IBOutlet fileprivate var resultsText: ResultsView! 24 | var fuzzyMatcher: FuzzyMatcher? 25 | var promptValue = "" 26 | func refreshFuzzyMatcher() { 27 | log("-> refreshFuzzyMatcher") 28 | clearFields() 29 | fuzzyMatcher?.rescan() 30 | } 31 | 32 | override func viewDidAppear() { 33 | log("-> SearchViewController.viewDidAppear") 34 | refreshFuzzyMatcher() 35 | self.resultsText.clear() 36 | } 37 | 38 | override func viewDidDisappear() { 39 | log("-> SearchViewController.viewDidDisappear") 40 | } 41 | 42 | override func viewWillAppear() { 43 | log("-> SearchViewController.viewWillAppear") 44 | } 45 | 46 | override func viewWillLayout() { 47 | log("-> SearchViewController.viewWillLayout") 48 | } 49 | 50 | override func viewDidLayout() { 51 | log("-> SearchViewController.viewDidLayout") 52 | } 53 | 54 | override func viewWillDisappear() { 55 | log("-> SearchViewController.viewWillDisappear") 56 | } 57 | 58 | override func viewDidLoad() { 59 | log("-> SearchViewController.viewDidLoad") 60 | 61 | Config.createDefaultConfig() 62 | 63 | fuzzyMatcher?.rescan() 64 | super.viewDidLoad() 65 | searchText.delegate = self 66 | 67 | DistributedNotificationCenter.default.addObserver( 68 | self, 69 | selector: #selector(interfaceModeChanged), 70 | name: .AppleInterfaceThemeChangedNotification, 71 | object: nil 72 | ) 73 | 74 | // let stdinStr = ReadStdin.read() 75 | // if stdinStr.count > 0 { 76 | // listProvider = PipeListProvider(str: stdinStr) 77 | // } else { 78 | // listProvider = AppListProvider() 79 | // } 80 | // 81 | fuzzyMatcher = FuzzyMatcher.init() 82 | 83 | let options = DmenuMac.parseOrExit() 84 | if options.prompt != nil { 85 | promptValue = options.prompt! 86 | } 87 | 88 | log("-> viewDidLoad clearing fields and resuming app normally") 89 | clearFields() 90 | } 91 | 92 | @objc func interfaceModeChanged(sender: NSNotification) { 93 | log("-> SearchViewController.interfaceModeChanged") 94 | updateColors() 95 | } 96 | 97 | func updateColors() { 98 | // log("-> SearchViewController.updateColors") 99 | 100 | guard let window = NSApp.windows.first else { return } 101 | window.isOpaque = true 102 | searchText.textColor = NSColor.textColor 103 | } 104 | 105 | func controlTextDidChange(_ obj: Notification) { 106 | log("-> SearchViewController.controlTextDidChange") 107 | if searchText.stringValue == "" { 108 | clearFields() 109 | return 110 | } 111 | 112 | // Get provider list, filter using fuzzy search, apply 113 | // var scoreDict = [Int: Double]() 114 | 115 | let searchResults = fuzzyMatcher?.search(pattern: searchText.stringValue) 116 | // let sortedScoreDict = searchResults?.map { ListItem(name: $0.name, data: $0.payload) } ?? [] 117 | // runtime error here 118 | let sortedScoreDict = searchResults?.compactMap { searchResult -> ListItem? in 119 | guard let name = searchResult.name else { 120 | return nil // Ignore items with missing name 121 | } 122 | return ListItem(name: name, data: searchResult) 123 | } ?? [] 124 | 125 | if !sortedScoreDict.isEmpty { 126 | self.resultsText.list = sortedScoreDict 127 | } else { 128 | self.resultsText.clear() 129 | } 130 | 131 | self.resultsText.updateWidth() 132 | } 133 | 134 | func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { 135 | log("-> SearchViewController.control") 136 | let movingLeft: Bool = 137 | commandSelector == #selector(moveLeft(_:)) || 138 | commandSelector == #selector(insertBacktab(_:)) 139 | let movingRight: Bool = 140 | commandSelector == #selector(moveRight(_:)) || 141 | commandSelector == #selector(insertTab(_:)) 142 | 143 | print("movingLeft = \(movingLeft) movingRight = \(movingRight) commandSelector = \(commandSelector)") 144 | 145 | if movingLeft { 146 | resultsText.selectedIndex = resultsText.selectedIndex == 0 ? 147 | resultsText.list.count - 1 : resultsText.selectedIndex - 1 148 | resultsText.updateWidth() 149 | return true 150 | } else if movingRight { 151 | resultsText.selectedIndex = (resultsText.selectedIndex + 0) % resultsText.list.count 152 | resultsText.updateWidth() 153 | return true 154 | } else if commandSelector == #selector(insertNewline(_:)) { 155 | // print("TODO open \(resultsText.selectedItem())") 156 | if let item = resultsText.selectedItem() { 157 | if let item = item.data { 158 | if let item = item as? Item { 159 | item.open() 160 | closeApp() 161 | clearFields() 162 | } 163 | } 164 | } 165 | // open current selected app 166 | // if let item = resultsText.selectedItem() { 167 | // listProvider?.doAction(item: item) 168 | // closeApp() 169 | // } 170 | 171 | 172 | return true 173 | } else if commandSelector == #selector(cancelOperation(_:)) { 174 | closeApp() 175 | return true 176 | } 177 | 178 | return false 179 | } 180 | 181 | func clearFields() { 182 | log("-> SearchViewController.clearFields") 183 | 184 | self.searchText.stringValue = promptValue 185 | // self.resultsText.list = listProvider?.get().sorted(by: {$0.name < $1.name}) ?? [] 186 | } 187 | 188 | func closeApp() { 189 | log("-> SearchViewController.closeApp") 190 | clearFields() 191 | NSApplication.shared.hide(nil) 192 | 193 | guard let window = NSApp.windows.first else { return } 194 | window.close() 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /mac-app/src/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 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 | 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 | 83 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /mac-app/src/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jose Pereira . 3 | * Copyright (c) 2024 Ivan 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import LaunchAtLogin 19 | import Cocoa 20 | 21 | // func log(_ message: String) { 22 | // let currentTime = Date() 23 | // 24 | // let dateFormatter = DateFormatter() 25 | // dateFormatter.dateFormat = "HH:mm:ss.SSS" 26 | // let formattedTime = dateFormatter.string(from: currentTime) 27 | // 28 | // let logMessage = "\(formattedTime) - \(message)" 29 | // print(logMessage) 30 | // } 31 | 32 | func log(_ message: String) { 33 | // let fileURL = URL(fileURLWithPath: "/Users/unmbp/unmenu.log") 34 | 35 | let formatter = DateFormatter() 36 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 37 | let timestamp = formatter.string(from: Date()) 38 | let logMessage = "[\(timestamp)] \(message)\n" 39 | 40 | // // Append the log message to the file 41 | // if let fileHandle = try? FileHandle(forWritingTo: fileURL) { 42 | // fileHandle.seekToEndOfFile() 43 | // fileHandle.write(logMessage.data(using: .utf8)!) 44 | // fileHandle.closeFile() 45 | // } else { 46 | // try? logMessage.write(to: fileURL, atomically: true, encoding: .utf8) 47 | // } 48 | 49 | // Print the log message 50 | print(logMessage) 51 | } 52 | 53 | @NSApplicationMain 54 | class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { 55 | private var statusItem: NSStatusItem! 56 | private var startAtLaunch: NSMenuItem! 57 | 58 | var eventMonitor: Any? 59 | 60 | func doWeHavePermissions() -> Bool { 61 | let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String : false] 62 | let accessEnabled = AXIsProcessTrustedWithOptions(options) 63 | return accessEnabled 64 | } 65 | 66 | func showPermissionsSettings() -> Bool { 67 | let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String : true] 68 | let accessEnabled = AXIsProcessTrustedWithOptions(options) 69 | return accessEnabled 70 | } 71 | 72 | func restartOurselves() { 73 | log("restarting...") 74 | 75 | // let path = (Bundle.main.resourcePath! as NSString).stringByDeletingLastPathComponent.stringByDeletingLastPathComponent 76 | let url = URL(fileURLWithPath: Bundle.main.resourcePath!) 77 | var newUrl = url.deletingLastPathComponent() 78 | newUrl = newUrl.deletingLastPathComponent() 79 | let path = newUrl.path 80 | 81 | log("path = \(path)") 82 | 83 | let task = Process() 84 | task.launchPath = "/usr/bin/open" 85 | task.arguments = [path] 86 | task.launch() 87 | exit(0) 88 | } 89 | 90 | func waitForPermissions() { 91 | let accessEnabled = doWeHavePermissions() 92 | if !accessEnabled { 93 | log("Access still not Enabled") 94 | // Delay the next iteration by 1 second 95 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 96 | log("Retrying...") 97 | self.waitForPermissions() 98 | } 99 | } else { 100 | // we got the permissions 101 | let alert = NSAlert() 102 | alert.messageText = "Permissions granted" 103 | alert.informativeText = "App restart required to apply new permissions. After clicking Continue, use your designated hotkey or the default (cmd-ctrl-b) if unchanged." 104 | alert.addButton(withTitle: "Continue") 105 | alert.beginSheetModal(for: NSApp.windows.first!) { response in 106 | switch response { 107 | case .alertFirstButtonReturn: 108 | self.restartOurselves() 109 | default: 110 | return 111 | } 112 | } 113 | } 114 | } 115 | 116 | func setupHotkeyHandler() { 117 | let alert = NSAlert() 118 | 119 | if !doWeHavePermissions() { 120 | alert.messageText = "Accessibility Permission Required" 121 | alert.informativeText = "To set up a global key handler, unmenu requires accessibility permissions. Please grant these permissions in the system settings." 122 | alert.addButton(withTitle: "Open System Settings") 123 | alert.addButton(withTitle: "Quit unmenu") 124 | alert.beginSheetModal(for: NSApp.windows.first!) { response in 125 | switch response { 126 | case .alertFirstButtonReturn: 127 | // show settings 128 | let _ = self.showPermissionsSettings() 129 | self.waitForPermissions() 130 | case .alertSecondButtonReturn: 131 | // exit 132 | log("User doesn't want to give us permissions") 133 | NSApplication.shared.terminate(nil) 134 | default: 135 | break 136 | } 137 | } 138 | 139 | log("Access Granted") 140 | } 141 | 142 | log("We already had permissions granted") 143 | 144 | var keyCode: UInt16 = 11 145 | var modifierFlags: UInt = 1310985 & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue 146 | 147 | // Read config and hotkeys 148 | let fileManager = FileManager.default 149 | let homeDir = fileManager.homeDirectoryForCurrentUser 150 | let confPath = homeDir.appendingPathComponent(".config/unmenu/config.toml") 151 | 152 | if let config = Config(tomlFilePath: String(confPath.path)) { 153 | if let kc = config.hotkeyCode, let mf = config.hotkeyModifierFlags { 154 | print("Key Code: \(kc), Modifier Flags: \(mf)") 155 | keyCode = UInt16(kc) 156 | modifierFlags = UInt(mf) 157 | } else { 158 | print("Could not find hotkey configuration") 159 | } 160 | } else { 161 | print("Failed to load the config file.") 162 | } 163 | 164 | let eventMask = NSEvent.EventTypeMask.keyDown 165 | let eventHandler = { (event: NSEvent) -> Void in 166 | log("-- event handler") 167 | 168 | let devIndFlags = event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue 169 | log("-> handler keycode = \(event.keyCode) flags = // \(event.modifierFlags.rawValue) dev_ind_flags = \(devIndFlags) win \(event.windowNumber) // \(event.window)") 170 | 171 | if event.keyCode == keyCode && devIndFlags == modifierFlags { 172 | log(" matched keycode = \(event.keyCode) flags = // \(event.modifierFlags.rawValue) win \(event.windowNumber) // \(event.window)") 173 | self.showAppWindow() 174 | } else { 175 | log(" not matched key. \(event.keyCode) is not expected \(keyCode); or \(devIndFlags) is not expected \(event.modifierFlags.rawValue)") 176 | } 177 | } 178 | 179 | self.eventMonitor = NSEvent.addGlobalMonitorForEvents(matching: eventMask, handler: eventHandler) 180 | } 181 | 182 | func showAppWindow() { 183 | log("-> showAppWindow") 184 | 185 | NSApp.activate(ignoringOtherApps: true) 186 | let window = NSApp.windows.first 187 | window?.makeKeyAndOrderFront(nil) 188 | } 189 | 190 | func applicationDidFinishLaunching(_ aNotification: Notification) { 191 | log("application did finish launching") 192 | 193 | let testkey1 = Keys.convertToKeyCodeAndModifierFlags(shortcut: "ctrl-cmd-b") 194 | log("testkey1 = \(testkey1)") 195 | 196 | if let testkey1 = testkey1 { 197 | var modifierFlags: NSEvent.ModifierFlags = NSEvent.ModifierFlags(rawValue: testkey1.modifierFlags) 198 | 199 | // Check each modifier key and print if the flag is set 200 | if modifierFlags.contains(.capsLock) { 201 | print("Caps Lock is pressed") 202 | } 203 | if modifierFlags.contains(.shift) { 204 | print("Shift is pressed") 205 | } 206 | if modifierFlags.contains(.control) { 207 | print("Control is pressed") 208 | } 209 | if modifierFlags.contains(.option) { 210 | print("Option is pressed") 211 | } 212 | if modifierFlags.contains(.command) { 213 | print("Command is pressed") 214 | } 215 | if modifierFlags.contains(.numericPad) { 216 | print("Numeric Pad is pressed") 217 | } 218 | if modifierFlags.contains(.help) { 219 | print("Help is pressed") 220 | } 221 | if modifierFlags.contains(.function) { 222 | print("Function is pressed") 223 | } 224 | } 225 | 226 | 227 | let testkey2 = Keys.convertToKeyCodeAndModifierFlags(shortcut: "cmd-ctrl-b") 228 | log("testkey2 = \(testkey2)") 229 | 230 | statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 231 | 232 | if let button = statusItem.button { 233 | button.title = "_" 234 | } 235 | 236 | self.setupHotkeyHandler() 237 | log("set up hotkey handler") 238 | } 239 | 240 | func applicationWillTerminate(_ aNotification: Notification) { 241 | } 242 | 243 | @objc func toggleLaunchAtLogin() { 244 | let enabled = !LaunchAtLogin.isEnabled 245 | LaunchAtLogin.isEnabled = enabled 246 | startAtLaunch.state = enabled ? .on : .off 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /mac-app/Keys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keys.swift 3 | // unmenu 4 | // 5 | // Copyright © 2024 Ivan . All rights reserved. 6 | // Copyright © 2024 Jose Pereira. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | class Keys { 13 | // converted from https://github.com/pqrs-org/cpp-hid/blob/bfee1644d54ab4c900b7585f83a356d08d123c51/include/pqrs/hid/usage.hpp#L123 14 | enum KeyboardOrKeypad: UInt16 { 15 | case keyboard_a = 0x0 16 | case keyboard_b = 0xb 17 | case keyboard_c = 0x8 18 | case keyboard_d = 0x2 19 | case keyboard_e = 0xe 20 | case keyboard_f = 0x3 21 | case keyboard_g = 0x5 22 | case keyboard_h = 0x4 23 | case keyboard_i = 0x22 24 | case keyboard_j = 0x26 25 | case keyboard_k = 0x28 26 | case keyboard_l = 0x25 27 | case keyboard_m = 0x2e 28 | case keyboard_n = 0x2d 29 | case keyboard_o = 0x1f 30 | case keyboard_p = 0x23 31 | case keyboard_q = 0xc 32 | case keyboard_r = 0xf 33 | case keyboard_s = 0x1 34 | case keyboard_t = 0x11 35 | case keyboard_u = 0x20 36 | case keyboard_v = 0x9 37 | case keyboard_w = 0xd 38 | case keyboard_x = 0x7 39 | case keyboard_y = 0x10 40 | case keyboard_z = 0x6 41 | case keyboard_1 = 0x12 42 | case keyboard_2 = 0x13 43 | case keyboard_3 = 0x14 44 | case keyboard_4 = 0x15 45 | case keyboard_5 = 0x17 46 | case keyboard_6 = 0x16 47 | case keyboard_7 = 0x1a 48 | case keyboard_8 = 0x1c 49 | case keyboard_9 = 0x19 50 | case keyboard_0 = 0x1d 51 | case keyboard_return_or_enter = 0x24 52 | case keyboard_escape = 0x35 53 | case keyboard_delete_or_backspace = 0x33 54 | case keyboard_tab = 0x30 55 | case keyboard_spacebar = 0x31 56 | case keyboard_hyphen = 0x1b 57 | case keyboard_equal_sign = 0x18 58 | case keyboard_open_bracket = 0x21 59 | case keyboard_close_bracket = 0x1e 60 | case keyboard_backslash = 0x2a 61 | // case keyboard_non_us_pound = 0x2a // non_us_pound == backslash // duplicate 62 | case keyboard_semicolon = 0x29 63 | case keyboard_quote = 0x27 64 | case keyboard_grave_accent_and_tilde = 0x32 65 | case keyboard_comma = 0x2b 66 | case keyboard_period = 0x2f 67 | case keyboard_slash = 0x2c 68 | case keyboard_caps_lock = 0x39 69 | case keyboard_f1 = 0x7a 70 | case keyboard_f2 = 0x78 71 | case keyboard_f3 = 0x63 72 | case keyboard_f4 = 0x76 73 | case keyboard_f5 = 0x60 74 | case keyboard_f6 = 0x61 75 | case keyboard_f7 = 0x62 76 | case keyboard_f8 = 0x64 77 | case keyboard_f9 = 0x65 78 | case keyboard_f10 = 0x6d 79 | case keyboard_f11 = 0x67 80 | case keyboard_f12 = 0x6f 81 | // case keyboard_print_screen = 0x69 // commented out because non-unique 82 | // case keyboard_scroll_lock = 0x6b // commented out because non-unique 83 | // case keyboard_pause = 0x71 // commented out because non-unique 84 | // case keyboard_insert = 0x72 // commented out because non-unique 85 | case keyboard_home = 0x73 86 | case keyboard_page_up = 0x74 87 | case keyboard_delete_forward = 0x75 88 | case keyboard_end = 0x77 89 | case keyboard_page_down = 0x79 90 | case keyboard_right_arrow = 0x7c 91 | case keyboard_left_arrow = 0x7b 92 | case keyboard_down_arrow = 0x7d 93 | case keyboard_up_arrow = 0x7e 94 | case keypad_num_lock = 0x47 95 | case keypad_slash = 0x4b 96 | case keypad_asterisk = 0x43 97 | case keypad_hyphen = 0x4e 98 | case keypad_plus = 0x45 99 | case keypad_enter = 0x4c 100 | case keypad_1 = 0x53 101 | case keypad_2 = 0x54 102 | case keypad_3 = 0x55 103 | case keypad_4 = 0x56 104 | case keypad_5 = 0x57 105 | case keypad_6 = 0x58 106 | case keypad_7 = 0x59 107 | case keypad_8 = 0x5b 108 | case keypad_9 = 0x5c 109 | case keypad_0 = 0x52 110 | case keypad_period = 0x41 111 | case keyboard_non_us_backslash = 0xa 112 | case keyboard_application = 0x6e 113 | case keypad_equal_sign = 0x51 114 | case keyboard_f13 = 0x69 115 | case keyboard_f14 = 0x6b 116 | case keyboard_f15 = 0x71 117 | case keyboard_f16 = 0x6a 118 | case keyboard_f17 = 0x40 119 | case keyboard_f18 = 0x4f 120 | case keyboard_f19 = 0x50 121 | case keyboard_f20 = 0x5a 122 | case keyboard_help = 0x72 123 | case keypad_comma = 0x5f 124 | case keyboard_international1 = 0x5e 125 | case keyboard_international3 = 0x5d 126 | case keyboard_lang1 = 0x68 127 | case keyboard_lang2 = 0x66 128 | case keyboard_left_control = 0x3b 129 | case keyboard_left_shift = 0x38 130 | case keyboard_left_alt = 0x3a 131 | case keyboard_left_gui = 0x37 132 | case keyboard_right_control = 0x3e 133 | case keyboard_right_shift = 0x3c 134 | case keyboard_right_alt = 0x3d 135 | case keyboard_right_gui = 0x36 136 | case apple_vendor_keyboard_dashboard = 0x82 137 | case apple_vendor_keyboard_function = 0x3f 138 | case apple_vendor_keyboard_launchpad = 0x83 139 | case apple_vendor_keyboard_expose_all = 0xa0 140 | // case apple_vendor_top_case_keyboard_fn = 0x3f // commented out because duplicate // apple_vendor_top_case_keyboard_fn == apple_vendor_keyboard_function 141 | } 142 | 143 | static let keyNameToCode: [String: UInt16] = [ 144 | "a": 0x0, 145 | "b": 0xb, 146 | "c": 0x8, 147 | "d": 0x2, 148 | "e": 0xe, 149 | "f": 0x3, 150 | "g": 0x5, 151 | "h": 0x4, 152 | "i": 0x22, 153 | "j": 0x26, 154 | "k": 0x28, 155 | "l": 0x25, 156 | "m": 0x2e, 157 | "n": 0x2d, 158 | "o": 0x1f, 159 | "p": 0x23, 160 | "q": 0xc, 161 | "r": 0xf, 162 | "s": 0x1, 163 | "t": 0x11, 164 | "u": 0x20, 165 | "v": 0x9, 166 | "w": 0xd, 167 | "x": 0x7, 168 | "y": 0x10, 169 | "z": 0x6, 170 | "1": 0x12, 171 | "2": 0x13, 172 | "3": 0x14, 173 | "4": 0x15, 174 | "5": 0x17, 175 | "6": 0x16, 176 | "7": 0x1a, 177 | "8": 0x1c, 178 | "9": 0x19, 179 | "0": 0x1d, 180 | 181 | "return_or_enter": 0x24, 182 | "return": 0x24, 183 | "enter": 0x24, 184 | 185 | "escape": 0x35, 186 | 187 | "delete_or_backspace": 0x33, 188 | "backspace": 0x33, 189 | 190 | "tab": 0x30, 191 | 192 | "space": 0x31, 193 | "spacebar": 0x31, 194 | 195 | "hyphen": 0x1b, 196 | "equal_sign": 0x18, 197 | "open_bracket": 0x21, 198 | "close_bracket": 0x1e, 199 | "backslash": 0x2a, 200 | "non_us_pound": 0x2a, // non_us_pound == backslash 201 | "semicolon": 0x29, 202 | "quote": 0x27, 203 | 204 | "grave_accent_and_tilde": 0x32, 205 | "grave": 0x32, 206 | "tilde": 0x32, 207 | 208 | "comma": 0x2b, 209 | "period": 0x2f, 210 | "slash": 0x2c, 211 | "caps_lock": 0x39, 212 | "f1": 0x7a, 213 | "f2": 0x78, 214 | "f3": 0x63, 215 | "f4": 0x76, 216 | "f5": 0x60, 217 | "f6": 0x61, 218 | "f7": 0x62, 219 | "f8": 0x64, 220 | "f9": 0x65, 221 | "f10": 0x6d, 222 | "f11": 0x67, 223 | "f12": 0x6f, 224 | "print_screen": 0x69, 225 | "scroll_lock": 0x6b, 226 | "pause": 0x71, 227 | "insert": 0x72, 228 | "home": 0x73, 229 | "page_up": 0x74, 230 | "delete_forward": 0x75, 231 | "end": 0x77, 232 | "page_down": 0x79, 233 | "right_arrow": 0x7c, 234 | "left_arrow": 0x7b, 235 | "down_arrow": 0x7d, 236 | "up_arrow": 0x7e, 237 | "keypad_num_lock": 0x47, 238 | "keypad_slash": 0x4b, 239 | "keypad_asterisk": 0x43, 240 | "keypad_hyphen": 0x4e, 241 | "keypad_plus": 0x45, 242 | "keypad_enter": 0x4c, 243 | "keypad_1": 0x53, 244 | "keypad_2": 0x54, 245 | "keypad_3": 0x55, 246 | "keypad_4": 0x56, 247 | "keypad_5": 0x57, 248 | "keypad_6": 0x58, 249 | "keypad_7": 0x59, 250 | "keypad_8": 0x5b, 251 | "keypad_9": 0x5c, 252 | "keypad_0": 0x52, 253 | "keypad_period": 0x41, 254 | "non_us_backslash": 0xa, 255 | "application": 0x6e, 256 | "keypad_equal_sign": 0x51, 257 | "f13": 0x69, 258 | "f14": 0x6b, 259 | "f15": 0x71, 260 | "f16": 0x6a, 261 | "f17": 0x40, 262 | "f18": 0x4f, 263 | "f19": 0x50, 264 | "f20": 0x5a, 265 | "help": 0x72, 266 | "keypad_comma": 0x5f, 267 | "international1": 0x5e, 268 | "international3": 0x5d, 269 | "lang1": 0x68, 270 | "lang2": 0x66, 271 | "left_control": 0x3b, 272 | "left_shift": 0x38, 273 | "left_alt": 0x3a, 274 | "left_gui": 0x37, 275 | "right_control": 0x3e, 276 | "right_shift": 0x3c, 277 | "right_alt": 0x3d, 278 | "right_gui": 0x36, 279 | "apple_vendor_keyboard_dashboard": 0x82, 280 | "apple_vendor_keyboard_function": 0x3f, 281 | "apple_vendor_keyboard_launchpad": 0x83, 282 | "apple_vendor_keyboard_expose_all": 0xa0, 283 | "apple_vendor_top_case_keyboard_fn": 0x3f, // apple_vendor_top_case_keyboard_fn == apple_vendor_keyboard_function 284 | ] 285 | 286 | static var keyCodeToName: [UInt16: String] = { 287 | var dict: [UInt16: String] = [:] 288 | keyNameToCode.forEach { (key, value) in 289 | dict[value] = key 290 | } 291 | return dict 292 | }() 293 | 294 | static func convertToKeyCodeAndModifierFlags(shortcut: String) -> (keyCode: UInt16, modifierFlags: UInt)? { 295 | let components = shortcut.lowercased().components(separatedBy: "-") 296 | guard let key = components.last, 297 | let keyCode: UInt16 = self.keyNameToCode[key] else { return nil } 298 | 299 | let modifierFlag: NSEvent.ModifierFlags = components.dropLast().reduce(into: NSEvent.ModifierFlags()) { flag, component in 300 | switch component { 301 | case "cmd": flag.formUnion(.command) 302 | 303 | case "ctrl": flag.formUnion(.control) 304 | case "control": flag.formUnion(.control) 305 | 306 | case "alt": flag.formUnion(.option) 307 | case "option": flag.formUnion(.option) 308 | 309 | case "shift": flag.formUnion(.shift) 310 | 311 | case "fn": flag.formUnion(.function) 312 | case "function": flag.formUnion(.function) 313 | default: break 314 | } 315 | } 316 | 317 | return (keyCode, modifierFlag.rawValue) 318 | } 319 | 320 | } 321 | -------------------------------------------------------------------------------- /fuzzylib/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "appkit-nsworkspace-bindings" 16 | version = "0.1.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "062382938604cfa02c03689ab75af0e7eb79175ba0d0b2bcfad18f5190702dd7" 19 | dependencies = [ 20 | "bindgen", 21 | "objc", 22 | ] 23 | 24 | [[package]] 25 | name = "bindgen" 26 | version = "0.68.1" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" 29 | dependencies = [ 30 | "bitflags 2.4.2", 31 | "cexpr", 32 | "clang-sys", 33 | "lazy_static", 34 | "lazycell", 35 | "log", 36 | "peeking_take_while", 37 | "prettyplease", 38 | "proc-macro2", 39 | "quote", 40 | "regex", 41 | "rustc-hash", 42 | "shlex", 43 | "syn", 44 | "which", 45 | ] 46 | 47 | [[package]] 48 | name = "bitflags" 49 | version = "1.3.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 52 | 53 | [[package]] 54 | name = "bitflags" 55 | version = "2.4.2" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 58 | 59 | [[package]] 60 | name = "block" 61 | version = "0.1.6" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 64 | 65 | [[package]] 66 | name = "cexpr" 67 | version = "0.6.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 70 | dependencies = [ 71 | "nom", 72 | ] 73 | 74 | [[package]] 75 | name = "cfg-if" 76 | version = "1.0.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 79 | 80 | [[package]] 81 | name = "clang-sys" 82 | version = "1.7.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" 85 | dependencies = [ 86 | "glob", 87 | "libc", 88 | "libloading", 89 | ] 90 | 91 | [[package]] 92 | name = "cocoa" 93 | version = "0.25.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" 96 | dependencies = [ 97 | "bitflags 1.3.2", 98 | "block", 99 | "cocoa-foundation", 100 | "core-foundation", 101 | "core-graphics", 102 | "foreign-types", 103 | "libc", 104 | "objc", 105 | ] 106 | 107 | [[package]] 108 | name = "cocoa-foundation" 109 | version = "0.1.2" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" 112 | dependencies = [ 113 | "bitflags 1.3.2", 114 | "block", 115 | "core-foundation", 116 | "core-graphics-types", 117 | "libc", 118 | "objc", 119 | ] 120 | 121 | [[package]] 122 | name = "core-foundation" 123 | version = "0.9.4" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 126 | dependencies = [ 127 | "core-foundation-sys", 128 | "libc", 129 | ] 130 | 131 | [[package]] 132 | name = "core-foundation-sys" 133 | version = "0.8.6" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 136 | 137 | [[package]] 138 | name = "core-graphics" 139 | version = "0.23.1" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" 142 | dependencies = [ 143 | "bitflags 1.3.2", 144 | "core-foundation", 145 | "core-graphics-types", 146 | "foreign-types", 147 | "libc", 148 | ] 149 | 150 | [[package]] 151 | name = "core-graphics-types" 152 | version = "0.1.3" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 155 | dependencies = [ 156 | "bitflags 1.3.2", 157 | "core-foundation", 158 | "libc", 159 | ] 160 | 161 | [[package]] 162 | name = "core-services" 163 | version = "0.2.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "92567e81db522550ebaf742c5d875624ec7820c2c7ee5f8c60e4ce7c2ae3c0fd" 166 | dependencies = [ 167 | "core-foundation", 168 | ] 169 | 170 | [[package]] 171 | name = "either" 172 | version = "1.10.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" 175 | 176 | [[package]] 177 | name = "equivalent" 178 | version = "1.0.1" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 181 | 182 | [[package]] 183 | name = "errno" 184 | version = "0.3.8" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 187 | dependencies = [ 188 | "libc", 189 | "windows-sys 0.52.0", 190 | ] 191 | 192 | [[package]] 193 | name = "foreign-types" 194 | version = "0.5.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" 197 | dependencies = [ 198 | "foreign-types-macros", 199 | "foreign-types-shared", 200 | ] 201 | 202 | [[package]] 203 | name = "foreign-types-macros" 204 | version = "0.2.3" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" 207 | dependencies = [ 208 | "proc-macro2", 209 | "quote", 210 | "syn", 211 | ] 212 | 213 | [[package]] 214 | name = "foreign-types-shared" 215 | version = "0.3.1" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" 218 | 219 | [[package]] 220 | name = "fuzzy-matcher" 221 | version = "0.3.7" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 224 | dependencies = [ 225 | "thread_local", 226 | ] 227 | 228 | [[package]] 229 | name = "fuzzylib" 230 | version = "0.1.0" 231 | dependencies = [ 232 | "appkit-nsworkspace-bindings", 233 | "cocoa", 234 | "core-foundation", 235 | "core-foundation-sys", 236 | "core-services", 237 | "fuzzy-matcher", 238 | "libc", 239 | "objc", 240 | "objc-foundation", 241 | "regex", 242 | "serde", 243 | "serde_derive", 244 | "toml", 245 | ] 246 | 247 | [[package]] 248 | name = "glob" 249 | version = "0.3.1" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 252 | 253 | [[package]] 254 | name = "hashbrown" 255 | version = "0.14.3" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 258 | 259 | [[package]] 260 | name = "home" 261 | version = "0.5.9" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 264 | dependencies = [ 265 | "windows-sys 0.52.0", 266 | ] 267 | 268 | [[package]] 269 | name = "indexmap" 270 | version = "2.2.3" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" 273 | dependencies = [ 274 | "equivalent", 275 | "hashbrown", 276 | ] 277 | 278 | [[package]] 279 | name = "lazy_static" 280 | version = "1.4.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 283 | 284 | [[package]] 285 | name = "lazycell" 286 | version = "1.3.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 289 | 290 | [[package]] 291 | name = "libc" 292 | version = "0.2.153" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 295 | 296 | [[package]] 297 | name = "libloading" 298 | version = "0.8.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" 301 | dependencies = [ 302 | "cfg-if", 303 | "windows-sys 0.48.0", 304 | ] 305 | 306 | [[package]] 307 | name = "linux-raw-sys" 308 | version = "0.4.13" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 311 | 312 | [[package]] 313 | name = "log" 314 | version = "0.4.20" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 317 | 318 | [[package]] 319 | name = "malloc_buf" 320 | version = "0.0.6" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 323 | dependencies = [ 324 | "libc", 325 | ] 326 | 327 | [[package]] 328 | name = "memchr" 329 | version = "2.7.1" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 332 | 333 | [[package]] 334 | name = "minimal-lexical" 335 | version = "0.2.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 338 | 339 | [[package]] 340 | name = "nom" 341 | version = "7.1.3" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 344 | dependencies = [ 345 | "memchr", 346 | "minimal-lexical", 347 | ] 348 | 349 | [[package]] 350 | name = "objc" 351 | version = "0.2.7" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 354 | dependencies = [ 355 | "malloc_buf", 356 | ] 357 | 358 | [[package]] 359 | name = "objc-foundation" 360 | version = "0.1.1" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" 363 | dependencies = [ 364 | "block", 365 | "objc", 366 | "objc_id", 367 | ] 368 | 369 | [[package]] 370 | name = "objc_id" 371 | version = "0.1.1" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" 374 | dependencies = [ 375 | "objc", 376 | ] 377 | 378 | [[package]] 379 | name = "once_cell" 380 | version = "1.19.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 383 | 384 | [[package]] 385 | name = "peeking_take_while" 386 | version = "0.1.2" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 389 | 390 | [[package]] 391 | name = "prettyplease" 392 | version = "0.2.16" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" 395 | dependencies = [ 396 | "proc-macro2", 397 | "syn", 398 | ] 399 | 400 | [[package]] 401 | name = "proc-macro2" 402 | version = "1.0.78" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 405 | dependencies = [ 406 | "unicode-ident", 407 | ] 408 | 409 | [[package]] 410 | name = "quote" 411 | version = "1.0.35" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 414 | dependencies = [ 415 | "proc-macro2", 416 | ] 417 | 418 | [[package]] 419 | name = "regex" 420 | version = "1.10.3" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" 423 | dependencies = [ 424 | "aho-corasick", 425 | "memchr", 426 | "regex-automata", 427 | "regex-syntax", 428 | ] 429 | 430 | [[package]] 431 | name = "regex-automata" 432 | version = "0.4.5" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" 435 | dependencies = [ 436 | "aho-corasick", 437 | "memchr", 438 | "regex-syntax", 439 | ] 440 | 441 | [[package]] 442 | name = "regex-syntax" 443 | version = "0.8.2" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 446 | 447 | [[package]] 448 | name = "rustc-hash" 449 | version = "1.1.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 452 | 453 | [[package]] 454 | name = "rustix" 455 | version = "0.38.31" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" 458 | dependencies = [ 459 | "bitflags 2.4.2", 460 | "errno", 461 | "libc", 462 | "linux-raw-sys", 463 | "windows-sys 0.52.0", 464 | ] 465 | 466 | [[package]] 467 | name = "serde" 468 | version = "1.0.196" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" 471 | dependencies = [ 472 | "serde_derive", 473 | ] 474 | 475 | [[package]] 476 | name = "serde_derive" 477 | version = "1.0.196" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" 480 | dependencies = [ 481 | "proc-macro2", 482 | "quote", 483 | "syn", 484 | ] 485 | 486 | [[package]] 487 | name = "serde_spanned" 488 | version = "0.6.5" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" 491 | dependencies = [ 492 | "serde", 493 | ] 494 | 495 | [[package]] 496 | name = "shlex" 497 | version = "1.3.0" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 500 | 501 | [[package]] 502 | name = "syn" 503 | version = "2.0.48" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 506 | dependencies = [ 507 | "proc-macro2", 508 | "quote", 509 | "unicode-ident", 510 | ] 511 | 512 | [[package]] 513 | name = "thread_local" 514 | version = "1.1.7" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 517 | dependencies = [ 518 | "cfg-if", 519 | "once_cell", 520 | ] 521 | 522 | [[package]] 523 | name = "toml" 524 | version = "0.7.8" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" 527 | dependencies = [ 528 | "serde", 529 | "serde_spanned", 530 | "toml_datetime", 531 | "toml_edit", 532 | ] 533 | 534 | [[package]] 535 | name = "toml_datetime" 536 | version = "0.6.5" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 539 | dependencies = [ 540 | "serde", 541 | ] 542 | 543 | [[package]] 544 | name = "toml_edit" 545 | version = "0.19.15" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 548 | dependencies = [ 549 | "indexmap", 550 | "serde", 551 | "serde_spanned", 552 | "toml_datetime", 553 | "winnow", 554 | ] 555 | 556 | [[package]] 557 | name = "unicode-ident" 558 | version = "1.0.12" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 561 | 562 | [[package]] 563 | name = "which" 564 | version = "4.4.2" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 567 | dependencies = [ 568 | "either", 569 | "home", 570 | "once_cell", 571 | "rustix", 572 | ] 573 | 574 | [[package]] 575 | name = "windows-sys" 576 | version = "0.48.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 579 | dependencies = [ 580 | "windows-targets 0.48.5", 581 | ] 582 | 583 | [[package]] 584 | name = "windows-sys" 585 | version = "0.52.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 588 | dependencies = [ 589 | "windows-targets 0.52.0", 590 | ] 591 | 592 | [[package]] 593 | name = "windows-targets" 594 | version = "0.48.5" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 597 | dependencies = [ 598 | "windows_aarch64_gnullvm 0.48.5", 599 | "windows_aarch64_msvc 0.48.5", 600 | "windows_i686_gnu 0.48.5", 601 | "windows_i686_msvc 0.48.5", 602 | "windows_x86_64_gnu 0.48.5", 603 | "windows_x86_64_gnullvm 0.48.5", 604 | "windows_x86_64_msvc 0.48.5", 605 | ] 606 | 607 | [[package]] 608 | name = "windows-targets" 609 | version = "0.52.0" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 612 | dependencies = [ 613 | "windows_aarch64_gnullvm 0.52.0", 614 | "windows_aarch64_msvc 0.52.0", 615 | "windows_i686_gnu 0.52.0", 616 | "windows_i686_msvc 0.52.0", 617 | "windows_x86_64_gnu 0.52.0", 618 | "windows_x86_64_gnullvm 0.52.0", 619 | "windows_x86_64_msvc 0.52.0", 620 | ] 621 | 622 | [[package]] 623 | name = "windows_aarch64_gnullvm" 624 | version = "0.48.5" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 627 | 628 | [[package]] 629 | name = "windows_aarch64_gnullvm" 630 | version = "0.52.0" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 633 | 634 | [[package]] 635 | name = "windows_aarch64_msvc" 636 | version = "0.48.5" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 639 | 640 | [[package]] 641 | name = "windows_aarch64_msvc" 642 | version = "0.52.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 645 | 646 | [[package]] 647 | name = "windows_i686_gnu" 648 | version = "0.48.5" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 651 | 652 | [[package]] 653 | name = "windows_i686_gnu" 654 | version = "0.52.0" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 657 | 658 | [[package]] 659 | name = "windows_i686_msvc" 660 | version = "0.48.5" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 663 | 664 | [[package]] 665 | name = "windows_i686_msvc" 666 | version = "0.52.0" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 669 | 670 | [[package]] 671 | name = "windows_x86_64_gnu" 672 | version = "0.48.5" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 675 | 676 | [[package]] 677 | name = "windows_x86_64_gnu" 678 | version = "0.52.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 681 | 682 | [[package]] 683 | name = "windows_x86_64_gnullvm" 684 | version = "0.48.5" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 687 | 688 | [[package]] 689 | name = "windows_x86_64_gnullvm" 690 | version = "0.52.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 693 | 694 | [[package]] 695 | name = "windows_x86_64_msvc" 696 | version = "0.48.5" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 699 | 700 | [[package]] 701 | name = "windows_x86_64_msvc" 702 | version = "0.52.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 705 | 706 | [[package]] 707 | name = "winnow" 708 | version = "0.5.40" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 711 | dependencies = [ 712 | "memchr", 713 | ] 714 | -------------------------------------------------------------------------------- /fuzzylib/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::fs::{DirEntry, Metadata, FileType}; 3 | use std::os::unix::fs::PermissionsExt; 4 | use serde_derive::{Serialize, Deserialize}; 5 | 6 | use fuzzy_matcher::skim::SkimMatcherV2; 7 | use fuzzy_matcher::FuzzyMatcher; 8 | 9 | use std::ffi::{CString, CStr, c_char}; 10 | use std::fs::File; 11 | use std::io::{BufReader, Read, Write}; 12 | use toml; 13 | 14 | use regex; 15 | extern crate libc; 16 | use std::ptr; 17 | 18 | // for checking if something is allocated 19 | // extern "C" { 20 | // fn malloc_size(ptr: *const c_void) -> size_t; 21 | // } 22 | 23 | // fn get_allocation_size(ptr: *const c_void) -> usize { 24 | // unsafe { malloc_size(ptr) as usize } 25 | // } 26 | 27 | use objc_foundation::{INSString, NSString, NSObject}; 28 | 29 | use objc::runtime::Object; 30 | use objc::{class, msg_send, sel, sel_impl}; 31 | 32 | fn open_application(app_path: &str) -> Result<(), Error> { 33 | let nsautorelease_pool = class!(NSAutoreleasePool); 34 | let pool: *mut Object = unsafe { msg_send![nsautorelease_pool, new] }; 35 | 36 | let workspace: *mut Object = unsafe { 37 | msg_send![class!(NSWorkspace), performSelector: sel!(sharedWorkspace)] 38 | }; 39 | 40 | // println!("creating a path"); 41 | let path: *mut Object = unsafe { 42 | let nsstr = NSString::from_str(app_path); 43 | msg_send![class!(NSURL), fileURLWithPath: nsstr] 44 | }; 45 | 46 | // println!("creating an open conf"); 47 | // let conf = NSWorkspace.Opennonfiguration.init() 48 | let conf: NSObject = unsafe { 49 | // msg_send![class!(NSWorkspace.OpenConfiguration), init] 50 | // let configuration: *mut Object = msg_send![class!(NSWorkspace), OpenConfiguration]; 51 | let configuration = objc::runtime::Class::get("NSWorkspaceOpenConfiguration") 52 | .expect("couldn't get openconf class"); 53 | msg_send![configuration, new] 54 | }; 55 | 56 | // println!("launching..."); 57 | unsafe { 58 | let _: () = msg_send![ 59 | workspace, 60 | launchApplicationAtURL: path 61 | options: 0 62 | configuration: conf //ptr::null_mut::() 63 | error: ptr::null_mut::() 64 | ]; 65 | } 66 | 67 | // println!("pool release"); 68 | unsafe { 69 | let _: () = msg_send![pool, release]; 70 | } 71 | 72 | // println!("end"); 73 | 74 | Ok(()) 75 | } 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | type Error = Box; 84 | 85 | // See unmenu Config.swift file 86 | #[derive(Debug, Clone, Serialize, Deserialize)] 87 | struct Hotkey { 88 | qwerty_hotkey: Option, 89 | key_code: Option, 90 | modifier_flags: Option, 91 | } 92 | 93 | impl Default for Hotkey { 94 | fn default() -> Hotkey { 95 | Hotkey { 96 | qwerty_hotkey: Some("ctrl-cmd-x".to_string()), 97 | key_code: None, 98 | modifier_flags: None, 99 | } 100 | } 101 | } 102 | 103 | #[derive(Debug, Clone, Serialize, Deserialize)] 104 | struct Config { 105 | find_apps: bool, 106 | find_executables: bool, 107 | dirs: Vec, 108 | ignore_names: Vec, 109 | ignore_patterns: Vec, 110 | } 111 | 112 | impl Default for Config { 113 | fn default() -> Config { 114 | let ignore_patterns = 115 | vec!["^Install.*", ".*Installer\\.app$", "\\.bundle$"] 116 | .into_iter().map(|s| s.to_string()) 117 | .collect(); 118 | 119 | let ignore_names = vec![ 120 | "unmenu.app", 121 | ".Karabiner-VirtualHIDDevice-Manager.app", "Install Command Line Developer Tools.app", 122 | "Install in Progress.app", "Migration Assistant.app", "TextInputMenuAgent.app", 123 | "TextInputSwitcher.app", "AOSUIPrefPaneLauncher.app", 124 | "Automator Application Stub.app", "NetAuthAgent.app", "Captive Network Assistant.app", 125 | "screencaptureui.app", "ScreenSaverEngine.app", "SystemUIServer.app", 126 | "UIKitSystem.app", "NowPlayingTouchUI.app", "WindowManager.app", 127 | "APFSUserAgent", "AVB Audio Configuration.app", "AddPrinter.app", 128 | "AddressBookUrlForwarder.app", "AirPlayUIAgent.app", "AirPort Base Station Agent.app", 129 | "AppleScript Utility.app", "Automator Application Stub.app", "Automator Installer.app", 130 | "BluetoothSetupAssistant.app", "BluetoothUIServer.app", 131 | "BluetoothUIService.app", "CSUserAgent", "CalendarFileHandler.app", 132 | "Captive Network Assistant.app", "Certificate Assistant.app", "CloudSettingsSyncAgent", 133 | "ControlCenter.app", "ControlStrip.app", "CoreLocationAgent.app", "CoreServicesUIAgent.app", 134 | "Coverage Details.app", "CrashReporterSupportHelper", "DMProxy", 135 | "Database Events.app", "DefaultBackground.jpg", "DefaultDesktop.heic", 136 | "Diagnostics Reporter.app", "DiscHelper.app", "DiskImageMounter.app", "Dock.app", 137 | "Dwell Control.app", "Erase Assistant.app", "EscrowSecurityAlert.app", 138 | "ExpansionSlotNotification", "FolderActionsDispatcher.app", "HelpViewer.app", 139 | "IOUIAgent.app", "Image Events.app", "Install Command Line Developer Tools.app", 140 | "Install in Progress.app", "Installer Progress.app", "Installer.app", 141 | "JavaLauncher.app", "KeyboardAccessAgent.app", "KeyboardSetupAssistant.app", 142 | "Keychain Circle Notification.app", "Language Chooser.app", "MTLReplayer.app", 143 | "ManagedClient.app", "MapsSuggestionsTransportModePrediction.mlmodelc", 144 | "MemorySlotNotification", "Menu Extras", "NetAuthAgent.app", 145 | "NowPlayingTouchUI.app", "OBEXAgent.app", "ODSAgent.app", "OSDUIHelper.app", 146 | "PIPAgent.app", "PodcastsAuthAgent.app", "PowerChime.app", "Pro Display Calibrator.app", 147 | "Problem Reporter.app", "ProfileHelper.app", "RapportUIAgent.app", 148 | "RegisterPluginIMApp.app", "RemoteManagement", "RemotePairTool", "ReportCrash", 149 | "Resources", "RestoreVersion.plist", "Rosetta 2 Updater.app", 150 | "ScopedBookmarkAgent", "ScreenSaverEngine.app", "Script Menu.app", 151 | "ScriptMonitor.app", "SecurityAgentPlugins", "ServicesUIAgent", 152 | "Setup Assistant.app", "SetupAssistantPlugins", "ShortcutDroplet.app", "Shortcuts Events.app", 153 | "SpacesTouchBarAgent.app", "StageManagerEducation.app", "SubmitDiagInfo", 154 | "SystemFolderLocalizations", "SystemUIServer.app", "SystemVersion.plist", 155 | "SystemVersionCompat.plist", "TextInputMenuAgent.app", "TextInputSwitcher.app", 156 | "ThermalTrap.app", "Tips.app", "UAUPlugins", "UIKitSystem.app", 157 | "UniversalAccessControl.app", "UniversalControl.app", "UnmountAssistantAgent.app", 158 | "UserAccountUpdater", "UserNotificationCenter.app", "UserPictureSyncAgent", 159 | "VoiceOver.app", "WatchFaceAlert.app", "WiFiAgent.app", 160 | "WidgetKit Simulator.app", "WindowManager.app", "Xcode Previews.app", 161 | "appleeventsd", "boot.efi", "cacheTimeZones", "cloudpaird", "com.apple.NSServicesRestrictions.plist", 162 | "coreservicesd", "destinationd", "diagnostics_agent", "iCloud+.app", 163 | "iCloud.app", "iOSSystemVersion.plist", "iTunesStoreURLPatterns.plist", 164 | "iconservicesagent", "iconservicesd", "ionodecache", "launchservicesd", 165 | "lockoutagent", "logind", "loginwindow.app", "mapspushd", "navd", 166 | "osanalyticshelper", "pbs", "rc.trampoline", "rcd.app", "screencaptureui.app", 167 | "sessionlogoutd", "sharedfilelistd", "talagent", "uncd", "NotificationCenter.app"]; 168 | let ignore_names = ignore_names.into_iter().map(|s| s.to_string()) 169 | .collect(); 170 | 171 | let dirs = vec![ 172 | "/System/Applications/".into(), 173 | "/Applications/".into(), 174 | "/System/Applications/Utilities/".into(), 175 | "/System/Library/CoreServices/".into(), 176 | ]; 177 | Config { 178 | find_apps: true, 179 | find_executables: true, 180 | dirs, 181 | ignore_names, 182 | ignore_patterns, 183 | } 184 | } 185 | } 186 | 187 | impl Config { 188 | fn get_file_path() -> Result { 189 | let home_dir = std::env::var("HOME") 190 | .map_err(|_| "Failed to get home directory")?; 191 | let path = format!("{}/.config/unmenu/config.toml", home_dir); 192 | let path_path = Path::new(&path); 193 | return Ok(path_path.to_path_buf()) 194 | } 195 | 196 | fn read_file() -> Result { 197 | let path = Config::get_file_path()?; 198 | eprintln!("fuzzylib: config path: {path:?}"); 199 | if !path.exists() { 200 | eprintln!("fuzzylib: config file doesn't exist"); 201 | return Ok(Config::default()) 202 | } 203 | 204 | let file = File::open(&path) 205 | .map_err(|_| format!("Failed to open file: {:?}", path))?; 206 | let mut reader = BufReader::new(file); 207 | let mut contents = String::new(); 208 | reader.read_to_string(&mut contents) 209 | .map_err(|_| format!("Failed to read file: {:?}", path))?; 210 | 211 | let config: Config = toml::from_str(&contents) 212 | .map_err(|e| format!("Failed to parse TOML: {}", e))?; 213 | 214 | eprintln!("fuzzylib: loaded config file"); 215 | 216 | Ok(config) 217 | } 218 | } 219 | 220 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 221 | #[repr(u8)] 222 | enum AppType { 223 | MacApp = 0, 224 | Executable = 1, 225 | } 226 | 227 | impl std::fmt::Display for AppType { 228 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 229 | match self { 230 | AppType::MacApp => write!(f, "A"), 231 | AppType::Executable => write!(f, "X"), 232 | } 233 | } 234 | } 235 | 236 | #[derive(Debug, Clone, Eq, PartialEq)] 237 | pub struct Item { 238 | name: String, 239 | cname: CString, 240 | path: PathBuf, 241 | app_type: AppType, 242 | } 243 | 244 | impl std::fmt::Display for Item { 245 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 246 | let name = &self.name; 247 | let ty = self.app_type; 248 | let mut path = self.path.as_os_str().to_str(); 249 | if path.is_none() { 250 | path = Some("") 251 | } 252 | let path = path.unwrap(); 253 | 254 | write!(f, "{ty}: {name} path: {path}") 255 | } 256 | } 257 | 258 | impl Item { 259 | pub fn open(&self) -> Result<(), Error> { 260 | match self.app_type { 261 | AppType::MacApp => { 262 | if let Err(err) = open_application(self.path.as_os_str().to_str().expect("Couldn't make str from app path")) { 263 | eprintln!("Error opening app {}: {err:?}", self.name); 264 | } 265 | }, 266 | AppType::Executable => { 267 | std::process::Command::new(&self.path) 268 | .stdout(std::process::Stdio::null()) 269 | .stderr(std::process::Stdio::null()) 270 | .spawn()?; 271 | }, 272 | } 273 | Ok(()) 274 | } 275 | } 276 | 277 | #[derive(Default)] 278 | pub struct Matcher { 279 | config: Config, 280 | matcher: SkimMatcherV2, 281 | pub items: Vec, 282 | ignore_patterns: Vec, 283 | } 284 | 285 | fn is_executable(file_type: &FileType, metadata: &Metadata) -> bool { 286 | metadata.permissions().mode() & 0o111 != 0 && file_type.is_file() 287 | } 288 | 289 | fn is_mac_app(file_path: &Path) -> bool { 290 | if let Some(file_name) = file_path.file_name() { 291 | if let Some(name) = file_name.to_str() { 292 | if name.ends_with(".app") { 293 | let info_plist_path = file_path.join("Contents"); 294 | if info_plist_path.exists() { 295 | return true; 296 | } 297 | } 298 | } 299 | } 300 | 301 | false 302 | } 303 | 304 | fn get_executable_name(entry: &DirEntry) -> String { 305 | let mut name = entry.path() 306 | .file_name().expect("couldn't get file name") 307 | .to_str().expect("couldn't get str for filename") 308 | .to_string(); 309 | 310 | if name.ends_with(".app") { 311 | let new_length = name.len() - 4; // Remove the length of ".app" 312 | name.truncate(new_length); 313 | } 314 | name 315 | } 316 | 317 | fn resolve_tilde(path: &Path) -> PathBuf { 318 | if path.starts_with("~") { 319 | let home_dir = std::env::home_dir().expect("Failed to get home directory"); 320 | home_dir.join(path.strip_prefix("~").unwrap()) 321 | } else { 322 | path.to_path_buf() 323 | } 324 | } 325 | 326 | impl Matcher { 327 | fn load_ignore_patterns(&mut self) -> Result<(), Error> { 328 | self.ignore_patterns.clear(); 329 | for pattern in self.config.ignore_patterns.iter() { 330 | self.ignore_patterns.push(regex::Regex::new(pattern)?); 331 | } 332 | Ok(()) 333 | } 334 | 335 | pub fn new() -> Result { 336 | let mut config = Config::read_file(); 337 | if let Err(err) = config { 338 | println!("Error loading config: {err:?}"); 339 | config = Ok(Config::default()); 340 | } 341 | let config = config.unwrap(); 342 | 343 | let mut matcher = Matcher { 344 | config, 345 | matcher: SkimMatcherV2::default(), 346 | items: Vec::new(), 347 | ignore_patterns: Vec::new(), 348 | }; 349 | matcher.load_ignore_patterns()?; 350 | 351 | Ok(matcher) 352 | } 353 | 354 | fn entry_filtered_out(&self, dir_entry: &DirEntry) -> bool { 355 | let file_name = dir_entry.file_name(); 356 | let file_name = file_name.to_str(); 357 | 358 | let file_path = dir_entry.path(); 359 | let file_path = file_path.to_str(); 360 | if file_path.is_none() { return true }; 361 | let file_path = file_path.unwrap(); 362 | 363 | if file_name.is_none() { return true }; 364 | let file_name = file_name.unwrap(); 365 | 366 | if self.config.ignore_names.iter().any(|n| file_name == n || file_path == n) { 367 | return true; 368 | } 369 | 370 | if self.ignore_patterns.iter().any(|rx| rx.is_match(file_name) || rx.is_match(file_path)) { 371 | return true 372 | } 373 | false 374 | } 375 | 376 | fn scan_dir(&mut self, orig_dir: &Path) { 377 | eprintln!("reading directory {orig_dir:?}"); 378 | let dir = &orig_dir.canonicalize().expect("Couldn't canonicalize dir path {orig_dir:?}"); 379 | 380 | let dir_items = std::fs::read_dir(dir); 381 | if let Err(err) = dir_items { 382 | eprintln!("Could not read dir {dir:?}: {err:?}"); 383 | return; 384 | } 385 | let dir_items = dir_items.unwrap(); 386 | 387 | for entry in dir_items { 388 | if let Err(err) = entry { 389 | panic!("dir entry error: {err:?}") 390 | } 391 | let entry = entry.unwrap(); 392 | 393 | 394 | let path = entry.path().canonicalize(); 395 | if let Err(err) = path { 396 | eprintln!("Could not resolve symlink for {entry:?}: {err:?}"); 397 | continue; 398 | } 399 | let path = path.unwrap(); 400 | 401 | let metadata = path.metadata(); 402 | if let Err(err) = metadata { 403 | eprintln!("Could not get metadata for {path:?}: {err:?}"); 404 | continue; 405 | } 406 | let metadata = metadata.unwrap(); 407 | 408 | let file_type = metadata.file_type(); 409 | 410 | if is_executable(&file_type, &metadata) { 411 | if self.config.find_executables && !self.entry_filtered_out(&entry) { 412 | let name = get_executable_name(&entry); 413 | self.items.push(Item { 414 | name: name.clone(), 415 | cname: CString::new(name).unwrap(), 416 | path: path, 417 | app_type: AppType::Executable, 418 | }); 419 | } 420 | } else if is_mac_app(&path) { 421 | if self.config.find_apps && !self.entry_filtered_out(&entry) { 422 | let name = get_executable_name(&entry); 423 | self.items.push(Item { 424 | name: name.clone(), 425 | cname: CString::new(name).unwrap(), 426 | path: path, 427 | app_type: AppType::MacApp, 428 | }); 429 | } 430 | } 431 | } 432 | } 433 | 434 | fn create_config_if_missing(&self) -> Result<(), Error> { 435 | let toml_content = toml::to_string(&self.config)?; 436 | 437 | // Specify the path to the file 438 | let path = Config::get_file_path()?; 439 | 440 | // Create the parent directory if it doesn't exist 441 | if let Some(parent) = path.parent() { 442 | std::fs::create_dir_all(parent)?; 443 | } 444 | 445 | // Open the file with write mode (if it exists) or create mode (if it doesn't exist) 446 | let file = std::fs::OpenOptions::new() 447 | .write(true) 448 | .create_new(true) 449 | .open(path); 450 | 451 | match file { 452 | Ok(mut file) => { 453 | // Write the TOML content to the file 454 | file.write_all(toml_content.as_bytes())?; 455 | } 456 | Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => { 457 | // File already exists, do nothing 458 | return Ok(()); 459 | } 460 | Err(e) => { 461 | return Err(e.into()); 462 | } 463 | } 464 | 465 | Ok(()) 466 | } 467 | 468 | fn reload_config(&mut self) -> Result<(), Error> { 469 | println!("-> reload_config"); 470 | println!(" prev config: {:?}", self.config); 471 | self.config = Config::read_file()?; 472 | self.load_ignore_patterns()?; 473 | println!("config: {:?}", self.config); 474 | Ok(()) 475 | } 476 | 477 | /// scans all dirs in config 478 | pub fn rescan(&mut self) { 479 | println!("-> rescan"); 480 | self.create_config_if_missing().expect("error while creating config file"); 481 | 482 | if let Err(err) = self.reload_config() { 483 | println!("Error loading config: {err:?}"); 484 | } 485 | 486 | self.items.clear(); 487 | let config = self.config.clone(); 488 | for dir in config.dirs { 489 | self.scan_dir(&resolve_tilde(&dir)); 490 | } 491 | } 492 | 493 | pub fn search(&self, pattern: &str) -> Vec<&Item> { 494 | println!("-> search \"{pattern}\""); 495 | if pattern == "" { 496 | return Vec::new(); 497 | } 498 | 499 | let mut result: Vec<(i64, &Item)> = Vec::new(); 500 | for item in &self.items { 501 | if let Some(score) = self.matcher.fuzzy_match(&item.name, pattern) { 502 | result.push((score, item)) 503 | } 504 | } 505 | result.sort_by_key(|(k, _)| -*k); 506 | for (rank, item) in result.iter() { 507 | println!(" {rank} {item}"); 508 | } 509 | let result = result.into_iter().map(|(_,v)| v).collect(); 510 | 511 | println!(""); 512 | result 513 | } 514 | } 515 | 516 | 517 | // C API 518 | 519 | #[no_mangle] 520 | pub extern "C" fn matcher_new() -> *mut Matcher { 521 | let matcher = Matcher::new(); 522 | if matcher.is_err() { 523 | return 0 as *mut Matcher 524 | } 525 | let matcher = matcher.unwrap(); 526 | Box::into_raw(Box::new(matcher)) 527 | } 528 | 529 | #[no_mangle] 530 | pub extern "C" fn matcher_rescan(matcher: *mut Matcher) { 531 | let matcher = unsafe { &mut *(matcher as *mut Matcher) }; 532 | matcher.rescan(); 533 | } 534 | 535 | // For debugging 536 | // fn print_mem_at_addr(addr: usize) { 537 | // if addr == 0 { 538 | // println!("print_mem_at_addr called on 0"); 539 | // return; 540 | // } 541 | // unsafe { 542 | // println!("bytes at {:0x}:", addr); 543 | // // Access the memory bytes 544 | // let byte_slice: &[u8] = std::slice::from_raw_parts(addr as *const u8, 128); // Replace with the desired size 545 | // 546 | // // Print the byte values 547 | // for (ii, byte) in byte_slice.iter().enumerate() { 548 | // if ii % 8 == 0 { 549 | // print!("\n"); 550 | // } 551 | // print!("{:02X} ", byte); 552 | // } 553 | // print!("\n"); 554 | // } 555 | // } 556 | 557 | #[no_mangle] 558 | pub extern "C" fn matcher_search(matcher: *const Matcher, pattern: *const c_char) -> *mut SearchResults { 559 | // println!(" ========================================================"); 560 | // println!(" ========== -> matcher_search ==========================="); 561 | // println!(" ========================================================"); 562 | 563 | let matcher = unsafe { &*(matcher as *const Matcher) }; 564 | let pattern = unsafe { CStr::from_ptr(pattern).to_string_lossy().into_owned() }; 565 | let items = matcher.search(&pattern); 566 | 567 | // for (ii, item) in items.iter().enumerate() { 568 | // println!("item {ii} name = {}", item.name); 569 | // } 570 | 571 | let num_items = items.len() as u64; 572 | 573 | let item_pointers: Vec<*const Item> = items.iter().map(|item| (*item) as *const Item).collect(); 574 | 575 | // println!("rust: matcher_search num_items = {num_items}"); 576 | 577 | // let mut prev_ptr = 0usize; 578 | // for (ii, ptr) in item_pointers.iter().enumerate() { 579 | // let ptr_usize = (*ptr as *const Item) as usize; 580 | // // println!("ptr {ii} = {ptr_usize:0x} diff = {:0x}", ptr_usize - prev_ptr); 581 | // // println!("ptr {ii} = {ptr_usize:0x}"); 582 | // prev_ptr = ptr_usize; 583 | // } 584 | 585 | 586 | let pointer_to_item_pointers = if item_pointers.is_empty() { 587 | 0 as *const *const Item 588 | } else { 589 | item_pointers.as_ptr() 590 | }; 591 | 592 | // Don't dealloc it now 593 | std::mem::forget(item_pointers); 594 | std::mem::forget(items); 595 | 596 | let results = Box::new(SearchResults { 597 | num_items, 598 | items: pointer_to_item_pointers, 599 | }); 600 | 601 | 602 | let result_ptr = Box::into_raw(results); 603 | 604 | // let items_ptr = unsafe { (*result_ptr).items as usize }; 605 | // println!("items_ptr = {:0x}", items_ptr); 606 | 607 | // println!("---------------------------------------------------------"); 608 | // println!("------------ returning from rust search -----------------"); 609 | // println!("------------ {:0x} -----------------------", result_ptr as usize); 610 | // println!("---------------------------------------------------------"); 611 | 612 | result_ptr 613 | } 614 | 615 | 616 | #[repr(C)] 617 | pub struct SearchResults { 618 | num_items: u64, 619 | items: *const *const Item, 620 | } 621 | 622 | #[no_mangle] 623 | pub extern "C" fn search_results_free(results: *mut SearchResults) { 624 | if !results.is_null() { 625 | let results = unsafe { Box::from_raw(results) }; 626 | if !results.items.is_null() { 627 | let num_items = results.num_items; 628 | let items = results.items; 629 | if num_items != 0 { 630 | let item_pointers = unsafe { 631 | Vec::from_raw_parts(items as *mut *const Item, 632 | results.num_items as usize, 633 | results.num_items as usize) }; 634 | drop(item_pointers); 635 | } 636 | drop(results); 637 | } 638 | } 639 | 640 | // let alloc_size = get_allocation_size(results as *const c_void); 641 | // println!("after alloc size of {:0x} - result_ptr = {alloc_size}", results as usize); 642 | } 643 | 644 | #[no_mangle] 645 | pub extern "C" fn item_open(item: *const Item) { 646 | if item.is_null() { return; } 647 | 648 | let item_ref = unsafe { &*item }; 649 | let _ = item_ref.open(); 650 | } 651 | 652 | #[no_mangle] 653 | pub extern "C" fn get_item_name(item: *const Item) -> *const std::ffi::c_char { 654 | // println!("-> get_item_name item ptr = {:0x}", item as usize); 655 | if item.is_null() { 656 | return std::ptr::null_mut(); 657 | } 658 | 659 | // let cname_ptr = unsafe { std::ptr::addr_of!((*item).cname) } as usize; 660 | // println!(" get_item_name cname_ptr = {cname_ptr:0x}"); 661 | 662 | let ret = unsafe { (*item).cname.as_c_str().as_ptr() }; 663 | 664 | // let item_ref = unsafe { &*item }; 665 | 666 | // let ret = item_ref.cname.as_c_str().as_ptr(); 667 | // println!(" get_item_name ret = {:0x}", ret as usize); 668 | ret 669 | 670 | // println!("item name = {}", item_ref.name); 671 | 672 | // // Convert the name to a C string 673 | // // let name_cstring = std::ffi::CString::new(item_ref.name.clone()).unwrap(); 674 | // // let name_cstring = std::ffi::CString::new(item_ref.name).unwrap(); 675 | 676 | // // Transfer ownership to C 677 | // name_cstring.into_raw() 678 | } 679 | 680 | -------------------------------------------------------------------------------- /mac-app/unmenu.xcodeproj/xcuserdata/ivan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 36 | 37 | 51 | 52 | 53 | 54 | 55 | 57 | 69 | 70 | 84 | 85 | 99 | 100 | 114 | 115 | 116 | 117 | 118 | 120 | 132 | 133 | 147 | 148 | 162 | 163 | 164 | 165 | 166 | 168 | 180 | 181 | 195 | 196 | 210 | 211 | 225 | 226 | 240 | 241 | 255 | 256 | 270 | 271 | 285 | 286 | 300 | 301 | 302 | 303 | 304 | 306 | 318 | 319 | 320 | 322 | 334 | 335 | 336 | 338 | 350 | 351 | 365 | 366 | 380 | 381 | 382 | 383 | 384 | 386 | 398 | 399 | 400 | 402 | 414 | 415 | 429 | 430 | 444 | 445 | 459 | 460 | 461 | 462 | 463 | 465 | 477 | 478 | 479 | 481 | 493 | 494 | 495 | 496 | 497 | -------------------------------------------------------------------------------- /mac-app/unmenu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2C1B016A2A3F4F2300434A7A /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1B01682A3F4F2300434A7A /* FuzzyMatcher.swift */; }; 11 | 2C3079C32B7D0EC6002487E3 /* TOMLDecoder in Frameworks */ = {isa = PBXBuildFile; productRef = 2C3079C22B7D0EC6002487E3 /* TOMLDecoder */; }; 12 | 2C3079C52B7D0FB7002487E3 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3079C42B7D0FB7002487E3 /* Config.swift */; }; 13 | 2C3079C72B7D4EEC002487E3 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3079C62B7D4EEC002487E3 /* Keys.swift */; }; 14 | 2CC637A22B7BCA1F00BCAF02 /* libfuzzylib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2CC637A12B7BCA1F00BCAF02 /* libfuzzylib.a */; }; 15 | 2CF6C41C2B84C01800B15C3E /* default_config.toml in Resources */ = {isa = PBXBuildFile; fileRef = 2CF6C41A2B83D96300B15C3E /* default_config.toml */; }; 16 | 7F25568F269CF945002324AC /* CommandArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F25568E269CF945002324AC /* CommandArguments.swift */; }; 17 | 7F255692269CF988002324AC /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 7F255691269CF988002324AC /* ArgumentParser */; }; 18 | 7F255694269D01FF002324AC /* InputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F255693269D01FF002324AC /* InputField.swift */; }; 19 | 7F2CCA23268B195F00C14B29 /* Notification+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2CCA22268B195F00C14B29 /* Notification+Name.swift */; }; 20 | 7F2CCA2E268FE37900C14B29 /* ListProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2CCA2D268FE37900C14B29 /* ListProvider.swift */; }; 21 | 7F2CCA30268FE38C00C14B29 /* AppListProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2CCA2F268FE38C00C14B29 /* AppListProvider.swift */; }; 22 | 7F2CCA32268FEDF600C14B29 /* PipeListProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2CCA31268FEDF600C14B29 /* PipeListProvider.swift */; }; 23 | 7F2CCA3F268FFDB500C14B29 /* ReadStdin.h.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F2CCA3E268FFDB500C14B29 /* ReadStdin.h.m */; }; 24 | 7F3278F01C71C9CE00AFB227 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F3278E81C71C9CE00AFB227 /* AppDelegate.swift */; }; 25 | 7F3278F11C71C9CE00AFB227 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7F3278E91C71C9CE00AFB227 /* Assets.xcassets */; }; 26 | 7F3278F51C71C9CE00AFB227 /* SearchWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F3278ED1C71C9CE00AFB227 /* SearchWindow.swift */; }; 27 | 7F3278F71C71C9CE00AFB227 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F3278EF1C71C9CE00AFB227 /* SearchViewController.swift */; }; 28 | 7F3279001C71D39800AFB227 /* VerticalAlignedTextFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F3278FF1C71D39800AFB227 /* VerticalAlignedTextFieldCell.swift */; }; 29 | 7F5D0E261CACCD0100268983 /* ResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F5D0E241CACCD0100268983 /* ResultsView.swift */; }; 30 | 7F5D0E2C1CACCDD400268983 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7F5D0E281CACCDD400268983 /* Main.storyboard */; }; 31 | 7F6511FB29A61A4E007D3EB3 /* GeneralSettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F6511F929A61A4E007D3EB3 /* GeneralSettingsViewController.xib */; }; 32 | 7F90D2D226FDE2400003EC7E /* unmenu in Resources */ = {isa = PBXBuildFile; fileRef = 7F90D2D126FDE2400003EC7E /* unmenu */; }; 33 | 7FA29F6C29A55B0B00B27359 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 7FA29F6B29A55B0B00B27359 /* LaunchAtLogin */; }; 34 | /* End PBXBuildFile section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 2C0B6D162A3C82B3007D65A6 /* BridgingHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BridgingHeader.h; path = src/BridgingHeader.h; sourceTree = ""; }; 38 | 2C1B01682A3F4F2300434A7A /* FuzzyMatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FuzzyMatcher.swift; path = src/FuzzyMatcher.swift; sourceTree = ""; }; 39 | 2C3079C42B7D0FB7002487E3 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 40 | 2C3079C62B7D4EEC002487E3 /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; 41 | 2C3079DB2B7E1E40002487E3 /* libfuzzylib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libfuzzylib.a; sourceTree = ""; }; 42 | 2C3079DD2B7E1E59002487E3 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 43 | 2C6617142A3DAD5C008C5A1E /* libfuzzylib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libfuzzylib.h; sourceTree = ""; }; 44 | 2CC637A12B7BCA1F00BCAF02 /* libfuzzylib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libfuzzylib.a; path = ../fuzzylib/target/release/libfuzzylib.a; sourceTree = ""; }; 45 | 2CF6C41A2B83D96300B15C3E /* default_config.toml */ = {isa = PBXFileReference; lastKnownFileType = text; path = default_config.toml; sourceTree = ""; }; 46 | 7F25568E269CF945002324AC /* CommandArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CommandArguments.swift; path = src/CommandArguments.swift; sourceTree = ""; }; 47 | 7F255693269D01FF002324AC /* InputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InputField.swift; path = src/InputField.swift; sourceTree = ""; }; 48 | 7F2CCA22268B195F00C14B29 /* Notification+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Notification+Name.swift"; path = "src/Notification+Name.swift"; sourceTree = ""; }; 49 | 7F2CCA2D268FE37900C14B29 /* ListProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ListProvider.swift; path = src/ListProvider.swift; sourceTree = ""; }; 50 | 7F2CCA2F268FE38C00C14B29 /* AppListProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppListProvider.swift; path = src/AppListProvider.swift; sourceTree = ""; }; 51 | 7F2CCA31268FEDF600C14B29 /* PipeListProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PipeListProvider.swift; path = src/PipeListProvider.swift; sourceTree = ""; }; 52 | 7F2CCA3D268FFDB500C14B29 /* ReadStdin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ReadStdin.h; path = src/ReadStdin.h; sourceTree = ""; }; 53 | 7F2CCA3E268FFDB500C14B29 /* ReadStdin.h.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ReadStdin.h.m; path = src/ReadStdin.h.m; sourceTree = ""; }; 54 | 7F3278E81C71C9CE00AFB227 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = src/AppDelegate.swift; sourceTree = ""; }; 55 | 7F3278E91C71C9CE00AFB227 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = src/Assets.xcassets; sourceTree = ""; }; 56 | 7F3278EC1C71C9CE00AFB227 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = src/Info.plist; sourceTree = ""; }; 57 | 7F3278ED1C71C9CE00AFB227 /* SearchWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SearchWindow.swift; path = src/SearchWindow.swift; sourceTree = ""; }; 58 | 7F3278EF1C71C9CE00AFB227 /* SearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SearchViewController.swift; path = src/SearchViewController.swift; sourceTree = ""; }; 59 | 7F3278FF1C71D39800AFB227 /* VerticalAlignedTextFieldCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VerticalAlignedTextFieldCell.swift; path = src/VerticalAlignedTextFieldCell.swift; sourceTree = ""; }; 60 | 7F5D0E241CACCD0100268983 /* ResultsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ResultsView.swift; path = src/ResultsView.swift; sourceTree = ""; }; 61 | 7F5D0E291CACCDD400268983 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = src/Base.lproj/Main.storyboard; sourceTree = ""; }; 62 | 7F6511FA29A61A4E007D3EB3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = src/Base.lproj/GeneralSettingsViewController.xib; sourceTree = ""; }; 63 | 7F7982A5241FF0FD0079AFD2 /* unmenu.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = unmenu.entitlements; sourceTree = ""; }; 64 | 7F90D2D126FDE2400003EC7E /* unmenu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = unmenu; sourceTree = ""; }; 65 | 7FE8A6481C717481000A2C4C /* unmenu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = unmenu.app; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | 7FE8A6451C717481000A2C4C /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | 2CC637A22B7BCA1F00BCAF02 /* libfuzzylib.a in Frameworks */, 74 | 7F255692269CF988002324AC /* ArgumentParser in Frameworks */, 75 | 7FA29F6C29A55B0B00B27359 /* LaunchAtLogin in Frameworks */, 76 | 2C3079C32B7D0EC6002487E3 /* TOMLDecoder in Frameworks */, 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | /* End PBXFrameworksBuildPhase section */ 81 | 82 | /* Begin PBXGroup section */ 83 | 7F3278E71C71C96100AFB227 /* unmenu */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 2CF6C41A2B83D96300B15C3E /* default_config.toml */, 87 | 7F6511F829A619FD007D3EB3 /* src */, 88 | 7F3278E91C71C9CE00AFB227 /* Assets.xcassets */, 89 | 7F3278FA1C71CA0200AFB227 /* Base.lproj */, 90 | 7F7982A5241FF0FD0079AFD2 /* unmenu.entitlements */, 91 | 7F3278EC1C71C9CE00AFB227 /* Info.plist */, 92 | 7F90D2D026FDE2240003EC7E /* scripts */, 93 | ); 94 | name = unmenu; 95 | sourceTree = ""; 96 | }; 97 | 7F3278FA1C71CA0200AFB227 /* Base.lproj */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 7F5D0E281CACCDD400268983 /* Main.storyboard */, 101 | 7F6511F929A61A4E007D3EB3 /* GeneralSettingsViewController.xib */, 102 | ); 103 | name = Base.lproj; 104 | sourceTree = ""; 105 | }; 106 | 7F32790F1C71DF4600AFB227 /* Frameworks */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 2C3079DD2B7E1E59002487E3 /* XCTest.framework */, 110 | 2C3079DB2B7E1E40002487E3 /* libfuzzylib.a */, 111 | 2CC637A12B7BCA1F00BCAF02 /* libfuzzylib.a */, 112 | ); 113 | name = Frameworks; 114 | sourceTree = ""; 115 | }; 116 | 7F6511F829A619FD007D3EB3 /* src */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 2C1B01682A3F4F2300434A7A /* FuzzyMatcher.swift */, 120 | 2C0B6D162A3C82B3007D65A6 /* BridgingHeader.h */, 121 | 7F3278E81C71C9CE00AFB227 /* AppDelegate.swift */, 122 | 2C3079C42B7D0FB7002487E3 /* Config.swift */, 123 | 7F2CCA2F268FE38C00C14B29 /* AppListProvider.swift */, 124 | 7F25568E269CF945002324AC /* CommandArguments.swift */, 125 | 7F255693269D01FF002324AC /* InputField.swift */, 126 | 7F2CCA2D268FE37900C14B29 /* ListProvider.swift */, 127 | 7F2CCA22268B195F00C14B29 /* Notification+Name.swift */, 128 | 7F2CCA31268FEDF600C14B29 /* PipeListProvider.swift */, 129 | 7F2CCA3D268FFDB500C14B29 /* ReadStdin.h */, 130 | 7F2CCA3E268FFDB500C14B29 /* ReadStdin.h.m */, 131 | 7F5D0E241CACCD0100268983 /* ResultsView.swift */, 132 | 7F3278EF1C71C9CE00AFB227 /* SearchViewController.swift */, 133 | 7F3278ED1C71C9CE00AFB227 /* SearchWindow.swift */, 134 | 7F3278FF1C71D39800AFB227 /* VerticalAlignedTextFieldCell.swift */, 135 | 2C6617142A3DAD5C008C5A1E /* libfuzzylib.h */, 136 | 2C3079C62B7D4EEC002487E3 /* Keys.swift */, 137 | ); 138 | name = src; 139 | sourceTree = ""; 140 | }; 141 | 7F90D2D026FDE2240003EC7E /* scripts */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 7F90D2D126FDE2400003EC7E /* unmenu */, 145 | ); 146 | path = scripts; 147 | sourceTree = ""; 148 | }; 149 | 7FE8A63F1C717481000A2C4C = { 150 | isa = PBXGroup; 151 | children = ( 152 | 7F3278E71C71C96100AFB227 /* unmenu */, 153 | 7F32790F1C71DF4600AFB227 /* Frameworks */, 154 | 7FE8A6491C717481000A2C4C /* Products */, 155 | ); 156 | sourceTree = ""; 157 | }; 158 | 7FE8A6491C717481000A2C4C /* Products */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 7FE8A6481C717481000A2C4C /* unmenu.app */, 162 | ); 163 | name = Products; 164 | sourceTree = ""; 165 | }; 166 | /* End PBXGroup section */ 167 | 168 | /* Begin PBXNativeTarget section */ 169 | 7FE8A6471C717481000A2C4C /* unmenu */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = 7FE8A6571C717481000A2C4C /* Build configuration list for PBXNativeTarget "unmenu" */; 172 | buildPhases = ( 173 | 7F2CCA26268F996200C14B29 /* ShellScript */, 174 | 7FE8A6441C717481000A2C4C /* Sources */, 175 | 7FE8A6451C717481000A2C4C /* Frameworks */, 176 | 7FE8A6461C717481000A2C4C /* Resources */, 177 | 7FA29F6D29A6139B00B27359 /* ShellScript */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | ); 183 | name = unmenu; 184 | packageProductDependencies = ( 185 | 7F255691269CF988002324AC /* ArgumentParser */, 186 | 7FA29F6B29A55B0B00B27359 /* LaunchAtLogin */, 187 | 2C3079C22B7D0EC6002487E3 /* TOMLDecoder */, 188 | ); 189 | productName = "unmenu"; 190 | productReference = 7FE8A6481C717481000A2C4C /* unmenu.app */; 191 | productType = "com.apple.product-type.application"; 192 | }; 193 | /* End PBXNativeTarget section */ 194 | 195 | /* Begin PBXProject section */ 196 | 7FE8A6401C717481000A2C4C /* Project object */ = { 197 | isa = PBXProject; 198 | attributes = { 199 | CLASSPREFIX = ""; 200 | LastSwiftUpdateCheck = 1430; 201 | LastUpgradeCheck = 1250; 202 | ORGANIZATIONNAME = "Jose Pereira"; 203 | TargetAttributes = { 204 | 7FE8A6471C717481000A2C4C = { 205 | CreatedOnToolsVersion = 7.2.1; 206 | LastSwiftMigration = 1020; 207 | ProvisioningStyle = Manual; 208 | }; 209 | }; 210 | }; 211 | buildConfigurationList = 7FE8A6431C717481000A2C4C /* Build configuration list for PBXProject "unmenu" */; 212 | compatibilityVersion = "Xcode 3.2"; 213 | developmentRegion = en; 214 | hasScannedForEncodings = 0; 215 | knownRegions = ( 216 | en, 217 | Base, 218 | ); 219 | mainGroup = 7FE8A63F1C717481000A2C4C; 220 | packageReferences = ( 221 | 7F2CCA1C268B10F700C14B29 /* XCRemoteSwiftPackageReference "FileWatcher" */, 222 | 7F255690269CF988002324AC /* XCRemoteSwiftPackageReference "swift-argument-parser" */, 223 | 7FA29F6A29A55B0B00B27359 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */, 224 | 2C3079C12B7D0EC6002487E3 /* XCRemoteSwiftPackageReference "TOMLDecoder" */, 225 | ); 226 | productRefGroup = 7FE8A6491C717481000A2C4C /* Products */; 227 | projectDirPath = ""; 228 | projectRoot = ""; 229 | targets = ( 230 | 7FE8A6471C717481000A2C4C /* unmenu */, 231 | ); 232 | }; 233 | /* End PBXProject section */ 234 | 235 | /* Begin PBXResourcesBuildPhase section */ 236 | 7FE8A6461C717481000A2C4C /* Resources */ = { 237 | isa = PBXResourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 7F6511FB29A61A4E007D3EB3 /* GeneralSettingsViewController.xib in Resources */, 241 | 7F90D2D226FDE2400003EC7E /* unmenu in Resources */, 242 | 7F5D0E2C1CACCDD400268983 /* Main.storyboard in Resources */, 243 | 7F3278F11C71C9CE00AFB227 /* Assets.xcassets in Resources */, 244 | 2CF6C41C2B84C01800B15C3E /* default_config.toml in Resources */, 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | }; 248 | /* End PBXResourcesBuildPhase section */ 249 | 250 | /* Begin PBXShellScriptBuildPhase section */ 251 | 7F2CCA26268F996200C14B29 /* ShellScript */ = { 252 | isa = PBXShellScriptBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | ); 256 | inputFileListPaths = ( 257 | ); 258 | inputPaths = ( 259 | ); 260 | outputFileListPaths = ( 261 | ); 262 | outputPaths = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | shellPath = /bin/sh; 266 | shellScript = "if which swiftlint >/dev/null; then\n # swiftlint\n echo \"skipping swiftlint\"\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 267 | }; 268 | 7FA29F6D29A6139B00B27359 /* ShellScript */ = { 269 | isa = PBXShellScriptBuildPhase; 270 | alwaysOutOfDate = 1; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | ); 274 | inputFileListPaths = ( 275 | ); 276 | inputPaths = ( 277 | ); 278 | outputFileListPaths = ( 279 | ); 280 | outputPaths = ( 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | shellPath = /bin/sh; 284 | shellScript = "\"${BUILT_PRODUCTS_DIR}/LaunchAtLogin_LaunchAtLogin.bundle/Contents/Resources/copy-helper-swiftpm.sh\"\n"; 285 | }; 286 | /* End PBXShellScriptBuildPhase section */ 287 | 288 | /* Begin PBXSourcesBuildPhase section */ 289 | 7FE8A6441C717481000A2C4C /* Sources */ = { 290 | isa = PBXSourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 7F3279001C71D39800AFB227 /* VerticalAlignedTextFieldCell.swift in Sources */, 294 | 7F2CCA30268FE38C00C14B29 /* AppListProvider.swift in Sources */, 295 | 7F5D0E261CACCD0100268983 /* ResultsView.swift in Sources */, 296 | 2C3079C52B7D0FB7002487E3 /* Config.swift in Sources */, 297 | 2C3079C72B7D4EEC002487E3 /* Keys.swift in Sources */, 298 | 7F3278F71C71C9CE00AFB227 /* SearchViewController.swift in Sources */, 299 | 7F3278F01C71C9CE00AFB227 /* AppDelegate.swift in Sources */, 300 | 2C1B016A2A3F4F2300434A7A /* FuzzyMatcher.swift in Sources */, 301 | 7F25568F269CF945002324AC /* CommandArguments.swift in Sources */, 302 | 7F2CCA2E268FE37900C14B29 /* ListProvider.swift in Sources */, 303 | 7F2CCA32268FEDF600C14B29 /* PipeListProvider.swift in Sources */, 304 | 7F2CCA3F268FFDB500C14B29 /* ReadStdin.h.m in Sources */, 305 | 7F2CCA23268B195F00C14B29 /* Notification+Name.swift in Sources */, 306 | 7F3278F51C71C9CE00AFB227 /* SearchWindow.swift in Sources */, 307 | 7F255694269D01FF002324AC /* InputField.swift in Sources */, 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | /* End PBXSourcesBuildPhase section */ 312 | 313 | /* Begin PBXVariantGroup section */ 314 | 7F5D0E281CACCDD400268983 /* Main.storyboard */ = { 315 | isa = PBXVariantGroup; 316 | children = ( 317 | 7F5D0E291CACCDD400268983 /* Base */, 318 | ); 319 | name = Main.storyboard; 320 | sourceTree = ""; 321 | }; 322 | 7F6511F929A61A4E007D3EB3 /* GeneralSettingsViewController.xib */ = { 323 | isa = PBXVariantGroup; 324 | children = ( 325 | 7F6511FA29A61A4E007D3EB3 /* Base */, 326 | ); 327 | name = GeneralSettingsViewController.xib; 328 | sourceTree = ""; 329 | }; 330 | /* End PBXVariantGroup section */ 331 | 332 | /* Begin XCBuildConfiguration section */ 333 | 7FE8A6551C717481000A2C4C /* Debug */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | ASSETCATALOG_COMPILER_OPTIMIZATION = time; 338 | CLANG_ANALYZER_GCD_PERFORMANCE = YES; 339 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 340 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 341 | CLANG_CXX_LIBRARY = "libc++"; 342 | CLANG_ENABLE_MODULES = YES; 343 | CLANG_ENABLE_OBJC_ARC = YES; 344 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_INTEGER = YES; 345 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES; 346 | CLANG_USE_OPTIMIZATION_PROFILE = NO; 347 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 348 | CLANG_WARN_BOOL_CONVERSION = YES; 349 | CLANG_WARN_COMMA = YES; 350 | CLANG_WARN_CONSTANT_CONVERSION = YES; 351 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 352 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 353 | CLANG_WARN_EMPTY_BODY = YES; 354 | CLANG_WARN_ENUM_CONVERSION = YES; 355 | CLANG_WARN_INFINITE_RECURSION = YES; 356 | CLANG_WARN_INT_CONVERSION = YES; 357 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 359 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 361 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 362 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 363 | CLANG_WARN_STRICT_PROTOTYPES = YES; 364 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 365 | CLANG_WARN_UNREACHABLE_CODE = YES; 366 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 367 | CODE_SIGN_IDENTITY = "-"; 368 | COPY_HEADERS_RUN_UNIFDEF = YES; 369 | COPY_PHASE_STRIP = YES; 370 | DEAD_CODE_STRIPPING = YES; 371 | DEBUG_INFORMATION_FORMAT = dwarf; 372 | ENABLE_STRICT_OBJC_MSGSEND = YES; 373 | ENABLE_TESTABILITY = YES; 374 | GCC_C_LANGUAGE_STANDARD = gnu99; 375 | GCC_DYNAMIC_NO_PIC = YES; 376 | GCC_FAST_MATH = YES; 377 | GCC_NO_COMMON_BLOCKS = YES; 378 | GCC_OPTIMIZATION_LEVEL = 0; 379 | GCC_PRECOMPILE_PREFIX_HEADER = NO; 380 | GCC_PREPROCESSOR_DEFINITIONS = ( 381 | "DEBUG=1", 382 | "$(inherited)", 383 | ); 384 | GCC_SHORT_ENUMS = YES; 385 | GCC_UNROLL_LOOPS = YES; 386 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 387 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 388 | GCC_WARN_UNDECLARED_SELECTOR = YES; 389 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 390 | GCC_WARN_UNUSED_FUNCTION = YES; 391 | GCC_WARN_UNUSED_VARIABLE = YES; 392 | LLVM_LTO = YES_THIN; 393 | MACH_O_TYPE = mh_execute; 394 | MACOSX_DEPLOYMENT_TARGET = 11.0; 395 | MTL_ENABLE_DEBUG_INFO = YES; 396 | ONLY_ACTIVE_ARCH = YES; 397 | SDKROOT = macosx; 398 | STRINGS_FILE_OUTPUT_ENCODING = "UTF-8"; 399 | SWIFT_DISABLE_SAFETY_CHECKS = YES; 400 | "SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = src/BridgingHeader.h; 401 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 402 | SWIFT_VERSION = 5.0; 403 | }; 404 | name = Debug; 405 | }; 406 | 7FE8A6561C717481000A2C4C /* Release */ = { 407 | isa = XCBuildConfiguration; 408 | buildSettings = { 409 | ALWAYS_SEARCH_USER_PATHS = NO; 410 | ARCHS = arm64; 411 | ASSETCATALOG_COMPILER_OPTIMIZATION = time; 412 | CLANG_ANALYZER_GCD_PERFORMANCE = YES; 413 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 414 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 415 | CLANG_CXX_LIBRARY = "libc++"; 416 | CLANG_ENABLE_MODULES = YES; 417 | CLANG_ENABLE_OBJC_ARC = YES; 418 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_INTEGER = YES; 419 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES; 420 | CLANG_USE_OPTIMIZATION_PROFILE = NO; 421 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 422 | CLANG_WARN_BOOL_CONVERSION = YES; 423 | CLANG_WARN_COMMA = YES; 424 | CLANG_WARN_CONSTANT_CONVERSION = YES; 425 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 426 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 427 | CLANG_WARN_EMPTY_BODY = YES; 428 | CLANG_WARN_ENUM_CONVERSION = YES; 429 | CLANG_WARN_INFINITE_RECURSION = YES; 430 | CLANG_WARN_INT_CONVERSION = YES; 431 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 432 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 433 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 434 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 435 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 436 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 437 | CLANG_WARN_STRICT_PROTOTYPES = YES; 438 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 439 | CLANG_WARN_UNREACHABLE_CODE = YES; 440 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 441 | CODE_SIGN_IDENTITY = "-"; 442 | COPY_HEADERS_RUN_UNIFDEF = YES; 443 | COPY_PHASE_STRIP = YES; 444 | DEAD_CODE_STRIPPING = YES; 445 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 446 | ENABLE_NS_ASSERTIONS = NO; 447 | ENABLE_STRICT_OBJC_MSGSEND = YES; 448 | GCC_C_LANGUAGE_STANDARD = gnu99; 449 | GCC_DYNAMIC_NO_PIC = YES; 450 | GCC_FAST_MATH = YES; 451 | GCC_NO_COMMON_BLOCKS = YES; 452 | GCC_OPTIMIZATION_LEVEL = fast; 453 | GCC_PRECOMPILE_PREFIX_HEADER = NO; 454 | GCC_SHORT_ENUMS = YES; 455 | GCC_UNROLL_LOOPS = YES; 456 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 457 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 458 | GCC_WARN_UNDECLARED_SELECTOR = YES; 459 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 460 | GCC_WARN_UNUSED_FUNCTION = YES; 461 | GCC_WARN_UNUSED_VARIABLE = YES; 462 | LLVM_LTO = YES_THIN; 463 | MACH_O_TYPE = mh_execute; 464 | MACOSX_DEPLOYMENT_TARGET = 11.0; 465 | MTL_ENABLE_DEBUG_INFO = NO; 466 | ONLY_ACTIVE_ARCH = YES; 467 | SDKROOT = macosx; 468 | STRINGS_FILE_OUTPUT_ENCODING = "UTF-8"; 469 | SWIFT_COMPILATION_MODE = wholemodule; 470 | SWIFT_DISABLE_SAFETY_CHECKS = YES; 471 | "SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = src/BridgingHeader.h; 472 | SWIFT_VERSION = 5.0; 473 | }; 474 | name = Release; 475 | }; 476 | 7FE8A6581C717481000A2C4C /* Debug */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 480 | CLANG_ENABLE_MODULES = YES; 481 | CODE_SIGN_ENTITLEMENTS = unmenu.entitlements; 482 | CODE_SIGN_IDENTITY = "-"; 483 | CODE_SIGN_STYLE = Manual; 484 | COMBINE_HIDPI_IMAGES = YES; 485 | DEVELOPMENT_TEAM = ""; 486 | INFOPLIST_FILE = src/Info.plist; 487 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 488 | LD_RUNPATH_SEARCH_PATHS = ( 489 | "$(inherited)", 490 | "@executable_path/../Frameworks", 491 | ); 492 | LIBRARY_SEARCH_PATHS = ( 493 | "$(inherited)", 494 | "$(PROJECT_DIR)", 495 | ); 496 | MACOSX_DEPLOYMENT_TARGET = 12.0; 497 | PRODUCT_BUNDLE_IDENTIFIER = Quick; 498 | PRODUCT_NAME = unmenu; 499 | PROVISIONING_PROFILE_SPECIFIER = ""; 500 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 501 | SWIFT_VERSION = 5.0; 502 | }; 503 | name = Debug; 504 | }; 505 | 7FE8A6591C717481000A2C4C /* Release */ = { 506 | isa = XCBuildConfiguration; 507 | buildSettings = { 508 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 509 | CLANG_ENABLE_MODULES = YES; 510 | CODE_SIGN_ENTITLEMENTS = unmenu.entitlements; 511 | CODE_SIGN_IDENTITY = "-"; 512 | CODE_SIGN_STYLE = Manual; 513 | COMBINE_HIDPI_IMAGES = YES; 514 | DEVELOPMENT_TEAM = ""; 515 | INFOPLIST_FILE = src/Info.plist; 516 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 517 | LD_RUNPATH_SEARCH_PATHS = ( 518 | "$(inherited)", 519 | "@executable_path/../Frameworks", 520 | ); 521 | LIBRARY_SEARCH_PATHS = ( 522 | "$(inherited)", 523 | "$(PROJECT_DIR)", 524 | ); 525 | MACOSX_DEPLOYMENT_TARGET = 12.0; 526 | PRODUCT_BUNDLE_IDENTIFIER = pw.unmb.unmenu; 527 | PRODUCT_NAME = unmenu; 528 | PROVISIONING_PROFILE_SPECIFIER = ""; 529 | SWIFT_VERSION = 5.0; 530 | }; 531 | name = Release; 532 | }; 533 | /* End XCBuildConfiguration section */ 534 | 535 | /* Begin XCConfigurationList section */ 536 | 7FE8A6431C717481000A2C4C /* Build configuration list for PBXProject "unmenu" */ = { 537 | isa = XCConfigurationList; 538 | buildConfigurations = ( 539 | 7FE8A6551C717481000A2C4C /* Debug */, 540 | 7FE8A6561C717481000A2C4C /* Release */, 541 | ); 542 | defaultConfigurationIsVisible = 0; 543 | defaultConfigurationName = Release; 544 | }; 545 | 7FE8A6571C717481000A2C4C /* Build configuration list for PBXNativeTarget "unmenu" */ = { 546 | isa = XCConfigurationList; 547 | buildConfigurations = ( 548 | 7FE8A6581C717481000A2C4C /* Debug */, 549 | 7FE8A6591C717481000A2C4C /* Release */, 550 | ); 551 | defaultConfigurationIsVisible = 0; 552 | defaultConfigurationName = Release; 553 | }; 554 | /* End XCConfigurationList section */ 555 | 556 | /* Begin XCRemoteSwiftPackageReference section */ 557 | 2C3079C12B7D0EC6002487E3 /* XCRemoteSwiftPackageReference "TOMLDecoder" */ = { 558 | isa = XCRemoteSwiftPackageReference; 559 | repositoryURL = "https://github.com/dduan/TOMLDecoder"; 560 | requirement = { 561 | kind = upToNextMajorVersion; 562 | minimumVersion = 0.2.2; 563 | }; 564 | }; 565 | 7F255690269CF988002324AC /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = { 566 | isa = XCRemoteSwiftPackageReference; 567 | repositoryURL = "https://github.com/apple/swift-argument-parser"; 568 | requirement = { 569 | kind = upToNextMajorVersion; 570 | minimumVersion = 0.4.3; 571 | }; 572 | }; 573 | 7F2CCA1C268B10F700C14B29 /* XCRemoteSwiftPackageReference "FileWatcher" */ = { 574 | isa = XCRemoteSwiftPackageReference; 575 | repositoryURL = "https://github.com/eonist/FileWatcher.git"; 576 | requirement = { 577 | kind = upToNextMajorVersion; 578 | minimumVersion = 0.2.3; 579 | }; 580 | }; 581 | 7FA29F6A29A55B0B00B27359 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = { 582 | isa = XCRemoteSwiftPackageReference; 583 | repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin"; 584 | requirement = { 585 | branch = main; 586 | kind = branch; 587 | }; 588 | }; 589 | /* End XCRemoteSwiftPackageReference section */ 590 | 591 | /* Begin XCSwiftPackageProductDependency section */ 592 | 2C3079C22B7D0EC6002487E3 /* TOMLDecoder */ = { 593 | isa = XCSwiftPackageProductDependency; 594 | package = 2C3079C12B7D0EC6002487E3 /* XCRemoteSwiftPackageReference "TOMLDecoder" */; 595 | productName = TOMLDecoder; 596 | }; 597 | 7F255691269CF988002324AC /* ArgumentParser */ = { 598 | isa = XCSwiftPackageProductDependency; 599 | package = 7F255690269CF988002324AC /* XCRemoteSwiftPackageReference "swift-argument-parser" */; 600 | productName = ArgumentParser; 601 | }; 602 | 7FA29F6B29A55B0B00B27359 /* LaunchAtLogin */ = { 603 | isa = XCSwiftPackageProductDependency; 604 | package = 7FA29F6A29A55B0B00B27359 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */; 605 | productName = LaunchAtLogin; 606 | }; 607 | /* End XCSwiftPackageProductDependency section */ 608 | }; 609 | rootObject = 7FE8A6401C717481000A2C4C /* Project object */; 610 | } 611 | --------------------------------------------------------------------------------