Allow Zed to run under multiple user accounts simultaneously (#14143)
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>
This commit is contained in:
parent
837639535c
commit
b386b6c237
3 changed files with 54 additions and 4 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -14260,6 +14260,7 @@ dependencies = [
|
||||||
"smol",
|
"smol",
|
||||||
"snippet_provider",
|
"snippet_provider",
|
||||||
"supermaven",
|
"supermaven",
|
||||||
|
"sysinfo",
|
||||||
"tab_switcher",
|
"tab_switcher",
|
||||||
"task",
|
"task",
|
||||||
"tasks_ui",
|
"tasks_ui",
|
||||||
|
|
|
@ -113,6 +113,7 @@ vim.workspace = true
|
||||||
welcome.workspace = true
|
welcome.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
zed_actions.workspace = true
|
zed_actions.workspace = true
|
||||||
|
sysinfo.workspace = true
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
windows.workspace = true
|
windows.workspace = true
|
||||||
|
|
|
@ -5,22 +5,70 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use sysinfo::System;
|
||||||
|
|
||||||
use release_channel::ReleaseChannel;
|
use release_channel::ReleaseChannel;
|
||||||
|
|
||||||
const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
|
const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
|
||||||
const CONNECT_TIMEOUT: Duration = Duration::from_millis(10);
|
const CONNECT_TIMEOUT: Duration = Duration::from_millis(10);
|
||||||
const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35);
|
const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35);
|
||||||
const SEND_TIMEOUT: Duration = Duration::from_millis(20);
|
const SEND_TIMEOUT: Duration = Duration::from_millis(20);
|
||||||
|
const USER_BLOCK: u16 = 100;
|
||||||
|
|
||||||
fn address() -> SocketAddr {
|
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 {
|
let port = match *release_channel::RELEASE_CHANNEL {
|
||||||
ReleaseChannel::Dev => 43737,
|
ReleaseChannel::Dev => 43737,
|
||||||
ReleaseChannel::Preview => 43738,
|
ReleaseChannel::Preview => 43737 + USER_BLOCK,
|
||||||
ReleaseChannel::Stable => 43739,
|
ReleaseChannel::Stable => 43737 + (2 * USER_BLOCK),
|
||||||
ReleaseChannel::Nightly => 43740,
|
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, port))
|
SocketAddr::V4(SocketAddrV4::new(LOCALHOST, user_port))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn get_uid_as_u32(uid: &sysinfo::Uid) -> u32 {
|
||||||
|
*uid.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {
|
fn instance_handshake() -> &'static str {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue