Cargo.lock 🔗
@@ -14260,6 +14260,7 @@ dependencies = [
"smol",
"snippet_provider",
"supermaven",
+ "sysinfo",
"tab_switcher",
"task",
"tasks_ui",
Sami Samhuri and Mikayla created
Closes #4607
This is an attempt to enable Zed to run under multiple user accounts on
the same Mac, because it's a blocker to me really giving Zed a fair shot
at being my primary editor.
According to some helpful info from @ForLoveOfCats in #4607 the main
reason why this doesn't work is because Zed is using a Unix socket or
maybe a TCP socket with a hard-coded path and/or port. To me it looks
like it's a TCP socket so I tried changing that code in here, but I'm
stuck at trying to test it out because running `target/debug/zed` or
`target/release/zed` seems to behave differently than running an actual
app bundle. I had no luck copying the binary over to
/Applications/Zed.app/Contents/MacOS/zed because it can't find
WebRTC.framework which resides at a different relative path in the app
bundle.
If this seems like a desirable change to the core team then I'm looking
for some guidance on how to build an app bundle or otherwise test out
this change, or a nudge in the correct direction if I'm way off base
with my current approach.
Release Notes:
- Added multiuser support for up to 100 users on the same machine.
---------
Co-authored-by: Mikayla <mikayla@zed.dev>
Cargo.lock | 1
crates/zed/Cargo.toml | 1
crates/zed/src/zed/mac_only_instance.rs | 56 +++++++++++++++++++++++++-
3 files changed, 54 insertions(+), 4 deletions(-)
@@ -14260,6 +14260,7 @@ dependencies = [
"smol",
"snippet_provider",
"supermaven",
+ "sysinfo",
"tab_switcher",
"task",
"tasks_ui",
@@ -113,6 +113,7 @@ vim.workspace = true
welcome.workspace = true
workspace.workspace = true
zed_actions.workspace = true
+sysinfo.workspace = true
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true
@@ -5,22 +5,70 @@ use std::{
time::Duration,
};
+use sysinfo::System;
+
use release_channel::ReleaseChannel;
const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
const CONNECT_TIMEOUT: Duration = Duration::from_millis(10);
const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35);
const SEND_TIMEOUT: Duration = Duration::from_millis(20);
+const USER_BLOCK: u16 = 100;
fn address() -> SocketAddr {
+ // These port numbers are offset by the user ID to avoid conflicts between
+ // different users on the same machine. In addition to that the ports for each
+ // release channel are spaced out by 100 to avoid conflicts between different
+ // users running different release channels on the same machine. This ends up
+ // interleaving the ports between different users and different release channels.
+ //
+ // On macOS user IDs start at 501 and on Linux they start at 1000. The first user
+ // on a Mac with ID 501 running a dev channel build will use port 44238, and the
+ // second user with ID 502 will use port 44239, and so on. User 501 will use ports
+ // 44338, 44438, and 44538 for the preview, stable, and nightly channels,
+ // respectively. User 502 will use ports 44339, 44439, and 44539 for the preview,
+ // stable, and nightly channels, respectively.
let port = match *release_channel::RELEASE_CHANNEL {
ReleaseChannel::Dev => 43737,
- ReleaseChannel::Preview => 43738,
- ReleaseChannel::Stable => 43739,
- ReleaseChannel::Nightly => 43740,
+ ReleaseChannel::Preview => 43737 + USER_BLOCK,
+ ReleaseChannel::Stable => 43737 + (2 * USER_BLOCK),
+ ReleaseChannel::Nightly => 43737 + (3 * USER_BLOCK),
};
+ let mut user_port = port;
+ let mut sys = System::new_all();
+ sys.refresh_all();
+ if let Ok(current_pid) = sysinfo::get_current_pid() {
+ if let Some(uid) = sys
+ .process(current_pid)
+ .and_then(|process| process.user_id())
+ {
+ let uid_u32 = get_uid_as_u32(uid);
+ // Ensure that the user ID is not too large to avoid overflow when
+ // calculating the port number. This seems unlikely but it doesn't
+ // hurt to be safe.
+ let max_port = 65535;
+ let max_uid: u32 = max_port - port as u32;
+ let wrapped_uid: u16 = (uid_u32 % max_uid) as u16;
+ user_port += wrapped_uid;
+ }
+ }
+
+ SocketAddr::V4(SocketAddrV4::new(LOCALHOST, user_port))
+}
+
+#[cfg(unix)]
+fn get_uid_as_u32(uid: &sysinfo::Uid) -> u32 {
+ *uid.clone()
+}
- SocketAddr::V4(SocketAddrV4::new(LOCALHOST, port))
+#[cfg(windows)]
+fn get_uid_as_u32(uid: &sysinfo::Uid) -> u32 {
+ // Extract the RID which is an integer
+ uid.to_string()
+ .rsplit('-')
+ .next()
+ .and_then(|rid| rid.parse::<u32>().ok())
+ .unwrap_or(0)
}
fn instance_handshake() -> &'static str {