Skip to content
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
4b66a39
add voice module, deps, wip models
llsc12 Feb 19, 2026
d49fcc1
more voice models
llsc12 Feb 19, 2026
f942d4d
clean up
llsc12 Feb 19, 2026
a820da1
added gateway actor, not ready yet tho
llsc12 Feb 20, 2026
4cf68cb
more work
llsc12 Feb 21, 2026
61a4263
Merge branch 'main' into voice
llsc12 Feb 21, 2026
8d412d3
test
llsc12 Feb 22, 2026
c9e96ae
fix builds
llsc12 Feb 22, 2026
15d959a
Squashed commit of the following:
llsc12 Feb 22, 2026
ef52d96
fix ios builds
llsc12 Feb 22, 2026
753c916
clean up, add dave work, tests
llsc12 Feb 23, 2026
5075107
max dave version
llsc12 Feb 23, 2026
1cf1c9a
more udp work
llsc12 Feb 23, 2026
2917dc6
add dave session handling
llsc12 Feb 23, 2026
8a767b5
i think my outbound code is wrong
llsc12 Feb 23, 2026
0e66982
wip app side voice stuff
llsc12 Feb 24, 2026
5645ece
dont forget to identify
llsc12 Feb 24, 2026
d4da406
connection doesnt always work
llsc12 Feb 24, 2026
368e7e4
voice gateway working
llsc12 Feb 24, 2026
d5aea9d
temporary leave button
llsc12 Feb 25, 2026
5be2f71
fix udp stuff, fix ip discovery
llsc12 Feb 25, 2026
bfe23ed
weird crash with libdave
llsc12 Feb 25, 2026
bf98f17
the crypto stuff is fixed
llsc12 Mar 3, 2026
9e6664f
Update VoiceGatewayManager.swift
llsc12 Mar 3, 2026
35a5599
forgot to encode opcode, works now
llsc12 Mar 3, 2026
6299376
fix a bunch of logical issues, fix sending audio packets, send silent…
llsc12 Mar 4, 2026
b8bcdf2
fix crypto
llsc12 Mar 5, 2026
dc7a63c
wip voice working
llsc12 Mar 5, 2026
8b20bbc
dont schedule buffers on main thread
llsc12 Mar 7, 2026
e3fade7
emit opus packets directly, emit fake speaking events
llsc12 Mar 7, 2026
56d79fa
voice states
llsc12 Mar 7, 2026
88aa05d
Update VoiceGatewayManager.swift
llsc12 Mar 7, 2026
6001c74
init decoder and player node per ssrc
llsc12 Mar 7, 2026
12ab4de
fix missing sendable payload
llsc12 Mar 7, 2026
3b6303f
add audio receive buffer
llsc12 Mar 7, 2026
e990997
fix encryption nonce, add additional data to chacha
llsc12 Mar 11, 2026
34f7ee4
paicordlib add dave ssrc codec info, better buffering impl, get rid o…
llsc12 Mar 11, 2026
c182f07
working bidirectional audio
llsc12 Mar 11, 2026
bde09a4
buffer per incoming stream
llsc12 Mar 11, 2026
5fe71f9
fix changing channels whilst in a channel
llsc12 Mar 11, 2026
7f7623f
Update VoiceConnectionStore.swift
llsc12 Mar 12, 2026
42ca30e
disconnect on another client connecting
llsc12 Mar 12, 2026
8b1e6ff
fix member row popout
llsc12 Mar 12, 2026
6f758a5
add voice states to guild
llsc12 Mar 12, 2026
7cdc426
expose guild and channel stores, voice channel members, call bar
llsc12 Mar 12, 2026
ddd4160
request member data
llsc12 Mar 12, 2026
d42b3d5
fix disconnection bug, fix weird voice channel spacing
llsc12 Mar 13, 2026
0525f9d
make call bar nicer
llsc12 Mar 14, 2026
65aaef7
change how navigation works
llsc12 Mar 16, 2026
90514e0
wip voice ui
llsc12 Mar 17, 2026
11f2c62
permissions
llsc12 Mar 17, 2026
a3d771a
fixes
llsc12 Mar 17, 2026
81e7b2c
fix dms button
llsc12 Mar 18, 2026
e7ccd8c
point links away from discordbm's issues
llsc12 Mar 18, 2026
ad0a4fb
discord can send no name for connected accounts
llsc12 Mar 18, 2026
de68d6c
Update VoiceGatewayManager.swift
llsc12 Mar 18, 2026
3c58754
ringing
llsc12 Mar 18, 2026
50a9be0
calls (dm vc) support, wip call ui
llsc12 Mar 18, 2026
ee443c1
fixes to channel order, dms ui wip
llsc12 Mar 23, 2026
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
14 changes: 14 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ jobs:
restore-keys: |
deriveddata-macos

