Skip to content
Draft
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
13 changes: 13 additions & 0 deletions OpenParsec.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
27E61AAA2929B92200FF6563 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E61AA92929B92200FF6563 /* MainView.swift */; };
27ED36FF292D4F9800B1BE3D /* NetworkHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27ED36FE292D4F9800B1BE3D /* NetworkHandler.swift */; };
84480EBE2ADC4FDA007DE5F1 /* GameController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84480EBD2ADC4FDA007DE5F1 /* GameController.swift */; };
C0D3F0012FDE000000000001 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C0D3F0032FDE000000000001 /* Localizable.strings */; };
E76224A12F8C017F00A2F86F /* ParsecBackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E76224A02F8C017700A2F86F /* ParsecBackgroundManager.swift */; };
E762250B2F8D0A0100A2F86F /* PictureInPictureManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E762250A2F8D0A0000A2F86F /* PictureInPictureManager.swift */; };
FC16A7AD29A97BDA00BB70A7 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC16A7AC29A97BDA00BB70A7 /* Shared.swift */; };
Expand Down Expand Up @@ -94,6 +95,7 @@
27E61AA92929B92200FF6563 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
27ED36FE292D4F9800B1BE3D /* NetworkHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkHandler.swift; sourceTree = "<group>"; };
84480EBD2ADC4FDA007DE5F1 /* GameController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameController.swift; sourceTree = "<group>"; };
C0D3F0022FDE000000000001 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
E76224A02F8C017700A2F86F /* ParsecBackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsecBackgroundManager.swift; sourceTree = "<group>"; };
E762250A2F8D0A0000A2F86F /* PictureInPictureManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PictureInPictureManager.swift; sourceTree = "<group>"; };
FC16A7AC29A97BDA00BB70A7 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -156,6 +158,7 @@
27E61A9F292965FD00FF6563 /* Info.plist */,
17EEB91B2BEE62BA00502A3A /* KeyBoardTest.swift */,
27E61A9C292965FD00FF6563 /* LaunchScreen.storyboard */,
C0D3F0032FDE000000000001 /* Localizable.strings */,
27E61AA52929817700FF6563 /* LoginView.swift */,
27E61AA92929B92200FF6563 /* MainView.swift */,
27ED36FE292D4F9800B1BE3D /* NetworkHandler.swift */,
Expand Down Expand Up @@ -234,6 +237,7 @@
knownRegions = (
en,
Base,
"zh-Hans",
);
mainGroup = 27E61A85292965FC00FF6563;
packageReferences = (
Expand All @@ -253,6 +257,7 @@
buildActionMask = 2147483647;
files = (
27E61A9E292965FD00FF6563 /* LaunchScreen.storyboard in Resources */,
C0D3F0012FDE000000000001 /* Localizable.strings in Resources */,
27E61A9B292965FD00FF6563 /* Preview Assets.xcassets in Resources */,
27E61A98292965FD00FF6563 /* Assets.xcassets in Resources */,
);
Expand Down Expand Up @@ -329,6 +334,14 @@
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
C0D3F0032FDE000000000001 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
C0D3F0022FDE000000000001 /* zh-Hans */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
Expand Down
7 changes: 6 additions & 1 deletion OpenParsec/CParsec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ enum ParsecResolution: String, CaseIterable, Hashable {
}

var desc: String {
return rawValue
return localized(rawValue)
}

static var resolutions: [ParsecResolution] {
Expand Down Expand Up @@ -153,6 +153,7 @@ protocol ParsecService {
func sendKeyboardMessage(keyCode: UInt32, pressed: Bool)
func sendVirtualKeyboardInput(text: String)
func sendVirtualKeyboardInput(text: String, isOn: Bool)
func sendKeyboardShortcut(modifier: ShortcutModifier, key: String)
func sendGameControllerButtonMessage(controllerId: UInt32, _ button: ParsecGamepadButton, pressed: Bool)
func sendGameControllerAxisMessage(controllerId: UInt32, _ button: ParsecGamepadAxis, _ value: Int16)
func sendGameControllerUnplugMessage(controllerId: UInt32)
Expand Down Expand Up @@ -259,6 +260,10 @@ class CParsec {
parsecImpl.sendVirtualKeyboardInput(text: text, isOn: isOn)
}

static func sendKeyboardShortcut(modifier: ShortcutModifier, key: String) {
parsecImpl.sendKeyboardShortcut(modifier: modifier, key: key)
}

static func sendGameControllerButtonMessage(controllerId: UInt32, _ button: ParsecGamepadButton, pressed: Bool) {
parsecImpl.sendGameControllerButtonMessage(controllerId: controllerId, button, pressed: pressed)
}
Expand Down
10 changes: 5 additions & 5 deletions OpenParsec/ExUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct CatTitle: View {

var body: some View {
HStack {
Text(text)
Text(localized(text))
Spacer()
}
.padding(.horizontal)
Expand Down Expand Up @@ -60,7 +60,7 @@ struct CatItem<Content: View>: View {

var body: some View {
HStack {
Text(title)
Text(localized(title))
.lineLimit(1)
Spacer()
content()
Expand All @@ -79,7 +79,7 @@ struct Choice<T: Hashable> {
var value: T

init(_ label: String, _ value: T) {
self.label = label
self.label = localized(label)
self.value = value
}
}
Expand Down Expand Up @@ -118,7 +118,7 @@ struct MultiPicker<SelectionValue: Hashable>: View {
var options: [Choice<SelectionValue>]

@State var showChoices: Bool = false
@State var valueText: String = "Choose..."
@State var valueText: String = localized("Choose...")

init(selection: Binding<SelectionValue>, options: [Choice<SelectionValue>]) {
self.selection = selection
Expand Down Expand Up @@ -163,7 +163,7 @@ struct MultiPicker<SelectionValue: Hashable>: View {
let buttons = options.enumerated().map { _, option in
Alert.Button.default(Text(option.value == selection.wrappedValue ? " \(option.label) ✓" : option.label), action: {select(option)})
}
return ActionSheet(title: Text("Pick your preference:"), buttons: buttons + [Alert.Button.cancel()])
return ActionSheet(title: Text(localized("Pick your preference:")), buttons: buttons + [Alert.Button.cancel()])
}

func select(_ option: Choice<SelectionValue>) {
Expand Down
2 changes: 1 addition & 1 deletion OpenParsec/LoginView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ struct LoginView: View {
if isTFARequired {
presentTFAAlert = true
} else {
alertText = "Error: \(info)"
alertText = localized("Error: %@", String(describing: info))
showAlert = true
}
} else {
Expand Down
35 changes: 13 additions & 22 deletions OpenParsec/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ struct MainView: View {
@State private var page: Page = .hosts

// Host page vars
@State var hostCountStr: String = "0 hosts"
@State var refreshTime: String = "Last refreshed at 1/1/1970 12:00 AM"
@State var hostCountStr: String = localized("%d hosts", 0)
@State var refreshTime: String = localized("Last refreshed at %@", "1/1/1970 12:00 AM")

@State var hosts: [IdentifiableHostInfo] = []

// Friend page vars
@State var friendCountStr: String = "0 friends"
@State var friendCountStr: String = localized("%d friends", 0)

@State var userInfo: IdentifiableUserInfo?
@State var friends: [IdentifiableUserInfo] = []
Expand Down Expand Up @@ -309,7 +309,7 @@ struct MainView: View {
VStack {
ActivityIndicator(isAnimating: $isConnecting, style: .large, tint: .white)
.padding()
Text("Requesting connection to \(connectingToName)...")
Text(localized("Requesting connection to %@...", connectingToName))
.multilineTextAlignment(.center)
Button(action: cancelConnection) {
ZStack {
Expand Down Expand Up @@ -371,7 +371,7 @@ struct MainView: View {
let clinfo = NetworkHandler.clinfo
if clinfo == nil {
isRefreshing = false
baseAlertText = "Error gathering hosts: Invalid session"
baseAlertText = localized("Error gathering hosts: Invalid session")
showBaseAlert = true
return
}
Expand Down Expand Up @@ -399,20 +399,16 @@ struct MainView: View {
}
}

var grammar: String = "hosts"
if hosts.count == 1 {
grammar = "host"
}

hostCountStr = "\(hosts.count) \(grammar)"
hostCountStr = localized(hosts.count == 1 ? "%d host" : "%d hosts", hosts.count)

let formatter = DateFormatter()
formatter.locale = Locale.current
formatter.dateFormat = "M/d/yyyy h:mm a"
refreshTime = "Last refreshed at \(formatter.string(from: Date()))"
refreshTime = localized("Last refreshed at %@", formatter.string(from: Date()))
} else if statusCode == 403 { // 403 Forbidden
guard let info: ErrorInfo = try? decoder.decode(ErrorInfo.self, from: data) else { return }

baseAlertText = "Error gathering hosts: \(info.error)"
baseAlertText = localized("Error gathering hosts: %@", info.error)
showBaseAlert = true
}
}
Expand Down Expand Up @@ -451,7 +447,7 @@ struct MainView: View {
} else {
guard let info: ErrorInfo = try? decoder.decode(ErrorInfo.self, from: data) else { return }

baseAlertText = "Error gathering user info: \(info.error)"
baseAlertText = localized("Error gathering user info: %@", info.error)
showBaseAlert = true
}
}
Expand Down Expand Up @@ -494,16 +490,11 @@ struct MainView: View {
}
}

var grammar: String = "friends"
if friends.count == 1 {
grammar = "friend"
}

friendCountStr = "\(friends.count) \(grammar)"
friendCountStr = localized(friends.count == 1 ? "%d friend" : "%d friends", friends.count)
} else {
guard let info: ErrorInfo = try? decoder.decode(ErrorInfo.self, from: data) else { return }

baseAlertText = "Error gathering friends: \(info.error)"
baseAlertText = localized("Error gathering friends: %@", info.error)
showBaseAlert = true
}
}
Expand Down Expand Up @@ -535,7 +526,7 @@ struct MainView: View {
c.setView(.parsec)
}
} else {
baseAlertText = "Error connecting to host (code \(status.rawValue))"
baseAlertText = localized("Error connecting to host (code %d)", Int(status.rawValue))
showBaseAlert = true
}

Expand Down
34 changes: 34 additions & 0 deletions OpenParsec/ParsecSDKBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@ enum CursorMode: Int {
case direct
}

enum DirectDragMode: Int {
case scroll
case drag
}

enum ShortcutModifier: Int {
case control
case command

var keyText: String {
switch self {
case .control:
return "CONTROL"
case .command:
return "LGUI"
}
}
}

enum RightClickPosition: Int {
case firstFinger
case middle
Expand Down Expand Up @@ -423,6 +442,21 @@ class ParsecSDKBridge: ParsecService {

}

func sendKeyboardShortcut(modifier: ShortcutModifier, key: String) {
guard let modifierKeyCode = KeyCodeTranslators.parsecKeyCodeTranslator(modifier.keyText),
let keyCode = KeyCodeTranslators.parsecKeyCodeTranslator(key.uppercased()) else {
return
}

sendKeyboardMessage(keyCode: modifierKeyCode.rawValue, pressed: true)
sendKeyboardMessage(keyCode: keyCode.rawValue, pressed: true)

DispatchQueue.global().asyncAfter(deadline: .now() + 0.02) {
self.sendKeyboardMessage(keyCode: keyCode.rawValue, pressed: false)
self.sendKeyboardMessage(keyCode: modifierKeyCode.rawValue, pressed: false)
}
}

func sendKeyboardMessage(event: KeyBoardKeyEvent) {
if event.input == nil {
return
Expand Down
49 changes: 41 additions & 8 deletions OpenParsec/ParsecView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import AVFoundation

struct ParsecStatusBar: View {
@Binding var showMenu: Bool
@State var metricInfo: String = "Loading..."
@State var metricInfo: String = localized("Loading...")
@Binding var showDCAlert: Bool
@Binding var DCAlertText: String
@State var parsecViewController: ParsecViewController?
Expand Down Expand Up @@ -72,14 +72,25 @@ struct ParsecStatusBar: View {
}

wasDisconnected = true
DCAlertText = "Disconnected (code \(status.rawValue))"
DCAlertText = localized("Disconnected (code %d)", Int(status.rawValue))
showDCAlert = true
return
}

if showMenu {
let str = String.fromBuffer(&pcs.decoder.0.name.0, length: 16)
metricInfo = "Decode \(String(format: "%.2f", pcs.`self`.metrics.0.decodeLatency))ms Encode \(String(format: "%.2f", pcs.`self`.metrics.0.encodeLatency))ms Network \(String(format: "%.2f", pcs.`self`.metrics.0.networkLatency))ms Bitrate \(String(format: "%.2f", pcs.`self`.metrics.0.bitrate))Mbps \(pcs.decoder.0.h265 ? "H265" : "H264") \(pcs.decoder.0.width)x\(pcs.decoder.0.height) \(pcs.decoder.0.color444 ? "4:4:4" : "4:2:0") \(str)"
metricInfo = localized(
"Decode %@ms Encode %@ms Network %@ms Bitrate %@Mbps %@ %@x%@ %@ %@",
String(format: "%.2f", pcs.`self`.metrics.0.decodeLatency),
String(format: "%.2f", pcs.`self`.metrics.0.encodeLatency),
String(format: "%.2f", pcs.`self`.metrics.0.networkLatency),
String(format: "%.2f", pcs.`self`.metrics.0.bitrate),
pcs.decoder.0.h265 ? "H265" : "H264",
String(pcs.decoder.0.width),
String(pcs.decoder.0.height),
pcs.decoder.0.color444 ? "4:4:4" : "4:2:0",
str
)
}
}
}
Expand All @@ -98,8 +109,8 @@ struct ParsecView: View {
var controller: ContentView?

@State var showDCAlert: Bool = false
@State var DCAlertText: String = "Disconnected (reason unknown)"
@State var metricInfo: String = "Loading..."
@State var DCAlertText: String = localized("Disconnected (reason unknown)")
@State var metricInfo: String = localized("Loading...")

@State var hideOverlay: Bool = false
@State var showMenu: Bool = false
Expand Down Expand Up @@ -204,8 +215,22 @@ struct ParsecView: View {
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
}
HStack(spacing: 3) {
Button(action: sendCopyShortcut) {
Label("Copy", systemImage: "doc.on.doc")
.padding(8)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
}
Button(action: sendPasteShortcut) {
Label("Paste", systemImage: "doc.on.clipboard")
.padding(8)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
}
}
Button(action: toggleMute) {
Text("Sound: \(muted ? "OFF" : "ON")")
Text(localized("Sound: %@", localized(muted ? "OFF" : "ON")))
.padding(8)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
Expand Down Expand Up @@ -265,13 +290,13 @@ struct ParsecView: View {
}

Button(action: toggleConstantFps) {
Text("Constant FPS: \(constantFps ? "ON" : "OFF")")
Text(localized("Constant FPS: %@", localized(constantFps ? "ON" : "OFF")))
.padding(8)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
}
Button(action: toggleZoom) {
Text("Zoom: \(zoomEnabled ? "ON" : "OFF")")
Text(localized("Zoom: %@", localized(zoomEnabled ? "ON" : "OFF")))
.padding(8)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
Expand Down Expand Up @@ -472,6 +497,14 @@ struct ParsecView: View {
}
}

func sendCopyShortcut() {
CParsec.sendKeyboardShortcut(modifier: SettingsHandler.shortcutModifier, key: "C")
}

func sendPasteShortcut() {
CParsec.sendKeyboardShortcut(modifier: SettingsHandler.shortcutModifier, key: "V")
}

func toggleZoom() {
DispatchQueue.main.async {
zoomEnabled.toggle()
Expand Down
Loading