Skip to content

Commit

Permalink
Initial commit with project setup including .gitignore, configuration…
Browse files Browse the repository at this point in the history
… files, and source code for TypeSwitch application. Added support for managing input methods and installed applications, along with UI components for user interaction.
  • Loading branch information
ygsgdbd committed Dec 5, 2024
1 parent 6e9d4e0 commit 21972c5
Show file tree
Hide file tree
Showing 18 changed files with 858 additions and 0 deletions.
70 changes: 70 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### Xcode ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings
xcuserdata/

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

### Xcode Patch ###
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno

### Projects ###
*.xcodeproj
*.xcworkspace

### Tuist derived files ###
graph.dot
Derived/

### Tuist managed dependencies ###
Tuist/.build
2 changes: 2 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tools]
tuist = "4.36.0"
33 changes: 33 additions & 0 deletions .package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"originHash" : "34bd913d9089fe41f02bf038193989039a85b3413c3faeddca7573d68c9a1f1d",
"pins" : [
{
"identity" : "defaults",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/Defaults",
"state" : {
"revision" : "ef1b2318fb549002bb533bec3a8ad98ae09f2cb6",
"version" : "9.0.0"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-syntax",
"state" : {
"revision" : "0687f71944021d616d34d922343dcef086855920",
"version" : "600.0.1"
}
},
{
"identity" : "swiftuix",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SwiftUIX/SwiftUIX",
"state" : {
"revision" : "e984fd2e08140ad5a95d084be38fe02b774bc15d",
"version" : "0.2.3"
}
}
],
"version" : 3
}
50 changes: 50 additions & 0 deletions Project.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import ProjectDescription

let project = Project(
name: "TypeSwitch",
packages: [
.remote(url: "https://github.com/sindresorhus/Defaults", requirement: .upToNextMajor(from: "9.0.0")),
.remote(url: "https://github.com/SwiftUIX/SwiftUIX", requirement: .upToNextMajor(from: "0.1.9")),
],
targets: [
.target(
name: "TypeSwitch",
destinations: .macOS,
product: .app,
bundleId: "top.ygsgdbd.TypeSwitch",
deploymentTargets: .macOS("13.0"),
infoPlist: .extendingDefault(with: [
"LSUIElement": true, // 设置为纯菜单栏应用
"com.apple.security.app-sandbox": true,
"com.apple.security.network.client": true,
"com.apple.security.files.user-selected.read-write": true,
"com.apple.developer.icloud-container-identifiers": ["iCloud.top.ygsgdbd.TypeSwitch"],
"com.apple.developer.icloud-services": ["CloudKit"],
"com.apple.developer.ubiquity-kvstore-identifier": "top.ygsgdbd.TypeSwitch",
"com.apple.security.application-groups": ["group.top.ygsgdbd.TypeSwitch"],
]),
sources: ["TypeSwitch/Sources/**"],
resources: ["TypeSwitch/Resources/**"],
dependencies: [
.package(product: "Defaults"),
.package(product: "SwiftUIX"),
],
settings: .settings(base: [
"ENABLE_APP_SANDBOX": "YES",
"ENABLE_ICLOUD_SERVICES": "YES",
"ENABLE_ICLOUD_KEYVALUE_STORAGE": "YES",
])
),
.target(
name: "TypeSwitchTests",
destinations: .macOS,
product: .unitTests,
bundleId: "top.ygsgdbd.TypeSwitchTests",
deploymentTargets: .macOS("13.0"),
infoPlist: .default,
sources: ["TypeSwitch/Tests/**"],
resources: [],
dependencies: [.target(name: "TypeSwitch")]
),
]
)
9 changes: 9 additions & 0 deletions Tuist.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import ProjectDescription

let tuist = Tuist(
// Create an account with "tuist auth" and a project with "tuist project create"
// then uncomment the section below and set the project full-handle.
// * Read more: https://docs.tuist.io/guides/quick-start/gather-insights
//
// fullHandle: "{account_handle}/{project_handle}",
)
22 changes: 22 additions & 0 deletions Tuist/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// swift-tools-version: 6.0
import PackageDescription

#if TUIST
import struct ProjectDescription.PackageSettings

let packageSettings = PackageSettings(
// Customize the product types for specific package product
// Default is .staticFramework
// productTypes: ["Alamofire": .framework,]
productTypes: [:]
)
#endif

let package = Package(
name: "InputMethodSwitcher",
dependencies: [
// Add your own dependencies here:
// .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"),
// You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions TypeSwitch/Resources/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
65 changes: 65 additions & 0 deletions TypeSwitch/Sources/AppListUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import Foundation
import SwiftUI

enum AppListUtils {
static let applicationDirs = [
"/Applications",
"~/Applications",
"/System/Applications"
].map { NSString(string: $0).expandingTildeInPath }

static func fetchInstalledApps() async -> [AppInfo] {
await withTaskGroup(of: [AppInfo].self) { group in
for dir in applicationDirs {
group.addTask {
await fetchAppsInDirectory(dir)
}
}

var apps: [AppInfo] = []
for await dirApps in group {
apps.append(contentsOf: dirApps)
}

let uniqueApps = Dictionary(grouping: apps, by: \.bundleId)
.values
.compactMap { $0.first }

return uniqueApps.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }
}
}

private static func fetchAppsInDirectory(_ dir: String) async -> [AppInfo] {
let fileManager = FileManager.default

guard fileManager.fileExists(atPath: dir),
fileManager.isReadableFile(atPath: dir) else {
return []
}

guard let enumerator = fileManager.enumerator(
at: URL(fileURLWithPath: dir),
includingPropertiesForKeys: [.isApplicationKey],
options: [.skipsHiddenFiles, .skipsPackageDescendants]
) else { return [] }

var dirApps: [AppInfo] = []
for case let fileURL as URL in enumerator {
guard fileManager.isReadableFile(atPath: fileURL.path) else { continue }

do {
let resourceValues = try fileURL.resourceValues(forKeys: [.isApplicationKey])
guard resourceValues.isApplication == true,
let bundle = Bundle(url: fileURL),
let bundleId = bundle.bundleIdentifier,
let name = bundle.infoDictionary?["CFBundleName"] as? String
else { continue }

dirApps.append(AppInfo(bundleId: bundleId, name: name, iconPath: fileURL.path))
} catch {
continue
}
}
return dirApps
}
}
Loading

0 comments on commit 21972c5

Please sign in to comment.