├── 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 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
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 |
--------------------------------------------------------------------------------