[
  {
    "path": ".github/workflows/wails-build.yaml",
    "content": "name: Wails build\n\non:\n  workflow_dispatch:\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n    runs-on: windows-latest\n    outputs:\n      version: ${{ steps.extract_version.outputs.version }}\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          submodules: recursive\n          token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20.10.0\n\n      - name: Set up Git\n        run: git config --global user.email \"actions@github.com\" && git config --global user.name \"GitHub Actions\"\n\n      - name: Install pnpm\n        run: npm install -g pnpm\n\n      - name: Extract Version\n        id: extract_version\n        shell: bash\n        run: |\n          version=$(jq -r '.info.productVersion' ./wails.json)\n          echo \"Version extracted from wails.json: $version\"\n          echo \"CURRENT_VERSION=$version\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Bump Version\n        id: bump_version\n        shell: bash\n        run: |\n          IFS='.' read -ra version_parts <<< \"${{ steps.extract_version.outputs.CURRENT_VERSION }}\"\n          major=\"${version_parts[0]}\"\n          minor=\"${version_parts[1]}\"\n          patch=\"${version_parts[2]}\"\n\n          patch=$((patch + 1))\n\n          new_version=\"$major.$minor.$patch\"\n          echo \"Bumped version to: $new_version\"\n          echo \"BUILD_VERSION=$new_version\" >> \"$GITHUB_OUTPUT\"\n\n          jq --arg new_version \"$new_version\" '.info.productVersion = $new_version' ./wails.json > temp_wails.json\n          mv temp_wails.json ./wails.json\n\n      - name: Commit and Push bump\n        run: |\n          git add wails.json\n          git commit -m \"Bump version to v${{ steps.bump_version.outputs.BUILD_VERSION }}\"\n          git push\n\n      - name: Setup GoLang\n        uses: actions/setup-go@v4\n        with:\n          check-latest: true\n          go-version: 1.21.3\n\n      - name: Install Wails\n        run: go install github.com/wailsapp/wails/v2/cmd/wails@v2.6.0\n        shell: bash\n\n      - name: Build Windows App\n        working-directory: .\n        run: wails build --clean --platform windows/amd64 -o SteamAutoShutdown.exe\n        shell: bash\n\n      - name: Upload Artifact\n        uses: actions/upload-artifact@v3\n        with:\n          name: Steam Auto Shutdown.exe\n          path: ./build/bin/SteamAutoShutdown.exe\n\n      - name: Create Release\n        id: create_release\n        uses: softprops/action-gh-release@v1\n        with:\n          files: ./build/bin/SteamAutoShutdown.exe\n          tag_name: v${{ steps.bump_version.outputs.BUILD_VERSION }}\n          name: Release v${{ steps.bump_version.outputs.BUILD_VERSION }}\n          body: |\n            ## Changelog\n            No changelog available yet.\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n.cache\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n# Windows thumbnail cache files\nThumbs.db\nThumbs.db:encryptable\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n# .nfs files are created when an open file is removed but is still being accessed\n.nfs*\n\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn.  Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n!.vscode/*.code-snippets\n\n# Local History for Visual Studio Code\n.history/\n\n# Built Visual Studio Code Extensions\n*.vsix\n\n!frontend/dist\n\nbuild/bin\nbuild/windows"
  },
  {
    "path": "README.md",
    "content": "# Steam Auto Shutdown\n\nThis 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.\n\n## Download\n\nYou can download the latest version from the [releases page](https://github.com/diogomartino/steam-auto-shutdown/releases).\n\n[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.\n\n## Instructions\n\n1. Open the application\n2. Toggle the switch to enable the shutdown\n3. Your computer will shutdown when your downloads are finished\n\nThe 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.\n\n## Features\n\n- Shutdown, sleep, hibernate or log off: you choose.\n- Uses network traffic to detect when your downloads are finished, so it works with any application.\n- Hability to choose the network interface to monitor.\n- You can set a delay to shutdown your computer after your downloads are finished.\n- 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.\n- 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).\n\n## Screenshots\n\n![Screenshot 1](https://i.imgur.com/jlJFmLC.jpg)\n\n## Development\n\n### Requirements\n\n- [Go](https://go.dev/)\n- [Wails](https://wails.io/)\n- [Node.js](https://nodejs.org/)\n- [pnpm](https://pnpm.io/)\n\n```\nwails dev\n```\n\nThis 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.\n\n## Building\n\n```\nwails build --clean --platform windows/amd64\n```\n\n## Contributing\n\nFeel free to contribute to this project by opening issues or pull requests. Please follow the code style of the project.\n"
  },
  {
    "path": "app.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os/exec\"\n)\n\n// App struct\ntype App struct {\n\tctx context.Context\n}\n\n// NewApp creates a new App application struct\nfunc NewApp() *App {\n\treturn &App{}\n}\n\n// startup is called at application startup\nfunc (a *App) startup(ctx context.Context) {\n\t// Perform your setup here\n\ta.ctx = ctx\n}\n\n// domReady is called after front-end resources have been loaded\nfunc (a App) domReady(ctx context.Context) {\n\t// Add your action here\n}\n\n// beforeClose is called when the application is about to quit,\n// either by clicking the window close button or calling runtime.Quit.\n// Returning true will cause the application to continue, false will continue shutdown as normal.\nfunc (a *App) beforeClose(ctx context.Context) (prevent bool) {\n\treturn false\n}\n\n// shutdown is called at application termination\nfunc (a *App) shutdown(ctx context.Context) {\n\t// Perform your teardown here\n}\n\nconst secondsToWait = \"10\"\n\nfunc (a *App) ExecuteAction(actionName string) {\n\tvar cmd *exec.Cmd\n\n\tswitch actionName {\n\tcase \"SHUTDOWN\":\n\t\tcmd = exec.Command(\"shutdown\", \"/s\", \"/t\", secondsToWait)\n\tcase \"RESTART\":\n\t\tcmd = exec.Command(\"shutdown\", \"/r\", \"/t\", secondsToWait)\n\tcase \"SLEEP\":\n\t\tcmd = exec.Command(\"rundll32.exe\", \"powrprof.dll,SetSuspendState\", \"0\")\n\tcase \"LOGOFF\":\n\t\tcmd = exec.Command(\"shutdown\", \"/l\", \"/t\", secondsToWait)\n\tcase \"HIBERNATE\":\n\t\tcmd = exec.Command(\"shutdown\", \"/h\", \"/t\", secondsToWait)\n\tdefault:\n\t\tfmt.Println(\"Unknown action:\", actionName)\n\t\treturn\n\t}\n\n\terr := cmd.Run()\n\tif err != nil {\n\t\tfmt.Println(\"Error executing command:\", err)\n\t}\n}\n\nfunc (a *App) CancelShutdown() {\n\tcmd := exec.Command(\"shutdown\", \"/a\")\n\terr := cmd.Run()\n\tif err != nil {\n\t\tfmt.Println(\"Error executing command:\", err)\n\t}\n}\n\nfunc (a *App) OpenInBrowser(url string) {\n\tcmd := exec.Command(\"cmd\", \"/c\", \"start\", url)\n\terr := cmd.Run()\n\tif err != nil {\n\t\tfmt.Println(\"Error executing command:\", err)\n\t}\n}\n"
  },
  {
    "path": "disk-manager.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/shirou/gopsutil/process\"\n)\n\ntype DiskManager struct{}\n\ntype Process struct {\n\tPid  int32\n\tName string\n}\n\nfunc createDiskManager() *DiskManager {\n\treturn &DiskManager{}\n}\n\nfunc getProcessByPID(pid int32) (*process.Process, error) {\n\tprocesses, err := process.Processes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, p := range processes {\n\t\tif p.Pid == pid {\n\t\t\treturn p, nil\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"No process found with PID: %d\", pid)\n}\n\nfunc (d *DiskManager) GetDiskWriteSpeed(processPID int32) (float64, error) {\n\tprocess, err := getProcessByPID(processPID)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tinitialIOCounters, err := process.IOCounters()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\ttime.Sleep(sampleInterval)\n\n\tfinalIOCounters, err := process.IOCounters()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tbytesWritten := finalIOCounters.WriteBytes - initialIOCounters.WriteBytes\n\twriteDuration := sampleInterval.Seconds()\n\tif writeDuration == 0 {\n\t\treturn 0, nil\n\t}\n\n\twriteSpeed := float64(bytesWritten) / writeDuration\n\n\treturn writeSpeed, nil\n}\n\nfunc (d *DiskManager) GetProcesses() ([]*Process, error) {\n\tvar processList []*Process = make([]*Process, 0)\n\n\tprocesses, err := process.Processes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, process := range processes {\n\t\tname, err := process.Name()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tprocessList = append(processList, &Process{\n\t\t\tPid:  process.Pid,\n\t\t\tName: name,\n\t\t})\n\t}\n\n\treturn processList, nil\n}\n"
  },
  {
    "path": "frontend/.eslintrc.cjs",
    "content": "module.exports = {\n  root: true,\n  env: { browser: true, es2020: true },\n  extends: [\n    'eslint:recommended',\n    'plugin:@typescript-eslint/recommended',\n    'plugin:react-hooks/recommended'\n  ],\n  ignorePatterns: ['dist', '.eslintrc.cjs', 'src/wailsjs/'],\n  parser: '@typescript-eslint/parser',\n  plugins: ['react-refresh', 'prettier', 'unused-imports'],\n  rules: {\n    'react-refresh/only-export-components': [\n      'warn',\n      { allowConstantExport: true }\n    ],\n    'prettier/prettier': [\n      'error',\n      {\n        endOfLine: 'auto',\n        useTabs: false\n      }\n    ],\n    'unused-imports/no-unused-imports': 'warn',\n    'unused-imports/no-unused-vars': [\n      'warn',\n      {\n        vars: 'all',\n        varsIgnorePattern: '^_',\n        args: 'after-used',\n        argsIgnorePattern: '^_'\n      }\n    ],\n    '@typescript-eslint/no-explicit-any': 'off'\n  }\n};\n"
  },
  {
    "path": "frontend/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "frontend/.npmrc",
    "content": "public-hoist-pattern[]=*@nextui-org/*"
  },
  {
    "path": "frontend/.prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"printWidth\": 80,\n  \"proseWrap\": \"always\",\n  \"tabWidth\": 2,\n  \"useTabs\": false,\n  \"trailingComma\": \"none\",\n  \"bracketSpacing\": true,\n  \"semi\": true\n}\n"
  },
  {
    "path": "frontend/index.html",
    "content": "<!doctype html>\n<html lang=\"en\" class=\"dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vite + React + TS</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "frontend/package.json",
    "content": "{\n  \"name\": \"steam-auto-shutdown\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"version\": \"0.0.0\",\n  \"dependencies\": {\n    \"@nextui-org/react\": \"^2.2.1\",\n    \"@reduxjs/toolkit\": \"^1.9.7\",\n    \"@tabler/icons-react\": \"^2.40.0\",\n    \"framer-motion\": \"^10.16.4\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-redux\": \"^8.1.3\",\n    \"redux\": \"^4.2.1\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^18.2.34\",\n    \"@types/react-dom\": \"^18.2.14\",\n    \"@typescript-eslint/eslint-plugin\": \"^6.9.1\",\n    \"@typescript-eslint/parser\": \"^6.9.1\",\n    \"@vitejs/plugin-react-swc\": \"^3.4.1\",\n    \"autoprefixer\": \"^10.4.16\",\n    \"eslint\": \"^8.53.0\",\n    \"eslint-config-prettier\": \"^9.0.0\",\n    \"eslint-plugin-prettier\": \"^5.0.1\",\n    \"eslint-plugin-react-hooks\": \"^4.6.0\",\n    \"eslint-plugin-react-refresh\": \"^0.4.4\",\n    \"eslint-plugin-unused-imports\": \"^3.0.0\",\n    \"postcss\": \"^8.4.31\",\n    \"prettier\": \"^3.0.3\",\n    \"tailwindcss\": \"^3.3.5\",\n    \"typescript\": \"^5.2.2\",\n    \"vite\": \"^4.5.1\"\n  },\n  \"scripts\": {\n    \"build\": \"tsc && vite build\",\n    \"build:dev\": \"tsc --noEmit  && vite build\",\n    \"dev\": \"vite\",\n    \"format\": \"prettier --write .\",\n    \"lint\": \"eslint ./src --ext ts,tsx --report-unused-disable-directives --max-warnings 0\",\n    \"lint:fix\": \"eslint ./src --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --fix\",\n    \"preview\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "frontend/package.json.md5",
    "content": "b9a0f26962856072c7fabc2c3db30c17"
  },
  {
    "path": "frontend/postcss.config.js",
    "content": "export default {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {}\n  }\n};\n"
  },
  {
    "path": "frontend/src/actions/app.ts",
    "content": "import { appSliceActions } from '../store/app-slice';\nimport { store } from '../store';\nimport { DesktopApi } from '../desktop';\nimport { TProcess } from '../types';\n\nexport const setSelectedMac = (mac: string | undefined) => {\n  store.dispatch(appSliceActions.setSelectedMac(mac));\n};\n\nexport const setNetworkSpeed = (speed: number | undefined) => {\n  store.dispatch(appSliceActions.setNetworkSpeed(speed || 0));\n};\n\nexport const setDiskSpeed = (speed: number | undefined) => {\n  store.dispatch(appSliceActions.setDiskSpeed(speed || 0));\n};\n\nexport const setSettings = (settings: any) => {\n  store.dispatch(appSliceActions.setSettings(settings));\n};\n\nexport const toggleMonitoring = () => {\n  store.dispatch(appSliceActions.toggleMonitoring());\n};\n\nexport const toggleTheme = () => {\n  store.dispatch(appSliceActions.toggleTheme());\n};\n\nexport const setTargetProcess = (process: TProcess) => {\n  store.dispatch(appSliceActions.setTargetProcess(process));\n};\n\nexport const setMonitorStatusMsg = (msg: string) => {\n  store.dispatch(appSliceActions.setMonitorStatusMsg(msg));\n};\n\nexport const loadInterfaces = async () => {\n  const results = await DesktopApi.getInterfaces();\n\n  store.dispatch(appSliceActions.setInterfaces(results));\n\n  try {\n    const detectedMac = await DesktopApi.autoDetectInterface();\n\n    if (detectedMac) {\n      setSelectedMac(detectedMac);\n    }\n  } catch {\n    // do nothing\n  }\n};\n\nexport const getSteamProcess = async () => {\n  const processes = await DesktopApi.getProcesses();\n\n  const result = processes?.find((p) => p.Name === 'steam.exe');\n\n  if (!result) return;\n\n  const process: TProcess = {\n    id: result.Pid,\n    name: result.Name\n  };\n\n  setTargetProcess(process);\n};\n"
  },
  {
    "path": "frontend/src/actions/modal.ts",
    "content": "import { Modal, TGenericObject } from '../types';\nimport { modalsSliceActions } from '../store/modals-slice';\nimport { store } from '../store';\n\nexport const openModal = (modal: Modal, props?: TGenericObject) => {\n  store.dispatch(modalsSliceActions.setIsOpen(true));\n\n  store.dispatch(\n    modalsSliceActions.setModalInfo({\n      modal,\n      props\n    })\n  );\n};\n\nexport const closeModals = () => {\n  store.dispatch(modalsSliceActions.setIsOpen(false));\n\n  // allow fade out animation to complete before stopping rendering, otherwise it looks choppy\n  setTimeout(() => {\n    store.dispatch(\n      modalsSliceActions.setModalInfo({ modal: undefined, props: {} })\n    );\n  }, 500);\n};\n"
  },
  {
    "path": "frontend/src/components/app/index.tsx",
    "content": "import { useEffect, useRef } from 'react';\nimport { getSteamProcess, loadInterfaces } from '../../actions/app';\nimport useTheme from '../../hooks/use-theme';\nimport useSystemMetrics from '../../hooks/use-system-metrics';\nimport useSystemMonitor from '../../hooks/use-system-monitor';\nimport Home from '../../screens/home';\n\nconst App = () => {\n  const inited = useRef<boolean>(false);\n  useSystemMetrics();\n  useTheme();\n  useSystemMonitor();\n\n  const initApp = async () => {\n    await Promise.all([loadInterfaces(), getSteamProcess()]);\n  };\n\n  useEffect(() => {\n    if (inited.current) return;\n\n    inited.current = true;\n    initApp();\n  }, []);\n\n  return <Home />;\n};\n\nexport default App;\n"
  },
  {
    "path": "frontend/src/components/disk-monitor/index.tsx",
    "content": "import { Switch, Tooltip } from '@nextui-org/react';\nimport {} from '../../wailsjs/go/models';\nimport { openModal } from '../../actions/modal';\nimport { IconListSearch } from '@tabler/icons-react';\nimport { Modal } from '../../types';\nimport useTargetProcess from '../../hooks/use-target-process';\n\nconst DiskMonitor = ({ value, onChange }) => {\n  const targetProcessName = useTargetProcess();\n\n  const onTargetProcessChangeClick = async () => {\n    openModal(Modal.PROCESS_PICKER);\n  };\n\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <p className=\"text-sm\">Monitor Disk</p>\n\n      <div className=\"flex\">\n        <Switch isSelected={value} onValueChange={onChange} size=\"sm\" name=\"\" />\n\n        <div className=\"flex items-center gap-1\">\n          <Tooltip\n            showArrow={true}\n            color=\"foreground\"\n            content=\"Change the process that is being monitored.\"\n            delay={300}\n            closeDelay={0}\n          >\n            <IconListSearch\n              className=\"text-gray-500\"\n              size=\"1rem\"\n              style={{\n                cursor: 'pointer'\n              }}\n              onClick={onTargetProcessChangeClick}\n            />\n          </Tooltip>\n          <Tooltip\n            showArrow={true}\n            color=\"foreground\"\n            content=\"This is the process that is being monitored.\"\n            delay={300}\n            closeDelay={0}\n          >\n            <p className=\"text-gray-500 text-sm\">{targetProcessName?.name}</p>\n          </Tooltip>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default DiskMonitor;\n"
  },
  {
    "path": "frontend/src/components/disk-speed/index.tsx",
    "content": "import { Tooltip } from '@nextui-org/react';\n\nimport { IconServer2 } from '@tabler/icons-react';\nimport useSettings from '../../hooks/use-settings';\nimport { useDiskSpeedInMegaBytes } from '../../hooks/use-disk-speed';\nimport useTargetProcess from '../../hooks/use-target-process';\n\nconst DiskSpeed = () => {\n  const { diskActivityMonitor } = useSettings();\n  const diskSpeed = useDiskSpeedInMegaBytes();\n  const targetProcess = useTargetProcess();\n\n  const color = diskActivityMonitor ? 'text-gray-500' : 'text-gray-700';\n\n  const tooltipContent = diskActivityMonitor ? (\n    <p>\n      Disk activity monitor is enabled and scanning for disk activity from{' '}\n      {targetProcess?.name}\n    </p>\n  ) : (\n    <p>Disk activity monitor is disabled. You can enable it in the settings.</p>\n  );\n\n  return (\n    <Tooltip\n      color=\"foreground\"\n      content={tooltipContent}\n      showArrow\n      delay={300}\n      closeDelay={0}\n    >\n      <div className={`flex gap-1 items-center ${color}`}>\n        <IconServer2 size=\"0.9rem\" />\n        <p className=\"text-sm\">{diskSpeed.toFixed(2)} MB/s</p>\n      </div>\n    </Tooltip>\n  );\n};\n\nexport default DiskSpeed;\n"
  },
  {
    "path": "frontend/src/components/footer/index.tsx",
    "content": "import DiskSpeed from '../disk-speed';\nimport NetworkSpeed from '../network-speed';\n\nconst Footer = () => {\n  return (\n    <div className=\"absolute bottom-0 bottom-0 flex gap-2 w-full\">\n      <div className=\"flex justify-center gap-5 w-full\">\n        <DiskSpeed />\n        <NetworkSpeed />\n      </div>\n    </div>\n  );\n};\n\nexport default Footer;\n"
  },
  {
    "path": "frontend/src/components/header/index.tsx",
    "content": "import { Image, Link } from '@nextui-org/react';\nimport Logo from '../../assets/icone-steam-bleue.png';\nimport { DesktopApi } from '../../desktop';\n\nconst Header = () => {\n  return (\n    <div className=\"flex flex-row justify-center items-center gap-4\">\n      <Image src={Logo} width=\"120\" height=\"120\" className=\"p-3\" />\n      <div>\n        <p className=\"text-3xl\">STEAM AUTO SHUTDOWN</p>\n        <div className=\"flex gap-2\">\n          <p className=\"text-xs text-gray-500\">Version {APP_VERSION}</p>\n          <Link\n            href=\"#\"\n            className=\"text-xs text-gray-500\"\n            onClick={() =>\n              DesktopApi.openInBrowser(\n                'https://github.com/diogomartino/steam-auto-shutdown'\n              )\n            }\n          >\n            Github\n          </Link>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default Header;\n"
  },
  {
    "path": "frontend/src/components/interface-picker/index.tsx",
    "content": "import { useMemo } from 'react';\nimport {\n  Button,\n  Dropdown,\n  DropdownItem,\n  DropdownMenu,\n  DropdownTrigger,\n  Tooltip\n} from '@nextui-org/react';\nimport { setSelectedMac } from '../../actions/app';\nimport useSelectedMac from '../../hooks/use-selected-mac';\nimport useInterfaces from '../../hooks/use-interfaces';\n\nconst InterfacePicker = () => {\n  const selectedMac = useSelectedMac();\n  const interfaces = useInterfaces();\n\n  const selectedValue = useMemo(() => {\n    const selected = interfaces.find(\n      (item) => item.hardwareaddr === selectedMac\n    );\n\n    return selected ? `${selected.name}` : '';\n  }, [selectedMac, interfaces]);\n\n  return (\n    <div className=\"flex flex-col justify-center items-center gap-2\">\n      <Tooltip\n        showArrow={true}\n        color=\"foreground\"\n        content=\"The network interface you want to monitor. It is usually Ethernet or Wi-Fi.\"\n        delay={300}\n        closeDelay={0}\n      >\n        <p className=\"text-sm\">Network Interface</p>\n      </Tooltip>\n\n      <Dropdown>\n        <DropdownTrigger>\n          <Button variant=\"bordered\" className=\"capitalize\">\n            {selectedValue || 'Select Network Interface'}\n          </Button>\n        </DropdownTrigger>\n        <DropdownMenu\n          aria-label=\"Single selection example\"\n          disallowEmptySelection\n          selectionMode=\"single\"\n          selectedKeys={[selectedMac as string]}\n          onSelectionChange={(selection) => {\n            const [first] = selection;\n            setSelectedMac(first.toString());\n          }}\n        >\n          {interfaces.map((item) => (\n            <DropdownItem key={item.hardwareaddr} className=\"text-foreground\">\n              {item.name}\n            </DropdownItem>\n          ))}\n        </DropdownMenu>\n      </Dropdown>\n    </div>\n  );\n};\n\nexport default InterfacePicker;\n"
  },
  {
    "path": "frontend/src/components/modals/action-confirmation/index.tsx",
    "content": "import {\n  Button,\n  Modal,\n  ModalBody,\n  ModalContent,\n  ModalFooter,\n  ModalHeader\n} from '@nextui-org/react';\nimport { closeModals } from '../../../actions/modal';\nimport useSettings from '../../../hooks/use-settings';\nimport { ActionType } from '../../../types';\nimport { DesktopApi } from '../../../desktop';\nimport useModalsInfo from '../../../hooks/use-modals-info';\n\nconst messageMap = {\n  [ActionType.HIBERNATE]: 'Your computer will hibernate in 10 seconds',\n  [ActionType.SHUTDOWN]: 'Your computer will shutdown in 10 seconds',\n  [ActionType.SLEEP]: 'Your computer will go to sleep in 10 seconds',\n  [ActionType.LOGOFF]: 'Your computer log your user off in 10 seconds'\n};\n\nconst ActionConfirmationModal = () => {\n  const { isModalOpen } = useModalsInfo();\n  const settings = useSettings();\n\n  const onCancelClick = async () => {\n    await DesktopApi.cancelShutdown();\n    closeModals();\n  };\n\n  return (\n    <Modal\n      backdrop=\"blur\"\n      isOpen={isModalOpen}\n      onClose={closeModals}\n      scrollBehavior=\"inside\"\n    >\n      <ModalContent>\n        <ModalHeader className=\"flex gap-1 items-center\"></ModalHeader>\n        <ModalBody>\n          <div className=\"flex full-w justify-center items-center\">\n            {messageMap[settings.actionType]}\n          </div>\n        </ModalBody>\n        <ModalFooter className=\"justify-center\">\n          <Button\n            onPress={onCancelClick}\n            size=\"lg\"\n            variant=\"solid\"\n            color=\"danger\"\n          >\n            CANCEL NOW\n          </Button>\n        </ModalFooter>\n      </ModalContent>\n    </Modal>\n  );\n};\n\nexport default ActionConfirmationModal;\n"
  },
  {
    "path": "frontend/src/components/modals/index.tsx",
    "content": "import { Modal } from '../../types';\nimport ProcessPicker from './process-picker';\nimport { createElement } from 'react';\nimport SettingsModal from './settings';\nimport ActionConfirmationModal from './action-confirmation';\nimport useModalsInfo from '../../hooks/use-modals-info';\n\nconst ModalsMap = {\n  [Modal.PROCESS_PICKER]: ProcessPicker,\n  [Modal.SETTINGS]: SettingsModal,\n  [Modal.ACTION_CONFIRMATION]: ActionConfirmationModal\n};\n\nconst ModalsProvider = () => {\n  const { openModal, modalProps } = useModalsInfo();\n\n  if (!openModal || !ModalsMap[openModal]) return null;\n\n  return createElement(ModalsMap[openModal], modalProps);\n};\n\nexport default ModalsProvider;\n"
  },
  {
    "path": "frontend/src/components/modals/process-picker/index.tsx",
    "content": "import {\n  Button,\n  Input,\n  Link,\n  Listbox,\n  ListboxItem,\n  Modal,\n  ModalBody,\n  ModalContent,\n  ModalFooter,\n  ModalHeader,\n  Spinner\n} from '@nextui-org/react';\nimport { openModal } from '../../../actions/modal';\nimport { DesktopApi } from '../../../desktop';\nimport { useEffect, useMemo, useState } from 'react';\nimport { TProcess } from '../../../types';\nimport { setTargetProcess } from '../../../actions/app';\nimport { IconSearch } from '@tabler/icons-react';\nimport { Modal as ModalList } from '../../../types';\nimport useModalsInfo from '../../../hooks/use-modals-info';\nimport useTargetProcess from '../../../hooks/use-target-process';\n\nconst ProcessPicker = () => {\n  const { isModalOpen } = useModalsInfo();\n  const targetProcess = useTargetProcess();\n  const [processes, setProcesses] = useState<any[]>([]);\n  const [search, setSearch] = useState('');\n  const [loading, setLoading] = useState(false);\n\n  const filteredProcesses = useMemo(() => {\n    return processes.filter((process) =>\n      process.Name.toString()\n        .trim()\n        .toLowerCase()\n        .includes(search.trim().toLowerCase())\n    );\n  }, [processes, search]);\n\n  const onSearchChange = (event: any) => {\n    setSearch(event.target.value);\n  };\n\n  const loadProcesses = async () => {\n    setLoading(true);\n\n    const processes = await DesktopApi.getProcesses();\n\n    setProcesses(processes);\n    setLoading(false);\n  };\n\n  useEffect(() => {\n    loadProcesses();\n  }, []);\n\n  return (\n    <Modal\n      backdrop=\"blur\"\n      isOpen={isModalOpen}\n      onClose={() => {\n        openModal(ModalList.SETTINGS);\n      }}\n      scrollBehavior=\"inside\"\n    >\n      <ModalContent>\n        {(onClose) => (\n          <>\n            <ModalHeader className=\"flex flex-col gap-3\">\n              <p>Pick a process to monitor</p>\n              <Input\n                size=\"sm\"\n                variant=\"underlined\"\n                label=\"Search process\"\n                value={search}\n                onChange={onSearchChange}\n                startContent={\n                  <IconSearch size=\"0.9rem\" className=\"text-gray-500\" />\n                }\n              />\n              <p className=\"text-gray-500 text-xs\">\n                The process you pick here will only be used to monitor the disk\n                activity.{' '}\n                <Link href=\"#\" className=\"text-xs\">\n                  Learn more.\n                </Link>\n              </p>\n            </ModalHeader>\n            <ModalBody>\n              <div\n                style={{\n                  height: '130px'\n                }}\n              >\n                {loading ? (\n                  <div className=\"flex flex-col justify-center items-center gap-2\">\n                    <Spinner size=\"sm\" />\n                    <p className=\"text-gray-500 text-sm\">\n                      Loading processes...\n                    </p>\n                  </div>\n                ) : (\n                  <Listbox\n                    aria-label=\"Single selection example\"\n                    variant=\"faded\"\n                    color=\"primary\"\n                    disallowEmptySelection\n                    selectionMode=\"single\"\n                    emptyContent=\"No processes found.\"\n                    selectedKeys={[targetProcess?.id?.toString() ?? '']}\n                    onSelectionChange={(selection) => {\n                      const [processId] = selection;\n\n                      const process: TProcess = {\n                        id: processId.toString(),\n                        name:\n                          processes.find((p) => p.Pid.toString() === processId)\n                            ?.Name ?? ''\n                      };\n\n                      setTargetProcess(process);\n                    }}\n                  >\n                    {filteredProcesses?.map((process) => (\n                      <ListboxItem key={process.Pid}>\n                        <p>\n                          {process.Name}{' '}\n                          <span className=\"text-xs text-gray-500\">\n                            {process.Pid}\n                          </span>\n                        </p>\n                      </ListboxItem>\n                    ))}\n                  </Listbox>\n                )}\n              </div>\n            </ModalBody>\n            <ModalFooter>\n              <Button onPress={onClose} size=\"sm\">\n                Close\n              </Button>\n            </ModalFooter>\n          </>\n        )}\n      </ModalContent>\n    </Modal>\n  );\n};\n\nexport default ProcessPicker;\n"
  },
  {
    "path": "frontend/src/components/modals/settings/index.tsx",
    "content": "import {\n  Button,\n  Chip,\n  Dropdown,\n  DropdownItem,\n  DropdownMenu,\n  DropdownTrigger,\n  Input,\n  Modal,\n  ModalBody,\n  ModalContent,\n  ModalFooter,\n  ModalHeader,\n  Tooltip\n} from '@nextui-org/react';\nimport { closeModals } from '../../../actions/modal';\nimport DiskMonitor from '../../disk-monitor';\nimport useSettings from '../../../hooks/use-settings';\nimport { setSettings } from '../../../actions/app';\nimport { ActionType } from '../../../types';\nimport { useMemo } from 'react';\nimport { IconInfoCircle } from '@tabler/icons-react';\nimport useModalsInfo from '../../../hooks/use-modals-info';\n\nconst SettingsModal = () => {\n  const { isModalOpen } = useModalsInfo();\n  const { speedThreshold, actionDelay, actionType, diskActivityMonitor } =\n    useSettings();\n\n  const onChangeSettings = (event) => {\n    const { name, value } = event.target;\n\n    setSettings({\n      [name]: isNaN(value) ? value : parseInt(value)\n    });\n  };\n\n  const selectedValue = useMemo(() => {\n    switch (actionType) {\n      case ActionType.SHUTDOWN:\n        return 'Shutdown';\n      case ActionType.HIBERNATE:\n        return 'Hibernate';\n      case ActionType.LOGOFF:\n        return 'Logoff';\n      case ActionType.SLEEP:\n        return 'Sleep';\n      default:\n        return '';\n    }\n  }, [actionType]);\n\n  return (\n    <Modal\n      backdrop=\"blur\"\n      isOpen={isModalOpen}\n      onClose={closeModals}\n      scrollBehavior=\"inside\"\n    >\n      <ModalContent>\n        {(onClose) => (\n          <>\n            <ModalHeader className=\"flex gap-1 items-center\">\n              <p>Settings</p>\n              <Tooltip\n                className=\"text-foreground\"\n                content={\n                  <div className=\"flex flex-col gap-2\">\n                    <div>\n                      <div className=\"text-small font-bold\">\n                        Shutdown after x seconds of inactivity\n                      </div>\n                      <div className=\"text-tiny\">\n                        The number of seconds to wait before shutting down the\n                        PC after no downloads are active. Using a small value\n                        will increase the chance of shutting down while a\n                        download is still active.\n                      </div>\n                    </div>\n\n                    <div>\n                      <div className=\"text-small font-bold\">\n                        Download speed threshold\n                      </div>\n                      <div className=\"text-tiny\">\n                        The minimum download speed to consider a download\n                        active.\n                      </div>\n                    </div>\n\n                    <div>\n                      <div className=\"text-small font-bold\">Monitor disk</div>\n                      <div className=\"text-tiny\">\n                        Monitors disk activity to prevent shutdowns when a\n                        process is writing to disk. This is useful, for example,\n                        in case steam is not actively downloading but is writing\n                        to disk, like uncompressing game files.\n                      </div>\n                    </div>\n\n                    <div>\n                      <div className=\"text-small font-bold\">\n                        Action to perform\n                      </div>\n                      <div className=\"text-tiny\">\n                        The action to perform when no downloads are active.\n                        Sleeping is the only action that can be cancelled by the\n                        user after it has been triggered.\n                      </div>\n                    </div>\n                  </div>\n                }\n              >\n                <IconInfoCircle size=\"1rem\" />\n              </Tooltip>\n            </ModalHeader>\n            <ModalBody>\n              <div className=\"flex flex-col gap-2\">\n                <Input\n                  type=\"number\"\n                  name=\"actionDelay\"\n                  size=\"sm\"\n                  label=\"Shutdown after x seconds of inactivity\"\n                  placeholder=\"10\"\n                  onChange={onChangeSettings}\n                  min={1}\n                  value={actionDelay.toString()}\n                  endContent={\n                    <Chip radius=\"sm\" size=\"sm\">\n                      Seconds\n                    </Chip>\n                  }\n                />\n                <Input\n                  type=\"number\"\n                  name=\"speedThreshold\"\n                  size=\"sm\"\n                  label=\"Download speed threshold\"\n                  placeholder=\"200\"\n                  onChange={onChangeSettings}\n                  min={1}\n                  value={speedThreshold.toString()}\n                  endContent={\n                    <Chip radius=\"sm\" size=\"sm\">\n                      KB/s\n                    </Chip>\n                  }\n                />\n\n                <div className=\"flex justify-between mt-1\">\n                  <DiskMonitor\n                    value={diskActivityMonitor}\n                    onChange={(value) =>\n                      setSettings({ diskActivityMonitor: value })\n                    }\n                  />\n\n                  <div className=\"flex flex-col gap-1\">\n                    <p className=\"text-sm\">Action to perform</p>\n\n                    <Dropdown>\n                      <DropdownTrigger>\n                        <Button variant=\"bordered\" size=\"sm\">\n                          {selectedValue || 'Select Network Interface'}\n                        </Button>\n                      </DropdownTrigger>\n                      <DropdownMenu\n                        disallowEmptySelection\n                        selectionMode=\"single\"\n                        selectedKeys={[actionType as string]}\n                        onSelectionChange={(selection) => {\n                          const [first] = selection;\n\n                          setSettings({\n                            actionType: first\n                          });\n                        }}\n                      >\n                        <DropdownItem\n                          key={ActionType.SHUTDOWN}\n                          className=\"text-foreground\"\n                        >\n                          Shutdown\n                        </DropdownItem>\n                        <DropdownItem\n                          key={ActionType.HIBERNATE}\n                          className=\"text-foreground\"\n                        >\n                          Hibernate\n                        </DropdownItem>\n                        <DropdownItem\n                          key={ActionType.LOGOFF}\n                          className=\"text-foreground\"\n                        >\n                          Logoff\n                        </DropdownItem>\n                        <DropdownItem\n                          key={ActionType.SLEEP}\n                          className=\"text-foreground\"\n                        >\n                          Sleep\n                        </DropdownItem>\n                      </DropdownMenu>\n                    </Dropdown>\n                  </div>\n                </div>\n              </div>\n            </ModalBody>\n            <ModalFooter>\n              <Button\n                onPress={() => {\n                  setSettings({\n                    actionDelay: 20,\n                    speedThreshold: 200,\n                    actionType: ActionType.SHUTDOWN\n                  });\n                }}\n                size=\"sm\"\n              >\n                Reset to defaults\n              </Button>\n              <Button onPress={onClose} size=\"sm\">\n                Close\n              </Button>\n            </ModalFooter>\n          </>\n        )}\n      </ModalContent>\n    </Modal>\n  );\n};\n\nexport default SettingsModal;\n"
  },
  {
    "path": "frontend/src/components/monitor-switch/index.tsx",
    "content": "import { Card, CardBody, Switch } from '@nextui-org/react';\nimport useMonitorStatusMsg from '../../hooks/use-monitor-status-msg';\nimport useSettings from '../../hooks/use-settings';\n\ntype TMonitorSwitchProps = {\n  isActive: boolean;\n  setIsActive: (value: boolean) => void;\n};\n\nconst MonitorSwitch = ({ isActive, setIsActive }: TMonitorSwitchProps) => {\n  const statusMsg = useMonitorStatusMsg();\n  const { actionType } = useSettings();\n\n  return (\n    <Card className=\"w-[400px] h-[180px]\">\n      <CardBody className=\"flex flex-col justify-center items-center gap-2\">\n        <Switch isSelected={isActive} onValueChange={setIsActive} size=\"lg\" />\n        {isActive ? (\n          <>\n            <p className=\"text-primary font-bold\">Auto shutdown is enabled</p>\n            <p className=\"text-foreground text-sm text-center\">{statusMsg}</p>\n            <p className=\"text-gray-500 text-sm text-center\">\n              Your network speed is being monitored and your PC will{' '}\n              {actionType.toLowerCase()} when no downloads are active.\n            </p>\n          </>\n        ) : (\n          <p className=\"text-gray-500\">Auto shutdown is disabled</p>\n        )}\n      </CardBody>\n    </Card>\n  );\n};\n\nexport default MonitorSwitch;\n"
  },
  {
    "path": "frontend/src/components/network-speed/index.tsx",
    "content": "import { Tooltip } from '@nextui-org/react';\nimport { IconWorldDownload } from '@tabler/icons-react';\nimport { useNetworkSpeedInMegaBytes } from '../../hooks/use-network-speed';\n\nconst NetworkSpeed = () => {\n  const speed = useNetworkSpeedInMegaBytes();\n\n  return (\n    <Tooltip\n      color=\"foreground\"\n      content=\"The download speed of the selected network interface. This value is not accurate and can fluctuate.\"\n      showArrow\n      delay={300}\n      closeDelay={0}\n    >\n      <div className=\"flex gap-1 items-center\">\n        <IconWorldDownload size=\"0.9rem\" className=\"text-gray-500\" />\n        <p className=\"text-gray-500 text-sm\">{speed.toFixed(2)} MB/s</p>\n      </div>\n    </Tooltip>\n  );\n};\n\nexport default NetworkSpeed;\n"
  },
  {
    "path": "frontend/src/components/top-right-slot/index.tsx",
    "content": "import { Button } from '@nextui-org/react';\nimport { IconMoon, IconSettings, IconSun } from '@tabler/icons-react';\nimport { toggleTheme } from '../../actions/app';\nimport { openModal } from '../../actions/modal';\nimport { Modal } from '../../types';\nimport useSelectedTheme from '../../hooks/use-selected-theme';\n\nconst TopRightSlot = () => {\n  const theme = useSelectedTheme();\n  const onToggleThemeClick = () => {\n    toggleTheme();\n  };\n\n  const onSettingsClick = () => {\n    openModal(Modal.SETTINGS);\n  };\n\n  return (\n    <div className=\"absolute top-2 right-2 flex gap-2\">\n      <Button isIconOnly onClick={onToggleThemeClick} size=\"sm\" variant=\"flat\">\n        {theme === 'light' ? <IconMoon /> : <IconSun />}\n      </Button>\n\n      <Button isIconOnly onClick={onSettingsClick} size=\"sm\" variant=\"flat\">\n        <IconSettings />\n      </Button>\n    </div>\n  );\n};\n\nexport default TopRightSlot;\n"
  },
  {
    "path": "frontend/src/desktop.ts",
    "content": "import * as NetworkManager from './wailsjs/go/main/NetworkManager';\nimport * as DiskManager from './wailsjs/go/main/DiskManager';\nimport * as GoApp from './wailsjs/go/main/App';\n\nexport const DesktopApi = {\n  getInterfaces: NetworkManager.GetInterfaces,\n  autoDetectInterface: NetworkManager.AutoDetectInterface,\n  getNetworkSpeed: NetworkManager.GetInterfaceDownloadSpeed,\n  getProcesses: DiskManager.GetProcesses,\n  getDiskWriteSpeed: DiskManager.GetDiskWriteSpeed,\n  executeAction: GoApp.ExecuteAction,\n  cancelShutdown: GoApp.CancelShutdown,\n  openInBrowser: GoApp.OpenInBrowser\n};\n"
  },
  {
    "path": "frontend/src/helpers/sleep.ts",
    "content": "export default (sleepTime: number) =>\n  new Promise((resolve) => setTimeout(resolve, sleepTime));\n"
  },
  {
    "path": "frontend/src/hooks/use-disk-speed.ts",
    "content": "import { useSelector } from 'react-redux';\nimport {\n  diskSpeedInBytesPerSecondSelector,\n  diskSpeedInMegabytesPerSecondSelector\n} from '../selectors/app';\n\nconst useDiskSpeedInMegaBytes = () =>\n  useSelector(diskSpeedInMegabytesPerSecondSelector);\n\nconst useDiskSpeedInBytes = () =>\n  useSelector(diskSpeedInBytesPerSecondSelector);\n\nexport { useDiskSpeedInMegaBytes, useDiskSpeedInBytes };\n"
  },
  {
    "path": "frontend/src/hooks/use-interfaces.ts",
    "content": "import { useSelector } from 'react-redux';\nimport { interfacesSelector } from '../selectors/app';\n\nconst useInterfaces = () => useSelector(interfacesSelector);\n\nexport default useInterfaces;\n"
  },
  {
    "path": "frontend/src/hooks/use-modals-info.ts",
    "content": "import { useSelector } from 'react-redux';\nimport { modalsInfoSelector } from '../selectors/modals';\n\nconst useModalsInfo = () => {\n  const modalInfo = useSelector(modalsInfoSelector);\n\n  return {\n    modalProps: modalInfo.props,\n    isModalOpen: modalInfo.isOpen,\n    openModal: modalInfo.openModal\n  };\n};\n\nexport default useModalsInfo;\n"
  },
  {
    "path": "frontend/src/hooks/use-monitor-status-msg.ts",
    "content": "import { useSelector } from 'react-redux';\nimport { monitorStatusMsgSelector } from '../selectors/app';\n\nconst useMonitorStatusMsg = () => useSelector(monitorStatusMsgSelector);\n\nexport default useMonitorStatusMsg;\n"
  },
  {
    "path": "frontend/src/hooks/use-monitor-status.ts",
    "content": "import { useSelector } from 'react-redux';\nimport { monitoringSelector } from '../selectors/app';\n\nconst useMonitorStatus = () => useSelector(monitoringSelector);\n\nexport default useMonitorStatus;\n"
  },
  {
    "path": "frontend/src/hooks/use-network-speed.ts",
    "content": "import { useSelector } from 'react-redux';\nimport {\n  networkSpeedInBytesPerSecondSelector,\n  networkSpeedInMegabytesPerSecondSelector\n} from '../selectors/app';\n\nconst useNetworkSpeedInMegaBytes = () =>\n  useSelector(networkSpeedInMegabytesPerSecondSelector);\n\nconst useNetworkSpeedInBytes = () =>\n  useSelector(networkSpeedInBytesPerSecondSelector);\n\nexport { useNetworkSpeedInMegaBytes, useNetworkSpeedInBytes };\n"
  },
  {
    "path": "frontend/src/hooks/use-selected-mac.ts",
    "content": "import { useSelector } from 'react-redux';\nimport { selectedMacSelector } from '../selectors/app';\n\nconst useSelectedMac = () => useSelector(selectedMacSelector);\n\nexport default useSelectedMac;\n"
  },
  {
    "path": "frontend/src/hooks/use-selected-theme.ts",
    "content": "import { useSelector } from 'react-redux';\nimport { themeSelector } from '../selectors/app';\n\nexport const useSelectedTheme = () => useSelector(themeSelector);\n\nexport default useSelectedTheme;\n"
  },
  {
    "path": "frontend/src/hooks/use-settings.ts",
    "content": "import { useSelector } from 'react-redux';\nimport { settingsSelector } from '../selectors/app';\n\nconst useSettings = () => useSelector(settingsSelector);\n\nexport default useSettings;\n"
  },
  {
    "path": "frontend/src/hooks/use-system-metrics.ts",
    "content": "import { selectedMacSelector, targetProcessSelector } from '../selectors/app';\nimport { useEffect, useRef } from 'react';\nimport { DesktopApi } from '../desktop';\nimport { setDiskSpeed, setNetworkSpeed } from '../actions/app';\nimport { store } from '../store';\n\nconst useSystemMetrics = () => {\n  const timeout = useRef<number | undefined>(undefined);\n\n  useEffect(() => {\n    // we use getState here so we don't need to manage useEffect dependencies\n    const loop = async () => {\n      const state = store.getState();\n      const selectedMac = selectedMacSelector(state);\n      const targetProcess = targetProcessSelector(state);\n\n      try {\n        const [networkSpeed, diskSpeed] = await Promise.all([\n          DesktopApi.getNetworkSpeed(selectedMac || ''),\n          DesktopApi.getDiskWriteSpeed(+(targetProcess?.id || 0))\n        ]);\n\n        setNetworkSpeed(networkSpeed);\n        setDiskSpeed(diskSpeed);\n      } catch {\n        // Ignore\n      }\n\n      clearTimeout(timeout.current);\n      timeout.current = setTimeout(loop, 1000);\n    };\n\n    clearTimeout(timeout.current);\n    loop();\n\n    return () => {\n      clearTimeout(timeout.current);\n    };\n  }, []);\n};\n\nexport default useSystemMetrics;\n"
  },
  {
    "path": "frontend/src/hooks/use-system-monitor.ts",
    "content": "import { useEffect, useRef } from 'react';\nimport { store } from '../store';\nimport {\n  diskSpeedInBytesPerSecondSelector,\n  networkSpeedInBytesPerSecondSelector,\n  settingsSelector\n} from '../selectors/app';\nimport useMonitorStatus from './use-monitor-status';\nimport { setMonitorStatusMsg } from '../actions/app';\nimport { openModal } from '../actions/modal';\nimport { Modal } from '../types';\nimport { DesktopApi } from '../desktop';\n\nconst INTERVAL_MS = 1000;\nconst IDLE_THRESHOLD = 5;\n\nlet idleCounter = 0;\nlet idleThresholdCounter = 0;\n\nconst useSystemMonitor = () => {\n  const interval = useRef<number | undefined>(undefined);\n  const isMonitoring = useMonitorStatus();\n\n  // we use getState here so we don't need to manage useEffect dependencies\n  const monitor = async () => {\n    const state = store.getState();\n    const { actionDelay, speedThreshold, diskActivityMonitor, actionType } =\n      settingsSelector(state);\n    const networkSpeedInBytes = networkSpeedInBytesPerSecondSelector(state);\n    const diskSpeedInBytes = diskSpeedInBytesPerSecondSelector(state);\n    const speedThresholdInBytes = speedThreshold * 1024;\n    const isBelowNetworkSpeedThreshold =\n      networkSpeedInBytes < speedThresholdInBytes;\n    const isBelowDiskSpeedThreshold = diskSpeedInBytes < speedThresholdInBytes;\n\n    // if diskActivityMonitor is enabled, we need to check both network and disk speed\n    const isIdle = diskActivityMonitor\n      ? isBelowNetworkSpeedThreshold && isBelowDiskSpeedThreshold\n      : isBelowNetworkSpeedThreshold;\n\n    if (isIdle) {\n      idleCounter += 1;\n      idleThresholdCounter = 0;\n\n      setMonitorStatusMsg(\n        `No activity detected for ${idleCounter}/${actionDelay} seconds.`\n      );\n    } else {\n      idleThresholdCounter += 1;\n\n      if (idleThresholdCounter >= IDLE_THRESHOLD) {\n        idleCounter = 0;\n      }\n\n      setMonitorStatusMsg('Download in progress.');\n    }\n\n    if (idleCounter >= actionDelay) {\n      clearTimeout(interval.current);\n      setMonitorStatusMsg('Detected download completion.');\n      DesktopApi.executeAction(actionType);\n      openModal(Modal.ACTION_CONFIRMATION);\n    }\n  };\n\n  useEffect(() => {\n    if (!isMonitoring) {\n      clearTimeout(interval.current);\n      return;\n    }\n\n    setMonitorStatusMsg('Starting...');\n    idleCounter = 0;\n    interval.current = setInterval(monitor, INTERVAL_MS);\n\n    return () => {\n      clearTimeout(interval.current);\n    };\n  }, [isMonitoring]);\n};\n\nexport default useSystemMonitor;\n"
  },
  {
    "path": "frontend/src/hooks/use-target-process.ts",
    "content": "import { useSelector } from 'react-redux';\nimport { targetProcessSelector } from '../selectors/app';\n\nconst useTargetProcess = () => useSelector(targetProcessSelector);\n\nexport default useTargetProcess;\n"
  },
  {
    "path": "frontend/src/hooks/use-theme.ts",
    "content": "import { useSelector } from 'react-redux';\nimport { themeSelector } from '../selectors/app';\nimport { useEffect } from 'react';\n\nconst useTheme = () => {\n  const theme = useSelector(themeSelector);\n\n  useEffect(() => {\n    const html = document.querySelector('html');\n    if (theme === 'light') {\n      html?.classList.remove('dark');\n      html?.classList.add('light');\n    } else {\n      html?.classList.remove('light');\n      html?.classList.add('dark');\n    }\n  }, [theme]);\n};\n\nexport default useTheme;\n"
  },
  {
    "path": "frontend/src/main.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml,\nbody,\n[data-overlay-container='true'] {\n  height: 100%;\n  overflow: hidden;\n}\n\n#root {\n  display: flex;\n  flex-direction: column;\n  min-height: 100vh;\n  height: 100%;\n}\n\np {\n  cursor: default;\n}\n"
  },
  {
    "path": "frontend/src/main.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './components/app';\nimport { store } from './store';\nimport { Provider } from 'react-redux';\nimport { NextUIProvider } from '@nextui-org/react';\nimport './main.css';\nimport ModalsProvider from './components/modals';\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n  <React.StrictMode>\n    <NextUIProvider>\n      <Provider store={store}>\n        <ModalsProvider />\n        <App />\n      </Provider>\n    </NextUIProvider>\n  </React.StrictMode>\n);\n"
  },
  {
    "path": "frontend/src/screens/home/index.tsx",
    "content": "import { Divider } from '@nextui-org/react';\nimport Header from '../../components/header';\nimport MonitorSwitch from '../../components/monitor-switch';\nimport InterfacePicker from '../../components/interface-picker';\nimport Footer from '../../components/footer';\nimport TopRightSlot from '../../components/top-right-slot';\nimport useMonitorStatus from '../../hooks/use-monitor-status';\nimport { toggleMonitoring } from '../../actions/app';\n\nconst Home = () => {\n  const isMonitoring = useMonitorStatus();\n\n  const onToggleClick = () => {\n    toggleMonitoring();\n  };\n\n  return (\n    <div className=\" flex flex-col justify-center items-center h-full\">\n      <TopRightSlot />\n      <Header />\n      <div className=\"flex flex-col justify-center items-center h-full gap-3\">\n        <MonitorSwitch isActive={isMonitoring} setIsActive={onToggleClick} />\n        <Divider className=\"my-4 w-1/4\" />\n        <div className=\"flex justify-between w-full\">\n          <div className=\"flex justify-center w-full\">\n            <InterfacePicker />\n          </div>\n        </div>\n      </div>\n      <Footer />\n    </div>\n  );\n};\n\nexport default Home;\n"
  },
  {
    "path": "frontend/src/selectors/app.ts",
    "content": "import { IRootState } from '../store';\n\nexport const selectedMacSelector = (state: IRootState) => state.app.selectedMac;\n\nexport const targetProcessSelector = (state: IRootState) =>\n  state.app.targetProcess;\n\nexport const interfacesSelector = (state: IRootState) => state.app.interfaces;\n\nexport const diskSpeedInBytesPerSecondSelector = (state: IRootState) =>\n  state.app.diskSpeed;\n\nexport const diskSpeedInMegabytesPerSecondSelector = (state: IRootState) =>\n  state.app.diskSpeed / 1000000;\n\nexport const networkSpeedInBytesPerSecondSelector = (state: IRootState) =>\n  state.app.networkSpeed;\n\nexport const networkSpeedInMegabytesPerSecondSelector = (state: IRootState) =>\n  state.app.networkSpeed / 1000000;\n\nexport const themeSelector = (state: IRootState) => state.app.settings.theme;\n\nexport const settingsSelector = (state: IRootState) => state.app.settings;\n\nexport const monitoringSelector = (state: IRootState) => state.app.monitoring;\n\nexport const monitorStatusMsgSelector = (state: IRootState) =>\n  state.app.monitorStatusMsg;\n"
  },
  {
    "path": "frontend/src/selectors/modals.ts",
    "content": "import { IRootState } from '../store';\n\nexport const openModalSelector = (state: IRootState) => state.modals.openModal;\n\nexport const modalPropsSelector = (state: IRootState) => state.modals.props;\n\nexport const modalIsOpenSelector = (state: IRootState) => state.modals.isOpen;\n\nexport const modalsInfoSelector = (state: IRootState) => state.modals;\n"
  },
  {
    "path": "frontend/src/store/app-slice.ts",
    "content": "import { createSlice } from '@reduxjs/toolkit';\nimport { net } from '../wailsjs/go/models';\nimport { ActionType, TProcess, TSettings } from '../types';\n\nconst getStoredSettings = () => {\n  const stored = JSON.parse(localStorage.getItem('settings') || '{}');\n\n  return {\n    theme: stored.theme || 'dark',\n    diskActivityMonitor: stored.diskActivityMonitor || false,\n    actionDelay: stored.actionDelay || 20,\n    actionType: stored.actionType || ActionType.SHUTDOWN,\n    speedThreshold: stored.speedThreshold || 200\n  } as TSettings;\n};\n\nexport interface IAppState {\n  selectedMac: string | undefined;\n  interfaces: net.InterfaceStat[];\n  networkSpeed: number;\n  diskSpeed: number;\n  targetProcess: TProcess | undefined;\n  settings: TSettings;\n  monitoring: boolean;\n  monitorStatusMsg: string;\n}\n\nconst initialState: IAppState = {\n  selectedMac: undefined,\n  interfaces: [],\n  networkSpeed: 0,\n  diskSpeed: 0,\n  targetProcess: {\n    name: undefined,\n    id: undefined\n  } as TProcess,\n  settings: getStoredSettings(),\n  monitoring: false,\n  monitorStatusMsg: ''\n};\n\nexport const appSlice = createSlice({\n  name: 'app',\n  initialState,\n  reducers: {\n    setSelectedMac: (state, action) => {\n      state.selectedMac = action.payload;\n    },\n    setInterfaces: (state, action) => {\n      state.interfaces = action.payload;\n    },\n    setNetworkSpeed: (state, action) => {\n      state.networkSpeed = action.payload;\n    },\n    setDiskSpeed: (state, action) => {\n      state.diskSpeed = action.payload;\n    },\n    toggleTheme: (state) => {\n      state.settings.theme = state.settings.theme === 'dark' ? 'light' : 'dark';\n\n      localStorage.setItem('settings', JSON.stringify(state.settings));\n    },\n    setTargetProcess: (state, action) => {\n      state.targetProcess = action.payload;\n    },\n    setSettings: (state, action) => {\n      state.settings = {\n        ...state.settings,\n        ...action.payload\n      };\n\n      localStorage.setItem('settings', JSON.stringify(action.payload));\n    },\n    toggleMonitoring: (state) => {\n      state.monitoring = !state.monitoring;\n    },\n    setMonitorStatusMsg: (state, action) => {\n      state.monitorStatusMsg = action.payload;\n    }\n  }\n});\n\nexport const appSliceActions = appSlice.actions;\n\nexport default appSlice.reducer;\n"
  },
  {
    "path": "frontend/src/store/index.ts",
    "content": "import { configureStore } from '@reduxjs/toolkit';\nimport appSlice from './app-slice';\nimport modalsSlice from './modals-slice';\n\nexport const store = configureStore({\n  reducer: {\n    app: appSlice,\n    modals: modalsSlice\n  }\n});\n\nexport type IRootState = ReturnType<typeof store.getState>;\n"
  },
  {
    "path": "frontend/src/store/modals-slice.ts",
    "content": "import { createSlice } from '@reduxjs/toolkit';\nimport { Modal, TGenericObject } from '../types';\n\nexport interface IAppState {\n  openModal: Modal | undefined;\n  props?: TGenericObject;\n  isOpen: boolean;\n}\n\nconst initialState: IAppState = {\n  openModal: undefined,\n  props: {},\n  isOpen: false\n};\n\nexport const appSlice = createSlice({\n  name: 'modals',\n  initialState,\n  reducers: {\n    setOpenModal: (state, action) => {\n      state.openModal = action.payload;\n    },\n    setModalProps: (state, action) => {\n      state.props = action.payload;\n    },\n    setIsOpen: (state, action) => {\n      state.isOpen = action.payload;\n    },\n    setModalInfo: (state, action) => {\n      state.openModal = action.payload.modal;\n      state.props = action.payload.props;\n    }\n  }\n});\n\nexport const modalsSliceActions = appSlice.actions;\n\nexport default appSlice.reducer;\n"
  },
  {
    "path": "frontend/src/types/index.ts",
    "content": "export type TTheme = 'light' | 'dark';\n\nexport type TGenericObject = {\n  [key: string]: any;\n};\n\nexport enum Modal {\n  PROCESS_PICKER = 'PROCESS_PICKER',\n  SETTINGS = 'SETTINGS',\n  ACTION_CONFIRMATION = 'ACTION_CONFIRMATION'\n}\n\nexport enum ActionType {\n  SHUTDOWN = 'SHUTDOWN',\n  HIBERNATE = 'HIBERNATE',\n  SLEEP = 'SLEEP',\n  LOGOFF = 'LOGOFF'\n}\n\nexport type TProcess = {\n  id: string | undefined;\n  name: string | undefined;\n};\n\nexport type TSettings = {\n  theme: TTheme;\n  diskActivityMonitor: boolean;\n  actionDelay: number; // seconds\n  actionType: ActionType;\n  speedThreshold: number; // KB/s\n};\n"
  },
  {
    "path": "frontend/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ndeclare const APP_VERSION: string;\n"
  },
  {
    "path": "frontend/src/wailsjs/go/main/App.d.ts",
    "content": "// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT EDIT\n\nexport function CancelShutdown():Promise<void>;\n\nexport function ExecuteAction(arg1:string):Promise<void>;\n\nexport function OpenInBrowser(arg1:string):Promise<void>;\n"
  },
  {
    "path": "frontend/src/wailsjs/go/main/App.js",
    "content": "// @ts-check\n// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT EDIT\n\nexport function CancelShutdown() {\n  return window['go']['main']['App']['CancelShutdown']();\n}\n\nexport function ExecuteAction(arg1) {\n  return window['go']['main']['App']['ExecuteAction'](arg1);\n}\n\nexport function OpenInBrowser(arg1) {\n  return window['go']['main']['App']['OpenInBrowser'](arg1);\n}\n"
  },
  {
    "path": "frontend/src/wailsjs/go/main/DiskManager.d.ts",
    "content": "// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT EDIT\nimport {main} from '../models';\n\nexport function GetDiskWriteSpeed(arg1:number):Promise<number>;\n\nexport function GetProcesses():Promise<Array<main.Process>>;\n"
  },
  {
    "path": "frontend/src/wailsjs/go/main/DiskManager.js",
    "content": "// @ts-check\n// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT EDIT\n\nexport function GetDiskWriteSpeed(arg1) {\n  return window['go']['main']['DiskManager']['GetDiskWriteSpeed'](arg1);\n}\n\nexport function GetProcesses() {\n  return window['go']['main']['DiskManager']['GetProcesses']();\n}\n"
  },
  {
    "path": "frontend/src/wailsjs/go/main/NetworkManager.d.ts",
    "content": "// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT EDIT\nimport {net} from '../models';\n\nexport function AutoDetectInterface():Promise<string>;\n\nexport function GetIOStatByMac(arg1:string):Promise<net.IOCountersStat>;\n\nexport function GetInterfaceByMac(arg1:string):Promise<net.InterfaceStat>;\n\nexport function GetInterfaceDownloadSpeed(arg1:string):Promise<number>;\n\nexport function GetInterfaces():Promise<Array<net.InterfaceStat>>;\n"
  },
  {
    "path": "frontend/src/wailsjs/go/main/NetworkManager.js",
    "content": "// @ts-check\n// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT EDIT\n\nexport function AutoDetectInterface() {\n  return window['go']['main']['NetworkManager']['AutoDetectInterface']();\n}\n\nexport function GetIOStatByMac(arg1) {\n  return window['go']['main']['NetworkManager']['GetIOStatByMac'](arg1);\n}\n\nexport function GetInterfaceByMac(arg1) {\n  return window['go']['main']['NetworkManager']['GetInterfaceByMac'](arg1);\n}\n\nexport function GetInterfaceDownloadSpeed(arg1) {\n  return window['go']['main']['NetworkManager']['GetInterfaceDownloadSpeed'](arg1);\n}\n\nexport function GetInterfaces() {\n  return window['go']['main']['NetworkManager']['GetInterfaces']();\n}\n"
  },
  {
    "path": "frontend/src/wailsjs/go/models.ts",
    "content": "export namespace net {\n\t\n\texport class IOCountersStat {\n\t    name: string;\n\t    bytesSent: number;\n\t    bytesRecv: number;\n\t    packetsSent: number;\n\t    packetsRecv: number;\n\t    errin: number;\n\t    errout: number;\n\t    dropin: number;\n\t    dropout: number;\n\t    fifoin: number;\n\t    fifoout: number;\n\t\n\t    static createFrom(source: any = {}) {\n\t        return new IOCountersStat(source);\n\t    }\n\t\n\t    constructor(source: any = {}) {\n\t        if ('string' === typeof source) source = JSON.parse(source);\n\t        this.name = source[\"name\"];\n\t        this.bytesSent = source[\"bytesSent\"];\n\t        this.bytesRecv = source[\"bytesRecv\"];\n\t        this.packetsSent = source[\"packetsSent\"];\n\t        this.packetsRecv = source[\"packetsRecv\"];\n\t        this.errin = source[\"errin\"];\n\t        this.errout = source[\"errout\"];\n\t        this.dropin = source[\"dropin\"];\n\t        this.dropout = source[\"dropout\"];\n\t        this.fifoin = source[\"fifoin\"];\n\t        this.fifoout = source[\"fifoout\"];\n\t    }\n\t}\n\texport class InterfaceAddr {\n\t    addr: string;\n\t\n\t    static createFrom(source: any = {}) {\n\t        return new InterfaceAddr(source);\n\t    }\n\t\n\t    constructor(source: any = {}) {\n\t        if ('string' === typeof source) source = JSON.parse(source);\n\t        this.addr = source[\"addr\"];\n\t    }\n\t}\n\texport class InterfaceStat {\n\t    index: number;\n\t    mtu: number;\n\t    name: string;\n\t    hardwareaddr: string;\n\t    flags: string[];\n\t    addrs: InterfaceAddr[];\n\t\n\t    static createFrom(source: any = {}) {\n\t        return new InterfaceStat(source);\n\t    }\n\t\n\t    constructor(source: any = {}) {\n\t        if ('string' === typeof source) source = JSON.parse(source);\n\t        this.index = source[\"index\"];\n\t        this.mtu = source[\"mtu\"];\n\t        this.name = source[\"name\"];\n\t        this.hardwareaddr = source[\"hardwareaddr\"];\n\t        this.flags = source[\"flags\"];\n\t        this.addrs = this.convertValues(source[\"addrs\"], InterfaceAddr);\n\t    }\n\t\n\t\tconvertValues(a: any, classs: any, asMap: boolean = false): any {\n\t\t    if (!a) {\n\t\t        return a;\n\t\t    }\n\t\t    if (a.slice) {\n\t\t        return (a as any[]).map(elem => this.convertValues(elem, classs));\n\t\t    } else if (\"object\" === typeof a) {\n\t\t        if (asMap) {\n\t\t            for (const key of Object.keys(a)) {\n\t\t                a[key] = new classs(a[key]);\n\t\t            }\n\t\t            return a;\n\t\t        }\n\t\t        return new classs(a);\n\t\t    }\n\t\t    return a;\n\t\t}\n\t}\n\n}\n\n"
  },
  {
    "path": "frontend/src/wailsjs/runtime/package.json",
    "content": "{\n  \"name\": \"@wailsapp/runtime\",\n  \"version\": \"2.0.0\",\n  \"description\": \"Wails Javascript runtime library\",\n  \"main\": \"runtime.js\",\n  \"types\": \"runtime.d.ts\",\n  \"scripts\": {\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/wailsapp/wails.git\"\n  },\n  \"keywords\": [\n    \"Wails\",\n    \"Javascript\",\n    \"Go\"\n  ],\n  \"author\": \"Lea Anthony <lea.anthony@gmail.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/wailsapp/wails/issues\"\n  },\n  \"homepage\": \"https://github.com/wailsapp/wails#readme\"\n}\n"
  },
  {
    "path": "frontend/src/wailsjs/runtime/runtime.d.ts",
    "content": "/*\n _       __      _ __\n| |     / /___ _(_) /____\n| | /| / / __ `/ / / ___/\n| |/ |/ / /_/ / / (__  )\n|__/|__/\\__,_/_/_/____/\nThe electron alternative for Go\n(c) Lea Anthony 2019-present\n*/\n\nexport interface Position {\n    x: number;\n    y: number;\n}\n\nexport interface Size {\n    w: number;\n    h: number;\n}\n\nexport interface Screen {\n    isCurrent: boolean;\n    isPrimary: boolean;\n    width : number\n    height : number\n}\n\n// Environment information such as platform, buildtype, ...\nexport interface EnvironmentInfo {\n    buildType: string;\n    platform: string;\n    arch: string;\n}\n\n// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)\n// emits the given event. Optional data may be passed with the event.\n// This will trigger any event listeners.\nexport function EventsEmit(eventName: string, ...data: any): void;\n\n// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.\nexport function EventsOn(eventName: string, callback: (...data: any) => void): () => void;\n\n// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)\n// sets up a listener for the given event name, but will only trigger a given number times.\nexport function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;\n\n// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)\n// sets up a listener for the given event name, but will only trigger once.\nexport function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;\n\n// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)\n// unregisters the listener for the given event name.\nexport function EventsOff(eventName: string, ...additionalEventNames: string[]): void;\n\n// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)\n// unregisters all listeners.\nexport function EventsOffAll(): void;\n\n// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)\n// logs the given message as a raw message\nexport function LogPrint(message: string): void;\n\n// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)\n// logs the given message at the `trace` log level.\nexport function LogTrace(message: string): void;\n\n// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)\n// logs the given message at the `debug` log level.\nexport function LogDebug(message: string): void;\n\n// [LogError](https://wails.io/docs/reference/runtime/log#logerror)\n// logs the given message at the `error` log level.\nexport function LogError(message: string): void;\n\n// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)\n// logs the given message at the `fatal` log level.\n// The application will quit after calling this method.\nexport function LogFatal(message: string): void;\n\n// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)\n// logs the given message at the `info` log level.\nexport function LogInfo(message: string): void;\n\n// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)\n// logs the given message at the `warning` log level.\nexport function LogWarning(message: string): void;\n\n// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)\n// Forces a reload by the main application as well as connected browsers.\nexport function WindowReload(): void;\n\n// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)\n// Reloads the application frontend.\nexport function WindowReloadApp(): void;\n\n// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)\n// Sets the window AlwaysOnTop or not on top.\nexport function WindowSetAlwaysOnTop(b: boolean): void;\n\n// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)\n// *Windows only*\n// Sets window theme to system default (dark/light).\nexport function WindowSetSystemDefaultTheme(): void;\n\n// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)\n// *Windows only*\n// Sets window to light theme.\nexport function WindowSetLightTheme(): void;\n\n// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)\n// *Windows only*\n// Sets window to dark theme.\nexport function WindowSetDarkTheme(): void;\n\n// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)\n// Centers the window on the monitor the window is currently on.\nexport function WindowCenter(): void;\n\n// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)\n// Sets the text in the window title bar.\nexport function WindowSetTitle(title: string): void;\n\n// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)\n// Makes the window full screen.\nexport function WindowFullscreen(): void;\n\n// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)\n// Restores the previous window dimensions and position prior to full screen.\nexport function WindowUnfullscreen(): void;\n\n// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)\n// Returns the state of the window, i.e. whether the window is in full screen mode or not.\nexport function WindowIsFullscreen(): Promise<boolean>;\n\n// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)\n// Sets the width and height of the window.\nexport function WindowSetSize(width: number, height: number): Promise<Size>;\n\n// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)\n// Gets the width and height of the window.\nexport function WindowGetSize(): Promise<Size>;\n\n// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)\n// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.\n// Setting a size of 0,0 will disable this constraint.\nexport function WindowSetMaxSize(width: number, height: number): void;\n\n// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)\n// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.\n// Setting a size of 0,0 will disable this constraint.\nexport function WindowSetMinSize(width: number, height: number): void;\n\n// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)\n// Sets the window position relative to the monitor the window is currently on.\nexport function WindowSetPosition(x: number, y: number): void;\n\n// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)\n// Gets the window position relative to the monitor the window is currently on.\nexport function WindowGetPosition(): Promise<Position>;\n\n// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)\n// Hides the window.\nexport function WindowHide(): void;\n\n// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)\n// Shows the window, if it is currently hidden.\nexport function WindowShow(): void;\n\n// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)\n// Maximises the window to fill the screen.\nexport function WindowMaximise(): void;\n\n// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)\n// Toggles between Maximised and UnMaximised.\nexport function WindowToggleMaximise(): void;\n\n// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)\n// Restores the window to the dimensions and position prior to maximising.\nexport function WindowUnmaximise(): void;\n\n// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)\n// Returns the state of the window, i.e. whether the window is maximised or not.\nexport function WindowIsMaximised(): Promise<boolean>;\n\n// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)\n// Minimises the window.\nexport function WindowMinimise(): void;\n\n// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)\n// Restores the window to the dimensions and position prior to minimising.\nexport function WindowUnminimise(): void;\n\n// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)\n// Returns the state of the window, i.e. whether the window is minimised or not.\nexport function WindowIsMinimised(): Promise<boolean>;\n\n// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)\n// Returns the state of the window, i.e. whether the window is normal or not.\nexport function WindowIsNormal(): Promise<boolean>;\n\n// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)\n// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.\nexport function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;\n\n// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)\n// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.\nexport function ScreenGetAll(): Promise<Screen[]>;\n\n// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)\n// Opens the given URL in the system browser.\nexport function BrowserOpenURL(url: string): void;\n\n// [Environment](https://wails.io/docs/reference/runtime/intro#environment)\n// Returns information about the environment\nexport function Environment(): Promise<EnvironmentInfo>;\n\n// [Quit](https://wails.io/docs/reference/runtime/intro#quit)\n// Quits the application.\nexport function Quit(): void;\n\n// [Hide](https://wails.io/docs/reference/runtime/intro#hide)\n// Hides the application.\nexport function Hide(): void;\n\n// [Show](https://wails.io/docs/reference/runtime/intro#show)\n// Shows the application.\nexport function Show(): void;\n\n// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)\n// Returns the current text stored on clipboard\nexport function ClipboardGetText(): Promise<string>;\n\n// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)\n// Sets a text on the clipboard\nexport function ClipboardSetText(text: string): Promise<boolean>;\n"
  },
  {
    "path": "frontend/src/wailsjs/runtime/runtime.js",
    "content": "/*\n _       __      _ __\n| |     / /___ _(_) /____\n| | /| / / __ `/ / / ___/\n| |/ |/ / /_/ / / (__  )\n|__/|__/\\__,_/_/_/____/\nThe electron alternative for Go\n(c) Lea Anthony 2019-present\n*/\n\nexport function LogPrint(message) {\n    window.runtime.LogPrint(message);\n}\n\nexport function LogTrace(message) {\n    window.runtime.LogTrace(message);\n}\n\nexport function LogDebug(message) {\n    window.runtime.LogDebug(message);\n}\n\nexport function LogInfo(message) {\n    window.runtime.LogInfo(message);\n}\n\nexport function LogWarning(message) {\n    window.runtime.LogWarning(message);\n}\n\nexport function LogError(message) {\n    window.runtime.LogError(message);\n}\n\nexport function LogFatal(message) {\n    window.runtime.LogFatal(message);\n}\n\nexport function EventsOnMultiple(eventName, callback, maxCallbacks) {\n    return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);\n}\n\nexport function EventsOn(eventName, callback) {\n    return EventsOnMultiple(eventName, callback, -1);\n}\n\nexport function EventsOff(eventName, ...additionalEventNames) {\n    return window.runtime.EventsOff(eventName, ...additionalEventNames);\n}\n\nexport function EventsOnce(eventName, callback) {\n    return EventsOnMultiple(eventName, callback, 1);\n}\n\nexport function EventsEmit(eventName) {\n    let args = [eventName].slice.call(arguments);\n    return window.runtime.EventsEmit.apply(null, args);\n}\n\nexport function WindowReload() {\n    window.runtime.WindowReload();\n}\n\nexport function WindowReloadApp() {\n    window.runtime.WindowReloadApp();\n}\n\nexport function WindowSetAlwaysOnTop(b) {\n    window.runtime.WindowSetAlwaysOnTop(b);\n}\n\nexport function WindowSetSystemDefaultTheme() {\n    window.runtime.WindowSetSystemDefaultTheme();\n}\n\nexport function WindowSetLightTheme() {\n    window.runtime.WindowSetLightTheme();\n}\n\nexport function WindowSetDarkTheme() {\n    window.runtime.WindowSetDarkTheme();\n}\n\nexport function WindowCenter() {\n    window.runtime.WindowCenter();\n}\n\nexport function WindowSetTitle(title) {\n    window.runtime.WindowSetTitle(title);\n}\n\nexport function WindowFullscreen() {\n    window.runtime.WindowFullscreen();\n}\n\nexport function WindowUnfullscreen() {\n    window.runtime.WindowUnfullscreen();\n}\n\nexport function WindowIsFullscreen() {\n    return window.runtime.WindowIsFullscreen();\n}\n\nexport function WindowGetSize() {\n    return window.runtime.WindowGetSize();\n}\n\nexport function WindowSetSize(width, height) {\n    window.runtime.WindowSetSize(width, height);\n}\n\nexport function WindowSetMaxSize(width, height) {\n    window.runtime.WindowSetMaxSize(width, height);\n}\n\nexport function WindowSetMinSize(width, height) {\n    window.runtime.WindowSetMinSize(width, height);\n}\n\nexport function WindowSetPosition(x, y) {\n    window.runtime.WindowSetPosition(x, y);\n}\n\nexport function WindowGetPosition() {\n    return window.runtime.WindowGetPosition();\n}\n\nexport function WindowHide() {\n    window.runtime.WindowHide();\n}\n\nexport function WindowShow() {\n    window.runtime.WindowShow();\n}\n\nexport function WindowMaximise() {\n    window.runtime.WindowMaximise();\n}\n\nexport function WindowToggleMaximise() {\n    window.runtime.WindowToggleMaximise();\n}\n\nexport function WindowUnmaximise() {\n    window.runtime.WindowUnmaximise();\n}\n\nexport function WindowIsMaximised() {\n    return window.runtime.WindowIsMaximised();\n}\n\nexport function WindowMinimise() {\n    window.runtime.WindowMinimise();\n}\n\nexport function WindowUnminimise() {\n    window.runtime.WindowUnminimise();\n}\n\nexport function WindowSetBackgroundColour(R, G, B, A) {\n    window.runtime.WindowSetBackgroundColour(R, G, B, A);\n}\n\nexport function ScreenGetAll() {\n    return window.runtime.ScreenGetAll();\n}\n\nexport function WindowIsMinimised() {\n    return window.runtime.WindowIsMinimised();\n}\n\nexport function WindowIsNormal() {\n    return window.runtime.WindowIsNormal();\n}\n\nexport function BrowserOpenURL(url) {\n    window.runtime.BrowserOpenURL(url);\n}\n\nexport function Environment() {\n    return window.runtime.Environment();\n}\n\nexport function Quit() {\n    window.runtime.Quit();\n}\n\nexport function Hide() {\n    window.runtime.Hide();\n}\n\nexport function Show() {\n    window.runtime.Show();\n}\n\nexport function ClipboardGetText() {\n    return window.runtime.ClipboardGetText();\n}\n\nexport function ClipboardSetText(text) {\n    return window.runtime.ClipboardSetText(text);\n}"
  },
  {
    "path": "frontend/tailwind.config.js",
    "content": "import { nextui } from '@nextui-org/react';\n\n/** @type {import('tailwindcss').Config} */\nexport default {\n  content: [\n    './index.html',\n    './src/**/*.{js,ts,jsx,tsx}',\n    './node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}'\n  ],\n  theme: {\n    extend: {}\n  },\n  darkMode: 'class',\n  plugins: [nextui()]\n};\n"
  },
  {
    "path": "frontend/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitAny\": false\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "frontend/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "frontend/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react-swc';\nimport wails from '../wails.json' assert { type: 'json' };\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n  server: {\n    port: 34115,\n    hmr: {\n      host: 'localhost',\n      port: 34115,\n      protocol: 'ws'\n    }\n  },\n  define: {\n    APP_VERSION: JSON.stringify(wails.info.productVersion)\n  }\n});\n"
  },
  {
    "path": "go.mod",
    "content": "module changeme\n\ngo 1.18\n\nrequire (\n\tgithub.com/shirou/gopsutil v3.21.11+incompatible\n\tgithub.com/wailsapp/wails/v2 v2.6.0\n)\n\nrequire (\n\tgithub.com/bep/debounce v1.2.1 // indirect\n\tgithub.com/go-ole/go-ole v1.2.6 // indirect\n\tgithub.com/google/uuid v1.3.0 // indirect\n\tgithub.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect\n\tgithub.com/labstack/echo/v4 v4.10.2 // indirect\n\tgithub.com/labstack/gommon v0.4.0 // indirect\n\tgithub.com/leaanthony/go-ansi-parser v1.6.0 // indirect\n\tgithub.com/leaanthony/gosod v1.0.3 // indirect\n\tgithub.com/leaanthony/slicer v1.6.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.19 // indirect\n\tgithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/rivo/uniseg v0.4.4 // indirect\n\tgithub.com/samber/lo v1.38.1 // indirect\n\tgithub.com/tklauser/go-sysconf v0.3.12 // indirect\n\tgithub.com/tklauser/numcpus v0.6.1 // indirect\n\tgithub.com/tkrajina/go-reflector v0.5.6 // indirect\n\tgithub.com/valyala/bytebufferpool v1.0.0 // indirect\n\tgithub.com/valyala/fasttemplate v1.2.2 // indirect\n\tgithub.com/wailsapp/go-webview2 v1.0.1 // indirect\n\tgithub.com/wailsapp/mimetype v1.4.1 // indirect\n\tgithub.com/yusufpapurcu/wmi v1.2.3 // indirect\n\tgolang.org/x/crypto v0.17.0 // indirect\n\tgolang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect\n\tgolang.org/x/net v0.10.0 // indirect\n\tgolang.org/x/sys v0.15.0 // indirect\n\tgolang.org/x/text v0.14.0 // indirect\n)\n\n// replace github.com/wailsapp/wails/v2 v2.6.0 => C:\\Users\\Martino\\go\\pkg\\mod\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=\ngithub.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=\ngithub.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=\ngithub.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=\ngithub.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=\ngithub.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=\ngithub.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=\ngithub.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=\ngithub.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=\ngithub.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg=\ngithub.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=\ngithub.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=\ngithub.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=\ngithub.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=\ngithub.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=\ngithub.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=\ngithub.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=\ngithub.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=\ngithub.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=\ngithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=\ngithub.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=\ngithub.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=\ngithub.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=\ngithub.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=\ngithub.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=\ngithub.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=\ngithub.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=\ngithub.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=\ngithub.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=\ngithub.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=\ngithub.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=\ngithub.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=\ngithub.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=\ngithub.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=\ngithub.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=\ngithub.com/wailsapp/go-webview2 v1.0.1 h1:dEJIeEApW/MhO2tTMISZBFZPuW7kwrFA1NtgFB1z1II=\ngithub.com/wailsapp/go-webview2 v1.0.1/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=\ngithub.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=\ngithub.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=\ngithub.com/wailsapp/wails/v2 v2.6.0 h1:EyH0zR/EO6dDiqNy8qU5spaXDfkluiq77xrkabPYD4c=\ngithub.com/wailsapp/wails/v2 v2.6.0/go.mod h1:WBG9KKWuw0FKfoepBrr/vRlyTmHaMibWesK3yz6nNiM=\ngithub.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=\ngithub.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=\ngolang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=\ngolang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=\ngolang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=\ngolang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=\ngolang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=\ngolang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"embed\"\n\t\"log\"\n\n\t\"github.com/wailsapp/wails/v2\"\n\t\"github.com/wailsapp/wails/v2/pkg/logger\"\n\t\"github.com/wailsapp/wails/v2/pkg/options\"\n\t\"github.com/wailsapp/wails/v2/pkg/options/assetserver\"\n\t\"github.com/wailsapp/wails/v2/pkg/options/windows\"\n)\n\n//go:embed all:frontend/dist\nvar assets embed.FS\n\nvar (\n\twidth  = 800\n\theight = 560\n)\n\nfunc main() {\n\t// Create an instance of the app structure\n\tapp := NewApp()\n\tnetworkManager := createNetworkManager()\n\tdiskManager := createDiskManager()\n\n\t// Create application with options\n\terr := wails.Run(&options.App{\n\t\tTitle:             \"Steam Auto Shutdown\",\n\t\tWidth:             width,\n\t\tHeight:            height,\n\t\tMinWidth:          width,\n\t\tMinHeight:         height,\n\t\tMaxWidth:          width,\n\t\tMaxHeight:         height,\n\t\tDisableResize:     false,\n\t\tFullscreen:        false,\n\t\tFrameless:         false,\n\t\tStartHidden:       false,\n\t\tHideWindowOnClose: false,\n\t\tBackgroundColour:  &options.RGBA{R: 255, G: 255, B: 255, A: 255},\n\t\tAssetServer: &assetserver.Options{\n\t\t\tAssets: assets,\n\t\t},\n\t\tMenu:             nil,\n\t\tLogger:           nil,\n\t\tLogLevel:         logger.DEBUG,\n\t\tOnStartup:        app.startup,\n\t\tOnDomReady:       app.domReady,\n\t\tOnBeforeClose:    app.beforeClose,\n\t\tOnShutdown:       app.shutdown,\n\t\tWindowStartState: options.Normal,\n\t\tBind: []interface{}{\n\t\t\tapp,\n\t\t\tnetworkManager,\n\t\t\tdiskManager,\n\t\t},\n\t\t// Windows platform specific options\n\t\tWindows: &windows.Options{\n\t\t\tWebviewIsTransparent: false,\n\t\t\tWindowIsTranslucent:  false,\n\t\t\tDisableWindowIcon:    false,\n\t\t\t// DisableFramelessWindowDecorations: false,\n\t\t\tWebviewUserDataPath: \"\",\n\t\t\tZoomFactor:          1.0,\n\t\t},\n\t})\n\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n}\n"
  },
  {
    "path": "network-manager.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/shirou/gopsutil/net\"\n)\n\ntype NetworkManager struct{}\n\ntype Interface struct {\n\tIndex        int\n\tName         string\n\tMTU          int\n\tHardwareAddr string\n\tFlags        []string\n}\n\nfunc createNetworkManager() *NetworkManager {\n\treturn &NetworkManager{}\n}\n\nfunc (n *NetworkManager) GetInterfaces() []net.InterfaceStat {\n\tnetInterfaces, err := net.Interfaces()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn netInterfaces\n}\n\nconst sampleInterval = 2 * time.Second\n\nfunc (n *NetworkManager) GetInterfaceDownloadSpeed(mac string) (float64, error) {\n\tinitialStat, err := n.GetIOStatByMac(mac)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tstartTime := time.Now()\n\tendTime := startTime.Add(sampleInterval)\n\tinitialBytesRecv := initialStat.BytesRecv\n\ttotalBytesRecv := uint64(0)\n\n\tfor time.Now().Before(endTime) {\n\t\tstat, err := n.GetIOStatByMac(mac)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tbytesReceivedNow := stat.BytesRecv - initialBytesRecv\n\t\tinitialBytesRecv = stat.BytesRecv\n\t\ttotalBytesRecv += bytesReceivedNow\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\tactualEndTime := time.Now()\n\telapsedSeconds := actualEndTime.Sub(startTime).Seconds()\n\n\tif elapsedSeconds == 0 {\n\t\treturn 0, nil\n\t}\n\n\tdownloadSpeed := float64(totalBytesRecv) / elapsedSeconds\n\n\treturn downloadSpeed, nil\n}\n\nfunc (n *NetworkManager) GetIOStatByMac(mac string) (net.IOCountersStat, error) {\n\tinterfaceName := n.GetInterfaceByMac(mac).Name\n\n\tnetInterface, err := net.IOCounters(true)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tstat := net.IOCountersStat{}\n\n\tfor _, v := range netInterface {\n\t\tif v.Name == interfaceName {\n\t\t\tstat = v\n\t\t}\n\t}\n\n\tif stat.Name == \"\" {\n\t\treturn stat, errors.New(\"Interface not found\")\n\t}\n\n\treturn stat, nil\n}\n\nfunc (n *NetworkManager) GetInterfaceByMac(mac string) net.InterfaceStat {\n\tnetInterfaces, err := net.Interfaces()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor _, v := range netInterfaces {\n\t\tif v.HardwareAddr == mac {\n\t\t\treturn v\n\t\t}\n\t}\n\n\treturn net.InterfaceStat{}\n}\n\nfunc (n *NetworkManager) AutoDetectInterface() string {\n\tnetInterface, err := net.IOCounters(true)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tvar highestInterfaceName string\n\tvar highestRecieved float64\n\n\tfor _, v := range netInterface {\n\t\tif float64(v.BytesRecv) > highestRecieved {\n\t\t\thighestRecieved = float64(v.BytesRecv)\n\t\t\thighestInterfaceName = v.Name\n\t\t}\n\t}\n\n\tinterfaces := n.GetInterfaces()\n\n\tvar highestInterfaceMac string\n\n\tfor _, v := range interfaces {\n\t\tif v.Name == highestInterfaceName {\n\t\t\thighestInterfaceMac = v.HardwareAddr\n\t\t}\n\t}\n\n\treturn highestInterfaceMac\n}\n"
  },
  {
    "path": "wails.json",
    "content": "{\n  \"$schema\": \"https://wails.io/schemas/config.v2.json\",\n  \"name\": \"Steam Auto Shutdown\",\n  \"outputfilename\": \"Steam Auto Shutdown\",\n  \"assetdir\": \"frontend/dist\",\n  \"wailsjsdir\": \"frontend/src\",\n  \"frontend:install\": \"pnpm install\",\n  \"frontend:build\": \"pnpm build\",\n  \"frontend:dev:watcher\": \"pnpm dev\",\n  \"frontend:dev:serverUrl\": \"auto\",\n  \"author\": {\n    \"name\": \"Diogo Martino\",\n    \"email\": \"diogo.martino10@gmail.com\"\n  },\n  \"info\": {\n    \"companyName\": \"DiogoMartino\",\n    \"productName\": \"Steam Auto Shutdown\",\n    \"productVersion\": \"6.0.0\",\n    \"copyright\": \"2024\",\n    \"comments\": \"https://github.com/diogomartino/steam-auto-shutdown\"\n  }\n}\n"
  }
]