Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
use registry::register_uri_handler;
use warpui::AppContext;
#[cfg(feature = "release_bundle")]
use {
service_impl::forward_uri_to_sole_running_instance,
single_instance_manager::SingleInstanceManager, thiserror::Error, url::Url,
warp_core::channel::ChannelState,
};
mod registry;
#[cfg(feature = "release_bundle")]
mod service_impl;
#[cfg(feature = "release_bundle")]
mod single_instance_manager;
#[derive(Error, Debug)]
#[cfg(feature = "release_bundle")]
pub enum StartupArgsForwardingError {
#[error("should not forward arguments after an auto-update")]
IgnoredAfterAutoUpdate,
#[error("there is no other instance of Warp")]
NoExistingInstance,
#[error("failed to construct url")]
CouldNotCreateUrl(#[from] url::ParseError),
#[error("IPC Client failed to send message")]
IpcError(#[from] ipc::ClientError),
#[error("Win32 error")]
WindowsError(#[from] windows::core::Error),
}
#[cfg(feature = "release_bundle")]
pub fn pass_startup_args_to_existing_instance(
args: &warp_cli::AppArgs,
) -> Result<(), StartupArgsForwardingError> {
if args.finish_update {
return Err(StartupArgsForwardingError::IgnoredAfterAutoUpdate);
}
if SingleInstanceManager::is_sole_running_instance()? {
return Err(StartupArgsForwardingError::NoExistingInstance);
}
warpui::r#async::block_on(async {
if args.urls.is_empty() {
// If there are no URLs on the command line, send one to open a new
// window using the same current working directory as this process.
let mut open_new_url = format!("{}://action/new_window", ChannelState::url_scheme());
if let Ok(current_dir) = std::env::current_dir() {
match current_dir.into_os_string().into_string() {
Ok(current_dir) => open_new_url.push_str(&format!("?path={}", current_dir)),
Err(os_string) => {
log::error!("Failed to convert OsString {os_string:?} to ");
}
}
}
let url = Url::parse(&open_new_url)?;
forward_uri_to_sole_running_instance(vec![url]).await?
} else {
forward_uri_to_sole_running_instance(args.urls.clone()).await?
}
Ok(())
})
}
pub(super) fn init(_ctx: &mut AppContext) {
#[cfg(feature = "release_bundle")]
_ctx.add_singleton_model(SingleInstanceManager::new);
register_uri_handler();
}
+70
View File
@@ -0,0 +1,70 @@
use std::ffi::OsString;
use warp_core::channel::ChannelState;
use windows_registry::{CURRENT_USER, HSTRING};
pub(super) fn register_uri_handler() {
// To change the settings for the user, changes must be made under
// HKEY_CURRENT_USER\Software\Classes instead of under HKEY_CLASSES_ROOT since only an
// administrator can modify it. It gets merged into HKEY_CLASSES_ROOT later.
let Ok(classes_key) = CURRENT_USER.open("Software\\Classes") else {
log::error!("Failed to get current_user\\software\\classes");
return;
};
// The Windows Registry entry for Warp (assuming the channel is WarpLocal):
// warplocal
// (Default) = "WarpLocal"
// URL Protocol = ""
// DefaultIcon
// (Default) = "{path_to_channel_icon},0" TODO(CORE-2860): Add icon file path here.
// shell
// open
// command
// (Default) = "{path_to_executable}" "%0"
let uri_scheme = ChannelState::url_scheme();
match classes_key.create(uri_scheme) {
Ok(parent_key) => {
// The empty string represents the "(Default)" value for a registry key.
if let Err(err) = parent_key.set_string("", ChannelState::app_id().application_name()) {
log::error!("Could not set URI Scheme display name: {err:?}");
return;
}
if let Err(err) = parent_key.set_string("URL Protocol", "") {
log::error!("Could not set URI Scheme URL Protocol Key: {err:?}");
return;
};
// TODO(CORE-2861): Add the `DefaultIcon` Default value here with the file path to
// Warp's icon once we figure out distribution on Windows.
let command_key = match parent_key.create("shell\\open\\command") {
Ok(command_key) => command_key,
Err(err) => {
log::error!("Could not create shell\\open\\command key: {err:?}");
return;
}
};
let command = match std::env::current_exe() {
Ok(path) => {
let mut command = OsString::new();
command.push("\"");
command.push(path.as_os_str());
command.push("\" \"%0\"");
HSTRING::from(command.as_os_str())
}
Err(err) => {
log::error!("Could not get path to current executable for registering URI scheme: {err:?}");
return;
}
};
// The empty string represents the "(Default)" value for a registry key.
if let Err(err) = command_key.set_hstring("", &command) {
log::error!("Could not set shell command path for URI Scheme: {err:?}");
}
}
Err(err) => {
log::error!("Failed to create URI Scheme registry entry: {err:?}");
}
}
}
@@ -0,0 +1,58 @@
use std::sync::Arc;
use async_channel::Sender;
use async_trait::async_trait;
use ipc::{Client, ConnectionAddress};
use url::Url;
use warpui::r#async::executor::Background;
use super::single_instance_manager::uri_named_pipe_name;
/// IPC Service to respond to URIs sent to the active Warp instance.
pub(super) struct UriService {}
impl ipc::Service for UriService {
type Request = Vec<Url>;
type Response = ();
}
#[derive(Clone)]
pub(super) struct UriServiceImpl {
tx: Sender<Vec<Url>>,
}
impl UriServiceImpl {
pub(super) fn new(tx: Sender<Vec<Url>>) -> Self {
Self { tx }
}
}
#[async_trait]
impl ipc::ServiceImpl for UriServiceImpl {
type Service = UriService;
async fn handle_request(&self, request: Vec<Url>) -> () {
log::info!("Uri Service received request: {request:?}");
if let Err(send_error) = self.tx.send(request).await {
log::error!("Error sending urls to local stream: {send_error:#}");
}
}
}
/// Forwards the given URLs to the main running instance of Warp.
pub(super) async fn forward_uri_to_sole_running_instance(
urls: Vec<Url>,
) -> Result<(), ipc::ClientError> {
// We need to construct a new background executor because this function is
// run before we have a `AppContext`. We explicitly create it with
// a single backing thread, as we don't need an entire pool of threads.
let background_executor = Arc::new(Background::new(1, |_| "forward-uris".to_owned()));
let client = Client::connect(
ConnectionAddress::from(uri_named_pipe_name()),
background_executor,
)
.await?;
let uri_service_caller = ipc::service_caller::<UriService>(Arc::new(client));
let _ = uri_service_caller.call(urls).await?;
Ok(())
}
@@ -0,0 +1,150 @@
use std::sync::LazyLock;
use ipc::ServerBuilder;
use parking_lot::Mutex;
use warp_core::channel::ChannelState;
use warpui::{Entity, ModelContext, SingletonEntity};
use windows::core::Error;
use windows::Win32::Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, HANDLE};
use windows::Win32::System::Threading::CreateMutexW;
use super::service_impl::UriServiceImpl;
/// RAII wrapper around a Windows mutex HANDLE that closes it on drop.
struct MutexHandle(HANDLE);
// SAFETY: Windows kernel mutexes are valid to use from any thread. For example it says here:
// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw#remarks
// > "Any thread of the calling process can specify the mutex-object handle in a call to one of the
// wait functions"
// The [`HANDLE`] is not Send or Sync b/c it's a common type used to point to a variety of Windows
// kernel objects, many of which are not safe to access from other threads.
unsafe impl Send for MutexHandle {}
unsafe impl Sync for MutexHandle {}
impl Drop for MutexHandle {
fn drop(&mut self) {
unsafe {
let _ = CloseHandle(self.0);
}
}
}
/// The single-instance mutex handle. Lives for the process lifetime.
///
/// It's a complex type. Breaking it down:
/// * LazyLock - This type lets us go from un-initialized to initialized without `mut` and _not_
/// vice-versa.
/// * Mutex - Gives us interior mutability. Unlike `RefCell` it can be used in statics since it is
/// Sync. We don't actually need to access it on other threads though.
/// * Result - CreateMutexW might fail for reasons other than another process holding the lock. In
/// those cases, we store the error type.
/// * Option - `Some` if we are the sole instance, `None` if another instance holds the lock.
static SOLE_INSTANCE_MUTEX: LazyLock<Mutex<Result<Option<MutexHandle>, Error>>> =
LazyLock::new(|| Mutex::new(try_create_mutex()));
pub(super) fn uri_named_pipe_name() -> String {
format!("Warp{:?}_URI_CHANNEL", ChannelState::channel())
}
fn try_create_mutex() -> Result<Option<MutexHandle>, Error> {
// Scope this lock to the specific user session.
// https://learn.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces
// > "client processes can use the "Local\" prefix to explicitly create an object in their
// session namespace"
//
// NOTE: This lock name must stay in sync with `AppMutexName` in
// `script/windows/windows-installer.iss`, which the installer uses to detect whether Warp is
// running.
let name = format!("Local\\Warp{:?}_SingleInstance", ChannelState::channel())
.encode_utf16()
.chain(std::iter::once(0))
.collect::<Vec<u16>>();
let handle = unsafe { CreateMutexW(None, true, windows::core::PCWSTR(name.as_ptr())) };
// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw#return-value
let already_exists = unsafe { GetLastError() } == ERROR_ALREADY_EXISTS;
handle
.inspect_err(|err| {
log::error!("Failed to create single-instance mutex: {err:#}");
})
.map(|handle| {
if already_exists {
// Another instance already owns this mutex. Close our duplicate handle.
unsafe {
let _ = CloseHandle(handle);
}
None
} else {
Some(MutexHandle(handle))
}
})
}
/// A singleton model that is responsible for ensuring there is only one instance of Warp running.
/// Uses a Windows named mutex (via `CreateMutexW`) which is a kernel object automatically cleaned
/// up by the OS when all handles are closed, including on crash.
pub(super) struct SingleInstanceManager {
_server: Option<ipc::Server>,
}
impl SingleInstanceManager {
/// Attempts to upgrade the current Warp instance to the "main" instance (i.e. the one that
/// holds the named mutex). This function enforces that a URI server is created iff the mutex
/// is held.
pub(super) fn new(ctx: &mut ModelContext<Self>) -> Self {
if let Ok(None) | Err(_) = &*SOLE_INSTANCE_MUTEX.lock() {
return Self { _server: None };
}
let (tx, rx) = async_channel::unbounded();
let server = match ServerBuilder::default()
.with_fixed_address(uri_named_pipe_name())
.with_service(UriServiceImpl::new(tx))
.build_and_run(ctx.background_executor())
{
Ok((server, _)) => {
ctx.spawn_stream_local(
rx,
|_single_instance_manager, event, ctx| {
for uri in event {
crate::uri::handle_incoming_uri(&uri, ctx);
}
},
|_, _| {},
);
server
}
Err(err) => {
log::error!("Failed to initialize UriService Server: {err:#}");
// If we failed to create a server, we can't receive URI requests so we drop the
// lock.
*SOLE_INSTANCE_MUTEX.lock() = Ok(None);
return Self { _server: None };
}
};
Self {
_server: Some(server),
}
}
/// Returns whether or not this process should be treated as the main instance of Warp.
///
/// NOTE: If an unexpected error occurs, we return `true` since it's better to open a second
/// instance than to fail to create a first instance.
pub(super) fn is_sole_running_instance() -> Result<bool, Error> {
SOLE_INSTANCE_MUTEX
.lock()
.as_ref()
.map(|handle| handle.is_some())
.map_err(Clone::clone)
}
}
impl Entity for SingleInstanceManager {
type Event = ();
}
impl SingletonEntity for SingleInstanceManager {}