- name: Force Git to use HTTPS
run: |
# rewrite any git@github.com:... clones to https://github.com/...
git config --global url."https://github.com/".insteadOf "git@github.com:"
# optional: debug the rule
git config --get-regexp '^url\.' || true

- name: Build macOS .app
run: |
COMMIT_HASH=$(git rev-parse --short HEAD)
Expand Down Expand Up @@ -226,6 +233,13 @@ jobs:
restore-keys: |
deriveddata-ios

- name: Force Git to use HTTPS
run: |
# rewrite any git@github.com:... clones to https://github.com/...
git config --global url."https://github.com/".insteadOf "git@github.com:"
# optional: debug the rule
git config --get-regexp '^url\.' || true

- name: Build iOS .ipa
run: |
COMMIT_HASH=$(git rev-parse --short HEAD)
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/build_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ jobs:
deriveddata-macos-pr-${{ github.event.pull_request.number }}-
deriveddata-macos-pr-

- name: Force Git to use HTTPS
run: |
# rewrite any git@github.com:... clones to https://github.com/...
git config --global url."https://github.com/".insteadOf "git@github.com:"
# optional: debug the rule
git config --get-regexp '^url\.' || true

- name: Build macOS
id: build
run: |
Expand Down Expand Up @@ -77,6 +84,13 @@ jobs:
deriveddata-ios-pr-${{ github.event.pull_request.number }}-
deriveddata-ios-pr-

- name: Force Git to use HTTPS
run: |
# rewrite any git@github.com:... clones to https://github.com/...
git config --global url."https://github.com/".insteadOf "git@github.com:"
# optional: debug the rule
git config --get-regexp '^url\.' || true

- name: Build iOS
id: build
run: |
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/release_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ jobs:
restore-keys: |
pr-release-deriveddata-macos-

- name: Force Git to use HTTPS
run: |
# rewrite any git@github.com:... clones to https://github.com/...
git config --global url."https://github.com/".insteadOf "git@github.com:"
# optional: debug the rule
git config --get-regexp '^url\.' || true

- name: Build macOS
run: |
set -euo pipefail
Expand Down Expand Up @@ -194,6 +201,13 @@ jobs:
echo "ldid installed at: $(command -v ldid)"
fi

- name: Force Git to use HTTPS
run: |
# rewrite any git@github.com:... clones to https://github.com/...
git config --global url."https://github.com/".insteadOf "git@github.com:"
# optional: debug the rule
git config --get-regexp '^url\.' || true

