Skip to content

Commit a054306

Browse files
committed
first commit
0 parents  commit a054306

33 files changed

+1021
-0
lines changed

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
7+
.theos
8+
/packages
9+
.theos/
10+
packages/
11+
.DS_Store

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Nightwind
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export TARGET := iphone:clang:latest:15.0
2+
export ARCHS = arm64 arm64e
3+
4+
INSTALL_TARGET_PROCESSES = SpringBoard
5+
6+
include $(THEOS)/makefiles/common.mk
7+
8+
TWEAK_NAME = VisionSearchPill
9+
10+
VisionSearchPill_FILES = $(shell find Sources/VisionSearchPill -name '*.swift') $(shell find Sources/VisionSearchPillC -name '*.m' -o -name '*.c' -o -name '*.mm' -o -name '*.cpp')
11+
VisionSearchPill_LIBRARIES = gcuniversal
12+
VisionSearchPill_SWIFTFLAGS = -ISources/VisionSearchPillC/include
13+
VisionSearchPill_PRIVATE_FRAMEWORKS = SpringBoardHome
14+
VisionSearchPill_CFLAGS = -fobjc-arc -ISources/VisionSearchPillC/include
15+
16+
include $(THEOS_MAKE_PATH)/tweak.mk
17+
SUBPROJECTS += visionsearchpillprefs
18+
include $(THEOS_MAKE_PATH)/aggregate.mk

Package.swift

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// swift-tools-version:5.2
2+
3+
import PackageDescription
4+
import Foundation
5+
6+
let projectDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent()
7+
8+
@dynamicMemberLookup struct TheosConfiguration {
9+
private let dict: [String: String]
10+
init(at path: String) {
11+
let configURL = URL(fileURLWithPath: path, relativeTo: projectDir)
12+
guard let infoString = try? String(contentsOf: configURL) else {
13+
fatalError("""
14+
Could not find Theos SPM config. Have you run `make spm` yet?
15+
""")
16+
}
17+
let pairs = infoString.split(separator: "\n").map {
18+
$0.split(
19+
separator: "=", maxSplits: 1,
20+
omittingEmptySubsequences: false
21+
).map(String.init)
22+
}.map { ($0[0], $0[1]) }
23+
dict = Dictionary(uniqueKeysWithValues: pairs)
24+
}
25+
subscript(
26+
key: String,
27+
or defaultValue: @autoclosure () -> String? = nil
28+
) -> String {
29+
if let value = dict[key] {
30+
return value
31+
} else if let def = defaultValue() {
32+
return def
33+
} else {
34+
fatalError("""
35+
Could not get value of key '\(key)' from Theos SPM config. \
36+
Try running `make spm` again.
37+
""")
38+
}
39+
}
40+
subscript(dynamicMember key: String) -> String { self[key] }
41+
}
42+
let conf = TheosConfiguration(at: ".theos/spm_config")
43+
44+
let theosPath = conf.theos
45+
let sdk = conf.sdk
46+
let resourceDir = conf.swiftResourceDir
47+
let deploymentTarget = conf.deploymentTarget
48+
let triple = "arm64-apple-ios\(deploymentTarget)"
49+
50+
let libFlags: [String] = [
51+
"-F\(theosPath)/vendor/lib", "-F\(theosPath)/lib",
52+
"-I\(theosPath)/vendor/include", "-I\(theosPath)/include"
53+
]
54+
55+
let cFlags: [String] = libFlags + [
56+
"-target", triple, "-isysroot", sdk,
57+
"-Wno-unused-command-line-argument", "-Qunused-arguments",
58+
]
59+
60+
let cxxFlags: [String] = [
61+
]
62+
63+
let swiftFlags: [String] = libFlags + [
64+
"-target", triple, "-sdk", sdk, "-resource-dir", resourceDir,
65+
]
66+
67+
let package = Package(
68+
name: "VisionSearchPill",
69+
platforms: [.iOS(deploymentTarget)],
70+
products: [
71+
.library(
72+
name: "VisionSearchPill",
73+
targets: ["VisionSearchPill"]
74+
),
75+
],
76+
targets: [
77+
.target(
78+
name: "VisionSearchPillC",
79+
cSettings: [.unsafeFlags(cFlags)],
80+
cxxSettings: [.unsafeFlags(cxxFlags)]
81+
),
82+
.target(
83+
name: "VisionSearchPill",
84+
dependencies: ["VisionSearchPillC"],
85+
swiftSettings: [.unsafeFlags(swiftFlags)]
86+
),
87+
]
88+
)

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<img width="70" src="visionsearchpillprefs/Resources/bigicon.png">
2+
3+
# VisionSearchPill
4+
#### Add a flare of visionOS to your search pill!
5+
###### Compatible with iOS 16+
6+
---
7+
8+
A simple tweak that allows customization of the search pill that comes with iOS 16.
9+
10+
---
11+
12+
<img width="200" style="border-radius:10px" src="Screenshot.jpeg">
13+
<img width="200" style="border-radius:10px" src="Screenshot_2.jpeg">
14+
15+
---
16+
17+
#### License
18+
This project is licensed under [MIT](LICENSE).
19+
20+
###### Copyright (c) 2024 Nightwind. All rights reserved.

