mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
various project wide changes to support win32-msvc target.
* Remove sha1 ASM acceleration (who really cares). We could specialize that feature but it's marginal.
* Create net-defaults feature so we can move p2p-unix out of the default feature set.
* I didn't figure out conditional enabling of p2p-unix though yet. See the Cargo.toml.
Maybe instead of using a feature, we just enable it for all unix platforms.
* Move UnixListener behind p2p-unix.
* darkwallet: for win32 use bundled rusqlite (used by tor-dirmgr).
* sdk: add wasm feature to avoid declaring ABI methods with no corresponding impl linked, which causes linker errors for release builds on windows.
* Move unix specific imports and function calls in src/ behind relevant config targets.
* TCP sockets on Windows call set_reuse_address().
* src/util/path.rs relevant home_dir() impls.
This commit is contained in:
18
Cargo.toml
18
Cargo.toml
@@ -68,7 +68,7 @@ futures-rustls = {version = "0.26.0", default-features = false, features = ["log
|
||||
|
||||
# Pluggable Transports
|
||||
socket2 = {version = "0.5.7", features = ["all"], optional = true}
|
||||
arti-client = {version = "0.23.0", default-features = false, features = ["async-std", "compression", "error_detail", "rustls", "accel-sha1-asm", "onion-service-client", "onion-service-service"], optional = true}
|
||||
arti-client = {version = "0.23.0", default-features = false, features = ["async-std", "compression", "error_detail", "rustls", "onion-service-client", "onion-service-service"], optional = true}
|
||||
tor-error = {version = "0.23.0", optional = true}
|
||||
tor-rtcompat = {version = "0.23.0", features = ["async-std", "rustls"], optional = true}
|
||||
tor-hscrypto = {version = "0.23.0", optional = true}
|
||||
@@ -201,7 +201,7 @@ p2p-tor = [
|
||||
"tor-cell",
|
||||
]
|
||||
|
||||
net = [
|
||||
net-defaults = [
|
||||
"async-trait",
|
||||
"ed25519-compact",
|
||||
"futures",
|
||||
@@ -227,11 +227,15 @@ net = [
|
||||
#"p2p-nym",
|
||||
]
|
||||
|
||||
p2p-unix = []
|
||||
|
||||
net = ["net-defaults"]
|
||||
|
||||
rpc = [
|
||||
"async-trait",
|
||||
"httparse",
|
||||
|
||||
"net",
|
||||
"net-defaults",
|
||||
]
|
||||
|
||||
system = [
|
||||
@@ -278,6 +282,14 @@ zk = [
|
||||
zkas = [
|
||||
"darkfi-serial",
|
||||
]
|
||||
|
||||
# Could not get this to work. Complains manifest-key is ignored.
|
||||
#[target.'cfg(target_family = "unix")'.features]
|
||||
#net = ["net-defaults", "p2p-unix"]
|
||||
#
|
||||
#[target.'cfg(target_family = "windows")'.features]
|
||||
#net = ["net-defaults"]
|
||||
|
||||
# -----END LIBRARY FEATURES-----
|
||||
|
||||
[patch.crates-io]
|
||||
|
||||
12
bin/darkwallet/Cargo.lock
generated
12
bin/darkwallet/Cargo.lock
generated
@@ -3054,7 +3054,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
[[package]]
|
||||
name = "miniquad"
|
||||
version = "0.4.7"
|
||||
source = "git+https://github.com/not-fl3/miniquad#3a5b56399ec446a35bf4a0e855dac969f920d3d4"
|
||||
source = "git+https://github.com/not-fl3/miniquad#0d4043157531b1972e58d96bcc4d4827d853d036"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"ndk-sys",
|
||||
@@ -4439,16 +4439,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"sha1-asm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1-asm"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "286acebaf8b67c1130aedffad26f594eff0c1292389158135327d2e23aed582b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -80,6 +80,10 @@ rusqlite = {version = "0.32.1", features = ["bundled"]}
|
||||
tor-dirmgr = {version="0.23.0", features=["static"]}
|
||||
#android-fileopen = {path = "./android-fileopen/"}
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
# Used by tor-dirmgr
|
||||
rusqlite = {version = "0.32", features = ["bundled"]}
|
||||
|
||||
[package.metadata.android.activity_attributes]
|
||||
"android:exported" = "true"
|
||||
"android:windowSoftInputMode" = "adjustResize"
|
||||
|
||||
141
bin/darkwallet/README.win32.md
Normal file
141
bin/darkwallet/README.win32.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Windows Build Guide (MSVC)
|
||||
|
||||
Skip the first step if you're already using Windows.
|
||||
|
||||
## Prepare the VM
|
||||
|
||||
You will need qemu and the remote-viewer tool.
|
||||
|
||||
Provision a disk:
|
||||
|
||||
```
|
||||
qemu-img create -f raw winblows-raw.disk 100G
|
||||
```
|
||||
|
||||
Download the Windows ISO from their website. Use this script to launch QEMU.
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
ISO=Win10_22H2_EnglishInternational_x64v1.iso
|
||||
|
||||
args=(
|
||||
--cdrom $ISO --boot order=d
|
||||
|
||||
-drive file=winblows-disk.raw,format=raw
|
||||
|
||||
-m 30G -accel kvm -cpu qemu64
|
||||
|
||||
# We forward 22 to 10022 for SSH
|
||||
#-net nic -net user,hostname=windowsvm
|
||||
-net nic -net user,hostname=windowsvm,hostfwd=tcp::10146-:22
|
||||
|
||||
# This fixes the fucked up mouse
|
||||
#-device qemu-xhci -device usb-mouse -device usb-tablet
|
||||
-machine vmport=off
|
||||
|
||||
# Auto-resize display
|
||||
# -vga qxl
|
||||
# We use virtio since it allows us the full res size at least
|
||||
-vga virtio -spice port=30001,disable-ticketing=on
|
||||
-device virtio-serial -chardev spicevmc,id=vdagent,debug=0,name=vdagent
|
||||
-device virtserialport,chardev=vdagent,name=com.redhat.spice.0
|
||||
)
|
||||
|
||||
qemu-system-x86_64 "${args[@]}"
|
||||
```
|
||||
|
||||
There will be no output. Use remote-viewer to attach the display:
|
||||
|
||||
```
|
||||
remote-viewer spice://localhost:30001
|
||||
```
|
||||
|
||||
Now install Windows. Then power off Windows. Download the [virtio-win ISO].
|
||||
Modify the `ISO=...` line of the script above and relaunch the VM.
|
||||
|
||||
Navigate to the CD drive in the file explorer and install the virtio x64 driver.
|
||||
|
||||
In your browser go to "spice windows guest" and scroll down the webpage.
|
||||
Download and install "Windows SPICE Guest Tools".
|
||||
|
||||
Relaunch the Windows VM. Adjust your display resolution and fullscreen the VM.
|
||||
|
||||
### (Optional) Enable SSH
|
||||
|
||||
This will enable you to work on Windows from within your host.
|
||||
|
||||
Open Settings -> Apps -> Optional features -> + Add a feature. Search
|
||||
for "OpenSSH Server" and install it.
|
||||
|
||||
Open Services -> OpenSSH SSH Server. Make "Startup type" Automatic.
|
||||
|
||||
You can now ssh into your windows and use cmd.exe. In the script above,
|
||||
we forward port 22 to 10146. You can put this in `~/.ssh/config`.
|
||||
|
||||
```
|
||||
Host winblows
|
||||
Hostname localhost
|
||||
User a
|
||||
Port 10146
|
||||
```
|
||||
|
||||
You can also make an `/etc/fstab` entry with:
|
||||
|
||||
```
|
||||
sshfs#winblows: /mnt/winblows fuse noauto,defaults 0 0
|
||||
```
|
||||
|
||||
Then `mount /mnt/winblows && cd /mnt/winblows/.ssh/` and copy your SSH pubkey
|
||||
to `authorized_keys`.
|
||||
Open "This PC", View -> Hidden files, open `C:\ProgramData\ssh\sshd_config`
|
||||
to disable password login and just use pubkey auth.
|
||||
Also disable the administrator auth keys setting in there too (bottom 2 lines).
|
||||
Then restart SSH.
|
||||
|
||||
## Setting Up the Dev Environment
|
||||
|
||||
Install rustup, which will also install Visual Studio. Next, next, finish.
|
||||
After visual studio, it will then proceed with the rustup install.
|
||||
Select 2 and enter nightly.
|
||||
|
||||
```
|
||||
1) Proceed with standard installation (default - just press enter)
|
||||
2) Customize installation
|
||||
3) Cancel installation
|
||||
>2
|
||||
|
||||
Default host triple? [x86_64-pc-windows-msvc]
|
||||
(leave this unchanged)
|
||||
|
||||
Default toolchain? (stable/beta/nightly/none)
|
||||
nightly
|
||||
|
||||
Profile (which tools and data to install)? (minimal/default/complete) [default]
|
||||
(leave this unchanged)
|
||||
```
|
||||
|
||||
Then proceed with the installation (option 1).
|
||||
|
||||
## Building the DarkFi App
|
||||
|
||||
Go to the [codeberg repo] and select "⋯", then Download ZIP. Unzip the folder
|
||||
in an accessible place.
|
||||
|
||||
Open cmd and navigate to the folder. Now run `cargo build`.
|
||||
|
||||
```
|
||||
C:\Users\a> cd ../../darkfi/bin/darkwallet/
|
||||
C:\darkfi\bin\darkwallet> cargo build
|
||||
```
|
||||
|
||||
## (Optional) Mesa GL
|
||||
|
||||
This is buggy af software renderer.
|
||||
|
||||
* Setup OpenGL using [this guide](https://thomas.inf3.ch/2019-06-12-opengl-kvm-mesa3d/index.html).
|
||||
* Download [mesa3d-xxx-release-msvc.7z](https://github.com/pal1000/mesa-dist-win/releases)
|
||||
and install the default options.
|
||||
|
||||
[virtio-win ISO]: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/virtio-win.iso
|
||||
|
||||
@@ -33,6 +33,7 @@ darkfi-contract-test-harness = {path = "../test-harness"}
|
||||
# so the wasm32-unknown-unknown target is enabled.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.8", features = ["custom"] }
|
||||
darkfi-sdk = { path = "../../sdk", features = ["wasm"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -29,6 +29,7 @@ smol = "2.0.2"
|
||||
# so the wasm32-unknown-unknown target is enabled.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.8", features = ["custom"] }
|
||||
darkfi-sdk = { path = "../../sdk", features = ["wasm"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -35,6 +35,7 @@ darkfi-contract-test-harness = {path = "../test-harness"}
|
||||
# so the wasm32-unknown-unknown target is enabled.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.8", features = ["custom"] }
|
||||
darkfi-sdk = { path = "../../sdk", features = ["wasm"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -24,11 +24,7 @@ use std::sync::{
|
||||
use futures::{stream::FuturesUnordered, TryFutureExt};
|
||||
use futures_rustls::rustls::crypto::{ring, CryptoProvider};
|
||||
use log::{debug, error, info, warn};
|
||||
use smol::{
|
||||
fs::{self, unix::PermissionsExt},
|
||||
lock::RwLock as AsyncRwLock,
|
||||
stream::StreamExt,
|
||||
};
|
||||
use smol::{fs, lock::RwLock as AsyncRwLock, stream::StreamExt};
|
||||
use url::Url;
|
||||
|
||||
use super::{
|
||||
@@ -49,6 +45,9 @@ use crate::{
|
||||
Result,
|
||||
};
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use smol::fs::unix::PermissionsExt;
|
||||
|
||||
/// Atomic pointer to the p2p interface
|
||||
pub type P2pPtr = Arc<P2p>;
|
||||
|
||||
@@ -92,6 +91,8 @@ impl P2p {
|
||||
if let Some(ref datastore) = settings.p2p_datastore {
|
||||
let datastore = expand_path(datastore)?;
|
||||
fs::create_dir_all(&datastore).await?;
|
||||
// Windows only has readonly so don't worry about it
|
||||
#[cfg(target_family = "unix")]
|
||||
fs::set_permissions(&datastore, PermissionsExt::from_mode(0o700)).await?;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
io::{self, ErrorKind},
|
||||
time::Duration,
|
||||
};
|
||||
use std::{io, time::Duration};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use log::error;
|
||||
use smol::io::{AsyncRead, AsyncWrite};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
use std::io::ErrorKind;
|
||||
|
||||
/// TLS upgrade mechanism
|
||||
pub(crate) mod tls;
|
||||
|
||||
@@ -44,6 +44,7 @@ pub(crate) mod tor;
|
||||
pub(crate) mod nym;
|
||||
|
||||
/// Unix socket transport
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
pub(crate) mod unix;
|
||||
|
||||
/// Dialer variants
|
||||
@@ -72,6 +73,7 @@ pub enum DialerVariant {
|
||||
NymTls(nym::NymDialer),
|
||||
|
||||
/// Unix socket
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
Unix(unix::UnixDialer),
|
||||
|
||||
/// SOCKS5 proxy
|
||||
@@ -92,6 +94,7 @@ pub enum ListenerVariant {
|
||||
Tor(tor::TorListener),
|
||||
|
||||
/// Unix socket
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
Unix(unix::UnixListener),
|
||||
}
|
||||
|
||||
@@ -111,6 +114,7 @@ macro_rules! enforce_hostport {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
macro_rules! enforce_abspath {
|
||||
($endpoint:ident) => {
|
||||
if $endpoint.host_str().is_some() || $endpoint.port().is_some() {
|
||||
@@ -179,6 +183,7 @@ impl Dialer {
|
||||
Ok(Self { endpoint, variant })
|
||||
}
|
||||
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
"unix" => {
|
||||
// Build a Unix socket dialer
|
||||
enforce_abspath!(endpoint);
|
||||
@@ -252,6 +257,7 @@ impl Dialer {
|
||||
todo!();
|
||||
}
|
||||
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
DialerVariant::Unix(dialer) => {
|
||||
let path = match self.endpoint.to_file_path() {
|
||||
Ok(v) => v,
|
||||
@@ -312,6 +318,7 @@ impl Listener {
|
||||
Ok(Self { endpoint, variant })
|
||||
}
|
||||
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
"unix" => {
|
||||
enforce_abspath!(endpoint);
|
||||
let variant = unix::UnixListener::new().await?;
|
||||
@@ -351,6 +358,7 @@ impl Listener {
|
||||
Ok(Box::new(l))
|
||||
}
|
||||
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
ListenerVariant::Unix(listener) => {
|
||||
let path = match self.endpoint.to_file_path() {
|
||||
Ok(v) => v,
|
||||
@@ -384,6 +392,7 @@ impl PtStream for arti_client::DataStream {}
|
||||
#[cfg(feature = "p2p-tor")]
|
||||
impl PtStream for futures_rustls::TlsStream<arti_client::DataStream> {}
|
||||
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
impl PtStream for smol::net::unix::UnixStream {}
|
||||
|
||||
/// Wrapper trait for async listeners
|
||||
|
||||
@@ -34,6 +34,23 @@ use url::Url;
|
||||
|
||||
use super::{PtListener, PtStream};
|
||||
|
||||
trait SocketExt {
|
||||
fn enable_reuse_port(&self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl SocketExt for Socket {
|
||||
fn enable_reuse_port(&self) -> io::Result<()> {
|
||||
#[cfg(target_family = "unix")]
|
||||
self.set_reuse_port(true)?;
|
||||
|
||||
// On Windows SO_REUSEPORT means the same thing as SO_REUSEADDR
|
||||
#[cfg(target_family = "windows")]
|
||||
self.set_reuse_address(true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP Dialer implementation
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TcpDialer {
|
||||
@@ -63,7 +80,7 @@ impl TcpDialer {
|
||||
socket.set_nodelay(true)?;
|
||||
let keepalive = TcpKeepalive::new().with_time(Duration::from_secs(20));
|
||||
socket.set_tcp_keepalive(&keepalive)?;
|
||||
socket.set_reuse_port(true)?;
|
||||
socket.enable_reuse_port()?;
|
||||
|
||||
Ok(socket)
|
||||
}
|
||||
@@ -147,7 +164,7 @@ impl TcpListener {
|
||||
socket.set_nodelay(true)?;
|
||||
let keepalive = TcpKeepalive::new().with_time(Duration::from_secs(20));
|
||||
socket.set_tcp_keepalive(&keepalive)?;
|
||||
socket.set_reuse_port(true)?;
|
||||
socket.enable_reuse_port()?;
|
||||
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ doctest = false
|
||||
[features]
|
||||
default = []
|
||||
async = ["darkfi-serial/async"]
|
||||
wasm = []
|
||||
|
||||
[dependencies]
|
||||
# Error handling
|
||||
|
||||
@@ -75,6 +75,7 @@ mod test;
|
||||
pub mod util;
|
||||
pub use util::Poseidon;
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
pub mod wasmdb;
|
||||
|
||||
// Bit size for Fp (and Fq)
|
||||
|
||||
@@ -52,5 +52,6 @@ pub use tx::ContractCall;
|
||||
pub mod util;
|
||||
|
||||
#[macro_use]
|
||||
#[cfg(feature = "wasm")]
|
||||
/// WASM API functions
|
||||
pub mod wasm;
|
||||
|
||||
@@ -18,59 +18,80 @@
|
||||
|
||||
use std::{
|
||||
env,
|
||||
ffi::{CStr, OsString},
|
||||
fs, mem,
|
||||
os::unix::prelude::OsStringExt,
|
||||
ffi::OsString,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
ptr,
|
||||
};
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// Returns the path to the user's home directory.
|
||||
/// Use `$HOME`, fallbacks to `libc::getpwuid_r`, otherwise `None`.
|
||||
pub fn home_dir() -> Option<PathBuf> {
|
||||
env::var_os("HOME")
|
||||
.and_then(|h| if h.is_empty() { None } else { Some(h) })
|
||||
.or_else(|| unsafe { home_fallback() })
|
||||
.map(PathBuf::from)
|
||||
}
|
||||
|
||||
/// Get the home directory from the passwd entry of the current user using
|
||||
/// `getpwuid_r(3)`. If it manages, returns an `OsString`, otherwise returns `None`.
|
||||
unsafe fn home_fallback() -> Option<OsString> {
|
||||
let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
|
||||
n if n < 0 => 512_usize,
|
||||
n => n as usize,
|
||||
#[cfg(target_family = "unix")]
|
||||
mod home_dir_impl {
|
||||
use std::{
|
||||
env,
|
||||
ffi::{CStr, OsString},
|
||||
mem,
|
||||
os::unix::prelude::OsStringExt,
|
||||
path::PathBuf,
|
||||
ptr,
|
||||
};
|
||||
|
||||
let mut buf = Vec::with_capacity(amt);
|
||||
let mut passwd: libc::passwd = mem::zeroed();
|
||||
let mut result = ptr::null_mut();
|
||||
/// Returns the path to the user's home directory.
|
||||
/// Use `$HOME`, fallbacks to `libc::getpwuid_r`, otherwise `None`.
|
||||
pub fn home_dir() -> Option<PathBuf> {
|
||||
env::var_os("HOME")
|
||||
.and_then(|h| if h.is_empty() { None } else { Some(h) })
|
||||
.or_else(|| unsafe { home_fallback() })
|
||||
.map(PathBuf::from)
|
||||
}
|
||||
|
||||
let r = libc::getpwuid_r(
|
||||
libc::getuid(),
|
||||
&mut passwd,
|
||||
buf.as_mut_ptr(),
|
||||
buf.capacity(),
|
||||
&mut result,
|
||||
);
|
||||
/// Get the home directory from the passwd entry of the current user using
|
||||
/// `getpwuid_r(3)`. If it manages, returns an `OsString`, otherwise returns `None`.
|
||||
unsafe fn home_fallback() -> Option<OsString> {
|
||||
let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
|
||||
n if n < 0 => 512_usize,
|
||||
n => n as usize,
|
||||
};
|
||||
|
||||
match r {
|
||||
0 if !result.is_null() => {
|
||||
let ptr = passwd.pw_dir as *const _;
|
||||
let bytes = CStr::from_ptr(ptr).to_bytes();
|
||||
if bytes.is_empty() {
|
||||
return None
|
||||
let mut buf = Vec::with_capacity(amt);
|
||||
let mut passwd: libc::passwd = mem::zeroed();
|
||||
let mut result = ptr::null_mut();
|
||||
|
||||
let r = libc::getpwuid_r(
|
||||
libc::getuid(),
|
||||
&mut passwd,
|
||||
buf.as_mut_ptr(),
|
||||
buf.capacity(),
|
||||
&mut result,
|
||||
);
|
||||
|
||||
match r {
|
||||
0 if !result.is_null() => {
|
||||
let ptr = passwd.pw_dir as *const _;
|
||||
let bytes = CStr::from_ptr(ptr).to_bytes();
|
||||
if bytes.is_empty() {
|
||||
return None
|
||||
}
|
||||
|
||||
Some(OsStringExt::from_vec(bytes.to_vec()))
|
||||
}
|
||||
|
||||
Some(OsStringExt::from_vec(bytes.to_vec()))
|
||||
_ => None,
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
mod home_dir_impl {
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
pub fn home_dir() -> Option<PathBuf> {
|
||||
env::var_os("APPDATA").map(PathBuf::from)
|
||||
}
|
||||
}
|
||||
|
||||
pub use home_dir_impl::home_dir;
|
||||
|
||||
/// Returns `$XDG_CONFIG_HOME`, `$HOME/.config`, or `None`.
|
||||
pub fn config_dir() -> Option<PathBuf> {
|
||||
env::var_os("XDG_CONFIG_HOME")
|
||||
|
||||
Reference in New Issue
Block a user