diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e6f9c413..b0c92fa1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -47,20 +47,8 @@ jobs:
- name: Install dependencies
run: pip install -r requirements.txt
- - name: Build with Nuitka
- uses: Nuitka/Nuitka-Action@main
- with:
- nuitka-version: main
- working-directory: torrent-client
- script-name: main.py
- standalone: true
- enable-plugins: libtorrent
- output-dir: resources/dist
- output-file: hydra-download-manager
- windows-icon-from-ico: images/icon.ico
-
- # - name: Build with pyinstaller
- # run: pyinstaller torrent-client/main.py --distpath resources/dist --icon=images/icon.ico -n hydra-download-manager
+ - name: Build with cx_Freeze
+ run: python torrent-client/setup.py build
- name: Publish
run: yarn run publish
diff --git a/README.md b/README.md
index 8c2a5d9b..a737756e 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,7 @@ yarn start
Build the bittorrent client by using this command:
```bash
-pyinstaller torrent-client/main.py --distpath resources/dist --icon=images/icon.ico -n hydra-download-manager
+python torrent-client/setup.py build
```
### Build the Electron application
diff --git a/forge.config.ts b/forge.config.ts
index 3bc8bf23..21718764 100644
--- a/forge.config.ts
+++ b/forge.config.ts
@@ -13,6 +13,16 @@ import { ElectronegativityPlugin } from "@electron-forge/plugin-electronegativit
import { mainConfig } from "./webpack.main.config";
import { rendererConfig } from "./webpack.renderer.config";
+const linuxPkgConfig = {
+ mimeType: ["x-scheme-handler/hydralauncher"],
+ bin: "./Hydra",
+ desktopTemplate: "./hydra-launcher.desktop",
+ icon: "images/icon.png",
+ genericName: "Games Launcher",
+ name: "hydra-launcher",
+ productName: "Hydra"
+};
+
const config: ForgeConfig = {
packagerConfig: {
asar: true,
@@ -40,16 +50,10 @@ const config: ForgeConfig = {
}),
new MakerZIP({}, ["darwin", "linux"]),
new MakerRpm({
- options: {
- mimeType: ["x-scheme-handler/hydralauncher"],
- bin: "./Hydra",
- },
+ options: linuxPkgConfig
}),
new MakerDeb({
- options: {
- mimeType: ["x-scheme-handler/hydralauncher"],
- bin: "./Hydra",
- },
+ options: linuxPkgConfig
}),
],
publishers: [
diff --git a/hydra-launcher.desktop b/hydra-launcher.desktop
new file mode 100644
index 00000000..e9e10e91
--- /dev/null
+++ b/hydra-launcher.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Name=Hydra
+Comment=No bullshit. Just play.
+GenericName=Games Launcher
+Exec=hydra-launcher %U
+Icon=hydra-launcher
+Type=Application
+StartupNotify=true
+Categories=GNOME;GTK;Utility;
+MimeType=x-scheme-handler/hydralauncher;
+StartupWMClass=Hydra
diff --git a/requirements.txt b/requirements.txt
index ab929a73..6cee730a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,5 @@
libtorrent
+cx_Freeze
+cx_Logging; sys_platform == 'win32'
+lief; sys_platform == 'win32'
pywin32; sys_platform == 'win32'
diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json
index 6c5aa32c..f59f1e87 100644
--- a/src/locales/en/translation.json
+++ b/src/locales/en/translation.json
@@ -7,7 +7,6 @@
"no_results": "No results found"
},
"sidebar": {
- "home": "Home",
"catalogue": "Catalogue",
"downloads": "Downloads",
"settings": "Settings",
@@ -16,7 +15,9 @@
"checking_files": "{{title}} ({{percentage}} - Checking files…)",
"paused": "{{title}} (Paused)",
"downloading": "{{title}} ({{percentage}} - Downloading…)",
- "filter": "Filter library"
+ "filter": "Filter library",
+ "follow_us": "Follow us",
+ "home": "Home"
},
"header": {
"search": "Search",
diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json
index 4cc90d15..9ac401bf 100644
--- a/src/locales/es/translation.json
+++ b/src/locales/es/translation.json
@@ -16,7 +16,8 @@
"paused": "{{title}} (Pausado)",
"downloading": "{{title}} ({{percentage}} - Descargando…)",
"filter": "Filtrar biblioteca",
- "home": "Hogar"
+ "home": "Hogar",
+ "follow_us": "Síganos"
},
"header": {
"search": "Buscar",
diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json
index f4250be2..c83a825b 100644
--- a/src/locales/fr/translation.json
+++ b/src/locales/fr/translation.json
@@ -16,7 +16,8 @@
"paused": "{{title}} (En pause)",
"downloading": "{{title}} ({{percentage}} - Téléchargement en cours…)",
"filter": "Filtrer la bibliothèque",
- "home": "Maison"
+ "home": "Maison",
+ "follow_us": "Suivez-nous"
},
"header": {
"search": "Recherche",
diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json
index a19ca24a..145163b4 100644
--- a/src/locales/pt/translation.json
+++ b/src/locales/pt/translation.json
@@ -16,7 +16,8 @@
"paused": "{{title}} (Pausado)",
"downloading": "{{title}} ({{percentage}} - Baixando…)",
"filter": "Filtrar biblioteca",
- "home": "Início"
+ "home": "Início",
+ "follow_us": "Acompanhe-nos"
},
"header": {
"search": "Buscar",
diff --git a/src/main/events/index.ts b/src/main/events/index.ts
index 866f8317..5eaceff5 100644
--- a/src/main/events/index.ts
+++ b/src/main/events/index.ts
@@ -1,30 +1,31 @@
-import { app, ipcMain } from "electron";
import { defaultDownloadsPath } from "@main/constants";
+import { app, ipcMain } from "electron";
-import "./library/add-game-to-library";
-import "./catalogue/search-games";
-import "./catalogue/get-game-shop-details";
import "./catalogue/get-catalogue";
-import "./library/get-library";
+import "./catalogue/get-game-shop-details";
+import "./catalogue/get-games";
+import "./catalogue/get-how-long-to-beat";
+import "./catalogue/get-random-game";
+import "./catalogue/search-games";
import "./hardware/get-disk-free-space";
+import "./library/add-game-to-library";
+import "./library/close-game";
+import "./library/delete-game-folder";
+import "./library/get-game-by-object-id";
+import "./library/get-library";
+import "./library/get-repackers-friendly-names";
+import "./library/open-game";
+import "./library/open-game-installer";
+import "./library/remove-game";
+import "./misc/get-or-cache-image";
+import "./misc/open-external";
+import "./misc/show-open-dialog";
import "./torrenting/cancel-game-download";
import "./torrenting/pause-game-download";
import "./torrenting/resume-game-download";
import "./torrenting/start-game-download";
-import "./misc/get-or-cache-image";
-import "./user-preferences/update-user-preferences";
import "./user-preferences/get-user-preferences";
-import "./library/get-repackers-friendly-names";
-import "./library/get-game-by-object-id";
-import "./library/open-game-installer";
-import "./library/open-game";
-import "./library/close-game";
-import "./misc/show-open-dialog";
-import "./library/remove-game";
-import "./library/delete-game-folder";
-import "./catalogue/get-random-game";
-import "./catalogue/get-how-long-to-beat";
-import "./catalogue/get-games";
+import "./user-preferences/update-user-preferences";
ipcMain.handle("ping", () => "pong");
ipcMain.handle("getVersion", () => app.getVersion());
diff --git a/src/main/events/misc/open-external.ts b/src/main/events/misc/open-external.ts
new file mode 100644
index 00000000..3d693c23
--- /dev/null
+++ b/src/main/events/misc/open-external.ts
@@ -0,0 +1,9 @@
+import { shell } from "electron";
+import { registerEvent } from "../register-event";
+
+const openExternal = async (_event: Electron.IpcMainInvokeEvent, src: string) =>
+ shell.openExternal(src);
+
+registerEvent(openExternal, {
+ name: "openExternal",
+});
diff --git a/src/main/services/torrent-client.ts b/src/main/services/torrent-client.ts
index a79f3f9c..fa1cd59d 100644
--- a/src/main/services/torrent-client.ts
+++ b/src/main/services/torrent-client.ts
@@ -1,7 +1,8 @@
import path from "node:path";
import cp from "node:child_process";
+import fs from "node:fs";
import * as Sentry from "@sentry/electron/main";
-import { Notification, app } from "electron";
+import { Notification, app, dialog } from "electron";
import type { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
import { Game } from "@main/entity";
@@ -54,6 +55,15 @@ export class TorrentClient {
binaryName
);
+ if (!fs.existsSync(binaryPath)) {
+ dialog.showErrorBox(
+ "Fatal",
+ "Hydra download manager binary not found. Please check if it has been removed by Windows Defender."
+ );
+
+ app.quit();
+ }
+
cp.spawn(binaryPath, commonArgs, {
stdio: "inherit",
windowsHide: true,
@@ -155,7 +165,6 @@ export class TorrentClient {
}
} catch (err) {
Sentry.captureException(err);
- Sentry.captureMessage(message, "error");
}
}
}
diff --git a/src/preload.ts b/src/preload.ts
index db765453..93acde24 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -86,6 +86,7 @@ contextBridge.exposeInMainWorld("electron", {
ping: () => ipcRenderer.invoke("ping"),
getVersion: () => ipcRenderer.invoke("getVersion"),
getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"),
+ openExternal: (src: string) => ipcRenderer.invoke("openExternal", src),
showOpenDialog: (options: Electron.OpenDialogOptions) =>
ipcRenderer.invoke("showOpenDialog", options),
platform: process.platform,
diff --git a/src/renderer/assets/discord-icon.svg b/src/renderer/assets/discord-icon.svg
new file mode 100644
index 00000000..2fba46cd
--- /dev/null
+++ b/src/renderer/assets/discord-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/renderer/assets/x-icon.svg b/src/renderer/assets/x-icon.svg
new file mode 100644
index 00000000..f594427b
--- /dev/null
+++ b/src/renderer/assets/x-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/renderer/components/bottom-panel/bottom-panel.css.ts b/src/renderer/components/bottom-panel/bottom-panel.css.ts
index 632e1bff..f251d1c0 100644
--- a/src/renderer/components/bottom-panel/bottom-panel.css.ts
+++ b/src/renderer/components/bottom-panel/bottom-panel.css.ts
@@ -1,5 +1,5 @@
-import { SPACING_UNIT, vars } from "../../theme.css";
import { style } from "@vanilla-extract/css";
+import { SPACING_UNIT, vars } from "../../theme.css";
export const bottomPanel = style({
width: "100%",
@@ -14,9 +14,10 @@ export const bottomPanel = style({
});
export const downloadsButton = style({
- cursor: "pointer",
color: vars.color.bodyText,
+ borderBottom: "1px solid transparent",
":hover": {
- textDecoration: "underline",
+ borderBottom: `1px solid ${vars.color.bodyText}`,
+ cursor: "pointer",
},
});
diff --git a/src/renderer/components/sidebar/sidebar.css.ts b/src/renderer/components/sidebar/sidebar.css.ts
index 8bf62781..e4ff22f8 100644
--- a/src/renderer/components/sidebar/sidebar.css.ts
+++ b/src/renderer/components/sidebar/sidebar.css.ts
@@ -6,6 +6,7 @@ export const sidebar = recipe({
base: {
backgroundColor: vars.color.darkBackground,
color: "#c0c1c7",
+ flexDirection: "column",
display: "flex",
transition: "opacity ease 0.2s",
borderRight: `solid 1px ${vars.color.borderColor}`,
@@ -134,3 +135,36 @@ export const section = recipe({
},
},
});
+
+export const sidebarFooter = style({
+ marginTop: "auto",
+ padding: `${SPACING_UNIT * 2}px`,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+});
+
+export const footerSocialsContainer = style({
+ display: "flex",
+ alignItems: "center",
+ gap: `${SPACING_UNIT * 1.5}px`,
+});
+
+export const footerSocialsItem = style({
+ color: vars.color.bodyText,
+ backgroundColor: vars.color.darkBackground,
+ width: "16px",
+ height: "16px",
+ display: "flex",
+ alignItems: "center",
+ transition: "all ease 0.15s",
+ ":hover": {
+ opacity: 0.75,
+ cursor: "pointer",
+ },
+});
+
+export const footerText = style({
+ color: vars.color.bodyText,
+ fontSize: "12px",
+});
diff --git a/src/renderer/components/sidebar/sidebar.tsx b/src/renderer/components/sidebar/sidebar.tsx
index 962ecdfe..ad1bcdf3 100644
--- a/src/renderer/components/sidebar/sidebar.tsx
+++ b/src/renderer/components/sidebar/sidebar.tsx
@@ -4,13 +4,32 @@ import { useLocation, useNavigate } from "react-router-dom";
import type { Game } from "@types";
-import { useDownload, useLibrary } from "@renderer/hooks";
import { AsyncImage, TextField } from "@renderer/components";
+import { useDownload, useLibrary } from "@renderer/hooks";
import { SPACING_UNIT } from "@renderer/theme.css";
-import * as styles from "./sidebar.css";
import { routes } from "./routes";
+import { MarkGithubIcon } from "@primer/octicons-react";
+import DiscordLogo from "@renderer/assets/discord-icon.svg";
+import XLogo from "@renderer/assets/x-icon.svg";
+import * as styles from "./sidebar.css";
+
+const socials = [
+ {
+ url: "https://discord.gg/hydralauncher",
+ icon: ,
+ },
+ {
+ url: "https://twitter.com/hydralauncher",
+ icon: ,
+ },
+ {
+ url: "https://github.com/hydralauncher/hydra",
+ icon: ,
+ },
+];
+
const SIDEBAR_MIN_WIDTH = 200;
const SIDEBAR_INITIAL_WIDTH = 250;
const SIDEBAR_MAX_WIDTH = 450;
@@ -212,6 +231,24 @@ export function Sidebar() {
className={styles.handle}
onMouseDown={handleMouseDown}
/>
+
+
);
}
diff --git a/src/renderer/declaration.d.ts b/src/renderer/declaration.d.ts
index 3db1a7c0..cceed299 100644
--- a/src/renderer/declaration.d.ts
+++ b/src/renderer/declaration.d.ts
@@ -1,12 +1,12 @@
import type {
- CatalogueEntry,
- GameShop,
- Game,
CatalogueCategory,
- TorrentProgress,
- ShopDetails,
- UserPreferences,
+ CatalogueEntry,
+ Game,
+ GameShop,
HowLongToBeatCategory,
+ ShopDetails,
+ TorrentProgress,
+ UserPreferences,
} from "@types";
import type { DiskSpace } from "check-disk-space";
@@ -78,6 +78,7 @@ declare global {
/* Misc */
getOrCacheImage: (url: string) => Promise;
+ openExternal: (src: string) => Promise;
getVersion: () => Promise;
ping: () => string;
getDefaultDownloadsPath: () => Promise;
diff --git a/src/renderer/pages/patch-notes/patch-notes-skeleton.tsx b/src/renderer/pages/patch-notes/patch-notes-skeleton.tsx
new file mode 100644
index 00000000..e69de29b
diff --git a/src/renderer/utils/format-bytes.ts b/src/renderer/utils/format-bytes.ts
index 5aa39072..b052b43b 100644
--- a/src/renderer/utils/format-bytes.ts
+++ b/src/renderer/utils/format-bytes.ts
@@ -1,8 +1,8 @@
const FORMAT = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
export const formatBytes = (bytes: number): string => {
- if (!Number.isFinite(bytes) || isNaN(bytes) || bytes < 0) {
- return `N/A ${FORMAT[0]}`;
+ if (!Number.isFinite(bytes) || isNaN(bytes) || bytes <= 0) {
+ return `0 ${FORMAT[0]}`;
}
const byteKBase = 1024;
diff --git a/torrent-client/setup.py b/torrent-client/setup.py
new file mode 100644
index 00000000..e098ec80
--- /dev/null
+++ b/torrent-client/setup.py
@@ -0,0 +1,16 @@
+from cx_Freeze import setup, Executable
+
+# Dependencies are automatically detected, but it might need fine tuning.
+build_exe_options = {
+ "packages": ["libtorrent"],
+ "build_exe": "resources/dist/hydra-download-manager",
+ "include_msvcr": True
+}
+
+setup(
+ name="hydra-download-manager",
+ version="0.1",
+ description="Hydra Torrent Client",
+ options={"build_exe": build_exe_options},
+ executables=[Executable("torrent-client/main.py", target_name="hydra-download-manager")]
+)