Screenshot.jpeg

77.2 KB
Loading

Screenshot_2.jpeg

32.8 KB
Loading
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import VisionSearchPillC
2+
3+
func remLog(_ objs: Any...) {
4+
let string = objs.map { String(describing: $0) }.joined(separator: "; ")
5+
let args: [CVarArg] = [ "[VisionSearchPill-\(Date().description)] \(string)" ]
6+
withVaList(args) { RLogv("%@", $0) }
7+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* Copyright (c) 2024 Nightwind. All rights reserved.
3+
*/
4+
5+
import Orion
6+
import VisionSearchPillC
7+
8+
class PillHook: ClassHook<SBHSearchPillView> {
9+
func didMoveToWindow() {
10+
orig.didMoveToWindow()
11+
12+
// Apply changes without respring
13+
DistributedNotificationCenter.default().addObserver(forName: Notification.Name("VisionSearchPillPreferenceSet"), object: nil, queue: .main) { _ in
14+
self.target.applyVisionStyle()
15+
}
16+
}
17+
18+
func layoutSubviews() {
19+
orig.layoutSubviews()
20+
target.applyVisionStyle()
21+
}
22+
23+
/**
24+
* Apply the VisionOS style to the search pill.
25+
*/
26+
// orion:new
27+
@objc func applyVisionStyle() {
28+
// Load the settings of the tweak
29+
do {
30+
try TweakPreferences.shared.loadSettings()
31+
} catch {
32+
remLog("error: \(error.localizedDescription)")
33+
return
34+
}
35+
36+
// Store the settings
37+
let tweakEnabled: Bool = TweakPreferences.shared.settings.tweakEnabled
38+
let borderWidth: CGFloat = TweakPreferences.shared.settings.borderWidth
39+
40+
let backgroundColor: UIColor = TweakPreferences.shared.colorFor(key: "backgroundColor", fallback: "#ffffffff")
41+
let borderColor: UIColor = TweakPreferences.shared.colorFor(key: "borderColor", fallback: "#ffffffff")
42+
43+
// Apply the style
44+
guard let backgroundView = target.backgroundView else { return }
45+
backgroundView.layer.borderWidth = tweakEnabled ? borderWidth : 0
46+
backgroundView.layer.borderColor = tweakEnabled ? borderColor.cgColor : UIColor.clear.cgColor
47+
backgroundView.alpha = tweakEnabled ? 0.1 : 1
48+
49+
target.alpha = tweakEnabled ? 0.1 : 1
50+
target.backgroundColor = tweakEnabled ? backgroundColor : .clear
51+
target.layer.cornerRadius = tweakEnabled ? backgroundView.layer.cornerRadius : 0
52+
target.clipsToBounds = tweakEnabled ? true : false
53+
}
54+
}
55+
56+
class BackgroundPillHook: ClassHook<SBFolderScrollAccessoryView> {
57+
func didMoveToWindow() {
58+
orig.didMoveToWindow()
59+
target.applyVisionStyle()
60+
61+
// Apply changes without respring
62+
DistributedNotificationCenter.default().addObserver(forName: Notification.Name("VisionSearchPillPreferenceSet"), object: nil, queue: .main) { _ in
63+
self.target.applyVisionStyle()
64+
}
65+
}
66+
67+
/**
68+
* Apply the VisionOS style to the search pill
69+
*/
70+
// orion:new
71+
@objc func applyVisionStyle() {
72+
// Load the settings of the tweak
73+
do {
74+
try TweakPreferences.shared.loadSettings()
75+
} catch {
76+
remLog("error: \(error.localizedDescription)")
77+
return
78+
}
79+
80+
// Store the settings
81+
let tweakEnabled: Bool = TweakPreferences.shared.settings.tweakEnabled
82+
let borderWidth: CGFloat = TweakPreferences.shared.settings.borderWidth
83+
84+
let backgroundColor: UIColor = TweakPreferences.shared.colorFor(key: "backgroundColor", fallback: "#ffffffff")
85+
let borderColor: UIColor = TweakPreferences.shared.colorFor(key: "borderColor", fallback: "#ffffffff")
86+
87+
// Apply the style
88+
guard let backgroundView = target.backgroundView else { return }
89+
backgroundView.layer.borderWidth = tweakEnabled ? borderWidth : 0
90+
backgroundView.layer.borderColor = tweakEnabled ? borderColor.cgColor : UIColor.clear.cgColor
91+
backgroundView.alpha = tweakEnabled ? 0.1 : 1
92+
backgroundView.backgroundColor = tweakEnabled ? backgroundColor : .clear
93+
}
94+
}
95+
96+
class VisionSearchPill: Tweak {
97+
required init() {
98+
remLog("VisionSearchPill starting")
99+
do {
100+
try TweakPreferences.shared.loadSettings()
101+
} catch {
102+
remLog("Unexpected error: \(error.localizedDescription)")
103+
}
104+
}
105+
106+
static func handleError(_ error: OrionHookError) {
107+
remLog(error)
108+
DefaultTweak.handleError(error)
109+
}
110+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Copyright (c) 2024 Nightwind. All rights reserved.
3+
*/
4+
5+
import Foundation
6+
import GcUniversal
7+
8+
// Store the tweak settings in a Codable struct in order to group them together
9+
struct Settings: Codable {
10+
var tweakEnabled: Bool = true
11+
var borderWidth: CGFloat = 1.0
12+
}
13+
14+
@objc(VSPTweakPreferences)
15+
@objcMembers
16+
final class TweakPreferences: NSObject {
17+
// Store the tweak settings in a readonly struct that can be accessed from the outside
18+
private(set) var settings: Settings!
19+
// Shared instance of TweakPreferences for convenience
20+
static let shared = TweakPreferences()
21+
22+
private let userDefaultsName: String = "com.nightwind.visionsearchpillprefs"
23+
24+
/**
25+
* Loads the tweak settings from NSUserDefaults to the settings Codable struct.
26+
*/
27+
@objc(loadSettingsWithError:)
28+
func loadSettings() throws {
29+
// If defaults haven't been set yet, set self.settings to the default values
30+
guard let prefs: UserDefaults = UserDefaults(suiteName: userDefaultsName) else {
31+
self.settings = Settings()
32+
return
33+
}
34+
35+
// Get the dictionary representation of the user defaults
36+
let dict = prefs.dictionaryRepresentation()
37+
// Make a dictionary that can be used with the Codable struct
38+
let codableDict = NSMutableDictionary()
39+
40+
// Loop over the dictionary and only take out the keys that correspond with the actual VisualSearchPill settings
41+
for (key, value) in dict {
42+
if key.hasPrefix("vsp_") {
43+
codableDict.setValue(value, forKey: key.components(separatedBy: "vsp_")[1])
44+
}
45+
}
46+
47+
// Get a JSON representation of the Codable dictionary
48+
let data = try JSONSerialization.data(withJSONObject: codableDict, options: [.fragmentsAllowed, .prettyPrinted])
49+
50+
// Decode the JSON representation to self.settings. If successful, self.settings is set to the loaded settings.
51+
// Otherwise, self.settings is set to the default settings
52+
if let loadedSettings = try? JSONDecoder().decode(Settings.self, from: data) {
53+
self.settings = loadedSettings
54+
} else {
55+
self.settings = Settings()
56+
}
57+
}
58+
59+
/**
60+
* Get the color from the preferences.
61+
*
62+
* @param key - The internal key that identifies the color in the preference bundle.
63+
* @param fallback - The fallback hex value to use if the color is not available. This argument is optional.
64+
*
65+
* @return The color from the preferences.
66+
*/
67+
@objc(colorForKey:fallback:)
68+
func colorFor(key: String, fallback: String? = nil) -> UIColor {
69+
return GcColorPickerUtils.color(fromDefaults: userDefaultsName, withKey: key, fallback: fallback)
70+
}
71+
72+
}

0 commit comments

Comments
 (0)