Skip to content

Commit

Permalink
Merge pull request #15 from team-unstablers/feature/hello-message
Browse files Browse the repository at this point in the history
feature: add definition / implementation for PROJECTION_HELLO message
  • Loading branch information
unstabler authored Jun 8, 2023
2 parents d565422 + a86aebf commit cef33dc
Show file tree
Hide file tree
Showing 14 changed files with 327 additions and 56 deletions.
6 changes: 4 additions & 2 deletions sessionbroker/sessionbroker/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ func main() {

DispatchQueue.global().async {
logger.info("Starting server: sesman")
sesman.start()
// FIXME: wrap using do-catch
try? sesman.start()
}

logger.info("Starting server: broker")
broker.start()
// FIXME: wrap using do-catch
try? broker.start()
}

main()
4 changes: 4 additions & 0 deletions sessionprojector/sessionprojector.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,11 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = sessionprojector/sessionprojector.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = XHA76UVA95;
FRAMEWORK_SEARCH_PATHS = "$(inherit)/**";
GCC_INPUT_FILETYPE = automatic;
GENERATE_INFOPLIST_FILE = YES;
Expand Down Expand Up @@ -557,9 +559,11 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = sessionprojector/sessionprojector.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = XHA76UVA95;
FRAMEWORK_SEARCH_PATHS = "$(inherit)/**";
GCC_INPUT_FILETYPE = automatic;
GENERATE_INFOPLIST_FILE = YES;
Expand Down
24 changes: 14 additions & 10 deletions sessionprojector/sessionprojector/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct SessionProjectorApp: App {
Divider()


Button("\(appState.connections) connection(s)") {
Button("\(appState.connections.count) connection(s)") {

}
.disabled(true)
Expand Down Expand Up @@ -240,7 +240,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {

try await startScreenRecorder()
try sesmanClient.start()
projectionServer.start()
try projectionServer.start()
} catch {
print(error.localizedDescription)

Expand Down Expand Up @@ -309,27 +309,31 @@ extension AppDelegate: ProjectionServerDelegate {
screenRecorder?.subscribeUpdate(session)
session.eventInjector = eventInjector

AppState.instance.connections += 1
AppState.instance.connections.insert(session)

let clientInfo = session.clientInfo

postUserNotificationSync(
title: "New connection",
message: (AppState.instance.connections > 1) ?
"A new connection has been established from (TODO: remote ip address).\nThe content of this session being shared with \(AppState.instance.connections) remote clients." :
"A new connection has been established from (TODO: remote ip address).\nThe content of this session will be shared with the remote client."
message: (AppState.instance.connections.count > 1) ?
"A new connection has been established from \(clientInfo.clientAddress).\nThe content of this session being shared with \(AppState.instance.connections.count) remote clients." :
"A new connection has been established from \(clientInfo.clientAddress).\nThe content of this session will be shared with the remote client."
)
}

func projectionServer(sessionClosed session: ProjectionSession, id: UInt64) {
let clientInfo = session.clientInfo

screenRecorder?.unsubscribeUpdate(session)
session.eventInjector = nil

AppState.instance.connections -= 1
AppState.instance.connections.remove(session)

postUserNotificationSync(
title: "Connection closed",
message: (AppState.instance.connections > 0) ?
"Connection has been closed from (TODO: remote ip address).\n\(AppState.instance.connections) remote clients are still connected." :
"Connection has been closed from (TODO: remote ip address)."
message: (AppState.instance.connections.count > 0) ?
"Connection has been closed from \(clientInfo.clientAddress).\n\(AppState.instance.connections.count) remote clients are still connected." :
"Connection has been closed from \(clientInfo.clientAddress)."
)
}
}
Expand Down
7 changes: 4 additions & 3 deletions sessionprojector/sessionprojector/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import Foundation
import Cocoa

import UlalacaCore

enum ScreenRecorderType: String, Identifiable, CaseIterable, Codable {
var id: Self {
return self
Expand Down Expand Up @@ -145,11 +147,11 @@ class AppState: NSObject, ObservableObject {
var userPreferences: UserPreferences

@Published
var connections: Int = 0
var connections: Set<ProjectionSession> = []

@Published
var isScreenLocked: Bool = false


var preferencesWindow: NSWindow {
get {
Expand All @@ -159,7 +161,6 @@ class AppState: NSObject, ObservableObject {

var aboutAppWindow: AboutAppWindow? = nil


override init() {
let fileManager = FileManager.default
let userPreferencesPath = fileManager.homeDirectoryForCurrentUser.appendingPathComponent(".sessionprojector.json")
Expand Down
45 changes: 30 additions & 15 deletions sessionprojector/sessionprojector/ipc/ProjectionServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,41 @@ class ProjectionServer {
.path
}

func start() {
serverSocket.bind()
serverSocket.listen()
func start() throws {
try ObjC.evaluate {
serverSocket.bind()
serverSocket.listen()
}

while (isServerRunning) {
guard let clientSocket = serverSocket.accept() else {
continue
}
let session = ProjectionSession.init(clientSocket)
sessions.append(session)
do {
try ObjC.evaluate {
guard let clientSocket = serverSocket.accept() else {
return
}
let session = ProjectionSession.init(clientSocket)
sessions.append(session)

delegate?.projectionServer(sessionInitiated: session, id: 0)
if (!session.readHello()) {
session.stopSession()
return
}

session.startSession(errorHandler: { error in
if let index = self.sessions.index(where: { $0.socket.descriptor() == session.socket.descriptor() }) {
self.sessions.remove(at: index)
}
delegate?.projectionServer(sessionInitiated: session, id: 0)

self.delegate?.projectionServer(sessionClosed: session, id: 0)
})
session.startSession(errorHandler: { error in
if let index = self.sessions.index(where: { $0.socket.descriptor() == session.socket.descriptor() }) {
self.sessions.remove(at: index)
}

self.delegate?.projectionServer(sessionClosed: session, id: 0)
})
}
} catch let error as NestedNSExceptionError {
// TODO: handle error
logger.error(error.localizedDescription)
continue
}
}
}

Expand Down
93 changes: 91 additions & 2 deletions sessionprojector/sessionprojector/ipc/ProjectionSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,61 @@ enum ProjectionSessionError: Error {
case socketReadError
}

enum ClientCodec: UInt8 {
case none = 0
case rfx = 1
case h264 = 2
case nsCodec = 3
}


struct ClientInfo: Hashable {
public static let CLIENT_ID_UNKNOWN = UUID(uuidString: "00000000-0000-0000-0000-000000000001")!

var id: UUID

var xrdpUlalacaVersion: String
var clientAddress: String
var clientDescription: String
var clientOSMajor: Int
var clientOSMinor: Int

var program: String

var codec: ClientCodec

var flags: Int

static func from(ipc message: ULIPCProjectionHello) -> ClientInfo {
return ClientInfo(
id: UUID(),
xrdpUlalacaVersion: withUnsafePointer(to: message.xrdpUlalacaVersion) { String(fromUnsafeCStr: $0, length: 32) },
clientAddress: withUnsafePointer(to: message.clientAddress) { String(fromUnsafeCStr: $0, length: 46) },
clientDescription: withUnsafePointer(to: message.clientDescription) { String(fromUnsafeCStr: $0, length: 256) },
clientOSMajor: Int(message.clientOSMajor),
clientOSMinor: Int(message.clientOSMinor),
program: withUnsafePointer(to: message.program) { String(fromUnsafeCStr: $0, length: 512) },
codec: ClientCodec(rawValue: message.codec) ?? .none,
flags: Int(message.flags)
)
}

static func unknown() -> ClientInfo {
return ClientInfo(
id: CLIENT_ID_UNKNOWN,
xrdpUlalacaVersion: "Unknown",
clientAddress: "Unknown",
clientDescription: "Unknown",
clientOSMajor: 0,
clientOSMinor: 0,
program: "Unknown",
codec: .none,
flags: 0
)
}
}

class ProjectionSession {
class ProjectionSession: Identifiable {
private let logger: ULLogger

public let socket: MMUnixSocketConnection
Expand All @@ -26,6 +78,10 @@ class ProjectionSession {
public let mainDisplayId = CGMainDisplayID()
public let serialQueue = DispatchQueue(label: "ProjectionSession")

public var id: UUID

private(set) public var clientInfo: ClientInfo = ClientInfo.unknown()

private(set) public var isSessionRunning: Bool = true

private(set) public var messageId: UInt64 = 1;
Expand All @@ -35,6 +91,7 @@ class ProjectionSession {
private(set) public var mainViewport: ViewportInfo?

init(_ socket: MMUnixSocketConnection) {
self.id = UUID()
self.socket = socket

self.logger = createLogger("ProjectionSession (fd \(self.socket.descriptor()))")
Expand Down Expand Up @@ -90,7 +147,9 @@ class ProjectionSession {
try socket.readCStruct(ULIPCProjectionStop.self)
suppressOutput = true
break

case TYPE_PROJECTION_HELLO:
// ???
break
case TYPE_PROJECTION_SET_VIEWPORT:
self.setViewport(with: try socket.readCStruct(ULIPCProjectionSetViewport.self))
break
Expand All @@ -111,6 +170,25 @@ class ProjectionSession {
mainViewport = ViewportInfo(width: message.width, height: message.height)
}

public func readHello(timeout: Double = 0.5) -> Bool {
logger.debug("ProjectionSession::readHello(): timeout is not implemented yet")

guard let header = try? socket.readCStruct(ULIPCHeader.self) else {
return false
}

if (header.messageType != TYPE_PROJECTION_HELLO) {
return false
}

guard let message = try? socket.readCStruct(ULIPCProjectionHello.self) else {
return false
}
self.clientInfo = ClientInfo.from(ipc: message)

return true
}


private func writeMessage<T>(_ message: T, type: UInt16) {
let messageLength = MemoryLayout.size(ofValue: message)
Expand All @@ -134,6 +212,17 @@ class ProjectionSession {

}

extension ProjectionSession: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(socket.descriptor())
}

public static func ==(lhs: ProjectionSession, rhs: ProjectionSession) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}

extension ProjectionSession: ScreenUpdateSubscriber {
var identifier: Int {
get { Int(socket.descriptor()) }
Expand Down
6 changes: 4 additions & 2 deletions sessionprojector/sessionprojector/ui/AboutAppWindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import SwiftUI

import UlalacaCore

struct AboutAppWindowView: View {

@Environment(\.openURL)
Expand All @@ -31,9 +33,9 @@ struct AboutAppWindowView: View {
.font(.system(size: 36, weight: .ultraLight, design: .default))
}
VStack(alignment: .center) {
Text("SessionProjector.app")
Text("sessionprojector.app")
.bold()
Text("Development Build")
Text("Version \(UlalacaVersion())")
}
}
}
Expand Down
18 changes: 15 additions & 3 deletions sessionprojector/sessionprojector/ui/PreferencesWindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@

import SwiftUI

struct ConnectionRow: View {
var connection: ProjectionSession

var body: some View {
VStack() {
Text("\(connection.clientInfo.clientAddress)")
.bold()
Text("ulalaca-xrdp version \(connection.clientInfo.xrdpUlalacaVersion)")
}
}
}

struct PreferencesWindowView: View {
@EnvironmentObject
var appState: AppState
Expand Down Expand Up @@ -89,9 +101,9 @@ struct PreferencesWindowView: View {
.frame(maxWidth: .infinity)
*/
HStack {
GroupBox(label: Text("Current Sessions")) {
List {

GroupBox(label: Text("Clients")) {
List(Array(appState.connections)) { connection in
ConnectionRow(connection: connection)
}
}
.frame(maxWidth: .infinity)
Expand Down
Loading

0 comments on commit cef33dc

Please sign in to comment.