Repository: diogomartino/steam-auto-shutdown
Branch: master
Commit: 7a4a33295934
Files: 72
Total size: 93.0 KB
Directory structure:
gitextract_doz3sxqr/
├── .github/
│ └── workflows/
│ └── wails-build.yaml
├── .gitignore
├── README.md
├── app.go
├── disk-manager.go
├── frontend/
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── .npmrc
│ ├── .prettierrc
│ ├── index.html
│ ├── package.json
│ ├── package.json.md5
│ ├── postcss.config.js
│ ├── src/
│ │ ├── actions/
│ │ │ ├── app.ts
│ │ │ └── modal.ts
│ │ ├── components/
│ │ │ ├── app/
│ │ │ │ └── index.tsx
│ │ │ ├── disk-monitor/
│ │ │ │ └── index.tsx
│ │ │ ├── disk-speed/
│ │ │ │ └── index.tsx
│ │ │ ├── footer/
│ │ │ │ └── index.tsx
│ │ │ ├── header/
│ │ │ │ └── index.tsx
│ │ │ ├── interface-picker/
│ │ │ │ └── index.tsx
│ │ │ ├── modals/
│ │ │ │ ├── action-confirmation/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ ├── process-picker/
│ │ │ │ │ └── index.tsx
│ │ │ │ └── settings/
│ │ │ │ └── index.tsx
│ │ │ ├── monitor-switch/
│ │ │ │ └── index.tsx
│ │ │ ├── network-speed/
│ │ │ │ └── index.tsx
│ │ │ └── top-right-slot/
│ │ │ └── index.tsx
│ │ ├── desktop.ts
│ │ ├── helpers/
│ │ │ └── sleep.ts
│ │ ├── hooks/
│ │ │ ├── use-disk-speed.ts
│ │ │ ├── use-interfaces.ts
│ │ │ ├── use-modals-info.ts
│ │ │ ├── use-monitor-status-msg.ts
│ │ │ ├── use-monitor-status.ts
│ │ │ ├── use-network-speed.ts
│ │ │ ├── use-selected-mac.ts
│ │ │ ├── use-selected-theme.ts
│ │ │ ├── use-settings.ts
│ │ │ ├── use-system-metrics.ts
│ │ │ ├── use-system-monitor.ts
│ │ │ ├── use-target-process.ts
│ │ │ └── use-theme.ts
│ │ ├── main.css
│ │ ├── main.tsx
│ │ ├── screens/
│ │ │ └── home/
│ │ │ └── index.tsx
│ │ ├── selectors/
│ │ │ ├── app.ts
│ │ │ └── modals.ts
│ │ ├── store/
│ │ │ ├── app-slice.ts
│ │ │ ├── index.ts
│ │ │ └── modals-slice.ts
│ │ ├── types/
│ │ │ └── index.ts
│ │ ├── vite-env.d.ts
│ │ └── wailsjs/
│ │ ├── go/
│ │ │ ├── main/
│ │ │ │ ├── App.d.ts
│ │ │ │ ├── App.js
│ │ │ │ ├── DiskManager.d.ts
│ │ │ │ ├── DiskManager.js
│ │ │ │ ├── NetworkManager.d.ts
│ │ │ │ └── NetworkManager.js
│ │ │ └── models.ts
│ │ └── runtime/
│ │ ├── package.json
│ │ ├── runtime.d.ts
│ │ └── runtime.js
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── go.mod
├── go.sum
├── main.go
├── network-manager.go
└── wails.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/wails-build.yaml
================================================
name: Wails build
on:
workflow_dispatch:
jobs:
build:
strategy:
fail-fast: false
runs-on: windows-latest
outputs:
version: ${{ steps.extract_version.outputs.version }}
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20.10.0
- name: Set up Git
run: git config --global user.email "actions@github.com" && git config --global user.name "GitHub Actions"
- name: Install pnpm
run: npm install -g pnpm
- name: Extract Version
id: extract_version
shell: bash
run: |
version=$(jq -r '.info.productVersion' ./wails.json)
echo "Version extracted from wails.json: $version"
echo "CURRENT_VERSION=$version" >> "$GITHUB_OUTPUT"
- name: Bump Version
id: bump_version
shell: bash
run: |
IFS='.' read -ra version_parts <<< "${{ steps.extract_version.outputs.CURRENT_VERSION }}"
major="${version_parts[0]}"
minor="${version_parts[1]}"
patch="${version_parts[2]}"
patch=$((patch + 1))
new_version="$major.$minor.$patch"
echo "Bumped version to: $new_version"
echo "BUILD_VERSION=$new_version" >> "$GITHUB_OUTPUT"
jq --arg new_version "$new_version" '.info.productVersion = $new_version' ./wails.json > temp_wails.json
mv temp_wails.json ./wails.json
- name: Commit and Push bump
run: |
git add wails.json
git commit -m "Bump version to v${{ steps.bump_version.outputs.BUILD_VERSION }}"
git push
- name: Setup GoLang
uses: actions/setup-go@v4
with:
check-latest: true
go-version: 1.21.3
- name: Install Wails
run: go install github.com/wailsapp/wails/v2/cmd/wails@v2.6.0
shell: bash
- name: Build Windows App
working-directory: .
run: wails build --clean --platform windows/amd64 -o SteamAutoShutdown.exe
shell: bash
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: Steam Auto Shutdown.exe
path: ./build/bin/SteamAutoShutdown.exe
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v1
with:
files: ./build/bin/SteamAutoShutdown.exe
tag_name: v${{ steps.bump_version.outputs.BUILD_VERSION }}
name: Release v${{ steps.bump_version.outputs.BUILD_VERSION }}
body: |
## Changelog
No changelog available yet.
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
!frontend/dist
build/bin
build/windows
================================================
FILE: README.md
================================================
# Steam Auto Shutdown
This is a Windows application that will automatically shutdown your computer when your downloads are finished. Originally it was intended to be used with Steam (ence the name), but it works with any application.
## Download
You can download the latest version from the [releases page](https://github.com/diogomartino/steam-auto-shutdown/releases).
[WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) is required. You most likely already have it installed, so you don't need to worry.
## Instructions
1. Open the application
2. Toggle the switch to enable the shutdown
3. Your computer will shutdown when your downloads are finished
The default settings should work for most people, but you can change them in the settings screen by clicking the gear icon in the top right corner.
## Features
- Shutdown, sleep, hibernate or log off: you choose.
- Uses network traffic to detect when your downloads are finished, so it works with any application.
- Hability to choose the network interface to monitor.
- You can set a delay to shutdown your computer after your downloads are finished.
- You can set a minimum download speed to prevent your computer from shutting down when your downloads are finished but your internet is still being used.
- Disk monitoring to prevent your computer from shutting down when your disk is being used. This is useful when your downloads are finished but your game is still being installed, uncompressed or decrypted. This feature is disabled by default, you can enable it in the settings. This feature works on a process level, so you need to pick the process that you want to monitor (it's the Steam process by default).
## Screenshots

## Development
### Requirements
- [Go](https://go.dev/)
- [Wails](https://wails.io/)
- [Node.js](https://nodejs.org/)
- [pnpm](https://pnpm.io/)
```
wails dev
```
This will launch the application in development mode. The interface will also run on http://localhost:34115 in case you want to run it in a browser.
## Building
```
wails build --clean --platform windows/amd64
```
## Contributing
Feel free to contribute to this project by opening issues or pull requests. Please follow the code style of the project.
================================================
FILE: app.go
================================================
package main
import (
"context"
"fmt"
"os/exec"
)
// App struct
type App struct {
ctx context.Context
}
// NewApp creates a new App application struct
func NewApp() *App {
return &App{}
}
// startup is called at application startup
func (a *App) startup(ctx context.Context) {
// Perform your setup here
a.ctx = ctx
}
// domReady is called after front-end resources have been loaded
func (a App) domReady(ctx context.Context) {
// Add your action here
}
// beforeClose is called when the application is about to quit,
// either by clicking the window close button or calling runtime.Quit.
// Returning true will cause the application to continue, false will continue shutdown as normal.
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
return false
}
// shutdown is called at application termination
func (a *App) shutdown(ctx context.Context) {
// Perform your teardown here
}
const secondsToWait = "10"
func (a *App) ExecuteAction(actionName string) {
var cmd *exec.Cmd
switch actionName {
case "SHUTDOWN":
cmd = exec.Command("shutdown", "/s", "/t", secondsToWait)
case "RESTART":
cmd = exec.Command("shutdown", "/r", "/t", secondsToWait)
case "SLEEP":
cmd = exec.Command("rundll32.exe", "powrprof.dll,SetSuspendState", "0")
case "LOGOFF":
cmd = exec.Command("shutdown", "/l", "/t", secondsToWait)
case "HIBERNATE":
cmd = exec.Command("shutdown", "/h", "/t", secondsToWait)
default:
fmt.Println("Unknown action:", actionName)
return
}
err := cmd.Run()
if err != nil {
fmt.Println("Error executing command:", err)
}
}
func (a *App) CancelShutdown() {
cmd := exec.Command("shutdown", "/a")
err := cmd.Run()
if err != nil {
fmt.Println("Error executing command:", err)
}
}
func (a *App) OpenInBrowser(url string) {
cmd := exec.Command("cmd", "/c", "start", url)
err := cmd.Run()
if err != nil {
fmt.Println("Error executing command:", err)
}
}
================================================
FILE: disk-manager.go
================================================
package main
import (
"fmt"
"time"
"github.com/shirou/gopsutil/process"
)
type DiskManager struct{}
type Process struct {
Pid int32
Name string
}
func createDiskManager() *DiskManager {
return &DiskManager{}
}
func getProcessByPID(pid int32) (*process.Process, error) {
processes, err := process.Processes()
if err != nil {
return nil, err
}
for _, p := range processes {
if p.Pid == pid {
return p, nil
}
}
return nil, fmt.Errorf("No process found with PID: %d", pid)
}
func (d *DiskManager) GetDiskWriteSpeed(processPID int32) (float64, error) {
process, err := getProcessByPID(processPID)
if err != nil {
return 0, err
}
initialIOCounters, err := process.IOCounters()
if err != nil {
return 0, err
}
time.Sleep(sampleInterval)
finalIOCounters, err := process.IOCounters()
if err != nil {
return 0, err
}
bytesWritten := finalIOCounters.WriteBytes - initialIOCounters.WriteBytes
writeDuration := sampleInterval.Seconds()
if writeDuration == 0 {
return 0, nil
}
writeSpeed := float64(bytesWritten) / writeDuration
return writeSpeed, nil
}
func (d *DiskManager) GetProcesses() ([]*Process, error) {
var processList []*Process = make([]*Process, 0)
processes, err := process.Processes()
if err != nil {
return nil, err
}
for _, process := range processes {
name, err := process.Name()
if err != nil {
return nil, err
}
processList = append(processList, &Process{
Pid: process.Pid,
Name: name,
})
}
return processList, nil
}
================================================
FILE: frontend/.eslintrc.cjs
================================================
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended'
],
ignorePatterns: ['dist', '.eslintrc.cjs', 'src/wailsjs/'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh', 'prettier', 'unused-imports'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true }
],
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
useTabs: false
}
],
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_'
}
],
'@typescript-eslint/no-explicit-any': 'off'
}
};
================================================
FILE: frontend/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: frontend/.npmrc
================================================
public-hoist-pattern[]=*@nextui-org/*
================================================
FILE: frontend/.prettierrc
================================================
{
"singleQuote": true,
"printWidth": 80,
"proseWrap": "always",
"tabWidth": 2,
"useTabs": false,
"trailingComma": "none",
"bracketSpacing": true,
"semi": true
}
================================================
FILE: frontend/index.html
================================================
Vite + React + TS
================================================
FILE: frontend/package.json
================================================
{
"name": "steam-auto-shutdown",
"private": true,
"type": "module",
"version": "0.0.0",
"dependencies": {
"@nextui-org/react": "^2.2.1",
"@reduxjs/toolkit": "^1.9.7",
"@tabler/icons-react": "^2.40.0",
"framer-motion": "^10.16.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.1.3",
"redux": "^4.2.1"
},
"devDependencies": {
"@types/react": "^18.2.34",
"@types/react-dom": "^18.2.14",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"@vitejs/plugin-react-swc": "^3.4.1",
"autoprefixer": "^10.4.16",
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-unused-imports": "^3.0.0",
"postcss": "^8.4.31",
"prettier": "^3.0.3",
"tailwindcss": "^3.3.5",
"typescript": "^5.2.2",
"vite": "^4.5.1"
},
"scripts": {
"build": "tsc && vite build",
"build:dev": "tsc --noEmit && vite build",
"dev": "vite",
"format": "prettier --write .",
"lint": "eslint ./src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint ./src --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --fix",
"preview": "vite preview"
}
}
================================================
FILE: frontend/package.json.md5
================================================
b9a0f26962856072c7fabc2c3db30c17
================================================
FILE: frontend/postcss.config.js
================================================
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};
================================================
FILE: frontend/src/actions/app.ts
================================================
import { appSliceActions } from '../store/app-slice';
import { store } from '../store';
import { DesktopApi } from '../desktop';
import { TProcess } from '../types';
export const setSelectedMac = (mac: string | undefined) => {
store.dispatch(appSliceActions.setSelectedMac(mac));
};
export const setNetworkSpeed = (speed: number | undefined) => {
store.dispatch(appSliceActions.setNetworkSpeed(speed || 0));
};
export const setDiskSpeed = (speed: number | undefined) => {
store.dispatch(appSliceActions.setDiskSpeed(speed || 0));
};
export const setSettings = (settings: any) => {
store.dispatch(appSliceActions.setSettings(settings));
};
export const toggleMonitoring = () => {
store.dispatch(appSliceActions.toggleMonitoring());
};
export const toggleTheme = () => {
store.dispatch(appSliceActions.toggleTheme());
};
export const setTargetProcess = (process: TProcess) => {
store.dispatch(appSliceActions.setTargetProcess(process));
};
export const setMonitorStatusMsg = (msg: string) => {
store.dispatch(appSliceActions.setMonitorStatusMsg(msg));
};
export const loadInterfaces = async () => {
const results = await DesktopApi.getInterfaces();
store.dispatch(appSliceActions.setInterfaces(results));
try {
const detectedMac = await DesktopApi.autoDetectInterface();
if (detectedMac) {
setSelectedMac(detectedMac);
}
} catch {
// do nothing
}
};
export const getSteamProcess = async () => {
const processes = await DesktopApi.getProcesses();
const result = processes?.find((p) => p.Name === 'steam.exe');
if (!result) return;
const process: TProcess = {
id: result.Pid,
name: result.Name
};
setTargetProcess(process);
};
================================================
FILE: frontend/src/actions/modal.ts
================================================
import { Modal, TGenericObject } from '../types';
import { modalsSliceActions } from '../store/modals-slice';
import { store } from '../store';
export const openModal = (modal: Modal, props?: TGenericObject) => {
store.dispatch(modalsSliceActions.setIsOpen(true));
store.dispatch(
modalsSliceActions.setModalInfo({
modal,
props
})
);
};
export const closeModals = () => {
store.dispatch(modalsSliceActions.setIsOpen(false));
// allow fade out animation to complete before stopping rendering, otherwise it looks choppy
setTimeout(() => {
store.dispatch(
modalsSliceActions.setModalInfo({ modal: undefined, props: {} })
);
}, 500);
};
================================================
FILE: frontend/src/components/app/index.tsx
================================================
import { useEffect, useRef } from 'react';
import { getSteamProcess, loadInterfaces } from '../../actions/app';
import useTheme from '../../hooks/use-theme';
import useSystemMetrics from '../../hooks/use-system-metrics';
import useSystemMonitor from '../../hooks/use-system-monitor';
import Home from '../../screens/home';
const App = () => {
const inited = useRef(false);
useSystemMetrics();
useTheme();
useSystemMonitor();
const initApp = async () => {
await Promise.all([loadInterfaces(), getSteamProcess()]);
};
useEffect(() => {
if (inited.current) return;
inited.current = true;
initApp();
}, []);
return ;
};
export default App;
================================================
FILE: frontend/src/components/disk-monitor/index.tsx
================================================
import { Switch, Tooltip } from '@nextui-org/react';
import {} from '../../wailsjs/go/models';
import { openModal } from '../../actions/modal';
import { IconListSearch } from '@tabler/icons-react';
import { Modal } from '../../types';
import useTargetProcess from '../../hooks/use-target-process';
const DiskMonitor = ({ value, onChange }) => {
const targetProcessName = useTargetProcess();
const onTargetProcessChangeClick = async () => {
openModal(Modal.PROCESS_PICKER);
};
return (
Monitor Disk
{targetProcessName?.name}
);
};
export default DiskMonitor;
================================================
FILE: frontend/src/components/disk-speed/index.tsx
================================================
import { Tooltip } from '@nextui-org/react';
import { IconServer2 } from '@tabler/icons-react';
import useSettings from '../../hooks/use-settings';
import { useDiskSpeedInMegaBytes } from '../../hooks/use-disk-speed';
import useTargetProcess from '../../hooks/use-target-process';
const DiskSpeed = () => {
const { diskActivityMonitor } = useSettings();
const diskSpeed = useDiskSpeedInMegaBytes();
const targetProcess = useTargetProcess();
const color = diskActivityMonitor ? 'text-gray-500' : 'text-gray-700';
const tooltipContent = diskActivityMonitor ? (
Disk activity monitor is enabled and scanning for disk activity from{' '}
{targetProcess?.name}
) : (
Disk activity monitor is disabled. You can enable it in the settings.
);
return (
{diskSpeed.toFixed(2)} MB/s
);
};
export default DiskSpeed;
================================================
FILE: frontend/src/components/footer/index.tsx
================================================
import DiskSpeed from '../disk-speed';
import NetworkSpeed from '../network-speed';
const Footer = () => {
return (
);
};
export default Footer;
================================================
FILE: frontend/src/components/header/index.tsx
================================================
import { Image, Link } from '@nextui-org/react';
import Logo from '../../assets/icone-steam-bleue.png';
import { DesktopApi } from '../../desktop';
const Header = () => {
return (
STEAM AUTO SHUTDOWN
Version {APP_VERSION}
DesktopApi.openInBrowser(
'https://github.com/diogomartino/steam-auto-shutdown'
)
}
>
Github
);
};
export default Header;
================================================
FILE: frontend/src/components/interface-picker/index.tsx
================================================
import { useMemo } from 'react';
import {
Button,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownTrigger,
Tooltip
} from '@nextui-org/react';
import { setSelectedMac } from '../../actions/app';
import useSelectedMac from '../../hooks/use-selected-mac';
import useInterfaces from '../../hooks/use-interfaces';
const InterfacePicker = () => {
const selectedMac = useSelectedMac();
const interfaces = useInterfaces();
const selectedValue = useMemo(() => {
const selected = interfaces.find(
(item) => item.hardwareaddr === selectedMac
);
return selected ? `${selected.name}` : '';
}, [selectedMac, interfaces]);
return (
Network Interface
{selectedValue || 'Select Network Interface'}
{
const [first] = selection;
setSelectedMac(first.toString());
}}
>
{interfaces.map((item) => (
{item.name}
))}
);
};
export default InterfacePicker;
================================================
FILE: frontend/src/components/modals/action-confirmation/index.tsx
================================================
import {
Button,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader
} from '@nextui-org/react';
import { closeModals } from '../../../actions/modal';
import useSettings from '../../../hooks/use-settings';
import { ActionType } from '../../../types';
import { DesktopApi } from '../../../desktop';
import useModalsInfo from '../../../hooks/use-modals-info';
const messageMap = {
[ActionType.HIBERNATE]: 'Your computer will hibernate in 10 seconds',
[ActionType.SHUTDOWN]: 'Your computer will shutdown in 10 seconds',
[ActionType.SLEEP]: 'Your computer will go to sleep in 10 seconds',
[ActionType.LOGOFF]: 'Your computer log your user off in 10 seconds'
};
const ActionConfirmationModal = () => {
const { isModalOpen } = useModalsInfo();
const settings = useSettings();
const onCancelClick = async () => {
await DesktopApi.cancelShutdown();
closeModals();
};
return (
{messageMap[settings.actionType]}
CANCEL NOW
);
};
export default ActionConfirmationModal;
================================================
FILE: frontend/src/components/modals/index.tsx
================================================
import { Modal } from '../../types';
import ProcessPicker from './process-picker';
import { createElement } from 'react';
import SettingsModal from './settings';
import ActionConfirmationModal from './action-confirmation';
import useModalsInfo from '../../hooks/use-modals-info';
const ModalsMap = {
[Modal.PROCESS_PICKER]: ProcessPicker,
[Modal.SETTINGS]: SettingsModal,
[Modal.ACTION_CONFIRMATION]: ActionConfirmationModal
};
const ModalsProvider = () => {
const { openModal, modalProps } = useModalsInfo();
if (!openModal || !ModalsMap[openModal]) return null;
return createElement(ModalsMap[openModal], modalProps);
};
export default ModalsProvider;
================================================
FILE: frontend/src/components/modals/process-picker/index.tsx
================================================
import {
Button,
Input,
Link,
Listbox,
ListboxItem,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Spinner
} from '@nextui-org/react';
import { openModal } from '../../../actions/modal';
import { DesktopApi } from '../../../desktop';
import { useEffect, useMemo, useState } from 'react';
import { TProcess } from '../../../types';
import { setTargetProcess } from '../../../actions/app';
import { IconSearch } from '@tabler/icons-react';
import { Modal as ModalList } from '../../../types';
import useModalsInfo from '../../../hooks/use-modals-info';
import useTargetProcess from '../../../hooks/use-target-process';
const ProcessPicker = () => {
const { isModalOpen } = useModalsInfo();
const targetProcess = useTargetProcess();
const [processes, setProcesses] = useState([]);
const [search, setSearch] = useState('');
const [loading, setLoading] = useState(false);
const filteredProcesses = useMemo(() => {
return processes.filter((process) =>
process.Name.toString()
.trim()
.toLowerCase()
.includes(search.trim().toLowerCase())
);
}, [processes, search]);
const onSearchChange = (event: any) => {
setSearch(event.target.value);
};
const loadProcesses = async () => {
setLoading(true);
const processes = await DesktopApi.getProcesses();
setProcesses(processes);
setLoading(false);
};
useEffect(() => {
loadProcesses();
}, []);
return (
{
openModal(ModalList.SETTINGS);
}}
scrollBehavior="inside"
>
{(onClose) => (
<>
Pick a process to monitor
}
/>
The process you pick here will only be used to monitor the disk
activity.{' '}
Learn more.
{loading ? (
) : (
{
const [processId] = selection;
const process: TProcess = {
id: processId.toString(),
name:
processes.find((p) => p.Pid.toString() === processId)
?.Name ?? ''
};
setTargetProcess(process);
}}
>
{filteredProcesses?.map((process) => (
{process.Name}{' '}
{process.Pid}
))}
)}
Close
>
)}
);
};
export default ProcessPicker;
================================================
FILE: frontend/src/components/modals/settings/index.tsx
================================================
import {
Button,
Chip,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownTrigger,
Input,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Tooltip
} from '@nextui-org/react';
import { closeModals } from '../../../actions/modal';
import DiskMonitor from '../../disk-monitor';
import useSettings from '../../../hooks/use-settings';
import { setSettings } from '../../../actions/app';
import { ActionType } from '../../../types';
import { useMemo } from 'react';
import { IconInfoCircle } from '@tabler/icons-react';
import useModalsInfo from '../../../hooks/use-modals-info';
const SettingsModal = () => {
const { isModalOpen } = useModalsInfo();
const { speedThreshold, actionDelay, actionType, diskActivityMonitor } =
useSettings();
const onChangeSettings = (event) => {
const { name, value } = event.target;
setSettings({
[name]: isNaN(value) ? value : parseInt(value)
});
};
const selectedValue = useMemo(() => {
switch (actionType) {
case ActionType.SHUTDOWN:
return 'Shutdown';
case ActionType.HIBERNATE:
return 'Hibernate';
case ActionType.LOGOFF:
return 'Logoff';
case ActionType.SLEEP:
return 'Sleep';
default:
return '';
}
}, [actionType]);
return (
{(onClose) => (
<>
Settings
Shutdown after x seconds of inactivity
The number of seconds to wait before shutting down the
PC after no downloads are active. Using a small value
will increase the chance of shutting down while a
download is still active.
Download speed threshold
The minimum download speed to consider a download
active.
Monitor disk
Monitors disk activity to prevent shutdowns when a
process is writing to disk. This is useful, for example,
in case steam is not actively downloading but is writing
to disk, like uncompressing game files.
Action to perform
The action to perform when no downloads are active.
Sleeping is the only action that can be cancelled by the
user after it has been triggered.
}
>
{
setSettings({
actionDelay: 20,
speedThreshold: 200,
actionType: ActionType.SHUTDOWN
});
}}
size="sm"
>
Reset to defaults
Close
>
)}
);
};
export default SettingsModal;
================================================
FILE: frontend/src/components/monitor-switch/index.tsx
================================================
import { Card, CardBody, Switch } from '@nextui-org/react';
import useMonitorStatusMsg from '../../hooks/use-monitor-status-msg';
import useSettings from '../../hooks/use-settings';
type TMonitorSwitchProps = {
isActive: boolean;
setIsActive: (value: boolean) => void;
};
const MonitorSwitch = ({ isActive, setIsActive }: TMonitorSwitchProps) => {
const statusMsg = useMonitorStatusMsg();
const { actionType } = useSettings();
return (
{isActive ? (
<>
Auto shutdown is enabled
{statusMsg}
Your network speed is being monitored and your PC will{' '}
{actionType.toLowerCase()} when no downloads are active.
>
) : (
Auto shutdown is disabled
)}
);
};
export default MonitorSwitch;
================================================
FILE: frontend/src/components/network-speed/index.tsx
================================================
import { Tooltip } from '@nextui-org/react';
import { IconWorldDownload } from '@tabler/icons-react';
import { useNetworkSpeedInMegaBytes } from '../../hooks/use-network-speed';
const NetworkSpeed = () => {
const speed = useNetworkSpeedInMegaBytes();
return (
);
};
export default NetworkSpeed;
================================================
FILE: frontend/src/components/top-right-slot/index.tsx
================================================
import { Button } from '@nextui-org/react';
import { IconMoon, IconSettings, IconSun } from '@tabler/icons-react';
import { toggleTheme } from '../../actions/app';
import { openModal } from '../../actions/modal';
import { Modal } from '../../types';
import useSelectedTheme from '../../hooks/use-selected-theme';
const TopRightSlot = () => {
const theme = useSelectedTheme();
const onToggleThemeClick = () => {
toggleTheme();
};
const onSettingsClick = () => {
openModal(Modal.SETTINGS);
};
return (
{theme === 'light' ? : }
);
};
export default TopRightSlot;
================================================
FILE: frontend/src/desktop.ts
================================================
import * as NetworkManager from './wailsjs/go/main/NetworkManager';
import * as DiskManager from './wailsjs/go/main/DiskManager';
import * as GoApp from './wailsjs/go/main/App';
export const DesktopApi = {
getInterfaces: NetworkManager.GetInterfaces,
autoDetectInterface: NetworkManager.AutoDetectInterface,
getNetworkSpeed: NetworkManager.GetInterfaceDownloadSpeed,
getProcesses: DiskManager.GetProcesses,
getDiskWriteSpeed: DiskManager.GetDiskWriteSpeed,
executeAction: GoApp.ExecuteAction,
cancelShutdown: GoApp.CancelShutdown,
openInBrowser: GoApp.OpenInBrowser
};
================================================
FILE: frontend/src/helpers/sleep.ts
================================================
export default (sleepTime: number) =>
new Promise((resolve) => setTimeout(resolve, sleepTime));
================================================
FILE: frontend/src/hooks/use-disk-speed.ts
================================================
import { useSelector } from 'react-redux';
import {
diskSpeedInBytesPerSecondSelector,
diskSpeedInMegabytesPerSecondSelector
} from '../selectors/app';
const useDiskSpeedInMegaBytes = () =>
useSelector(diskSpeedInMegabytesPerSecondSelector);
const useDiskSpeedInBytes = () =>
useSelector(diskSpeedInBytesPerSecondSelector);
export { useDiskSpeedInMegaBytes, useDiskSpeedInBytes };
================================================
FILE: frontend/src/hooks/use-interfaces.ts
================================================
import { useSelector } from 'react-redux';
import { interfacesSelector } from '../selectors/app';
const useInterfaces = () => useSelector(interfacesSelector);
export default useInterfaces;
================================================
FILE: frontend/src/hooks/use-modals-info.ts
================================================
import { useSelector } from 'react-redux';
import { modalsInfoSelector } from '../selectors/modals';
const useModalsInfo = () => {
const modalInfo = useSelector(modalsInfoSelector);
return {
modalProps: modalInfo.props,
isModalOpen: modalInfo.isOpen,
openModal: modalInfo.openModal
};
};
export default useModalsInfo;
================================================
FILE: frontend/src/hooks/use-monitor-status-msg.ts
================================================
import { useSelector } from 'react-redux';
import { monitorStatusMsgSelector } from '../selectors/app';
const useMonitorStatusMsg = () => useSelector(monitorStatusMsgSelector);
export default useMonitorStatusMsg;
================================================
FILE: frontend/src/hooks/use-monitor-status.ts
================================================
import { useSelector } from 'react-redux';
import { monitoringSelector } from '../selectors/app';
const useMonitorStatus = () => useSelector(monitoringSelector);
export default useMonitorStatus;
================================================
FILE: frontend/src/hooks/use-network-speed.ts
================================================
import { useSelector } from 'react-redux';
import {
networkSpeedInBytesPerSecondSelector,
networkSpeedInMegabytesPerSecondSelector
} from '../selectors/app';
const useNetworkSpeedInMegaBytes = () =>
useSelector(networkSpeedInMegabytesPerSecondSelector);
const useNetworkSpeedInBytes = () =>
useSelector(networkSpeedInBytesPerSecondSelector);
export { useNetworkSpeedInMegaBytes, useNetworkSpeedInBytes };
================================================
FILE: frontend/src/hooks/use-selected-mac.ts
================================================
import { useSelector } from 'react-redux';
import { selectedMacSelector } from '../selectors/app';
const useSelectedMac = () => useSelector(selectedMacSelector);
export default useSelectedMac;
================================================
FILE: frontend/src/hooks/use-selected-theme.ts
================================================
import { useSelector } from 'react-redux';
import { themeSelector } from '../selectors/app';
export const useSelectedTheme = () => useSelector(themeSelector);
export default useSelectedTheme;
================================================
FILE: frontend/src/hooks/use-settings.ts
================================================
import { useSelector } from 'react-redux';
import { settingsSelector } from '../selectors/app';
const useSettings = () => useSelector(settingsSelector);
export default useSettings;
================================================
FILE: frontend/src/hooks/use-system-metrics.ts
================================================
import { selectedMacSelector, targetProcessSelector } from '../selectors/app';
import { useEffect, useRef } from 'react';
import { DesktopApi } from '../desktop';
import { setDiskSpeed, setNetworkSpeed } from '../actions/app';
import { store } from '../store';
const useSystemMetrics = () => {
const timeout = useRef(undefined);
useEffect(() => {
// we use getState here so we don't need to manage useEffect dependencies
const loop = async () => {
const state = store.getState();
const selectedMac = selectedMacSelector(state);
const targetProcess = targetProcessSelector(state);
try {
const [networkSpeed, diskSpeed] = await Promise.all([
DesktopApi.getNetworkSpeed(selectedMac || ''),
DesktopApi.getDiskWriteSpeed(+(targetProcess?.id || 0))
]);
setNetworkSpeed(networkSpeed);
setDiskSpeed(diskSpeed);
} catch {
// Ignore
}
clearTimeout(timeout.current);
timeout.current = setTimeout(loop, 1000);
};
clearTimeout(timeout.current);
loop();
return () => {
clearTimeout(timeout.current);
};
}, []);
};
export default useSystemMetrics;
================================================
FILE: frontend/src/hooks/use-system-monitor.ts
================================================
import { useEffect, useRef } from 'react';
import { store } from '../store';
import {
diskSpeedInBytesPerSecondSelector,
networkSpeedInBytesPerSecondSelector,
settingsSelector
} from '../selectors/app';
import useMonitorStatus from './use-monitor-status';
import { setMonitorStatusMsg } from '../actions/app';
import { openModal } from '../actions/modal';
import { Modal } from '../types';
import { DesktopApi } from '../desktop';
const INTERVAL_MS = 1000;
const IDLE_THRESHOLD = 5;
let idleCounter = 0;
let idleThresholdCounter = 0;
const useSystemMonitor = () => {
const interval = useRef(undefined);
const isMonitoring = useMonitorStatus();
// we use getState here so we don't need to manage useEffect dependencies
const monitor = async () => {
const state = store.getState();
const { actionDelay, speedThreshold, diskActivityMonitor, actionType } =
settingsSelector(state);
const networkSpeedInBytes = networkSpeedInBytesPerSecondSelector(state);
const diskSpeedInBytes = diskSpeedInBytesPerSecondSelector(state);
const speedThresholdInBytes = speedThreshold * 1024;
const isBelowNetworkSpeedThreshold =
networkSpeedInBytes < speedThresholdInBytes;
const isBelowDiskSpeedThreshold = diskSpeedInBytes < speedThresholdInBytes;
// if diskActivityMonitor is enabled, we need to check both network and disk speed
const isIdle = diskActivityMonitor
? isBelowNetworkSpeedThreshold && isBelowDiskSpeedThreshold
: isBelowNetworkSpeedThreshold;
if (isIdle) {
idleCounter += 1;
idleThresholdCounter = 0;
setMonitorStatusMsg(
`No activity detected for ${idleCounter}/${actionDelay} seconds.`
);
} else {
idleThresholdCounter += 1;
if (idleThresholdCounter >= IDLE_THRESHOLD) {
idleCounter = 0;
}
setMonitorStatusMsg('Download in progress.');
}
if (idleCounter >= actionDelay) {
clearTimeout(interval.current);
setMonitorStatusMsg('Detected download completion.');
DesktopApi.executeAction(actionType);
openModal(Modal.ACTION_CONFIRMATION);
}
};
useEffect(() => {
if (!isMonitoring) {
clearTimeout(interval.current);
return;
}
setMonitorStatusMsg('Starting...');
idleCounter = 0;
interval.current = setInterval(monitor, INTERVAL_MS);
return () => {
clearTimeout(interval.current);
};
}, [isMonitoring]);
};
export default useSystemMonitor;
================================================
FILE: frontend/src/hooks/use-target-process.ts
================================================
import { useSelector } from 'react-redux';
import { targetProcessSelector } from '../selectors/app';
const useTargetProcess = () => useSelector(targetProcessSelector);
export default useTargetProcess;
================================================
FILE: frontend/src/hooks/use-theme.ts
================================================
import { useSelector } from 'react-redux';
import { themeSelector } from '../selectors/app';
import { useEffect } from 'react';
const useTheme = () => {
const theme = useSelector(themeSelector);
useEffect(() => {
const html = document.querySelector('html');
if (theme === 'light') {
html?.classList.remove('dark');
html?.classList.add('light');
} else {
html?.classList.remove('light');
html?.classList.add('dark');
}
}, [theme]);
};
export default useTheme;
================================================
FILE: frontend/src/main.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body,
[data-overlay-container='true'] {
height: 100%;
overflow: hidden;
}
#root {
display: flex;
flex-direction: column;
min-height: 100vh;
height: 100%;
}
p {
cursor: default;
}
================================================
FILE: frontend/src/main.tsx
================================================
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './components/app';
import { store } from './store';
import { Provider } from 'react-redux';
import { NextUIProvider } from '@nextui-org/react';
import './main.css';
import ModalsProvider from './components/modals';
ReactDOM.createRoot(document.getElementById('root')!).render(
);
================================================
FILE: frontend/src/screens/home/index.tsx
================================================
import { Divider } from '@nextui-org/react';
import Header from '../../components/header';
import MonitorSwitch from '../../components/monitor-switch';
import InterfacePicker from '../../components/interface-picker';
import Footer from '../../components/footer';
import TopRightSlot from '../../components/top-right-slot';
import useMonitorStatus from '../../hooks/use-monitor-status';
import { toggleMonitoring } from '../../actions/app';
const Home = () => {
const isMonitoring = useMonitorStatus();
const onToggleClick = () => {
toggleMonitoring();
};
return (
);
};
export default Home;
================================================
FILE: frontend/src/selectors/app.ts
================================================
import { IRootState } from '../store';
export const selectedMacSelector = (state: IRootState) => state.app.selectedMac;
export const targetProcessSelector = (state: IRootState) =>
state.app.targetProcess;
export const interfacesSelector = (state: IRootState) => state.app.interfaces;
export const diskSpeedInBytesPerSecondSelector = (state: IRootState) =>
state.app.diskSpeed;
export const diskSpeedInMegabytesPerSecondSelector = (state: IRootState) =>
state.app.diskSpeed / 1000000;
export const networkSpeedInBytesPerSecondSelector = (state: IRootState) =>
state.app.networkSpeed;
export const networkSpeedInMegabytesPerSecondSelector = (state: IRootState) =>
state.app.networkSpeed / 1000000;
export const themeSelector = (state: IRootState) => state.app.settings.theme;
export const settingsSelector = (state: IRootState) => state.app.settings;
export const monitoringSelector = (state: IRootState) => state.app.monitoring;
export const monitorStatusMsgSelector = (state: IRootState) =>
state.app.monitorStatusMsg;
================================================
FILE: frontend/src/selectors/modals.ts
================================================
import { IRootState } from '../store';
export const openModalSelector = (state: IRootState) => state.modals.openModal;
export const modalPropsSelector = (state: IRootState) => state.modals.props;
export const modalIsOpenSelector = (state: IRootState) => state.modals.isOpen;
export const modalsInfoSelector = (state: IRootState) => state.modals;
================================================
FILE: frontend/src/store/app-slice.ts
================================================
import { createSlice } from '@reduxjs/toolkit';
import { net } from '../wailsjs/go/models';
import { ActionType, TProcess, TSettings } from '../types';
const getStoredSettings = () => {
const stored = JSON.parse(localStorage.getItem('settings') || '{}');
return {
theme: stored.theme || 'dark',
diskActivityMonitor: stored.diskActivityMonitor || false,
actionDelay: stored.actionDelay || 20,
actionType: stored.actionType || ActionType.SHUTDOWN,
speedThreshold: stored.speedThreshold || 200
} as TSettings;
};
export interface IAppState {
selectedMac: string | undefined;
interfaces: net.InterfaceStat[];
networkSpeed: number;
diskSpeed: number;
targetProcess: TProcess | undefined;
settings: TSettings;
monitoring: boolean;
monitorStatusMsg: string;
}
const initialState: IAppState = {
selectedMac: undefined,
interfaces: [],
networkSpeed: 0,
diskSpeed: 0,
targetProcess: {
name: undefined,
id: undefined
} as TProcess,
settings: getStoredSettings(),
monitoring: false,
monitorStatusMsg: ''
};
export const appSlice = createSlice({
name: 'app',
initialState,
reducers: {
setSelectedMac: (state, action) => {
state.selectedMac = action.payload;
},
setInterfaces: (state, action) => {
state.interfaces = action.payload;
},
setNetworkSpeed: (state, action) => {
state.networkSpeed = action.payload;
},
setDiskSpeed: (state, action) => {
state.diskSpeed = action.payload;
},
toggleTheme: (state) => {
state.settings.theme = state.settings.theme === 'dark' ? 'light' : 'dark';
localStorage.setItem('settings', JSON.stringify(state.settings));
},
setTargetProcess: (state, action) => {
state.targetProcess = action.payload;
},
setSettings: (state, action) => {
state.settings = {
...state.settings,
...action.payload
};
localStorage.setItem('settings', JSON.stringify(action.payload));
},
toggleMonitoring: (state) => {
state.monitoring = !state.monitoring;
},
setMonitorStatusMsg: (state, action) => {
state.monitorStatusMsg = action.payload;
}
}
});
export const appSliceActions = appSlice.actions;
export default appSlice.reducer;
================================================
FILE: frontend/src/store/index.ts
================================================
import { configureStore } from '@reduxjs/toolkit';
import appSlice from './app-slice';
import modalsSlice from './modals-slice';
export const store = configureStore({
reducer: {
app: appSlice,
modals: modalsSlice
}
});
export type IRootState = ReturnType;
================================================
FILE: frontend/src/store/modals-slice.ts
================================================
import { createSlice } from '@reduxjs/toolkit';
import { Modal, TGenericObject } from '../types';
export interface IAppState {
openModal: Modal | undefined;
props?: TGenericObject;
isOpen: boolean;
}
const initialState: IAppState = {
openModal: undefined,
props: {},
isOpen: false
};
export const appSlice = createSlice({
name: 'modals',
initialState,
reducers: {
setOpenModal: (state, action) => {
state.openModal = action.payload;
},
setModalProps: (state, action) => {
state.props = action.payload;
},
setIsOpen: (state, action) => {
state.isOpen = action.payload;
},
setModalInfo: (state, action) => {
state.openModal = action.payload.modal;
state.props = action.payload.props;
}
}
});
export const modalsSliceActions = appSlice.actions;
export default appSlice.reducer;
================================================
FILE: frontend/src/types/index.ts
================================================
export type TTheme = 'light' | 'dark';
export type TGenericObject = {
[key: string]: any;
};
export enum Modal {
PROCESS_PICKER = 'PROCESS_PICKER',
SETTINGS = 'SETTINGS',
ACTION_CONFIRMATION = 'ACTION_CONFIRMATION'
}
export enum ActionType {
SHUTDOWN = 'SHUTDOWN',
HIBERNATE = 'HIBERNATE',
SLEEP = 'SLEEP',
LOGOFF = 'LOGOFF'
}
export type TProcess = {
id: string | undefined;
name: string | undefined;
};
export type TSettings = {
theme: TTheme;
diskActivityMonitor: boolean;
actionDelay: number; // seconds
actionType: ActionType;
speedThreshold: number; // KB/s
};
================================================
FILE: frontend/src/vite-env.d.ts
================================================
///
declare const APP_VERSION: string;
================================================
FILE: frontend/src/wailsjs/go/main/App.d.ts
================================================
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function CancelShutdown():Promise;
export function ExecuteAction(arg1:string):Promise;
export function OpenInBrowser(arg1:string):Promise;
================================================
FILE: frontend/src/wailsjs/go/main/App.js
================================================
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function CancelShutdown() {
return window['go']['main']['App']['CancelShutdown']();
}
export function ExecuteAction(arg1) {
return window['go']['main']['App']['ExecuteAction'](arg1);
}
export function OpenInBrowser(arg1) {
return window['go']['main']['App']['OpenInBrowser'](arg1);
}
================================================
FILE: frontend/src/wailsjs/go/main/DiskManager.d.ts
================================================
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {main} from '../models';
export function GetDiskWriteSpeed(arg1:number):Promise;
export function GetProcesses():Promise>;
================================================
FILE: frontend/src/wailsjs/go/main/DiskManager.js
================================================
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function GetDiskWriteSpeed(arg1) {
return window['go']['main']['DiskManager']['GetDiskWriteSpeed'](arg1);
}
export function GetProcesses() {
return window['go']['main']['DiskManager']['GetProcesses']();
}
================================================
FILE: frontend/src/wailsjs/go/main/NetworkManager.d.ts
================================================
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {net} from '../models';
export function AutoDetectInterface():Promise;
export function GetIOStatByMac(arg1:string):Promise;
export function GetInterfaceByMac(arg1:string):Promise;
export function GetInterfaceDownloadSpeed(arg1:string):Promise;
export function GetInterfaces():Promise>;
================================================
FILE: frontend/src/wailsjs/go/main/NetworkManager.js
================================================
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function AutoDetectInterface() {
return window['go']['main']['NetworkManager']['AutoDetectInterface']();
}
export function GetIOStatByMac(arg1) {
return window['go']['main']['NetworkManager']['GetIOStatByMac'](arg1);
}
export function GetInterfaceByMac(arg1) {
return window['go']['main']['NetworkManager']['GetInterfaceByMac'](arg1);
}
export function GetInterfaceDownloadSpeed(arg1) {
return window['go']['main']['NetworkManager']['GetInterfaceDownloadSpeed'](arg1);
}
export function GetInterfaces() {
return window['go']['main']['NetworkManager']['GetInterfaces']();
}
================================================
FILE: frontend/src/wailsjs/go/models.ts
================================================
export namespace net {
export class IOCountersStat {
name: string;
bytesSent: number;
bytesRecv: number;
packetsSent: number;
packetsRecv: number;
errin: number;
errout: number;
dropin: number;
dropout: number;
fifoin: number;
fifoout: number;
static createFrom(source: any = {}) {
return new IOCountersStat(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.bytesSent = source["bytesSent"];
this.bytesRecv = source["bytesRecv"];
this.packetsSent = source["packetsSent"];
this.packetsRecv = source["packetsRecv"];
this.errin = source["errin"];
this.errout = source["errout"];
this.dropin = source["dropin"];
this.dropout = source["dropout"];
this.fifoin = source["fifoin"];
this.fifoout = source["fifoout"];
}
}
export class InterfaceAddr {
addr: string;
static createFrom(source: any = {}) {
return new InterfaceAddr(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.addr = source["addr"];
}
}
export class InterfaceStat {
index: number;
mtu: number;
name: string;
hardwareaddr: string;
flags: string[];
addrs: InterfaceAddr[];
static createFrom(source: any = {}) {
return new InterfaceStat(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.index = source["index"];
this.mtu = source["mtu"];
this.name = source["name"];
this.hardwareaddr = source["hardwareaddr"];
this.flags = source["flags"];
this.addrs = this.convertValues(source["addrs"], InterfaceAddr);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
================================================
FILE: frontend/src/wailsjs/runtime/package.json
================================================
{
"name": "@wailsapp/runtime",
"version": "2.0.0",
"description": "Wails Javascript runtime library",
"main": "runtime.js",
"types": "runtime.d.ts",
"scripts": {
},
"repository": {
"type": "git",
"url": "git+https://github.com/wailsapp/wails.git"
},
"keywords": [
"Wails",
"Javascript",
"Go"
],
"author": "Lea Anthony ",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/wails/issues"
},
"homepage": "https://github.com/wailsapp/wails#readme"
}
================================================
FILE: frontend/src/wailsjs/runtime/runtime.d.ts
================================================
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export interface Position {
x: number;
y: number;
}
export interface Size {
w: number;
h: number;
}
export interface Screen {
isCurrent: boolean;
isPrimary: boolean;
width : number
height : number
}
// Environment information such as platform, buildtype, ...
export interface EnvironmentInfo {
buildType: string;
platform: string;
arch: string;
}
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
// emits the given event. Optional data may be passed with the event.
// This will trigger any event listeners.
export function EventsEmit(eventName: string, ...data: any): void;
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
// sets up a listener for the given event name, but will only trigger a given number times.
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
// sets up a listener for the given event name, but will only trigger once.
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
// unregisters the listener for the given event name.
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
// unregisters all listeners.
export function EventsOffAll(): void;
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
// logs the given message as a raw message
export function LogPrint(message: string): void;
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
// logs the given message at the `trace` log level.
export function LogTrace(message: string): void;
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
// logs the given message at the `debug` log level.
export function LogDebug(message: string): void;
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
// logs the given message at the `error` log level.
export function LogError(message: string): void;
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
// logs the given message at the `fatal` log level.
// The application will quit after calling this method.
export function LogFatal(message: string): void;
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
// logs the given message at the `info` log level.
export function LogInfo(message: string): void;
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
// logs the given message at the `warning` log level.
export function LogWarning(message: string): void;
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
// Forces a reload by the main application as well as connected browsers.
export function WindowReload(): void;
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
// Reloads the application frontend.
export function WindowReloadApp(): void;
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
// Sets the window AlwaysOnTop or not on top.
export function WindowSetAlwaysOnTop(b: boolean): void;
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
// *Windows only*
// Sets window theme to system default (dark/light).
export function WindowSetSystemDefaultTheme(): void;
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
// *Windows only*
// Sets window to light theme.
export function WindowSetLightTheme(): void;
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
// *Windows only*
// Sets window to dark theme.
export function WindowSetDarkTheme(): void;
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
// Centers the window on the monitor the window is currently on.
export function WindowCenter(): void;
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
// Sets the text in the window title bar.
export function WindowSetTitle(title: string): void;
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
// Makes the window full screen.
export function WindowFullscreen(): void;
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
// Restores the previous window dimensions and position prior to full screen.
export function WindowUnfullscreen(): void;
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
export function WindowIsFullscreen(): Promise;
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window.
export function WindowSetSize(width: number, height: number): Promise;
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
// Gets the width and height of the window.
export function WindowGetSize(): Promise;
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMaxSize(width: number, height: number): void;
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMinSize(width: number, height: number): void;
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
// Sets the window position relative to the monitor the window is currently on.
export function WindowSetPosition(x: number, y: number): void;
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
// Gets the window position relative to the monitor the window is currently on.
export function WindowGetPosition(): Promise;
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
// Hides the window.
export function WindowHide(): void;
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
// Shows the window, if it is currently hidden.
export function WindowShow(): void;
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
// Maximises the window to fill the screen.
export function WindowMaximise(): void;
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
// Toggles between Maximised and UnMaximised.
export function WindowToggleMaximise(): void;
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
// Restores the window to the dimensions and position prior to maximising.
export function WindowUnmaximise(): void;
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
// Returns the state of the window, i.e. whether the window is maximised or not.
export function WindowIsMaximised(): Promise;
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
// Minimises the window.
export function WindowMinimise(): void;
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
// Restores the window to the dimensions and position prior to minimising.
export function WindowUnminimise(): void;
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
// Returns the state of the window, i.e. whether the window is minimised or not.
export function WindowIsMinimised(): Promise;
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
// Returns the state of the window, i.e. whether the window is normal or not.
export function WindowIsNormal(): Promise;
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
export function ScreenGetAll(): Promise;
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
// Opens the given URL in the system browser.
export function BrowserOpenURL(url: string): void;
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
// Returns information about the environment
export function Environment(): Promise;
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
// Quits the application.
export function Quit(): void;
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
// Hides the application.
export function Hide(): void;
// [Show](https://wails.io/docs/reference/runtime/intro#show)
// Shows the application.
export function Show(): void;
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
// Returns the current text stored on clipboard
export function ClipboardGetText(): Promise;
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
// Sets a text on the clipboard
export function ClipboardSetText(text: string): Promise;
================================================
FILE: frontend/src/wailsjs/runtime/runtime.js
================================================
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export function LogPrint(message) {
window.runtime.LogPrint(message);
}
export function LogTrace(message) {
window.runtime.LogTrace(message);
}
export function LogDebug(message) {
window.runtime.LogDebug(message);
}
export function LogInfo(message) {
window.runtime.LogInfo(message);
}
export function LogWarning(message) {
window.runtime.LogWarning(message);
}
export function LogError(message) {
window.runtime.LogError(message);
}
export function LogFatal(message) {
window.runtime.LogFatal(message);
}
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
}
export function EventsOn(eventName, callback) {
return EventsOnMultiple(eventName, callback, -1);
}
export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}
export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
export function EventsEmit(eventName) {
let args = [eventName].slice.call(arguments);
return window.runtime.EventsEmit.apply(null, args);
}
export function WindowReload() {
window.runtime.WindowReload();
}
export function WindowReloadApp() {
window.runtime.WindowReloadApp();
}
export function WindowSetAlwaysOnTop(b) {
window.runtime.WindowSetAlwaysOnTop(b);
}
export function WindowSetSystemDefaultTheme() {
window.runtime.WindowSetSystemDefaultTheme();
}
export function WindowSetLightTheme() {
window.runtime.WindowSetLightTheme();
}
export function WindowSetDarkTheme() {
window.runtime.WindowSetDarkTheme();
}
export function WindowCenter() {
window.runtime.WindowCenter();
}
export function WindowSetTitle(title) {
window.runtime.WindowSetTitle(title);
}
export function WindowFullscreen() {
window.runtime.WindowFullscreen();
}
export function WindowUnfullscreen() {
window.runtime.WindowUnfullscreen();
}
export function WindowIsFullscreen() {
return window.runtime.WindowIsFullscreen();
}
export function WindowGetSize() {
return window.runtime.WindowGetSize();
}
export function WindowSetSize(width, height) {
window.runtime.WindowSetSize(width, height);
}
export function WindowSetMaxSize(width, height) {
window.runtime.WindowSetMaxSize(width, height);
}
export function WindowSetMinSize(width, height) {
window.runtime.WindowSetMinSize(width, height);
}
export function WindowSetPosition(x, y) {
window.runtime.WindowSetPosition(x, y);
}
export function WindowGetPosition() {
return window.runtime.WindowGetPosition();
}
export function WindowHide() {
window.runtime.WindowHide();
}
export function WindowShow() {
window.runtime.WindowShow();
}
export function WindowMaximise() {
window.runtime.WindowMaximise();
}
export function WindowToggleMaximise() {
window.runtime.WindowToggleMaximise();
}
export function WindowUnmaximise() {
window.runtime.WindowUnmaximise();
}
export function WindowIsMaximised() {
return window.runtime.WindowIsMaximised();
}
export function WindowMinimise() {
window.runtime.WindowMinimise();
}
export function WindowUnminimise() {
window.runtime.WindowUnminimise();
}
export function WindowSetBackgroundColour(R, G, B, A) {
window.runtime.WindowSetBackgroundColour(R, G, B, A);
}
export function ScreenGetAll() {
return window.runtime.ScreenGetAll();
}
export function WindowIsMinimised() {
return window.runtime.WindowIsMinimised();
}
export function WindowIsNormal() {
return window.runtime.WindowIsNormal();
}
export function BrowserOpenURL(url) {
window.runtime.BrowserOpenURL(url);
}
export function Environment() {
return window.runtime.Environment();
}
export function Quit() {
window.runtime.Quit();
}
export function Hide() {
window.runtime.Hide();
}
export function Show() {
window.runtime.Show();
}
export function ClipboardGetText() {
return window.runtime.ClipboardGetText();
}
export function ClipboardSetText(text) {
return window.runtime.ClipboardSetText(text);
}
================================================
FILE: frontend/tailwind.config.js
================================================
import { nextui } from '@nextui-org/react';
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}'
],
theme: {
extend: {}
},
darkMode: 'class',
plugins: [nextui()]
};
================================================
FILE: frontend/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": false
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
================================================
FILE: frontend/tsconfig.node.json
================================================
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
================================================
FILE: frontend/vite.config.ts
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import wails from '../wails.json' assert { type: 'json' };
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
port: 34115,
hmr: {
host: 'localhost',
port: 34115,
protocol: 'ws'
}
},
define: {
APP_VERSION: JSON.stringify(wails.info.productVersion)
}
});
================================================
FILE: go.mod
================================================
module changeme
go 1.18
require (
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/wailsapp/wails/v2 v2.6.0
)
require (
github.com/bep/debounce v1.2.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/labstack/echo/v4 v4.10.2 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.1 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
// replace github.com/wailsapp/wails/v2 v2.6.0 => C:\Users\Martino\go\pkg\mod
================================================
FILE: go.sum
================================================
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg=
github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wailsapp/go-webview2 v1.0.1 h1:dEJIeEApW/MhO2tTMISZBFZPuW7kwrFA1NtgFB1z1II=
github.com/wailsapp/go-webview2 v1.0.1/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.6.0 h1:EyH0zR/EO6dDiqNy8qU5spaXDfkluiq77xrkabPYD4c=
github.com/wailsapp/wails/v2 v2.6.0/go.mod h1:WBG9KKWuw0FKfoepBrr/vRlyTmHaMibWesK3yz6nNiM=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
================================================
FILE: main.go
================================================
package main
import (
"embed"
"log"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/windows"
)
//go:embed all:frontend/dist
var assets embed.FS
var (
width = 800
height = 560
)
func main() {
// Create an instance of the app structure
app := NewApp()
networkManager := createNetworkManager()
diskManager := createDiskManager()
// Create application with options
err := wails.Run(&options.App{
Title: "Steam Auto Shutdown",
Width: width,
Height: height,
MinWidth: width,
MinHeight: height,
MaxWidth: width,
MaxHeight: height,
DisableResize: false,
Fullscreen: false,
Frameless: false,
StartHidden: false,
HideWindowOnClose: false,
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
AssetServer: &assetserver.Options{
Assets: assets,
},
Menu: nil,
Logger: nil,
LogLevel: logger.DEBUG,
OnStartup: app.startup,
OnDomReady: app.domReady,
OnBeforeClose: app.beforeClose,
OnShutdown: app.shutdown,
WindowStartState: options.Normal,
Bind: []interface{}{
app,
networkManager,
diskManager,
},
// Windows platform specific options
Windows: &windows.Options{
WebviewIsTransparent: false,
WindowIsTranslucent: false,
DisableWindowIcon: false,
// DisableFramelessWindowDecorations: false,
WebviewUserDataPath: "",
ZoomFactor: 1.0,
},
})
if err != nil {
log.Fatal(err)
}
}
================================================
FILE: network-manager.go
================================================
package main
import (
"errors"
"time"
"github.com/shirou/gopsutil/net"
)
type NetworkManager struct{}
type Interface struct {
Index int
Name string
MTU int
HardwareAddr string
Flags []string
}
func createNetworkManager() *NetworkManager {
return &NetworkManager{}
}
func (n *NetworkManager) GetInterfaces() []net.InterfaceStat {
netInterfaces, err := net.Interfaces()
if err != nil {
panic(err)
}
return netInterfaces
}
const sampleInterval = 2 * time.Second
func (n *NetworkManager) GetInterfaceDownloadSpeed(mac string) (float64, error) {
initialStat, err := n.GetIOStatByMac(mac)
if err != nil {
return 0, err
}
startTime := time.Now()
endTime := startTime.Add(sampleInterval)
initialBytesRecv := initialStat.BytesRecv
totalBytesRecv := uint64(0)
for time.Now().Before(endTime) {
stat, err := n.GetIOStatByMac(mac)
if err != nil {
return 0, err
}
bytesReceivedNow := stat.BytesRecv - initialBytesRecv
initialBytesRecv = stat.BytesRecv
totalBytesRecv += bytesReceivedNow
time.Sleep(100 * time.Millisecond)
}
actualEndTime := time.Now()
elapsedSeconds := actualEndTime.Sub(startTime).Seconds()
if elapsedSeconds == 0 {
return 0, nil
}
downloadSpeed := float64(totalBytesRecv) / elapsedSeconds
return downloadSpeed, nil
}
func (n *NetworkManager) GetIOStatByMac(mac string) (net.IOCountersStat, error) {
interfaceName := n.GetInterfaceByMac(mac).Name
netInterface, err := net.IOCounters(true)
if err != nil {
panic(err)
}
stat := net.IOCountersStat{}
for _, v := range netInterface {
if v.Name == interfaceName {
stat = v
}
}
if stat.Name == "" {
return stat, errors.New("Interface not found")
}
return stat, nil
}
func (n *NetworkManager) GetInterfaceByMac(mac string) net.InterfaceStat {
netInterfaces, err := net.Interfaces()
if err != nil {
panic(err)
}
for _, v := range netInterfaces {
if v.HardwareAddr == mac {
return v
}
}
return net.InterfaceStat{}
}
func (n *NetworkManager) AutoDetectInterface() string {
netInterface, err := net.IOCounters(true)
if err != nil {
panic(err)
}
var highestInterfaceName string
var highestRecieved float64
for _, v := range netInterface {
if float64(v.BytesRecv) > highestRecieved {
highestRecieved = float64(v.BytesRecv)
highestInterfaceName = v.Name
}
}
interfaces := n.GetInterfaces()
var highestInterfaceMac string
for _, v := range interfaces {
if v.Name == highestInterfaceName {
highestInterfaceMac = v.HardwareAddr
}
}
return highestInterfaceMac
}
================================================
FILE: wails.json
================================================
{
"$schema": "https://wails.io/schemas/config.v2.json",
"name": "Steam Auto Shutdown",
"outputfilename": "Steam Auto Shutdown",
"assetdir": "frontend/dist",
"wailsjsdir": "frontend/src",
"frontend:install": "pnpm install",
"frontend:build": "pnpm build",
"frontend:dev:watcher": "pnpm dev",
"frontend:dev:serverUrl": "auto",
"author": {
"name": "Diogo Martino",
"email": "diogo.martino10@gmail.com"
},
"info": {
"companyName": "DiogoMartino",
"productName": "Steam Auto Shutdown",
"productVersion": "6.0.0",
"copyright": "2024",
"comments": "https://github.com/diogomartino/steam-auto-shutdown"
}
}