mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-06-22 04:11:32 +10:00
Compare commits
2 Commits
0290718ee0
...
cbecd1161d
| Author | SHA1 | Date | |
|---|---|---|---|
| cbecd1161d | |||
| 9185089c99 |
@@ -6,6 +6,7 @@ on:
|
|||||||
branches: [develop]
|
branches: [develop]
|
||||||
paths:
|
paths:
|
||||||
- "sites/promo/**"
|
- "sites/promo/**"
|
||||||
|
- "sites/docs/**"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "pnpm-lock.yaml"
|
- "pnpm-lock.yaml"
|
||||||
- "pnpm-workspace.yaml"
|
- "pnpm-workspace.yaml"
|
||||||
@@ -43,10 +44,11 @@ jobs:
|
|||||||
node-version: "22"
|
node-version: "22"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
# Only install the promo site (radiant) and its dependencies so the public
|
# Only install the promo site (radiant) and docs site (docs-next) and their
|
||||||
# website deploy stays decoupled from the server/desktop build pipelines.
|
# dependencies so the public website deploy stays decoupled from the
|
||||||
|
# server/desktop build pipelines.
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --filter radiant...
|
run: pnpm install --filter radiant... --filter docs-next...
|
||||||
|
|
||||||
- name: Setup Pages
|
- name: Setup Pages
|
||||||
id: setup_pages
|
id: setup_pages
|
||||||
@@ -63,12 +65,24 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml') }}-
|
${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml') }}-
|
||||||
|
|
||||||
- name: Build with Next.js
|
- name: Build promo site with Next.js
|
||||||
working-directory: sites/promo
|
working-directory: sites/promo
|
||||||
run: pnpm run build
|
run: pnpm run build
|
||||||
env:
|
env:
|
||||||
PAGES_BASE_PATH: ${{ steps.setup_pages.outputs.base_path }}
|
PAGES_BASE_PATH: ${{ steps.setup_pages.outputs.base_path }}
|
||||||
|
|
||||||
|
- name: Build docs site with Astro
|
||||||
|
working-directory: sites/docs
|
||||||
|
run: pnpm run build
|
||||||
|
|
||||||
|
# Nest the Starlight docs (built with base: "/docs") inside the promo export
|
||||||
|
# so both ship from a single GitHub Pages deployment at /docs.
|
||||||
|
- name: Assemble docs into /docs
|
||||||
|
run: |
|
||||||
|
rm -rf sites/promo/out/docs
|
||||||
|
mkdir -p sites/promo/out/docs
|
||||||
|
cp -r sites/docs/dist/. sites/promo/out/docs/
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
+9
-1
@@ -63,6 +63,14 @@ FROM base AS run-system
|
|||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV NUXT_TELEMETRY_DISABLED=1
|
ENV NUXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
# The base stage's `COPY . .` puts the whole repo into the runtime WORKDIR (/app),
|
||||||
|
# but at runtime only the artifacts copied explicitly below are needed. Drop the
|
||||||
|
# inherited `torrential` source dir: the service resolves the binary by scanning
|
||||||
|
# the cwd for `torrential`, and a directory there is spawned as ./torrential and
|
||||||
|
# fails with EACCES. With it gone, resolution falls through to the `torrential`
|
||||||
|
# binary installed on PATH (/usr/bin/torrential) below.
|
||||||
|
RUN rm -rf /app/torrential
|
||||||
|
|
||||||
# RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn add --network-timeout 1000000 --no-lockfile --ignore-scripts prisma@6.11.1
|
# RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn add --network-timeout 1000000 --no-lockfile --ignore-scripts prisma@6.11.1
|
||||||
## runtime deps:
|
## runtime deps:
|
||||||
## - libarchive13: torrential now links libarchive dynamically (glibc build)
|
## - libarchive13: torrential now links libarchive dynamically (glibc build)
|
||||||
@@ -77,7 +85,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
RUN pnpm install prisma@7.3.0 --global
|
RUN pnpm install prisma@7.7.0 --global
|
||||||
# init prisma to download all required files
|
# init prisma to download all required files
|
||||||
RUN pnpm prisma init
|
RUN pnpm prisma init
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
# Drop
|
# Drop
|
||||||
|
|
||||||
[](https://droposs.org)
|
[](https://droposs.org)
|
||||||
[](https://docs.droposs.org/)
|
[](https://droposs.org/docs)
|
||||||
[](https://forum.droposs.org)
|
[](https://forum.droposs.org)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://discord.gg/ACq4qZp4a9)
|
[](https://discord.gg/ACq4qZp4a9)
|
||||||
@@ -28,7 +28,7 @@ Drop is an open-source game distribution platform, similar to GameVault or Steam
|
|||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
See our documentation on how to [deploy Drop](https://docs.droposs.org/docs/guides/quickstart) for more information.
|
See our documentation on how to [deploy Drop](https://droposs.org/docs/admin/quickstart) for more information.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<Listbox
|
||||||
|
as="div"
|
||||||
|
v-model="model.overrideHandler"
|
||||||
|
class="mt-6"
|
||||||
|
v-if="handlers.length > 1"
|
||||||
|
>
|
||||||
|
<ListboxLabel class="block text-sm/6 font-medium text-white"
|
||||||
|
>Launch method</ListboxLabel
|
||||||
|
>
|
||||||
|
<div class="relative mt-2">
|
||||||
|
<ListboxButton
|
||||||
|
class="grid w-full cursor-default grid-cols-1 rounded-md bg-white/5 py-1.5 pr-2 pl-3 text-left text-white outline-1 -outline-offset-1 outline-white/10 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-500 sm:text-sm/6"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="currentHandler"
|
||||||
|
class="col-start-1 row-start-1 truncate pr-6"
|
||||||
|
>{{ currentHandler.name }}</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="col-start-1 row-start-1 truncate pr-6 italic text-zinc-400"
|
||||||
|
>Automatic</span
|
||||||
|
>
|
||||||
|
<ChevronUpDownIcon
|
||||||
|
class="col-start-1 row-start-1 size-5 self-center justify-self-end text-zinc-400 sm:size-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</ListboxButton>
|
||||||
|
|
||||||
|
<transition
|
||||||
|
leave-active-class="transition ease-in duration-100"
|
||||||
|
leave-from-class=""
|
||||||
|
leave-to-class="opacity-0"
|
||||||
|
>
|
||||||
|
<ListboxOptions
|
||||||
|
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-800 py-1 text-base outline-1 -outline-offset-1 outline-white/10 sm:text-sm"
|
||||||
|
>
|
||||||
|
<ListboxOption
|
||||||
|
as="template"
|
||||||
|
:value="undefined"
|
||||||
|
v-slot="{ active, selected }"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
:class="[
|
||||||
|
active ? 'bg-blue-500 text-white outline-hidden' : 'text-white',
|
||||||
|
'relative cursor-default py-2 pr-9 pl-3 select-none',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
selected ? 'font-semibold' : 'font-normal',
|
||||||
|
'block truncate italic',
|
||||||
|
]"
|
||||||
|
>Automatic</span
|
||||||
|
>
|
||||||
|
<span class="block truncate text-xs text-zinc-400"
|
||||||
|
>Pick the best method for this game.</span
|
||||||
|
>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="selected"
|
||||||
|
:class="[
|
||||||
|
active ? 'text-white' : 'text-blue-400',
|
||||||
|
'absolute inset-y-0 right-0 flex items-center pr-4',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<CheckIcon class="size-5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ListboxOption>
|
||||||
|
<ListboxOption
|
||||||
|
as="template"
|
||||||
|
v-for="handler in handlers"
|
||||||
|
:key="handler.id"
|
||||||
|
:value="handler.id"
|
||||||
|
v-slot="{ active, selected }"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
:class="[
|
||||||
|
active ? 'bg-blue-500 text-white outline-hidden' : 'text-white',
|
||||||
|
'relative cursor-default py-2 pr-9 pl-3 select-none',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
selected ? 'font-semibold' : 'font-normal',
|
||||||
|
'block truncate',
|
||||||
|
]"
|
||||||
|
>{{ handler.name }}</span
|
||||||
|
>
|
||||||
|
<span class="block truncate text-xs text-zinc-400">{{
|
||||||
|
handler.description
|
||||||
|
}}</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="selected"
|
||||||
|
:class="[
|
||||||
|
active ? 'text-white' : 'text-blue-400',
|
||||||
|
'absolute inset-y-0 right-0 flex items-center pr-4',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<CheckIcon class="size-5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ListboxOption>
|
||||||
|
</ListboxOptions>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-sm text-zinc-400">
|
||||||
|
Override how this game is launched.
|
||||||
|
</p>
|
||||||
|
</Listbox>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import {
|
||||||
|
Listbox,
|
||||||
|
ListboxButton,
|
||||||
|
ListboxLabel,
|
||||||
|
ListboxOption,
|
||||||
|
ListboxOptions,
|
||||||
|
} from "@headlessui/vue";
|
||||||
|
import { ChevronUpDownIcon } from "@heroicons/vue/16/solid";
|
||||||
|
import { CheckIcon } from "@heroicons/vue/20/solid";
|
||||||
|
import type { GameVersion } from "~/types";
|
||||||
|
|
||||||
|
type ProcessHandlerOption = { id: string; name: string; description: string };
|
||||||
|
|
||||||
|
const model = defineModel<GameVersion["userConfiguration"]>({ required: true });
|
||||||
|
const props = defineProps<{ gameId: string }>();
|
||||||
|
|
||||||
|
const handlers = await invoke<ProcessHandlerOption[]>("get_process_handlers", {
|
||||||
|
id: props.gameId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentHandler = computed(() =>
|
||||||
|
handlers.find((v) => v.id == model.value.overrideHandler),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
@@ -23,16 +23,19 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ProtonSelector v-model="model" v-if="$props.protonEnabled" />
|
<ProtonSelector v-model="model" v-if="$props.protonEnabled" />
|
||||||
|
<HandlerSelector v-model="model" :game-id="$props.gameId" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { GameVersion } from "~/types";
|
import type { GameVersion } from "~/types";
|
||||||
import ProtonSelector from "./ProtonSelector.vue";
|
import ProtonSelector from "./ProtonSelector.vue";
|
||||||
|
import HandlerSelector from "./HandlerSelector.vue";
|
||||||
|
|
||||||
const model = defineModel<GameVersion["userConfiguration"]>({ required: true });
|
const model = defineModel<GameVersion["userConfiguration"]>({ required: true });
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
protonEnabled: boolean;
|
protonEnabled: boolean;
|
||||||
|
gameId: string;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<ModalTemplate size-class="max-w-4xl" v-model="open">
|
<ModalTemplate size-class="max-w-4xl" v-model="open">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="flex flex-row gap-x-4 h-96">
|
<div class="flex flex-row gap-x-4 min-h-96">
|
||||||
<nav class="flex flex-1 flex-col" aria-label="Sidebar">
|
<nav class="flex flex-1 flex-col" aria-label="Sidebar">
|
||||||
<ul role="list" class="-mx-2 space-y-1">
|
<ul role="list" class="-mx-2 space-y-1">
|
||||||
<li v-for="(tab, tabIdx) in tabs" :key="tab.name">
|
<li v-for="(tab, tabIdx) in tabs" :key="tab.name">
|
||||||
@@ -29,11 +29,12 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="border-l-2 border-zinc-800 w-full grow pl-4 overflow-y-scroll">
|
<div class="border-l-2 border-zinc-800 w-full grow pl-4">
|
||||||
<component
|
<component
|
||||||
v-model="configuration"
|
v-model="configuration"
|
||||||
:is="tabs[currentTabIndex]?.page"
|
:is="tabs[currentTabIndex]?.page"
|
||||||
:proton-enabled="protonEnabled"
|
:proton-enabled="protonEnabled"
|
||||||
|
:game-id="props.gameId"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"koa": "^2.16.1",
|
"koa": "^2.16.1",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"micromark": "^4.0.1",
|
"micromark": "^4.0.1",
|
||||||
"nuxt": "^3.21.6",
|
"nuxt": "^4.4.8",
|
||||||
"scss": "^0.2.4",
|
"scss": "^0.2.4",
|
||||||
"vue-router": "latest",
|
"vue-router": "latest",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
|
|||||||
Generated
+794
-1525
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,7 @@ export type GameVersion = {
|
|||||||
userConfiguration: {
|
userConfiguration: {
|
||||||
launchTemplate: string;
|
launchTemplate: string;
|
||||||
overrideProtonPath: string;
|
overrideProtonPath: string;
|
||||||
|
overrideHandler: string | undefined;
|
||||||
enableUpdates: boolean
|
enableUpdates: boolean
|
||||||
};
|
};
|
||||||
setups: Array<{ platform: string }>;
|
setups: Array<{ platform: string }>;
|
||||||
|
|||||||
Generated
+1
@@ -1392,6 +1392,7 @@ dependencies = [
|
|||||||
"http-serde 2.1.1",
|
"http-serde 2.1.1",
|
||||||
"humansize",
|
"humansize",
|
||||||
"known-folders",
|
"known-folders",
|
||||||
|
"libloading",
|
||||||
"log",
|
"log",
|
||||||
"log4rs",
|
"log4rs",
|
||||||
"md5 0.7.0",
|
"md5 0.7.0",
|
||||||
|
|||||||
@@ -136,6 +136,9 @@ tauri-build = { version = "*", features = [] }
|
|||||||
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies]
|
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies]
|
||||||
tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
|
tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
|
||||||
|
|
||||||
|
[target."cfg(target_os = \"linux\")".dependencies]
|
||||||
|
libloading = "0.7"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ pub mod data {
|
|||||||
UserConfiguration {
|
UserConfiguration {
|
||||||
launch_template: "{}".to_owned(),
|
launch_template: "{}".to_owned(),
|
||||||
override_proton_path: None,
|
override_proton_path: None,
|
||||||
|
override_handler: None,
|
||||||
enable_updates: false,
|
enable_updates: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,6 +89,8 @@ pub mod data {
|
|||||||
pub struct UserConfiguration {
|
pub struct UserConfiguration {
|
||||||
pub launch_template: String,
|
pub launch_template: String,
|
||||||
pub override_proton_path: Option<String>,
|
pub override_proton_path: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub override_handler: Option<String>,
|
||||||
pub enable_updates: bool,
|
pub enable_updates: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
use std::{fs::create_dir_all, path::PathBuf, process::Command};
|
use std::{
|
||||||
|
fs::create_dir_all,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Command,
|
||||||
|
};
|
||||||
|
|
||||||
use client::compat::{COMPAT_INFO, UMU_LAUNCHER_EXECUTABLE};
|
use client::compat::{COMPAT_INFO, UMU_LAUNCHER_EXECUTABLE};
|
||||||
use database::{
|
use database::{
|
||||||
Database, DownloadableMetadata, GameVersion, db::DATA_ROOT_DIR, platform::Platform,
|
Database, DownloadableMetadata, GameVersion, db::DATA_ROOT_DIR, platform::Platform,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{error::ProcessError, process_manager::ProcessHandler};
|
use crate::{error::ProcessError, parser::ParsedCommand, process_manager::ProcessHandler};
|
||||||
|
|
||||||
pub struct MacLauncher;
|
pub struct MacLauncher;
|
||||||
impl ProcessHandler for MacLauncher {
|
impl ProcessHandler for MacLauncher {
|
||||||
@@ -25,11 +29,89 @@ impl ProcessHandler for MacLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn modify_command(&self, _command: &mut Command) {}
|
fn modify_command(&self, _command: &mut Command) {}
|
||||||
|
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"macos"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Direct"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Launches the game directly on macOS."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_os = "windows"), allow(unused_variables))]
|
||||||
|
fn apply_no_window(command: &mut Command) {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
command.creation_flags(CREATE_NO_WINDOW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WindowsLaunchStrategy {
|
||||||
|
Direct,
|
||||||
|
Cmd,
|
||||||
|
Powershell,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap a launch command for Windows; with no strategy, detect it from the file extension.
|
||||||
|
fn windows_launch_command(
|
||||||
|
launch_command: String,
|
||||||
|
current_dir: &str,
|
||||||
|
strategy: Option<WindowsLaunchStrategy>,
|
||||||
|
) -> Result<String, ProcessError> {
|
||||||
|
let mut parsed = ParsedCommand::parse(launch_command)?;
|
||||||
|
|
||||||
|
let strategy = strategy.unwrap_or_else(|| {
|
||||||
|
let extension = Path::new(&parsed.command)
|
||||||
|
.extension()
|
||||||
|
.and_then(|ext| ext.to_str())
|
||||||
|
.map(str::to_ascii_lowercase);
|
||||||
|
match extension.as_deref() {
|
||||||
|
Some("ps1") => WindowsLaunchStrategy::Powershell,
|
||||||
|
Some("exe") | Some("com") => WindowsLaunchStrategy::Direct,
|
||||||
|
_ => WindowsLaunchStrategy::Cmd,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match strategy {
|
||||||
|
// PowerShell scripts
|
||||||
|
WindowsLaunchStrategy::Powershell => {
|
||||||
|
parsed.make_absolute(PathBuf::from(current_dir));
|
||||||
|
let script = std::mem::replace(&mut parsed.command, "powershell".to_owned());
|
||||||
|
let mut args = vec![
|
||||||
|
"-NoProfile".to_owned(),
|
||||||
|
"-ExecutionPolicy".to_owned(),
|
||||||
|
"Bypass".to_owned(),
|
||||||
|
"-File".to_owned(),
|
||||||
|
script,
|
||||||
|
];
|
||||||
|
args.append(&mut parsed.args);
|
||||||
|
parsed.args = args;
|
||||||
|
}
|
||||||
|
// Direct executables
|
||||||
|
WindowsLaunchStrategy::Direct => {
|
||||||
|
parsed.make_absolute(PathBuf::from(current_dir));
|
||||||
|
}
|
||||||
|
// cmd.exe, for batch files, builtins, PATHEXT resolution, %VAR% expansion, etc.
|
||||||
|
WindowsLaunchStrategy::Cmd => {
|
||||||
|
let command = std::mem::replace(&mut parsed.command, "cmd".to_owned());
|
||||||
|
let mut args = vec!["/C".to_owned(), command];
|
||||||
|
args.append(&mut parsed.args);
|
||||||
|
parsed.args = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(parsed.reconstruct())
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WindowsLauncher;
|
pub struct WindowsLauncher;
|
||||||
impl ProcessHandler for WindowsLauncher {
|
impl ProcessHandler for WindowsLauncher {
|
||||||
fn create_launch_process(
|
fn create_launch_process(
|
||||||
@@ -37,22 +119,169 @@ impl ProcessHandler for WindowsLauncher {
|
|||||||
_meta: &DownloadableMetadata,
|
_meta: &DownloadableMetadata,
|
||||||
launch_command: String,
|
launch_command: String,
|
||||||
_game_version: &GameVersion,
|
_game_version: &GameVersion,
|
||||||
_current_dir: &str,
|
current_dir: &str,
|
||||||
_database: &Database,
|
_database: &Database,
|
||||||
) -> Result<String, ProcessError> {
|
) -> Result<String, ProcessError> {
|
||||||
Ok(format!("pwsh \"cmd /C \"{}\"\"", launch_command))
|
windows_launch_command(launch_command, current_dir, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn modify_command(&self, command: &mut Command) {
|
fn modify_command(&self, command: &mut Command) {
|
||||||
#[cfg(target_os = "windows")]
|
apply_no_window(command);
|
||||||
use std::os::windows::process::CommandExt;
|
}
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
command.creation_flags(CREATE_NO_WINDOW);
|
fn id(&self) -> &'static str {
|
||||||
|
"windows-auto"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Automatic"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Detects the file type and launches it directly, or through cmd or PowerShell."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WindowsDirectLauncher;
|
||||||
|
impl ProcessHandler for WindowsDirectLauncher {
|
||||||
|
fn create_launch_process(
|
||||||
|
&self,
|
||||||
|
_meta: &DownloadableMetadata,
|
||||||
|
launch_command: String,
|
||||||
|
_game_version: &GameVersion,
|
||||||
|
current_dir: &str,
|
||||||
|
_database: &Database,
|
||||||
|
) -> Result<String, ProcessError> {
|
||||||
|
windows_launch_command(launch_command, current_dir, Some(WindowsLaunchStrategy::Direct))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modify_command(&self, command: &mut Command) {
|
||||||
|
apply_no_window(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"windows-direct"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Direct executable"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Runs the executable directly, without a shell."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WindowsCmdLauncher;
|
||||||
|
impl ProcessHandler for WindowsCmdLauncher {
|
||||||
|
fn create_launch_process(
|
||||||
|
&self,
|
||||||
|
_meta: &DownloadableMetadata,
|
||||||
|
launch_command: String,
|
||||||
|
_game_version: &GameVersion,
|
||||||
|
current_dir: &str,
|
||||||
|
_database: &Database,
|
||||||
|
) -> Result<String, ProcessError> {
|
||||||
|
windows_launch_command(launch_command, current_dir, Some(WindowsLaunchStrategy::Cmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modify_command(&self, command: &mut Command) {
|
||||||
|
apply_no_window(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"windows-cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Command Prompt (cmd)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Launches through cmd.exe. Supports batch files, builtins and %VAR% expansion."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WindowsPowershellLauncher;
|
||||||
|
impl ProcessHandler for WindowsPowershellLauncher {
|
||||||
|
fn create_launch_process(
|
||||||
|
&self,
|
||||||
|
_meta: &DownloadableMetadata,
|
||||||
|
launch_command: String,
|
||||||
|
_game_version: &GameVersion,
|
||||||
|
current_dir: &str,
|
||||||
|
_database: &Database,
|
||||||
|
) -> Result<String, ProcessError> {
|
||||||
|
windows_launch_command(
|
||||||
|
launch_command,
|
||||||
|
current_dir,
|
||||||
|
Some(WindowsLaunchStrategy::Powershell),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modify_command(&self, command: &mut Command) {
|
||||||
|
apply_no_window(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"windows-powershell"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"PowerShell"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Runs the command as a PowerShell script (-File)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LinuxNativeLauncher;
|
||||||
|
impl ProcessHandler for LinuxNativeLauncher {
|
||||||
|
fn create_launch_process(
|
||||||
|
&self,
|
||||||
|
_meta: &DownloadableMetadata,
|
||||||
|
launch_command: String,
|
||||||
|
_game_version: &GameVersion,
|
||||||
|
_current_dir: &str,
|
||||||
|
_database: &Database,
|
||||||
|
) -> Result<String, ProcessError> {
|
||||||
|
// Run native Linux games directly, no umu-run wrapper
|
||||||
|
Ok(launch_command)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modify_command(&self, _command: &mut Command) {}
|
||||||
|
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"linux-native"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Native (direct)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Runs the native Linux game directly on the host."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +330,18 @@ impl ProcessHandler for UMUNativeLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn modify_command(&self, _command: &mut Command) {}
|
fn modify_command(&self, _command: &mut Command) {}
|
||||||
|
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"linux-umu"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Steam Linux Runtime (umu-run)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Runs the native Linux game inside umu-run's Steam Linux Runtime."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UMUCompatLauncher;
|
pub struct UMUCompatLauncher;
|
||||||
@@ -168,6 +409,18 @@ impl ProcessHandler for UMUCompatLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn modify_command(&self, _command: &mut Command) {}
|
fn modify_command(&self, _command: &mut Command) {}
|
||||||
|
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"proton-umu"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Proton (umu-run)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Runs the Windows game through Proton using umu-run."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AsahiMuvmLauncher;
|
pub struct AsahiMuvmLauncher;
|
||||||
@@ -228,4 +481,16 @@ impl ProcessHandler for AsahiMuvmLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn modify_command(&self, _command: &mut Command) {}
|
fn modify_command(&self, _command: &mut Command) {}
|
||||||
|
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"proton-muvm"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Proton + muvm (Asahi)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Runs through Proton inside a muvm microVM, for Apple Silicon / Asahi Linux."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ use crate::{
|
|||||||
format::DropFormatArgs,
|
format::DropFormatArgs,
|
||||||
parser::{LaunchParameters, ParsedCommand},
|
parser::{LaunchParameters, ParsedCommand},
|
||||||
process_handlers::{
|
process_handlers::{
|
||||||
AsahiMuvmLauncher, MacLauncher, UMUCompatLauncher, UMUNativeLauncher, WindowsLauncher,
|
AsahiMuvmLauncher, LinuxNativeLauncher, MacLauncher, UMUCompatLauncher, UMUNativeLauncher,
|
||||||
|
WindowsCmdLauncher, WindowsDirectLauncher, WindowsLauncher, WindowsPowershellLauncher,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -54,6 +55,13 @@ pub struct LaunchOption {
|
|||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ProcessHandlerOption {
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl ProcessManager<'_> {
|
impl ProcessManager<'_> {
|
||||||
pub fn new(app_handle: AppHandle) -> Self {
|
pub fn new(app_handle: AppHandle) -> Self {
|
||||||
let log_output_dir = DATA_ROOT_DIR.join("logs");
|
let log_output_dir = DATA_ROOT_DIR.join("logs");
|
||||||
@@ -76,6 +84,22 @@ impl ProcessManager<'_> {
|
|||||||
(Platform::Windows, Platform::Windows),
|
(Platform::Windows, Platform::Windows),
|
||||||
&WindowsLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
&WindowsLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
(Platform::Windows, Platform::Windows),
|
||||||
|
&WindowsDirectLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(Platform::Windows, Platform::Windows),
|
||||||
|
&WindowsCmdLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(Platform::Windows, Platform::Windows),
|
||||||
|
&WindowsPowershellLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(Platform::Linux, Platform::Linux),
|
||||||
|
&LinuxNativeLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
(Platform::Linux, Platform::Linux),
|
(Platform::Linux, Platform::Linux),
|
||||||
&UMUNativeLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
&UMUNativeLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||||
@@ -101,7 +125,7 @@ impl ProcessManager<'_> {
|
|||||||
match self.processes.get_mut(&game_id) {
|
match self.processes.get_mut(&game_id) {
|
||||||
Some(process) => {
|
Some(process) => {
|
||||||
process.manually_killed = true;
|
process.manually_killed = true;
|
||||||
process.handle.kill()?;
|
kill_process_tree(&process.handle)?;
|
||||||
let exit_status = process.handle.wait()?;
|
let exit_status = process.handle.wait()?;
|
||||||
info!("exit status: {:?}", exit_status);
|
info!("exit status: {:?}", exit_status);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -188,7 +212,21 @@ impl ProcessManager<'_> {
|
|||||||
&self,
|
&self,
|
||||||
db_lock: &Database,
|
db_lock: &Database,
|
||||||
target_platform: &Platform,
|
target_platform: &Platform,
|
||||||
|
override_id: Option<&str>,
|
||||||
) -> Result<&(dyn ProcessHandler + Send + Sync), ProcessError> {
|
) -> Result<&(dyn ProcessHandler + Send + Sync), ProcessError> {
|
||||||
|
// An explicit override wins, as long as it's valid for the current platform.
|
||||||
|
if let Some(override_id) = override_id
|
||||||
|
&& let Some(handler) = self.game_launchers.iter().find(|e| {
|
||||||
|
let (e_current, e_target) = e.0;
|
||||||
|
e_current == self.current_platform
|
||||||
|
&& e_target == *target_platform
|
||||||
|
&& e.1.id() == override_id
|
||||||
|
&& e.1.valid_for_platform(db_lock, target_platform)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Ok(handler.1);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(self
|
Ok(self
|
||||||
.game_launchers
|
.game_launchers
|
||||||
.iter()
|
.iter()
|
||||||
@@ -204,10 +242,44 @@ impl ProcessManager<'_> {
|
|||||||
|
|
||||||
pub fn valid_platform(&self, platform: &Platform) -> bool {
|
pub fn valid_platform(&self, platform: &Platform) -> bool {
|
||||||
let db_lock = borrow_db_checked();
|
let db_lock = borrow_db_checked();
|
||||||
let process_handler = self.fetch_process_handler(&db_lock, platform);
|
let process_handler = self.fetch_process_handler(&db_lock, platform, None);
|
||||||
process_handler.is_ok()
|
process_handler.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_process_handlers(
|
||||||
|
&self,
|
||||||
|
game_id: String,
|
||||||
|
) -> Result<Vec<ProcessHandlerOption>, ProcessError> {
|
||||||
|
let db_lock = borrow_db_checked();
|
||||||
|
|
||||||
|
let meta = db_lock
|
||||||
|
.applications
|
||||||
|
.installed_game_version
|
||||||
|
.get(&game_id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(ProcessError::NotInstalled)?;
|
||||||
|
|
||||||
|
let target_platform = meta.target_platform;
|
||||||
|
|
||||||
|
let handlers = self
|
||||||
|
.game_launchers
|
||||||
|
.iter()
|
||||||
|
.filter(|e| {
|
||||||
|
let (e_current, e_target) = e.0;
|
||||||
|
e_current == self.current_platform
|
||||||
|
&& e_target == target_platform
|
||||||
|
&& e.1.valid_for_platform(&db_lock, &target_platform)
|
||||||
|
})
|
||||||
|
.map(|e| ProcessHandlerOption {
|
||||||
|
id: e.1.id().to_string(),
|
||||||
|
name: e.1.name().to_string(),
|
||||||
|
description: e.1.description().to_string(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(handlers)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_launch_options(game_id: String) -> Result<Vec<LaunchOption>, ProcessError> {
|
pub fn get_launch_options(game_id: String) -> Result<Vec<LaunchOption>, ProcessError> {
|
||||||
let db_lock = borrow_db_checked();
|
let db_lock = borrow_db_checked();
|
||||||
|
|
||||||
@@ -310,7 +382,12 @@ impl ProcessManager<'_> {
|
|||||||
|
|
||||||
let target_platform = meta.target_platform;
|
let target_platform = meta.target_platform;
|
||||||
|
|
||||||
let process_handler = self.fetch_process_handler(&db_lock, &target_platform)?;
|
let process_handler = self.fetch_process_handler(
|
||||||
|
&db_lock,
|
||||||
|
&target_platform,
|
||||||
|
game_version.user_configuration.override_handler.as_deref(),
|
||||||
|
)?;
|
||||||
|
debug!("using process handler {:?}", process_handler.id());
|
||||||
|
|
||||||
let (target_command, emulator) = match game_status {
|
let (target_command, emulator) = match game_status {
|
||||||
GameDownloadStatus::Installed {
|
GameDownloadStatus::Installed {
|
||||||
@@ -516,6 +593,30 @@ impl ProcessManager<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn kill_process_tree(handle: &SharedChild) -> io::Result<()> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
// handle.kill() only terminates the launched process (often a cmd or
|
||||||
|
// powershell wrapper), orphaning the actual game. taskkill /T kills the
|
||||||
|
// whole process tree.
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||||
|
let pid = handle.id().to_string();
|
||||||
|
let killed = Command::new("taskkill")
|
||||||
|
.args(["/F", "/T", "/PID", pid.as_str()])
|
||||||
|
.creation_flags(CREATE_NO_WINDOW)
|
||||||
|
.stdout(std::process::Stdio::null())
|
||||||
|
.stderr(std::process::Stdio::null())
|
||||||
|
.status()
|
||||||
|
.map(|status| status.success())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if killed {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handle.kill()
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ProcessHandler: Send + 'static {
|
pub trait ProcessHandler: Send + 'static {
|
||||||
fn create_launch_process(
|
fn create_launch_process(
|
||||||
&self,
|
&self,
|
||||||
@@ -529,4 +630,8 @@ pub trait ProcessHandler: Send + 'static {
|
|||||||
fn valid_for_platform(&self, db: &Database, target: &Platform) -> bool;
|
fn valid_for_platform(&self, db: &Database, target: &Platform) -> bool;
|
||||||
|
|
||||||
fn modify_command(&self, command: &mut Command);
|
fn modify_command(&self, command: &mut Command);
|
||||||
|
|
||||||
|
fn id(&self) -> &'static str;
|
||||||
|
fn name(&self) -> &'static str;
|
||||||
|
fn description(&self) -> &'static str;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,17 @@
|
|||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env, fs::File, io::Write, panic::PanicHookInfo, path::Path, str::FromStr,
|
env,
|
||||||
sync::nonpoison::Mutex, time::SystemTime,
|
fs::File,
|
||||||
|
io::Write,
|
||||||
|
panic::PanicHookInfo,
|
||||||
|
path::Path,
|
||||||
|
str::FromStr,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
nonpoison::Mutex,
|
||||||
|
},
|
||||||
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ::client::{
|
use ::client::{
|
||||||
@@ -260,6 +269,7 @@ pub fn run() {
|
|||||||
get_autostart_enabled,
|
get_autostart_enabled,
|
||||||
open_process_logs,
|
open_process_logs,
|
||||||
get_launch_options,
|
get_launch_options,
|
||||||
|
get_process_handlers,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
::process::compat::fetch_proton_paths,
|
::process::compat::fetch_proton_paths,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -359,8 +369,17 @@ pub fn run() {
|
|||||||
)
|
)
|
||||||
.expect("Failed to generate menu");
|
.expect("Failed to generate menu");
|
||||||
|
|
||||||
|
if env::var("NO_TRAY_ICON").is_ok_and(|value| value.to_lowercase() == "true") {
|
||||||
|
TRAY_DISABLED.store(true, Ordering::Relaxed);
|
||||||
|
} else if !tray_icon_supported() {
|
||||||
|
warn!(
|
||||||
|
"appindicator library not available at runtime, disabling system tray icon"
|
||||||
|
);
|
||||||
|
TRAY_DISABLED.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
run_on_tray(|| {
|
run_on_tray(|| {
|
||||||
TrayIconBuilder::new()
|
let tray = TrayIconBuilder::new()
|
||||||
.icon(
|
.icon(
|
||||||
app.default_window_icon()
|
app.default_window_icon()
|
||||||
.expect("Failed to get default window icon")
|
.expect("Failed to get default window icon")
|
||||||
@@ -383,8 +402,12 @@ pub fn run() {
|
|||||||
warn!("menu event not handled: {:?}", event.id);
|
warn!("menu event not handled: {:?}", event.id);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build(app)
|
.build(app);
|
||||||
.expect("error while setting up tray menu");
|
|
||||||
|
if let Err(e) = tray {
|
||||||
|
warn!("failed to set up system tray icon, disabling tray: {e}");
|
||||||
|
TRAY_DISABLED.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -445,13 +468,30 @@ pub fn run() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TRAY_DISABLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn tray_icon_supported() -> bool {
|
||||||
|
[
|
||||||
|
"libayatana-appindicator3.so.1",
|
||||||
|
"libappindicator3.so.1",
|
||||||
|
"libayatana-appindicator3.so",
|
||||||
|
"libappindicator3.so",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.any(|name| unsafe { libloading::Library::new(name) }.is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
fn tray_icon_supported() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn run_on_tray<T: FnOnce()>(f: T) {
|
fn run_on_tray<T: FnOnce()>(f: T) {
|
||||||
if match std::env::var("NO_TRAY_ICON") {
|
if TRAY_DISABLED.load(Ordering::Relaxed) {
|
||||||
Ok(s) => s.to_lowercase() != "true",
|
return;
|
||||||
Err(_) => true,
|
|
||||||
} {
|
|
||||||
(f)();
|
|
||||||
}
|
}
|
||||||
|
(f)();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor
|
// TODO: Refactor
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
use process::{
|
use process::{
|
||||||
PROCESS_MANAGER,
|
PROCESS_MANAGER,
|
||||||
error::ProcessError,
|
error::ProcessError,
|
||||||
process_manager::{LaunchOption, ProcessManager},
|
process_manager::{LaunchOption, ProcessHandlerOption, ProcessManager},
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
@@ -16,6 +16,11 @@ pub fn get_launch_options(id: String) -> Result<Vec<LaunchOption>, ProcessError>
|
|||||||
Ok(launch_options)
|
Ok(launch_options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_process_handlers(id: String) -> Result<Vec<ProcessHandlerOption>, ProcessError> {
|
||||||
|
PROCESS_MANAGER.lock().get_process_handlers(id)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(tag = "result", content = "data")]
|
#[serde(tag = "result", content = "data")]
|
||||||
pub enum LaunchResult {
|
pub enum LaunchResult {
|
||||||
|
|||||||
Generated
+897
-604
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
class="mt-4 rounded-md inline-flex items-center text-sm font-semibold text-blue-500 hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
|
class="mt-4 rounded-md inline-flex items-center text-sm font-semibold text-blue-500 hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
|
||||||
href="https://docs.droposs.org/docs/authentication/simple"
|
href="https://droposs.org/docs/admin/authentication/simple"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<i18n-t
|
<i18n-t
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
class="mt-4 rounded-md inline-flex items-center text-sm font-semibold text-blue-500 hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
|
class="mt-4 rounded-md inline-flex items-center text-sm font-semibold text-blue-500 hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
|
||||||
href="https://docs.droposs.org/docs/authentication/oidc"
|
href="https://droposs.org/docs/admin/authentication/oidc"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<i18n-t
|
<i18n-t
|
||||||
|
|||||||
@@ -174,13 +174,14 @@ const optionsMetadata: {
|
|||||||
Filesystem: {
|
Filesystem: {
|
||||||
title: t("library.admin.sources.fsTitle"),
|
title: t("library.admin.sources.fsTitle"),
|
||||||
description: t("library.admin.sources.fsDesc"),
|
description: t("library.admin.sources.fsDesc"),
|
||||||
docsLink: "https://docs.droposs.org/docs/library#drop-style",
|
docsLink: "https://droposs.org/docs/reference/library-sources#drop-style",
|
||||||
icon: DropLogo,
|
icon: DropLogo,
|
||||||
},
|
},
|
||||||
FlatFilesystem: {
|
FlatFilesystem: {
|
||||||
title: t("library.admin.sources.fsFlatTitle"),
|
title: t("library.admin.sources.fsFlatTitle"),
|
||||||
description: t("library.admin.sources.fsFlatDesc"),
|
description: t("library.admin.sources.fsFlatDesc"),
|
||||||
docsLink: "https://docs.droposs.org/docs/library#flat-style-or-compat",
|
docsLink:
|
||||||
|
"https://droposs.org/docs/reference/library-sources#compatibility-flat-style",
|
||||||
icon: BackwardIcon,
|
icon: BackwardIcon,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -134,11 +134,11 @@ const navigation = computed(() => ({
|
|||||||
// { name: t("footer.api"), href: "https://api.droposs.org/" },
|
// { name: t("footer.api"), href: "https://api.droposs.org/" },
|
||||||
{
|
{
|
||||||
name: t("footer.docs.server"),
|
name: t("footer.docs.server"),
|
||||||
href: "https://docs.droposs.org/docs/guides/quickstart",
|
href: "https://droposs.org/docs/admin/quickstart",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t("footer.docs.client"),
|
name: t("footer.docs.client"),
|
||||||
href: "https://docs.droposs.org/docs/guides/client",
|
href: "https://droposs.org/docs/user",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
about: [
|
about: [
|
||||||
|
|||||||
+3
-3
@@ -29,8 +29,8 @@
|
|||||||
"@nuxt/image": "^1.10.0",
|
"@nuxt/image": "^1.10.0",
|
||||||
"@nuxt/kit": "^3.20.1",
|
"@nuxt/kit": "^3.20.1",
|
||||||
"@nuxtjs/i18n": "^9.5.5",
|
"@nuxtjs/i18n": "^9.5.5",
|
||||||
"@prisma/adapter-pg": "^7.3.0",
|
"@prisma/adapter-pg": "7.7.0",
|
||||||
"@prisma/client": "^7.3.0",
|
"@prisma/client": "7.7.0",
|
||||||
"@simplewebauthn/browser": "^13.2.2",
|
"@simplewebauthn/browser": "^13.2.2",
|
||||||
"@simplewebauthn/server": "^13.2.2",
|
"@simplewebauthn/server": "^13.2.2",
|
||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.0.6",
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"parse-cosekey": "^1.0.2",
|
"parse-cosekey": "^1.0.2",
|
||||||
"pino": "^9.14.0",
|
"pino": "^9.14.0",
|
||||||
"pino-pretty": "^13.1.1",
|
"pino-pretty": "^13.1.1",
|
||||||
"prisma": "7.3.0",
|
"prisma": "7.7.0",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
"shescape": "^2.1.10",
|
"shescape": "^2.1.10",
|
||||||
|
|||||||
@@ -438,7 +438,7 @@
|
|||||||
|
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
class="transition text-xs text-zinc-600 hover:underline hover:text-zinc-400"
|
class="transition text-xs text-zinc-600 hover:underline hover:text-zinc-400"
|
||||||
href="https://docs.droposs.org/docs/library"
|
href="https://droposs.org/docs/reference/library-sources"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<i18n-t
|
<i18n-t
|
||||||
|
|||||||
@@ -279,13 +279,14 @@ const optionsMetadata: {
|
|||||||
Filesystem: {
|
Filesystem: {
|
||||||
title: t("library.admin.sources.fsTitle"),
|
title: t("library.admin.sources.fsTitle"),
|
||||||
description: t("library.admin.sources.fsDesc"),
|
description: t("library.admin.sources.fsDesc"),
|
||||||
docsLink: "https://docs.droposs.org/docs/library#drop-style",
|
docsLink: "https://droposs.org/docs/reference/library-sources#drop-style",
|
||||||
icon: DropLogo,
|
icon: DropLogo,
|
||||||
},
|
},
|
||||||
FlatFilesystem: {
|
FlatFilesystem: {
|
||||||
title: t("library.admin.sources.fsFlatTitle"),
|
title: t("library.admin.sources.fsFlatTitle"),
|
||||||
description: t("library.admin.sources.fsFlatDesc"),
|
description: t("library.admin.sources.fsFlatDesc"),
|
||||||
docsLink: "https://docs.droposs.org/docs/library#flat-style-or-compat",
|
docsLink:
|
||||||
|
"https://droposs.org/docs/reference/library-sources#compatibility-flat-style",
|
||||||
icon: BackwardIcon,
|
icon: BackwardIcon,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default defineConfig({
|
|||||||
{ slug: "user" },
|
{ slug: "user" },
|
||||||
{
|
{
|
||||||
label: "Install",
|
label: "Install",
|
||||||
autogenerate: { directory: "user/install" },
|
items: [{ autogenerate: { directory: "user/install" } }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Usage",
|
label: "Usage",
|
||||||
@@ -65,25 +65,26 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Going further",
|
label: "Going further",
|
||||||
autogenerate: { directory: "admin/going-further" },
|
items: [{ autogenerate: { directory: "admin/going-further" } }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Metadata",
|
label: "Metadata",
|
||||||
autogenerate: { directory: "admin/metadata" },
|
items: [{ autogenerate: { directory: "admin/metadata" } }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Authentication",
|
label: "Authentication",
|
||||||
autogenerate: { directory: "admin/authentication" },
|
items: [{ autogenerate: { directory: "admin/authentication" } }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Reference",
|
label: "Reference",
|
||||||
autogenerate: { directory: "reference" },
|
items: [{ autogenerate: { directory: "reference" } }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
customCss: ["./src/styles/drop.css"],
|
customCss: ["./src/styles/drop.css"],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
site: "https://docs-next.droposs.org/",
|
site: "https://droposs.org",
|
||||||
|
base: "/docs",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/starlight": "^0.37.4",
|
"@astrojs/starlight": "^0.40.0",
|
||||||
"astro": "^5.6.1",
|
"astro": "^6.4.8",
|
||||||
"sharp": "^0.34.2",
|
"sharp": "^0.35.2",
|
||||||
"starlight-image-zoom": "^0.13.2",
|
"starlight-image-zoom": "^0.14.2",
|
||||||
"starlight-links-validator": "^0.19.2",
|
"starlight-links-validator": "^0.24.1",
|
||||||
"starlight-theme-rapide": "^0.5.2"
|
"starlight-theme-rapide": "^0.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ For convenience's sake, we can also specify file extensions for Drop's auto-dete
|
|||||||
|
|
||||||
Add a launch executable for every platform you want to support. The options are fairly self-explanatory, but make sure to use the `{rom}` placeholder, and optionally add the file extensions.
|
Add a launch executable for every platform you want to support. The options are fairly self-explanatory, but make sure to use the `{rom}` placeholder, and optionally add the file extensions.
|
||||||
|
|
||||||
Read the [Command Parsing](/reference/command-parsing/) article to understand how it's parsed and substituted.
|
Read the [Command Parsing](/docs/reference/command-parsing/) article to understand how it's parsed and substituted.
|
||||||
|
|
||||||
3. ## Import your game
|
3. ## Import your game
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ If you're using a library source that supports versioning, you can add and impor
|
|||||||
|
|
||||||
2. ### Follow the import guide again
|
2. ### Follow the import guide again
|
||||||
|
|
||||||
Follow the [import guide again](/admin/guides/import-version/), but this time for your new version folder.
|
Follow the [import guide again](/docs/admin/guides/import-version/), but this time for your new version folder.
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ You can stack many "update mode" versions on top of each other, and they will pi
|
|||||||
|
|
||||||
2. ### Follow the import guide again
|
2. ### Follow the import guide again
|
||||||
|
|
||||||
Follow the [import guide again](/admin/guides/import-version/), but this time for your new version folder.
|
Follow the [import guide again](/docs/admin/guides/import-version/), but this time for your new version folder.
|
||||||
|
|
||||||
3. ### Before import, enable update mode
|
3. ### Before import, enable update mode
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: Setting up OIDC
|
|||||||
---
|
---
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
You can find reference information in the [OIDC authentication docs](/admin/authentication/oidc/).
|
You can find reference information in the [OIDC authentication docs](/docs/admin/authentication/oidc/).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Authentik
|
## Authentik
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ To import games and start using Drop, you must first create a library to import
|
|||||||
|
|
||||||
1. **Decide on a library layout.**
|
1. **Decide on a library layout.**
|
||||||
|
|
||||||
Drop supports different layouts for your files on disk, you can read more about them in the [Library Sources](/reference/library-sources) reference section.
|
Drop supports different layouts for your files on disk, you can read more about them in the [Library Sources](/docs/reference/library-sources) reference section.
|
||||||
|
|
||||||
2. **Mount your library in the Docker container.**
|
2. **Mount your library in the Docker container.**
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ To import games and start using Drop, you must first create a library to import
|
|||||||
- `/mnt/media/my-drop-library` is the path to your library.
|
- `/mnt/media/my-drop-library` is the path to your library.
|
||||||
- `/library` is a **unique** path inside the container. **Use something else if another volume mounts to `/library`**.
|
- `/library` is a **unique** path inside the container. **Use something else if another volume mounts to `/library`**.
|
||||||
|
|
||||||
If you followed the [Quickstart](/admin/quickstart/) guide, you'll have already set up a library at `./library` pointing to `/library` within the container. You may want to instead edit that line in the `volumes` section to point to where your library is located.
|
If you followed the [Quickstart](/docs/admin/quickstart/) guide, you'll have already set up a library at `./library` pointing to `/library` within the container. You may want to instead edit that line in the `volumes` section to point to where your library is located.
|
||||||
|
|
||||||
3. **Open library source interface in Admin Dashboard.**
|
3. **Open library source interface in Admin Dashboard.**
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Drop automatically parses and formats the URL, so there are no requirements on t
|
|||||||
|
|
||||||
## LAN
|
## LAN
|
||||||
|
|
||||||
The `compose.yaml` provided in the [Quickstart guide](/admin/quickstart/) already exposes the Drop instance on port 3000. If you're on the same LAN as your Drop instance, you can find it's IP and then use:
|
The `compose.yaml` provided in the [Quickstart guide](/docs/admin/quickstart/) already exposes the Drop instance on port 3000. If you're on the same LAN as your Drop instance, you can find it's IP and then use:
|
||||||
|
|
||||||
```
|
```
|
||||||
http://[instance IP]:3000
|
http://[instance IP]:3000
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ Once you've got a library set up, and have imported a game, you can import a ver
|
|||||||
A installer version uses "setup mode". Enable the option, and then add the installer executable in setup commands.
|
A installer version uses "setup mode". Enable the option, and then add the installer executable in setup commands.
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
Setup and launch commands are parsed in a cross-platform, POSIX style. It's not relevant for simple setups, but useful to know. Read more about it in [Command Parsing](/reference/command-parsing/).
|
Setup and launch commands are parsed in a cross-platform, POSIX style. It's not relevant for simple setups, but useful to know. Read more about it in [Command Parsing](/docs/reference/command-parsing/).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
6. ### **Wait for import.**
|
6. ### **Wait for import.**
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ services:
|
|||||||
|
|
||||||
**The main things in this `compose.yaml` is the volumes attached to the `drop` service:**
|
**The main things in this `compose.yaml` is the volumes attached to the `drop` service:**
|
||||||
|
|
||||||
1. `./library` is where you will put your games to be imported into Drop. See '[Creating a library](/admin/guides/creating-library/)' once you're set up.
|
1. `./library` is where you will put your games to be imported into Drop. See '[Creating a library](/docs/admin/guides/creating-library/)' once you're set up.
|
||||||
2. `./data` is where Drop will store anything that's using the default file-system backed storage system. Typically, these are objects.
|
2. `./data` is where Drop will store anything that's using the default file-system backed storage system. Typically, these are objects.
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ hero:
|
|||||||
file: ../../assets/drop.svg
|
file: ../../assets/drop.svg
|
||||||
actions:
|
actions:
|
||||||
- text: Quickstart
|
- text: Quickstart
|
||||||
link: /admin/quickstart
|
link: /docs/admin/quickstart
|
||||||
icon: right-arrow
|
icon: right-arrow
|
||||||
- text: Download client
|
- text: Download client
|
||||||
link: https://droposs.org/download
|
link: https://droposs.org/download
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ Drop's [dockerfile](https://github.com/Drop-OSS/drop/blob/develop/Dockerfile) pr
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install prisma@7.3.0 dotenv # dotenv is required
|
npm install prisma@7.7.0 dotenv # dotenv is required
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, with your database running:
|
Then, with your database running:
|
||||||
|
|||||||
@@ -21,12 +21,40 @@ Then, what happens with this, depends on the type of game we're launching:
|
|||||||
|
|
||||||
## Normal (no emulator)
|
## Normal (no emulator)
|
||||||
|
|
||||||
Drop reconstructs the original shell string, and passes it into platform-specific command wrappers. For Windows, this means nothing. For Linux, it gets wrapped in `umu-run`.
|
Drop reconstructs the original shell string, and passes it into a platform-specific command wrapper, called a **launch method**. Drop picks a sensible launch method automatically, but you can override it per-game for troubleshooting — see [Launch methods](#launch-methods) below.
|
||||||
|
|
||||||
|
By default, on Windows the command is launched based on its file type: `.exe` files run directly, `.bat` and `.cmd` files run through `cmd`, `.ps1` files run through PowerShell, and anything else is handed to `cmd` so builtins, `PATHEXT` resolution and `%VAR%` expansion all work. On Linux, native games run directly on the host, while games targeting Windows are wrapped in `umu-run` (with Proton).
|
||||||
|
|
||||||
It is then parsed again, and then passed into process creation, mapping the environment variable, command, and arguments into their respective platform-dependent places.
|
It is then parsed again, and then passed into process creation, mapping the environment variable, command, and arguments into their respective platform-dependent places.
|
||||||
|
|
||||||
Drop logs out it's final parsed command, if you want to look at it in the client logs.
|
Drop logs out it's final parsed command, if you want to look at it in the client logs.
|
||||||
|
|
||||||
|
## Launch methods
|
||||||
|
|
||||||
|
The wrapper Drop uses to start a game is called a **launch method** (a *process handler* internally). Drop automatically selects the best available method for each game, but if a game won't launch you can override it under **Game Options → Launch → Launch method**.
|
||||||
|
|
||||||
|
Only methods supported by your current platform (and the game's target platform) are listed, each with a short description in the client.
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
| ------ | ----------- |
|
||||||
|
| **Automatic** *(default)* | Detects the file type and launches it directly, or through `cmd` or PowerShell. |
|
||||||
|
| **Direct executable** | Runs the executable directly, without a shell. |
|
||||||
|
| **Command Prompt (cmd)** | Launches through `cmd.exe`. Supports batch files, builtins and `%VAR%` expansion. |
|
||||||
|
| **PowerShell** | Runs the command as a PowerShell script (`-File`). |
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
| ------ | ----------- |
|
||||||
|
| **Native (direct)** *(default for Linux games)* | Runs the native Linux game directly on the host. |
|
||||||
|
| **Steam Linux Runtime (umu-run)** | Runs the native Linux game inside `umu-run`'s Steam Linux Runtime. Requires [UMU launcher](/docs/user/usage/proton/). |
|
||||||
|
| **Proton (umu-run)** *(default for Windows games)* | Runs a Windows game through Proton, using `umu-run`. Requires [Proton](/docs/user/usage/proton/). |
|
||||||
|
| **Proton + muvm (Asahi)** | Runs a Windows game through Proton inside a muvm microVM, for Apple Silicon / Asahi Linux. |
|
||||||
|
|
||||||
|
On macOS, games are always launched directly.
|
||||||
|
|
||||||
## Emulators
|
## Emulators
|
||||||
For emulators, we have the "emulator version" (version containing the emulator), and the "emulated version" (version containing the ROM).
|
For emulators, we have the "emulator version" (version containing the emulator), and the "emulated version" (version containing the ROM).
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ In the UI, you'll be prompted to "import" each folder separately:
|
|||||||
|
|
||||||
So your game has gotten an update and you've got new files. All you need to do is create a new version folder inside the game folder, and move all the files you have into that folder. Then, import it within the Drop admin UI.
|
So your game has gotten an update and you've got new files. All you need to do is create a new version folder inside the game folder, and move all the files you have into that folder. Then, import it within the Drop admin UI.
|
||||||
|
|
||||||
If you have files that you're supposed to **paste over the previous version**, Drop supports that! Read [Update mode](/reference/update-mode/) to find out more.
|
If you have files that you're supposed to **paste over the previous version**, Drop supports that! Read [Update mode](/docs/reference/update-mode/) to find out more.
|
||||||
|
|
||||||
# Compatibility (flat-style)
|
# Compatibility (flat-style)
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ title: Getting Started
|
|||||||
|
|
||||||
Drop clients are available for download from [our website](https://droposs.org/download), or follow one of our installation guides on the sidebar. Download the correct version for your platform, and open it up.
|
Drop clients are available for download from [our website](https://droposs.org/download), or follow one of our installation guides on the sidebar. Download the correct version for your platform, and open it up.
|
||||||
|
|
||||||
The client will walk you through the setup and sign-in process to get started. You'll need a Drop instance you can connect to, and an account on the server. If you don't have one, you can follow the [Quickstart](/admin/quickstart/) guide to set up your own.
|
The client will walk you through the setup and sign-in process to get started. You'll need a Drop instance you can connect to, and an account on the server. If you don't have one, you can follow the [Quickstart](/docs/admin/quickstart/) guide to set up your own.
|
||||||
|
|||||||
@@ -55,4 +55,8 @@ To launch any Windows game, you **must** first set a default Proton version.
|
|||||||
|
|
||||||
Drop uses a global default Proton version to launch games by default. You can override this in a game's options.
|
Drop uses a global default Proton version to launch games by default. You can override this in a game's options.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Choosing a launch method
|
||||||
|
|
||||||
|
Proton isn't the only thing you can change per-game. If a game won't start, you can also try a different **launch method** from the same **Game Options → Launch** menu — for example, forcing a Windows game through Proton, or running a native Linux game inside the Steam Linux Runtime. See [Launch methods](/docs/reference/command-parsing/#launch-methods) for the full list.
|
||||||
@@ -46,7 +46,7 @@ function Header() {
|
|||||||
<div className="flex flex-col gap-y-2 border-b border-dotted border-zinc-800 pb-4">
|
<div className="flex flex-col gap-y-2 border-b border-dotted border-zinc-800 pb-4">
|
||||||
<dt className="text-sm/6 text-zinc-400">Lines of code</dt>
|
<dt className="text-sm/6 text-zinc-400">Lines of code</dt>
|
||||||
<dd className="order-first text-6xl font-medium tracking-tight">
|
<dd className="order-first text-6xl font-medium tracking-tight">
|
||||||
<AnimatedNumber start={10} end={40} />k
|
<AnimatedNumber start={0} end={75} />k
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-y-2 border-b border-dotted border-zinc-800 pb-4">
|
<div className="flex flex-col gap-y-2 border-b border-dotted border-zinc-800 pb-4">
|
||||||
@@ -61,7 +61,7 @@ function Header() {
|
|||||||
<div className="flex flex-col gap-y-2 max-sm:border-b max-sm:border-dotted max-sm:border-gray-200 max-sm:pb-4">
|
<div className="flex flex-col gap-y-2 max-sm:border-b max-sm:border-dotted max-sm:border-gray-200 max-sm:pb-4">
|
||||||
<dt className="text-sm/6 text-zinc-400">Docker pulls</dt>
|
<dt className="text-sm/6 text-zinc-400">Docker pulls</dt>
|
||||||
<dd className="order-first text-6xl font-medium tracking-tight">
|
<dd className="order-first text-6xl font-medium tracking-tight">
|
||||||
<AnimatedNumber start={0} end={48.8} decimals={1} />k
|
<AnimatedNumber start={0} end={210} decimals={1} />k
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-y-2">
|
<div className="flex flex-col gap-y-2">
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function Hero() {
|
|||||||
Steam and Epic.
|
Steam and Epic.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-12 flex flex-col gap-x-6 gap-y-4 sm:flex-row">
|
<div className="mt-12 flex flex-col gap-x-6 gap-y-4 sm:flex-row">
|
||||||
<Button href="https://docs.droposs.org/docs/guides/quickstart">
|
<Button href="/docs/admin/quickstart">
|
||||||
Get started
|
Get started
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" href="/about">
|
<Button variant="outline" href="/about">
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function CallToAction() {
|
|||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Button
|
<Button
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
href="https://docs.droposs.org/docs/guides/quickstart"
|
href="/docs/admin/quickstart"
|
||||||
>
|
>
|
||||||
Quickstart →
|
Quickstart →
|
||||||
</Button>
|
</Button>
|
||||||
@@ -65,7 +65,7 @@ function Sitemap() {
|
|||||||
<div>
|
<div>
|
||||||
<SitemapHeading>Documentation</SitemapHeading>
|
<SitemapHeading>Documentation</SitemapHeading>
|
||||||
<SitemapLinks>
|
<SitemapLinks>
|
||||||
<SitemapLink href="https://docs.droposs.org/">
|
<SitemapLink href="/docs">
|
||||||
Self-hosters
|
Self-hosters
|
||||||
</SitemapLink>
|
</SitemapLink>
|
||||||
<SitemapLink href="https://developer.droposs.org/">
|
<SitemapLink href="https://developer.droposs.org/">
|
||||||
|
|||||||
@@ -80,11 +80,11 @@ export function Gallery() {
|
|||||||
)
|
)
|
||||||
.map((file) => (
|
.map((file) => (
|
||||||
<div key={file.url} className="relative w-full">
|
<div key={file.url} className="relative w-full">
|
||||||
<div className="group overflow-hidden rounded-lg bg-gray-100 focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-blue-600">
|
<div className="group relative block w-full overflow-hidden rounded-lg bg-gray-100 focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-blue-600">
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
src={file.url}
|
src={file.url}
|
||||||
className="pointer-events-none aspect-10/7 aspect-auto rounded-lg object-cover outline -outline-offset-1 outline-black/5 group-hover:opacity-75"
|
className="pointer-events-none block w-full rounded-lg object-cover outline -outline-offset-1 outline-black/5 group-hover:opacity-75"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -96,10 +96,10 @@ export function Gallery() {
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-gray-900">
|
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-zinc-100">
|
||||||
{file.name}
|
{file.name}
|
||||||
</p>
|
</p>
|
||||||
<p className="pointer-events-none block text-xs font-medium text-gray-500">
|
<p className="pointer-events-none block text-xs font-medium text-zinc-400">
|
||||||
{file.description}
|
{file.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user