- name: Build iOS
run: |
set -euo pipefail
Expand Down
15 changes: 12 additions & 3 deletions DiscordMarkdownParser/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ let package = Package(
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "DiscordMarkdownParser",
targets: ["DiscordMarkdownParser"])
targets: ["DiscordMarkdownParser"]
)
],
dependencies: [
.package(path: "../PaicordLib")
Expand All @@ -24,9 +25,17 @@ let package = Package(
name: "DiscordMarkdownParser",
dependencies: [
"PaicordLib"
]),
],
swiftSettings: [
.interoperabilityMode(.Cxx)
]
),
.testTarget(
name: "DiscordMarkdownParserTests",
dependencies: ["DiscordMarkdownParser"]),
dependencies: ["DiscordMarkdownParser"],
swiftSettings: [
.interoperabilityMode(.Cxx)
]
),
]
)
31 changes: 31 additions & 0 deletions Paicord.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@
AAEEC71F2E65120000EB5FC9 /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEEC71E2E6511F800EB5FC9 /* TokenStore.swift */; };
AAEEC7222E6515C400EB5FC9 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = AAEEC7212E6515C400EB5FC9 /* KeychainAccess */; };
AAEEF5012E9900F60034FA04 /* Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEEF5002E9900E50034FA04 /* Default.swift */; };
AAFBC52E2F4C946800C5B644 /* VoiceConnectionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFBC52D2F4C945F00C5B644 /* VoiceConnectionStore.swift */; };
AAFBC5312F4CA9C000C5B644 /* Copus in Frameworks */ = {isa = PBXBuildFile; productRef = AAFBC5302F4CA9C000C5B644 /* Copus */; };
AAFBC5332F4CA9C000C5B644 /* Opus in Frameworks */ = {isa = PBXBuildFile; productRef = AAFBC5322F4CA9C000C5B644 /* Opus */; };
AAFC9DDA2EB7DAE300BB8028 /* VariableBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFC9DD92EB7DAE200BB8028 /* VariableBlurView.swift */; };
AAFD41382E92FA43002BC9BE /* Array+safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFD41372E92FA42002BC9BE /* Array+safe.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -297,6 +300,7 @@
AAEB3D942ED60812008BDD1D /* ImpactGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImpactGenerator.swift; sourceTree = "<group>"; };
AAEEC71E2E6511F800EB5FC9 /* TokenStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenStore.swift; sourceTree = "<group>"; };
AAEEF5002E9900E50034FA04 /* Default.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Default.swift; sourceTree = "<group>"; };
AAFBC52D2F4C945F00C5B644 /* VoiceConnectionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceConnectionStore.swift; sourceTree = "<group>"; };
AAFC9DD92EB7DAE200BB8028 /* VariableBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariableBlurView.swift; sourceTree = "<group>"; };
AAFD41372E92FA42002BC9BE /* Array+safe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+safe.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand All @@ -316,6 +320,8 @@
AA8ABACA2F2A794E00557278 /* SwiftEmoji in Frameworks */,
AAD446A22ED07916006CB07C /* SettingsKit in Frameworks */,
579DF34B2E6D9B9800BD8B97 /* SwiftUIX in Frameworks */,
AAFBC5312F4CA9C000C5B644 /* Copus in Frameworks */,
AAFBC5332F4CA9C000C5B644 /* Opus in Frameworks */,
AA47AF622EDF1FF7008A50C9 /* Conditionals in Frameworks */,
AA21D2832EAA557C00C75093 /* Sparkle in Frameworks */,
AAA9EEFD2F23062B00770CAD /* CodeScanner in Frameworks */,
Expand Down Expand Up @@ -978,6 +984,7 @@
AABED5A32E7F4DAE005BDD63 /* GuildStore.swift */,
AABED59D2E7F4637005BDD63 /* ChannelStore.swift */,
AA7B38F32EB50EFD00CA4A3C /* MessageDrainStore.swift */,
AAFBC52D2F4C945F00C5B644 /* VoiceConnectionStore.swift */,
AABED5A52E7F5148005BDD63 /* SettingsStore.swift */,
AAAF797F2ED1E9ED004B5B3F /* ExternalBadgeStore.swift */,
AA79479D2EDC7B9400B7A1EE /* PresenceStore.swift */,
Expand Down Expand Up @@ -1034,6 +1041,8 @@
AA8ABACB2F2A794E00557278 /* SwiftEmojiIndex */,
AA8ABACE2F2A79B500557278 /* Loupe */,
AA8ABAD12F2A7A0800557278 /* MijickCamera */,
AAFBC5302F4CA9C000C5B644 /* Copus */,
AAFBC5322F4CA9C000C5B644 /* Opus */,
);
productName = PaiCord;
productReference = AA1096DB2E63BE84005BC3D2 /* Paicord.app */;
Expand Down Expand Up @@ -1083,6 +1092,7 @@
AA8ABAC82F2A794E00557278 /* XCRemoteSwiftPackageReference "SwiftEmoji" */,
AA8ABACD2F2A79B500557278 /* XCRemoteSwiftPackageReference "Loupe" */,
AA8ABAD02F2A7A0800557278 /* XCRemoteSwiftPackageReference "Camera" */,
AAFBC52F2F4CA9C000C5B644 /* XCRemoteSwiftPackageReference "swift-opus" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = AA1096DC2E63BE84005BC3D2 /* Products */;
Expand Down Expand Up @@ -1175,6 +1185,7 @@
AA47AF1F2EDEF04F008A50C9 /* AuthorisedAppsSection.swift in Sources */,
AA21D2802EAA220300C75093 /* MaskEdgesModifier.swift in Sources */,
57F5AF552E7CBDF400AD5674 /* MFAView.swift in Sources */,
AAFBC52E2F4C946800C5B644 /* VoiceConnectionStore.swift in Sources */,
AA7B38F22EB37F9B00CA4A3C /* PermsHelper.swift in Sources */,
AA63EB722EA711D000A5F21D /* EmbedsView.swift in Sources */,
AAB50A582E9AD4BB0048E8B0 /* Utilities.swift in Sources */,
Expand Down Expand Up @@ -1425,6 +1436,7 @@
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_INTEROP_MODE = objcxx;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting SWIFT_OBJC_INTEROP_MODE to objcxx enables C++ interoperability which is required for some dependencies (likely DaveKit). However, this is a significant compiler mode change that affects the entire project. Ensure this is intentional and documented, as it may have implications for build times and compatibility with other dependencies.

