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 ![Screenshot 1](https://i.imgur.com/jlJFmLC.jpg) ## 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

{ 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]}
); }; 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 ? (

Loading processes...

) : ( { 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}

))}
)}
)}
); }; 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.
} >
Seconds } /> KB/s } />
setSettings({ diskActivityMonitor: value }) } />

Action to perform

{ const [first] = selection; setSettings({ actionType: first }); }} > Shutdown Hibernate Logoff Sleep
)}
); }; 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 (

{speed.toFixed(2)} MB/s

); }; 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 (
); }; 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" } }