Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ let package = Package(
targets: [
.binaryTarget(
name: "OpenMultitouchSupportXCF",
url: "https://github.com/Kyome22/OpenMultitouchSupport/releases/download/3.0.3/OpenMultitouchSupportXCF.xcframework.zip",
checksum: "9ffe72a65f0107e87a003485ab9530e772a6b45953af2f7d0cc41665d1873dea"
url: "https://github.com/KrishKrosh/OpenMultitouchSupport/releases/download/v1.0.0/OpenMultitouchSupportXCF.xcframework.zip",
checksum: "dbee657fea27f427e5b05c89cfa923f9c1c733b1ac6a6cb71360cd77c93f08ca"
),
.target(
name: "OpenMultitouchSupport",
Expand Down
33 changes: 32 additions & 1 deletion Sources/OpenMultitouchSupport/OMSManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@
*/

import Combine
import OpenMultitouchSupportXCF
@preconcurrency import OpenMultitouchSupportXCF
import os

public struct OMSDeviceInfo: Sendable, Hashable {
public let deviceName: String
public let deviceID: String
public let isBuiltIn: Bool
internal nonisolated(unsafe) let deviceInfo: OpenMTDeviceInfo

internal init(_ deviceInfo: OpenMTDeviceInfo) {
self.deviceInfo = deviceInfo
self.deviceName = deviceInfo.deviceName
self.deviceID = deviceInfo.deviceID
self.isBuiltIn = deviceInfo.isBuiltIn
}
}

public final class OMSManager: Sendable {
public static let shared = OMSManager()

Expand All @@ -30,6 +44,17 @@ public final class OMSManager: Sendable {
public var isListening: Bool {
protectedListener.withLockUnchecked { $0 != nil }
}

public var availableDevices: [OMSDeviceInfo] {
guard let xcfManager = protectedManager.withLockUnchecked(\.self) else { return [] }
return xcfManager.availableDevices().map { OMSDeviceInfo($0) }
}

public var currentDevice: OMSDeviceInfo? {
guard let xcfManager = protectedManager.withLockUnchecked(\.self),
let current = xcfManager.currentDevice() else { return nil }
return OMSDeviceInfo(current)
}

private init() {
protectedManager = .init(uncheckedState: OpenMTManager.shared())
Expand Down Expand Up @@ -60,6 +85,12 @@ public final class OMSManager: Sendable {
protectedListener.withLockUnchecked { $0 = nil }
return true
}

@discardableResult
public func selectDevice(_ device: OMSDeviceInfo) -> Bool {
guard let xcfManager = protectedManager.withLockUnchecked(\.self) else { return false }
return xcfManager.selectDevice(device.deviceInfo)
}

@objc func listen(_ event: OpenMTEvent) {
guard let touches = (event.touches as NSArray) as? [OpenMTTouch] else { return }
Expand Down
4 changes: 4 additions & 0 deletions TrackWeight.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
77292AA22B931E04001CA3F6 /* DebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA32B931E04001CA3F6 /* DebugView.swift */; };
77292AA42B931E05001CA3F6 /* ScaleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA52B931E05001CA3F6 /* ScaleView.swift */; };
77292AA62B931E06001CA3F6 /* ScaleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA72B931E06001CA3F6 /* ScaleViewModel.swift */; };
93A095122E33359600E1E1D1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A095112E33359600E1E1D1 /* SettingsView.swift */; };
93ABD0212E2E01E200668D4F /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ABD0202E2E01E200668D4F /* HomeView.swift */; };
/* End PBXBuildFile section */

Expand All @@ -37,6 +38,7 @@
77292AA32B931E04001CA3F6 /* DebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = "<group>"; };
77292AA52B931E05001CA3F6 /* ScaleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleView.swift; sourceTree = "<group>"; };
77292AA72B931E06001CA3F6 /* ScaleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleViewModel.swift; sourceTree = "<group>"; };
93A095112E33359600E1E1D1 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
93ABD0202E2E01E200668D4F /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -81,6 +83,7 @@
77292A9D2B931E01001CA3F6 /* WeighingState.swift */,
93ABD0202E2E01E200668D4F /* HomeView.swift */,
77292A9F2B931E02001CA3F6 /* WeighingViewModel.swift */,
93A095112E33359600E1E1D1 /* SettingsView.swift */,
77292AA12B931E03001CA3F6 /* TrackWeightView.swift */,
77292AA32B931E04001CA3F6 /* DebugView.swift */,
77292AA52B931E05001CA3F6 /* ScaleView.swift */,
Expand Down Expand Up @@ -188,6 +191,7 @@
77292AA22B931E04001CA3F6 /* DebugView.swift in Sources */,
77292AA42B931E05001CA3F6 /* ScaleView.swift in Sources */,
77292AA62B931E06001CA3F6 /* ScaleViewModel.swift in Sources */,
93A095122E33359600E1E1D1 /* SettingsView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
6 changes: 3 additions & 3 deletions TrackWeight/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ struct ContentView: View {
}
.tag(1)

DebugView()
SettingsView()
.tabItem {
Image(systemName: "hand.point.up.left")
Text("Debug")
Image(systemName: "gearshape")
Text("Settings")
}
.tag(2)
}
Expand Down
20 changes: 17 additions & 3 deletions TrackWeight/ContentViewModel.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// ContentViewModel.swift
// TrackWeight
// OMSDemo
//
// Created by Takuto Nakamura on 2024/03/02.
//
Expand All @@ -12,16 +12,19 @@ import SwiftUI
final class ContentViewModel: ObservableObject {
@Published var touchData = [OMSTouchData]()
@Published var isListening: Bool = false
@Published var availableDevices = [OMSDeviceInfo]()
@Published var selectedDevice: OMSDeviceInfo?

private let manager = OMSManager.shared
private var task: Task<Void, Never>?

init() {}
init() {
loadDevices()
}

func onAppear() {
task = Task { [weak self, manager] in
for await touchData in manager.touchDataStream {
print(touchData)
await MainActor.run {
self?.touchData = touchData
}
Expand All @@ -45,4 +48,15 @@ final class ContentViewModel: ObservableObject {
isListening = false
}
}

func loadDevices() {
availableDevices = manager.availableDevices
selectedDevice = manager.currentDevice
}

func selectDevice(_ device: OMSDeviceInfo) {
if manager.selectDevice(device) {
selectedDevice = device
}
}
}
24 changes: 24 additions & 0 deletions TrackWeight/DebugView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@ struct DebugView: View {

var body: some View {
VStack {

// Device Selector
if !viewModel.availableDevices.isEmpty {
VStack(alignment: .leading) {
Text("Trackpad Device:")
.font(.headline)
Picker("Select Device", selection: Binding(
get: { viewModel.selectedDevice },
set: { device in
if let device = device {
viewModel.selectDevice(device)
}
}
)) {
ForEach(viewModel.availableDevices, id: \.self) { device in
Text("\(device.deviceName) (ID: \(device.deviceID))")
.tag(device as OMSDeviceInfo?)
}
}
.pickerStyle(MenuPickerStyle())
}
.padding(.bottom)
}

if viewModel.isListening {
Button {
viewModel.stop()
Expand Down
147 changes: 147 additions & 0 deletions TrackWeight/SettingsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//
// SettingsView.swift
// TrackWeight
//

import OpenMultitouchSupport
import SwiftUI

struct SettingsView: View {
@StateObject private var viewModel = ContentViewModel()
@State private var showDebugView = false

var body: some View {
VStack(spacing: 0) {
// Minimal Header
Text("Settings")
.font(.title)
.fontWeight(.medium)
.padding(.top, 32)
.padding(.bottom, 32)

// Settings Cards
VStack(spacing: 20) {
// Device Card
SettingsCard {
VStack(spacing: 20) {
// Status Row
HStack {
HStack(spacing: 12) {
Text("Trackpad")
.font(.headline)
.fontWeight(.medium)
}

Spacer()

if !viewModel.availableDevices.isEmpty {
Text("\(viewModel.availableDevices.count) device\(viewModel.availableDevices.count == 1 ? "" : "s")")
.font(.caption)
.foregroundColor(.secondary)
}
}

// Device Selector
if !viewModel.availableDevices.isEmpty {
HStack {
Picker("", selection: Binding(
get: { viewModel.selectedDevice },
set: { device in
if let device = device {
viewModel.selectDevice(device)
}
}
)) {
ForEach(viewModel.availableDevices, id: \.self) { device in
Text(device.deviceName)
.tag(device as OMSDeviceInfo?)
}
}
.pickerStyle(MenuPickerStyle())

Spacer()
}
} else {
HStack {
Text("No devices available")
.foregroundColor(.secondary)
Spacer()
}
}
}
}

// Debug Card
SettingsCard {
Button(action: { showDebugView = true }) {
HStack(spacing: 16) {
VStack(alignment: .leading, spacing: 4) {
Text("Debug Console")
.font(.headline)
.fontWeight(.medium)
.foregroundColor(.primary)

Text("Raw touch data & diagnostics")
.font(.caption)
.foregroundColor(.secondary)
}

Spacer()

Image(systemName: "chevron.right")
.font(.caption)
.fontWeight(.medium)
.foregroundColor(.secondary.opacity(0.6))
}
.contentShape(Rectangle())
}
.buttonStyle(CardButtonStyle())
}
}
.frame(maxWidth: 480)
.padding(.horizontal, 40)

Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(NSColor.windowBackgroundColor))
.sheet(isPresented: $showDebugView) {
DebugView()
.frame(minWidth: 700, minHeight: 500)
}
.onAppear {
viewModel.loadDevices()
}
}
}

struct SettingsCard<Content: View>: View {
let content: Content

init(@ViewBuilder content: () -> Content) {
self.content = content()
}

var body: some View {
VStack {
content
}
.padding(24)
.background(Color(NSColor.controlBackgroundColor))
.cornerRadius(16)
.shadow(color: Color.black.opacity(0.03), radius: 1, x: 0, y: 1)
.shadow(color: Color.black.opacity(0.05), radius: 8, x: 0, y: 4)
}
}

struct CardButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
}
}

#Preview {
SettingsView()
}