Suggested change
SWIFT_OBJC_INTEROP_MODE = objcxx;
/* SWIFT_OBJC_INTEROP_MODE not forced to objcxx here; use default (objc) to avoid project-wide ObjC++ mode. */

Copilot uses AI. Check for mistakes.
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
Expand Down Expand Up @@ -1488,6 +1500,7 @@
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_INTEROP_MODE = objcxx;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
XROS_DEPLOYMENT_TARGET = 1.0;
Expand Down Expand Up @@ -1662,6 +1675,14 @@
kind = branch;
};
};
AAFBC52F2F4CA9C000C5B644 /* XCRemoteSwiftPackageReference "swift-opus" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/alta/swift-opus.git";
requirement = {
branch = main;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
Expand Down Expand Up @@ -1773,6 +1794,16 @@
package = AAEEC7202E6515C400EB5FC9 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
productName = KeychainAccess;
};
AAFBC5302F4CA9C000C5B644 /* Copus */ = {
isa = XCSwiftPackageProductDependency;
package = AAFBC52F2F4CA9C000C5B644 /* XCRemoteSwiftPackageReference "swift-opus" */;
productName = Copus;
};
AAFBC5322F4CA9C000C5B644 /* Opus */ = {
isa = XCSwiftPackageProductDependency;
package = AAFBC52F2F4CA9C000C5B644 /* XCRemoteSwiftPackageReference "swift-opus" */;
productName = Opus;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = AA1096D32E63BE84005BC3D2 /* Project object */;
Expand Down
33 changes: 30 additions & 3 deletions Paicord.xcworkspace/xcshareddata/swiftpm/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions Paicord/Common/Guilds/ChannelButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,79 @@ struct ChannelButton: View {
}
}

struct VoiceChannelButton<Content: View>: View {
@Environment(\.appState) var appState
@Environment(\.gateway) var gw
@Environment(\.guildStore) var guild
@State private var isHovered = false
var channels: [ChannelSnowflake: DiscordChannel]
var channel: DiscordChannel
var content: (_ hovered: Bool) -> Content

var shouldHide: Bool {
guard let guild else { return false }
return guild.hasPermission(
channel: channel,
.viewChannel
) == false
}
var body: some View {
if !shouldHide {
Button {
Task {
await gw.voice.updateVoiceConnection(
.join(
channelId: channel.id,
guildId: channel.guild_id!
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The force unwrap on channel.guild_id! could cause a crash if guild_id is nil. Voice channels can exist in DMs or group DMs where guild_id would be nil. Add a guard statement to handle this case properly or verify that this button is only shown for guild voice channels.

Suggested change
await gw.voice.updateVoiceConnection(
.join(
channelId: channel.id,
guildId: channel.guild_id!
guard let guildId = channel.guild_id else { return }
await gw.voice.updateVoiceConnection(
.join(
channelId: channel.id,
guildId: guildId

Copilot uses AI. Check for mistakes.
)
)
}
} label: {
content(isHovered)
}
.onHover { isHovered = $0 }
.buttonStyle(.borderless)
}
}
}

/// Button that triggers voice channel actions.
@ViewBuilder
func voiceChannelButton<Content: View>(
@ViewBuilder label: @escaping (_ hovered: Bool) -> Content
)
-> some View
{
VoiceChannelButton(
channels: channels,
channel: channel
) { hovered in
label(hovered)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
.background(
Group {
if hovered {
Color.gray.opacity(0.2)
} else {
Color.clear
}
}
.clipShape(.rounded)
)
.background(
Group {
if appState.selectedChannel == channel.id {
Color.gray.opacity(0.13)
} else {
Color.clear
}
}
.clipShape(.rounded)
)
}
}

struct CategoryButton: View {
@Environment(\.userInterfaceIdiom) var idiom
@Environment(\.guildStore) var guild
Expand Down
10 changes: 8 additions & 2 deletions Paicord/Common/Guilds/GuildButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,14 @@ struct GuildButton: View {
/// A button representing a guild or DMs
func guildButton(from guild: Guild?) -> some View {
Button {
ImpactGenerator.impact(style: .light)
appState.selectedGuild = guild?.id
if appState.selectedGuild == guild?.id {
#if os(iOS)
appState.chatOpen = true
#endif
} else {
ImpactGenerator.impact(style: .light)
appState.selectedGuild = guild?.id
}
} label: {
let isSelected = appState.selectedGuild == guild?.id
Group {
Expand Down
Loading