Showing preview only (2,635K chars total). Download the full file or copy to clipboard to get everything.
Repository: exelban/stats
Branch: master
Commit: c457e0fafb58
Files: 283
Total size: 17.7 MB
Directory structure:
gitextract_0i6vpw9i/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ └── bug_report.md
│ └── workflows/
│ ├── build.yaml
│ ├── i18n.yaml
│ └── linter.yaml
├── .gitignore
├── .swiftlint.yml
├── Kit/
│ ├── Supporting Files/
│ │ ├── Assets.xcassets/
│ │ │ ├── Contents.json
│ │ │ ├── calendar.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── cancel.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── chart.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── close.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── refresh.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── settings.imageset/
│ │ │ │ └── Contents.json
│ │ │ └── tune.imageset/
│ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ └── Kit.h
│ ├── Widgets/
│ │ ├── BarChart.swift
│ │ ├── Battery.swift
│ │ ├── Label.swift
│ │ ├── LineChart.swift
│ │ ├── Memory.swift
│ │ ├── Mini.swift
│ │ ├── NetworkChart.swift
│ │ ├── PieChart.swift
│ │ ├── Speed.swift
│ │ ├── Stack.swift
│ │ ├── State.swift
│ │ ├── Tachometer.swift
│ │ └── Text.swift
│ ├── constants.swift
│ ├── extensions.swift
│ ├── helpers.swift
│ ├── lldb/
│ │ ├── LICENSE.txt
│ │ ├── include/
│ │ │ ├── c.h
│ │ │ ├── cache.h
│ │ │ ├── comparator.h
│ │ │ ├── db.h
│ │ │ ├── dumpfile.h
│ │ │ ├── env.h
│ │ │ ├── export.h
│ │ │ ├── filter_policy.h
│ │ │ ├── iterator.h
│ │ │ ├── options.h
│ │ │ ├── slice.h
│ │ │ ├── status.h
│ │ │ ├── table.h
│ │ │ ├── table_builder.h
│ │ │ └── write_batch.h
│ │ ├── libleveldb.a
│ │ ├── lldb.h
│ │ └── lldb.m
│ ├── module/
│ │ ├── module.swift
│ │ ├── notifications.swift
│ │ ├── popup.swift
│ │ ├── portal.swift
│ │ ├── reader.swift
│ │ ├── widget.swift
│ │ └── window.swift
│ ├── plugins/
│ │ ├── Charts.swift
│ │ ├── DB.swift
│ │ ├── Logger.swift
│ │ ├── Reachability.swift
│ │ ├── Remote.swift
│ │ ├── Repeater.swift
│ │ ├── Store.swift
│ │ ├── SystemKit.swift
│ │ └── Updater.swift
│ ├── process.swift
│ ├── scripts/
│ │ ├── SMJobBlessUtil.py
│ │ ├── changelog.py
│ │ ├── i18n.py
│ │ ├── uninstall.sh
│ │ └── updater.sh
│ └── types.swift
├── LICENSE
├── LaunchAtLogin/
│ ├── Info.plist
│ ├── LaunchAtLogin.entitlements
│ └── main.swift
├── Makefile
├── Modules/
│ ├── Battery/
│ │ ├── Info.plist
│ │ ├── config.plist
│ │ ├── main.swift
│ │ ├── notifications.swift
│ │ ├── popup.swift
│ │ ├── portal.swift
│ │ ├── readers.swift
│ │ └── settings.swift
│ ├── Bluetooth/
│ │ ├── Info.plist
│ │ ├── config.plist
│ │ ├── main.swift
│ │ ├── notifications.swift
│ │ ├── popup.swift
│ │ ├── readers.swift
│ │ └── settings.swift
│ ├── CPU/
│ │ ├── Info.plist
│ │ ├── bridge.h
│ │ ├── config.plist
│ │ ├── main.swift
│ │ ├── notifications.swift
│ │ ├── popup.swift
│ │ ├── portal.swift
│ │ ├── readers.swift
│ │ ├── settings.swift
│ │ └── widget.swift
│ ├── Clock/
│ │ ├── config.plist
│ │ ├── main.swift
│ │ ├── popup.swift
│ │ ├── portal.swift
│ │ ├── reader.swift
│ │ └── settings.swift
│ ├── Disk/
│ │ ├── Info.plist
│ │ ├── config.plist
│ │ ├── header.h
│ │ ├── main.swift
│ │ ├── notifications.swift
│ │ ├── popup.swift
│ │ ├── portal.swift
│ │ ├── readers.swift
│ │ ├── settings.swift
│ │ └── widget.swift
│ ├── GPU/
│ │ ├── Info.plist
│ │ ├── config.plist
│ │ ├── main.swift
│ │ ├── notifications.swift
│ │ ├── popup.swift
│ │ ├── portal.swift
│ │ ├── reader.swift
│ │ ├── settings.swift
│ │ └── widget.swift
│ ├── Net/
│ │ ├── Info.plist
│ │ ├── config.plist
│ │ ├── main.swift
│ │ ├── notifications.swift
│ │ ├── popup.swift
│ │ ├── portal.swift
│ │ ├── readers.swift
│ │ ├── settings.swift
│ │ └── widget.swift
│ ├── RAM/
│ │ ├── Info.plist
│ │ ├── config.plist
│ │ ├── main.swift
│ │ ├── notifications.swift
│ │ ├── popup.swift
│ │ ├── portal.swift
│ │ ├── readers.swift
│ │ ├── settings.swift
│ │ └── widget.swift
│ └── Sensors/
│ ├── Info.plist
│ ├── bridge.h
│ ├── config.plist
│ ├── main.swift
│ ├── notifications.swift
│ ├── popup.swift
│ ├── portal.swift
│ ├── reader.m
│ ├── readers.swift
│ ├── settings.swift
│ └── values.swift
├── README.md
├── SMC/
│ ├── Helper/
│ │ ├── Info.plist
│ │ ├── Launchd.plist
│ │ ├── main.swift
│ │ └── protocol.swift
│ ├── Makefile
│ ├── main.swift
│ └── smc.swift
├── Stats/
│ ├── AppDelegate.swift
│ ├── Supporting Files/
│ │ ├── Assets.xcassets/
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── ac_unit.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── apps.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── bug.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── devices/
│ │ │ │ ├── Contents.json
│ │ │ │ ├── imac.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── imacPro.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── macMini.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── macMini2020.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── macMini2024.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── macPro.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── macPro2019.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── macStudio.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── macbookAir.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── macbookAir4thGen.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── macbookNeo.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── macbookPro.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── macbookPro5thGen.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── donate.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── high-battery.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── low-battery.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── pause.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── power.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── record.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── resume.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── settings.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── stop.imageset/
│ │ │ │ └── Contents.json
│ │ │ └── support/
│ │ │ ├── Contents.json
│ │ │ ├── github.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── ko-fi.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── patreon.imageset/
│ │ │ │ └── Contents.json
│ │ │ └── paypal.imageset/
│ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ ├── Stats/
│ │ │ └── en.xcloc/
│ │ │ ├── Localized Contents/
│ │ │ │ └── en.xliff
│ │ │ ├── Source Contents/
│ │ │ │ ├── LaunchAtLogin/
│ │ │ │ │ └── en.lproj/
│ │ │ │ │ └── InfoPlist.strings
│ │ │ │ ├── ModuleKit/
│ │ │ │ │ └── Supporting Files/
│ │ │ │ │ └── en.lproj/
│ │ │ │ │ └── InfoPlist.strings
│ │ │ │ ├── Modules/
│ │ │ │ │ ├── Battery/
│ │ │ │ │ │ └── en.lproj/
│ │ │ │ │ │ └── InfoPlist.strings
│ │ │ │ │ ├── CPU/
│ │ │ │ │ │ └── en.lproj/
│ │ │ │ │ │ └── InfoPlist.strings
│ │ │ │ │ ├── Disk/
│ │ │ │ │ │ └── en.lproj/
│ │ │ │ │ │ └── InfoPlist.strings
│ │ │ │ │ ├── GPU/
│ │ │ │ │ │ └── en.lproj/
│ │ │ │ │ │ └── InfoPlist.strings
│ │ │ │ │ ├── Memory/
│ │ │ │ │ │ └── en.lproj/
│ │ │ │ │ │ └── InfoPlist.strings
│ │ │ │ │ ├── Net/
│ │ │ │ │ │ └── en.lproj/
│ │ │ │ │ │ └── InfoPlist.strings
│ │ │ │ │ └── Sensors/
│ │ │ │ │ └── en.lproj/
│ │ │ │ │ └── InfoPlist.strings
│ │ │ │ ├── Stats/
│ │ │ │ │ └── Supporting Files/
│ │ │ │ │ └── en.lproj/
│ │ │ │ │ ├── InfoPlist.strings
│ │ │ │ │ └── Localizable.strings
│ │ │ │ └── StatsKit/
│ │ │ │ └── en.lproj/
│ │ │ │ └── InfoPlist.strings
│ │ │ └── contents.json
│ │ ├── Stats.entitlements
│ │ ├── ar.lproj/
│ │ │ └── Localizable.strings
│ │ ├── bg.lproj/
│ │ │ └── Localizable.strings
│ │ ├── ca.lproj/
│ │ │ └── Localizable.strings
│ │ ├── cs.lproj/
│ │ │ └── Localizable.strings
│ │ ├── da.lproj/
│ │ │ └── Localizable.strings
│ │ ├── de.lproj/
│ │ │ └── Localizable.strings
│ │ ├── el.lproj/
│ │ │ └── Localizable.strings
│ │ ├── en-AU.lproj/
│ │ │ └── Localizable.strings
│ │ ├── en-GB.lproj/
│ │ │ └── Localizable.strings
│ │ ├── en.lproj/
│ │ │ └── Localizable.strings
│ │ ├── es.lproj/
│ │ │ └── Localizable.strings
│ │ ├── et.lproj/
│ │ │ └── Localizable.strings
│ │ ├── fa.lproj/
│ │ │ └── Localizable.strings
│ │ ├── fi.lproj/
│ │ │ └── Localizable.strings
│ │ ├── fr.lproj/
│ │ │ └── Localizable.strings
│ │ ├── he.lproj/
│ │ │ └── Localizable.strings
│ │ ├── hi.lproj/
│ │ │ └── Localizable.strings
│ │ ├── hr.lproj/
│ │ │ └── Localizable.strings
│ │ ├── hu.lproj/
│ │ │ └── Localizable.strings
│ │ ├── id.lproj/
│ │ │ └── Localizable.strings
│ │ ├── it.lproj/
│ │ │ └── Localizable.strings
│ │ ├── ja.lproj/
│ │ │ └── Localizable.strings
│ │ ├── ko.lproj/
│ │ │ └── Localizable.strings
│ │ ├── menus.psd
│ │ ├── nb.lproj/
│ │ │ └── Localizable.strings
│ │ ├── nl.lproj/
│ │ │ └── Localizable.strings
│ │ ├── pl.lproj/
│ │ │ └── Localizable.strings
│ │ ├── popups.psd
│ │ ├── pt-BR.lproj/
│ │ │ └── Localizable.strings
│ │ ├── pt-PT.lproj/
│ │ │ └── Localizable.strings
│ │ ├── ro.lproj/
│ │ │ └── Localizable.strings
│ │ ├── ru.lproj/
│ │ │ └── Localizable.strings
│ │ ├── sk.lproj/
│ │ │ └── Localizable.strings
│ │ ├── sl.lproj/
│ │ │ └── Localizable.strings
│ │ ├── sv.lproj/
│ │ │ └── Localizable.strings
│ │ ├── th.lproj/
│ │ │ └── Localizable.strings
│ │ ├── tr.lproj/
│ │ │ └── Localizable.strings
│ │ ├── uk.lproj/
│ │ │ └── Localizable.strings
│ │ ├── vi.lproj/
│ │ │ └── Localizable.strings
│ │ ├── zh-Hans.lproj/
│ │ │ └── Localizable.strings
│ │ └── zh-Hant.lproj/
│ │ └── Localizable.strings
│ ├── Views/
│ │ ├── AppSettings.swift
│ │ ├── CombinedView.swift
│ │ ├── Dashboard.swift
│ │ ├── Settings.swift
│ │ ├── Setup.swift
│ │ ├── Support.swift
│ │ └── Update.swift
│ └── helpers.swift
├── Stats.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata/
│ └── xcschemes/
│ ├── SMC.xcscheme
│ ├── Stats.xcscheme
│ └── WidgetsExtension.xcscheme
├── Tests/
│ ├── Info.plist
│ └── RAM.swift
├── Widgets/
│ ├── Supporting Files/
│ │ ├── Assets.xcassets/
│ │ │ ├── AccentColor.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ └── WidgetBackground.colorset/
│ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ └── Widgets.entitlements
│ ├── UnitedWidget.swift
│ └── widgets.swift
└── exportOptions.plist
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: [exelban]
patreon: exelban
ko_fi: exelban
custom: ["https://www.paypal.com/donate?hosted_button_id=3DS5JHDBATMTC"]
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Details:**
- Device: [e.g. Macbook Pro 2016]
- macOS: [e.g. 10.15.5]
- Application version: [e.g. 2.1.11]
================================================
FILE: .github/workflows/build.yaml
================================================
name: build
on:
push:
branches:
- master
pull_request:
branches:
- master
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- run: xcodebuild -scheme Stats -destination 'platform=macOS' -configuration Release archive CODE_SIGNING_ALLOWED=NO
================================================
FILE: .github/workflows/i18n.yaml
================================================
name: i18n check
on:
push:
paths:
- '.github/workflows/i18n.yaml'
- '**/*.strings'
pull_request:
paths:
- '.github/workflows/i18n.yaml'
- '**/*.strings'
jobs:
i18n:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v2
- run: python3 Kit/scripts/i18n.py
================================================
FILE: .github/workflows/linter.yaml
================================================
name: Linter
on:
push:
paths:
- '.github/workflows/linter.yaml'
- '.swiftlint.yml'
- '**/*.swift'
pull_request:
paths:
- '.github/workflows/linter.yaml'
- '.swiftlint.yml'
- '**/*.swift'
jobs:
SwiftLint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: norio-nomura/action-swiftlint@3.2.1
================================================
FILE: .gitignore
================================================
.DS_Store
Pods
Carthage
build
xcuserdata
Stats.dmg
Stats.app
create-dmg
dSYMs.zip
Stats.dmg.zip
SMC/smc
Cartfile.resolved
web
================================================
FILE: .swiftlint.yml
================================================
disabled_rules:
- force_cast # todo
- type_name # todo
- cyclomatic_complexity # todo
- trailing_whitespace
- opening_brace
- implicit_getter
- implicit_optional_initialization
- large_tuple
- function_body_length
opt_in_rules:
- control_statement
- empty_count
- trailing_newline
- colon
- comma
identifier_name:
min_length: 1
excluded:
- AppUpdateIntervals
- TemperatureUnits
- SpeedBase
- SensorsWidgetMode
- SpeedPictogram
- BatteryAdditionals
- ShortLong
- ReaderUpdateIntervals
- NumbersOfProcesses
- NetworkReaders
- SensorsList
- Alignments
- _devices
- _uuidAddress
- AppleSiliconSensorsList
- FanValues
- CombinedModulesSpacings
- BatteryInfo
- PublicIPAddressRefreshIntervals
- _values
- _writeTS
- LineChartHistory
- SpeedPictogramColor
- SensorsWidgetValue
- access_token
- refresh_token
- device_code
- user_code
- verification_uri_complete
- expires_in
line_length: 200
type_body_length:
- 700
- 1000
file_length:
- 1400
- 1800
================================================
FILE: Kit/Supporting Files/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Kit/Supporting Files/Assets.xcassets/calendar.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "baseline_calendar_month_black_24pt_1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "baseline_calendar_month_black_24pt_2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "baseline_calendar_month_black_24pt_3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: Kit/Supporting Files/Assets.xcassets/cancel.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "outline_close_white_12pt_1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "outline_close_white_12pt_2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "outline_close_white_12pt_3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: Kit/Supporting Files/Assets.xcassets/chart.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "baseline_insert_chart_outlined_white_24pt_1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "baseline_insert_chart_outlined_white_24pt_2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "baseline_insert_chart_outlined_white_24pt_3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: Kit/Supporting Files/Assets.xcassets/close.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "baseline_cancel_white_24pt_1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "baseline_cancel_white_24pt_2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "baseline_cancel_white_24pt_3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: Kit/Supporting Files/Assets.xcassets/refresh.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "outline_refresh_black_18pt_1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "outline_refresh_black_18pt_2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "outline_refresh_black_18pt_3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: Kit/Supporting Files/Assets.xcassets/settings.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "baseline_settings_black_24pt_1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "baseline_settings_black_24pt_2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "baseline_settings_black_24pt_3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: Kit/Supporting Files/Assets.xcassets/tune.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "outline_tune_black_18pt_1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "outline_tune_black_18pt_2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "outline_tune_black_18pt_3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: Kit/Supporting Files/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.</string>
</dict>
</plist>
================================================
FILE: Kit/Supporting Files/Kit.h
================================================
//
// Kit.h
// Kit
//
// Created by Serhiy Mytrovtsiy on 05/02/2024
// Using Swift 5.0
// Running on macOS 14.3
//
// Copyright © 2024 Serhiy Mytrovtsiy. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for Kit.
FOUNDATION_EXPORT double KitVersionNumber;
//! Project version string for Kit.
FOUNDATION_EXPORT const unsigned char KitVersionString[];
#import "lldb.h"
================================================
FILE: Kit/Widgets/BarChart.swift
================================================
//
// BarChart.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 26/04/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class BarChart: WidgetWrapper {
private var labelState: Bool = false
private var boxState: Bool = true
private var frameState: Bool = false
public var colorState: SColor = .systemAccent
private var colors: [SColor] = SColor.allCases
private var _value: [[ColorValue]] = [[]]
private var _pressureLevel: RAMPressure = .normal
private var _colorZones: colorZones = (0.6, 0.8)
private var boxSettingsView: NSSwitch? = nil
private var frameSettingsView: NSSwitch? = nil
public var NSLabelCharts: [NSAttributedString] = []
public init(title: String, config: NSDictionary?, preview: Bool = false) {
var widgetTitle: String = title
if config != nil {
var configuration = config!
if let titleFromConfig = config!["Title"] as? String {
widgetTitle = titleFromConfig
}
if preview {
if let previewConfig = config!["Preview"] as? NSDictionary {
configuration = previewConfig
if let value = configuration["Value"] as? String {
self._value = value.split(separator: ",").map{ ([ColorValue(Double($0) ?? 0)]) }
}
}
}
if let label = configuration["Label"] as? Bool {
self.labelState = label
}
if let box = configuration["Box"] as? Bool {
self.boxState = box
}
if let unsupportedColors = configuration["Unsupported colors"] as? [String] {
self.colors = self.colors.filter{ !unsupportedColors.contains($0.key) }
}
if let color = configuration["Color"] as? String {
if let defaultColor = self.colors.first(where: { "\($0.self)" == color }) {
self.colorState = defaultColor
}
}
}
super.init(.barChart, title: widgetTitle, frame: CGRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: Constants.Widget.width + (2*Constants.Widget.margin.x),
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
if !preview {
self.boxState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_box", defaultValue: self.boxState)
self.frameState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_frame", defaultValue: self.frameState)
self.labelState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState)
self.colorState = SColor.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState.key))
}
if preview {
if self._value.isEmpty {
self._value = [[ColorValue(0.72)], [ColorValue(0.38)]]
}
self.setFrameSize(NSSize(width: 36, height: self.frame.size.height))
self.invalidateIntrinsicContentSize()
self.display()
}
let style = NSMutableParagraphStyle()
style.alignment = .center
let stringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .regular),
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
for char in String(self.title.prefix(3)).uppercased().reversed() {
let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes)
self.NSLabelCharts.append(str)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
var value: [[ColorValue]] = []
var pressureLevel: RAMPressure = .normal
var colorZones: colorZones = (0.6, 0.8)
self.queue.sync {
value = self._value
pressureLevel = self._pressureLevel
colorZones = self._colorZones
}
guard !value.isEmpty else {
self.setWidth(0)
return
}
var width: CGFloat = Constants.Widget.margin.x*2
var x: CGFloat = 0
let lineWidth = 1 / (NSScreen.main?.backingScaleFactor ?? 1)
let offset = lineWidth / 2
switch value.count {
case 0, 1:
width += 10 + (offset*2)
case 2:
width += 22
case 3...4: // 3,4
width += 30
case 5...8: // 5,6,7,8
width += 40
case 9...12: // 9..12
width += 50
case 13...16: // 13..16
width += 76
case 17...32: // 17..32
width += 84
default: // > 32
width += 118
}
if self.labelState {
let letterHeight = self.frame.height / 3
let letterWidth: CGFloat = 6.0
var yMargin: CGFloat = 0
for char in self.NSLabelCharts {
let rect = CGRect(x: x, y: yMargin, width: letterWidth, height: letterHeight)
char.draw(with: rect)
yMargin += letterHeight
}
width += letterWidth + Constants.Widget.spacing
x = letterWidth + Constants.Widget.spacing
}
let box = NSBezierPath(roundedRect: NSRect(
x: x + offset,
y: offset,
width: width - x - (offset*2) - (Constants.Widget.margin.x*2),
height: self.frame.size.height - (offset*2)
), xRadius: 2, yRadius: 2)
if self.boxState {
(isDarkMode ? NSColor.white : NSColor.black).set()
box.stroke()
box.fill()
}
let widthForBarChart = box.bounds.width
let partitionMargin: CGFloat = 0.5
let partitionsMargin: CGFloat = (CGFloat(value.count - 1)) * partitionMargin / CGFloat(value.count - 1)
let partitionWidth: CGFloat = (widthForBarChart / CGFloat(value.count)) - CGFloat(partitionsMargin.isNaN ? 0 : partitionsMargin)
let maxPartitionHeight: CGFloat = box.bounds.height
x += offset
for i in 0..<value.count {
var y = offset
for a in 0..<value[i].count {
let partitionValue = value[i][a]
let partitionHeight = maxPartitionHeight * CGFloat(partitionValue.value)
let partition = NSBezierPath(rect: NSRect(x: x, y: y, width: partitionWidth, height: partitionHeight))
if partitionValue.color == nil {
switch self.colorState {
case .systemAccent: NSColor.controlAccentColor.set()
case .utilization: partitionValue.value.usageColor(zones: colorZones, reversed: self.title == "Battery").set()
case .pressure: pressureLevel.pressureColor().set()
case .monochrome:
if self.boxState {
(isDarkMode ? NSColor.black : NSColor.white).set()
} else {
(isDarkMode ? NSColor.white : NSColor.black).set()
}
default: (self.colorState.additional as? NSColor ?? .controlAccentColor).set()
}
} else {
partitionValue.color?.set()
}
partition.fill()
partition.close()
y += partitionHeight
}
x += partitionWidth + partitionMargin
}
if self.boxState || self.frameState {
(isDarkMode ? NSColor.white : NSColor.black).set()
box.lineWidth = lineWidth
box.stroke()
}
self.setWidth(width)
}
public func setValue(_ newValue: [[ColorValue]]) {
DispatchQueue.main.async(execute: {
let tolerance: CGFloat = 0.01
let isDifferent = self._value.count != newValue.count || zip(self._value, newValue).contains { row1, row2 in
row1.count != row2.count || zip(row1, row2).contains { val1, val2 in
abs(val1.value - val2.value) > tolerance || val1.color != val2.color
}
}
guard isDifferent else { return }
self._value = newValue
self.redraw()
})
}
public func setPressure(_ newPressureLevel: RAMPressure) {
DispatchQueue.main.async(execute: {
guard self._pressureLevel != newPressureLevel else { return }
self._pressureLevel = newPressureLevel
self.redraw()
})
}
public func setColorZones(_ newColorZones: colorZones) {
DispatchQueue.main.async(execute: {
guard self._colorZones != newColorZones else { return }
self._colorZones = newColorZones
self.redraw()
})
}
// MARK: - Settings
public override func settings() -> NSView {
let view = SettingsContainerView()
let box = switchView(
action: #selector(self.toggleBox),
state: self.boxState
)
self.boxSettingsView = box
let frame = switchView(
action: #selector(self.toggleFrame),
state: self.frameState
)
self.frameSettingsView = frame
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Label"), component: switchView(
action: #selector(self.toggleLabel),
state: self.labelState
)),
PreferencesRow(localizedString("Color"), component: selectView(
action: #selector(self.toggleColor),
items: self.colors,
selected: self.colorState.key
)),
PreferencesRow(localizedString("Box"), component: box),
PreferencesRow(localizedString("Frame"), component: frame)
]))
return view
}
@objc private func toggleLabel(_ sender: NSControl) {
self.labelState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState)
self.redraw()
}
@objc private func toggleBox(_ sender: NSControl) {
self.boxState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_box", value: self.boxState)
if self.frameState {
self.frameSettingsView?.state = .off
self.frameState = false
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_frame", value: self.frameState)
}
self.redraw()
}
@objc private func toggleFrame(_ sender: NSControl) {
self.frameState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_frame", value: self.frameState)
if self.boxState {
self.boxSettingsView?.state = .off
self.boxState = false
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_box", value: self.boxState)
}
self.redraw()
}
@objc private func toggleColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newColor = self.colors.first(where: { $0.key == key }) {
self.colorState = newColor
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_color", value: key)
self.redraw()
}
}
================================================
FILE: Kit/Widgets/Battery.swift
================================================
//
// Battery.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 06/06/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class BatteryWidget: WidgetWrapper {
private var additional: String = "none"
private var timeFormat: String = "short"
private var iconState: Bool = true
private var colorState: Bool = false
private var hideAdditionalWhenFull: Bool = true
private var xlSizeState: Bool = false
private var chargerIconInside: Bool = true
private var _percentage: Double? = nil
private var _time: Int = 0
private var _charging: Bool = false
private var _ACStatus: Bool = false
private var _optimizedCharging: Bool = false
public init(title: String, preview: Bool = false) {
let widgetTitle: String = title
super.init(.battery, title: widgetTitle, frame: CGRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: 30 + (2*Constants.Widget.margin.x),
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
if !preview {
self.additional = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_additional", defaultValue: self.additional)
self.timeFormat = Store.shared.string(key: "\(self.title)_timeFormat", defaultValue: self.timeFormat)
self.iconState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_icon", defaultValue: self.iconState)
self.colorState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState)
self.hideAdditionalWhenFull = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_hideAdditionalWhenFull", defaultValue: self.hideAdditionalWhenFull)
self.xlSizeState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_xlSize", defaultValue: self.xlSizeState)
self.chargerIconInside = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_chargerInside", defaultValue: self.chargerIconInside)
}
if preview {
self._percentage = 0.72
self.additional = "none"
self.iconState = true
self.colorState = false
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let ctx = NSGraphicsContext.current?.cgContext else { return }
var percentage: Double? = nil
var time: Int = 0
var charging: Bool = false
var ACStatus: Bool = false
var optimizedCharging: Bool = false
self.queue.sync {
percentage = self._percentage
time = self._time
charging = self._charging
ACStatus = self._ACStatus
optimizedCharging = self._optimizedCharging
}
var width: CGFloat = 0
var x: CGFloat = 0
let isShortTimeFormat: Bool = self.timeFormat == "short"
if !self.hideAdditionalWhenFull || (self.hideAdditionalWhenFull && percentage != 1 && !optimizedCharging) {
switch self.additional {
case "percentage":
var value = "n/a"
if let percentage {
value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
}
let rowWidth = self.drawOneRow(value: value, x: x).rounded(.up)
width += rowWidth
x += rowWidth + Constants.Widget.spacing
case "time":
let rowWidth = self.drawOneRow(
value: Double(time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
x: x
).rounded(.up)
width += rowWidth
x += rowWidth + Constants.Widget.spacing
case "percentageAndTime":
var value = "n/a"
if let percentage {
value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
}
let rowWidth = self.drawTwoRows(
first: value,
second: Double(time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
x: x
).rounded(.up)
width += rowWidth
x += rowWidth + Constants.Widget.spacing
case "timeAndPercentage":
var value = "n/a"
if let percentage {
value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
}
let rowWidth = self.drawTwoRows(
first: Double(time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
second: value,
x: x
).rounded(.up)
width += rowWidth
x += rowWidth + Constants.Widget.spacing
default: break
}
}
let batterySize: CGSize = self.xlSizeState ? CGSize(width: 26, height: 14) : CGSize(width: 22, height: 12)
if ACStatus && !self.chargerIconInside {
if x != 0 {
width += Constants.Widget.spacing
x += Constants.Widget.spacing
}
self.drawACIcon(
ctx: ctx,
center: CGPoint(x: x+3, y: self.frame.size.height/2),
height: 12,
charging: charging
)
width += 6
x += 6 + Constants.Widget.spacing
}
let borderWidth: CGFloat = 1
let batteryRadius: CGFloat = self.xlSizeState ? 3 : 2
let offset: CGFloat = 0.5 // contant!
width += batterySize.width + borderWidth*2 // add battery width
if x != 0 {
width += Constants.Widget.spacing
x += Constants.Widget.spacing
}
let batteryFrame = NSBezierPath(roundedRect: NSRect(
x: x + borderWidth + offset,
y: ((self.frame.size.height - batterySize.height)/2) + offset,
width: batterySize.width - borderWidth,
height: batterySize.height - borderWidth
), xRadius: batteryRadius, yRadius: batteryRadius)
NSColor.textColor.withAlphaComponent(0.5).set()
batteryFrame.lineWidth = borderWidth
batteryFrame.stroke()
let bPX: CGFloat = batteryFrame.bounds.origin.x + batteryFrame.bounds.width + 1
let bPY: CGFloat = batteryFrame.bounds.origin.y + batteryFrame.bounds.height/2 - 2
let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX - 1, y: bPY, width: 3, height: 4), xRadius: 2, yRadius: 2)
batteryPoint.fill()
let batteryPointSeparator = NSBezierPath()
batteryPointSeparator.move(to: CGPoint(x: bPX, y: batteryFrame.bounds.origin.y))
batteryPointSeparator.line(to: CGPoint(x: bPX, y: batteryFrame.bounds.origin.y + batteryFrame.bounds.height))
ctx.saveGState()
ctx.setBlendMode(.destinationOut)
NSColor.white.set()
batteryPointSeparator.lineWidth = borderWidth
batteryPointSeparator.stroke()
ctx.restoreGState()
width += 2 // add battery point width
if let percentage {
let maxWidth = batterySize.width - offset*2 - borderWidth*2 - 1
let innerWidth: CGFloat = max(1, maxWidth * CGFloat(percentage))
let innerOffset: CGFloat = -offset + borderWidth + 1
let innerRadius: CGFloat = self.xlSizeState ? 2 : 1
var colorState = self.colorState
let color = percentage.batteryColor(color: colorState)
let innerPercentage = self.additional == "innerPercentage" && (!ACStatus || !self.chargerIconInside)
if innerPercentage {
colorState = false
let innerUnderground = NSBezierPath(roundedRect: NSRect(
x: batteryFrame.bounds.origin.x + innerOffset,
y: batteryFrame.bounds.origin.y + innerOffset,
width: maxWidth,
height: batterySize.height - offset*2 - borderWidth*2 - 1
), xRadius: innerRadius, yRadius: innerRadius)
(self.colorState ? color : NSColor.textColor).withAlphaComponent(0.5).set()
innerUnderground.fill()
}
let inner = NSBezierPath(roundedRect: NSRect(
x: batteryFrame.bounds.origin.x + innerOffset,
y: batteryFrame.bounds.origin.y + innerOffset,
width: innerWidth,
height: batterySize.height - offset*2 - borderWidth*2 - 1
), xRadius: innerRadius, yRadius: innerRadius)
color.set()
inner.fill()
if innerPercentage {
let fontSize: CGFloat = self.xlSizeState ? 9 : 8
let style = NSMutableParagraphStyle()
style.alignment = .center
let attributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: fontSize, weight: .bold),
NSAttributedString.Key.foregroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: style
]
let value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))"
let rect = CGRect(x: inner.bounds.origin.x, y: (Constants.Widget.height-(fontSize+2))/2, width: maxWidth, height: fontSize)
let str = NSAttributedString.init(string: value, attributes: attributes)
ctx.saveGState()
ctx.setBlendMode(.destinationIn)
str.draw(with: rect)
ctx.restoreGState()
}
} else {
let attributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 11, weight: .regular),
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
let batteryCenter: CGPoint = CGPoint(
x: batteryFrame.bounds.origin.x + (batteryFrame.bounds.width/2),
y: batteryFrame.bounds.origin.y + (batteryFrame.bounds.height/2)
)
let rect = CGRect(x: batteryCenter.x-3, y: batteryCenter.y-4, width: 8, height: 12)
NSAttributedString.init(string: "?", attributes: attributes).draw(with: rect)
}
if ACStatus && self.chargerIconInside {
let batteryCenter: CGPoint = CGPoint(
x: batteryFrame.bounds.origin.x + (batteryFrame.bounds.width/2),
y: batteryFrame.bounds.origin.y + (batteryFrame.bounds.height/2)
)
self.drawACIcon(
ctx: ctx,
center: batteryCenter,
height: 12,
charging: charging
)
}
self.setWidth(width)
}
private func drawOneRow(value: String, x: CGFloat) -> CGFloat {
let attributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 12, weight: .regular),
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
let rowWidth = value.widthOfString(usingFont: .systemFont(ofSize: 12, weight: .regular))
let rect = CGRect(x: x, y: (Constants.Widget.height-13)/2, width: rowWidth, height: 12)
let str = NSAttributedString.init(string: value, attributes: attributes)
str.draw(with: rect)
return rowWidth
}
private func drawTwoRows(first: String, second: String, x: CGFloat) -> CGFloat {
let style = NSMutableParagraphStyle()
style.alignment = .center
let attributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
let rowHeight: CGFloat = self.frame.height / 2
let rowWidth = max(
first.widthOfString(usingFont: .systemFont(ofSize: 9, weight: .regular)),
second.widthOfString(usingFont: .systemFont(ofSize: 9, weight: .regular))
)
var str = NSAttributedString.init(string: first, attributes: attributes)
str.draw(with: CGRect(x: x, y: rowHeight+1, width: rowWidth, height: rowHeight))
str = NSAttributedString.init(string: second, attributes: attributes)
str.draw(with: CGRect(x: x, y: 1, width: rowWidth, height: rowHeight))
return rowWidth
}
private func drawACIcon(ctx: CGContext, center batteryCenter: CGPoint, height: CGFloat, charging: Bool) {
var points: [CGPoint] = []
if charging {
let iconSize: CGSize = CGSize(width: 9, height: height + 6)
let min = CGPoint(
x: batteryCenter.x - (iconSize.width/2),
y: batteryCenter.y - (iconSize.height/2)
)
let max = CGPoint(
x: batteryCenter.x + (iconSize.width/2),
y: batteryCenter.y + (iconSize.height/2)
)
points = [
CGPoint(x: batteryCenter.x-3, y: min.y), // bottom
CGPoint(x: max.x, y: batteryCenter.y+1.5),
CGPoint(x: batteryCenter.x+1, y: batteryCenter.y+1.5),
CGPoint(x: batteryCenter.x+3, y: max.y), // top
CGPoint(x: min.x, y: batteryCenter.y-1.5),
CGPoint(x: batteryCenter.x-1, y: batteryCenter.y-1.5)
]
} else {
let iconSize: CGSize = CGSize(width: 9, height: height + 2)
let minY = batteryCenter.y - (iconSize.height/2)
let maxY = batteryCenter.y + (iconSize.height/2)
points = [
CGPoint(x: batteryCenter.x-1.5, y: minY+0.5),
CGPoint(x: batteryCenter.x+1.5, y: minY+0.5),
CGPoint(x: batteryCenter.x+1.5, y: batteryCenter.y - 2.5),
CGPoint(x: batteryCenter.x+4, y: batteryCenter.y + 0.5),
CGPoint(x: batteryCenter.x+4, y: batteryCenter.y + 4.25),
// right
CGPoint(x: batteryCenter.x+2.75, y: batteryCenter.y + 4.25),
CGPoint(x: batteryCenter.x+2.75, y: maxY-0.25),
CGPoint(x: batteryCenter.x+0.25, y: maxY-0.25),
CGPoint(x: batteryCenter.x+0.25, y: batteryCenter.y + 4.25),
// left
CGPoint(x: batteryCenter.x-0.25, y: batteryCenter.y + 4.25),
CGPoint(x: batteryCenter.x-0.25, y: maxY-0.25),
CGPoint(x: batteryCenter.x-2.75, y: maxY-0.25),
CGPoint(x: batteryCenter.x-2.75, y: batteryCenter.y + 4.25),
CGPoint(x: batteryCenter.x-4, y: batteryCenter.y + 4.25),
CGPoint(x: batteryCenter.x-4, y: batteryCenter.y + 0.5),
CGPoint(x: batteryCenter.x-1.5, y: batteryCenter.y - 2.5),
CGPoint(x: batteryCenter.x-1.5, y: minY+0.5)
]
}
let linePath = NSBezierPath()
linePath.move(to: CGPoint(x: points[0].x, y: points[0].y))
for i in 1..<points.count {
linePath.line(to: CGPoint(x: points[i].x, y: points[i].y))
}
linePath.line(to: CGPoint(x: points[0].x, y: points[0].y))
NSColor.textColor.set()
linePath.fill()
ctx.saveGState()
ctx.setBlendMode(.destinationOut)
NSColor.textColor.set()
linePath.lineWidth = 1
linePath.stroke()
ctx.restoreGState()
}
public func setValue(percentage: Double? = nil, ACStatus: Bool? = nil, isCharging: Bool? = nil, optimizedCharging: Bool? = nil, time: Int? = nil) {
var updated: Bool = false
let timeFormat: String = Store.shared.string(key: "\(self.title)_timeFormat", defaultValue: self.timeFormat)
if self._percentage != percentage {
self._percentage = percentage
updated = true
}
if let status = ACStatus, self._ACStatus != status {
self._ACStatus = status
updated = true
}
if let charging = isCharging, self._charging != charging {
self._charging = charging
updated = true
}
if let time = time, self._time != time {
self._time = time
updated = true
}
if self.timeFormat != timeFormat {
self.timeFormat = timeFormat
updated = true
}
if let state = optimizedCharging, self._optimizedCharging != state {
self._optimizedCharging = state
updated = true
}
if updated {
self.needsDisplay = true
DispatchQueue.main.async(execute: {
self.display()
})
}
}
// MARK: - Settings
public override func settings() -> NSView {
let view = SettingsContainerView()
var additionalOptions = BatteryAdditionals
if self.title == "Bluetooth" {
additionalOptions = additionalOptions.filter({ $0.key == "none" || $0.key == "percentage" })
}
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Additional information"), component: selectView(
action: #selector(self.toggleAdditional),
items: additionalOptions,
selected: self.additional
)),
PreferencesRow(localizedString("Hide additional information when full"), component: switchView(
action: #selector(self.toggleHideAdditionalWhenFull),
state: self.hideAdditionalWhenFull
)),
PreferencesRow(localizedString("Colorize"), component: switchView(
action: #selector(self.toggleColor),
state: self.colorState
)),
PreferencesRow(localizedString("XL size"), component: switchView(
action: #selector(self.toggleXLSize),
state: self.xlSizeState
)),
PreferencesRow(localizedString("Charger state inside the battery"), component: switchView(
action: #selector(self.toggleChargerIconInside),
state: self.chargerIconInside
))
]))
return view
}
@objc private func toggleAdditional(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.additional = key
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_additional", value: key)
self.display()
}
@objc private func toggleHideAdditionalWhenFull(_ sender: NSControl) {
self.hideAdditionalWhenFull = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_hideAdditionalWhenFull", value: self.hideAdditionalWhenFull)
self.display()
}
@objc private func toggleColor(_ sender: NSControl) {
self.colorState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState)
self.display()
}
@objc private func toggleXLSize(_ sender: NSControl) {
self.xlSizeState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_xlSize", value: self.xlSizeState)
self.display()
}
@objc private func toggleChargerIconInside(_ sender: NSControl) {
self.chargerIconInside = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_chargerInside", value: self.chargerIconInside)
self.display()
}
}
public class BatteryDetailsWidget: WidgetWrapper {
private var mode: String = "percentage"
private var timeFormat: String = "short"
private var percentage: Double? = nil
private var time: Int = 0
public init(title: String, preview: Bool = false) {
super.init(.batteryDetails, title: title, frame: CGRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: 20 + (2*Constants.Widget.margin.x),
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
if preview {
self.percentage = 0.72
self.time = 415
self.mode = "percentageAndTime"
} else {
self.mode = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_mode", defaultValue: self.mode)
self.timeFormat = Store.shared.string(key: "\(self.title)_timeFormat", defaultValue: self.timeFormat)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
var width: CGFloat = Constants.Widget.margin.x*2
let x: CGFloat = Constants.Widget.margin.x
let isShortTimeFormat: Bool = self.timeFormat == "short"
switch self.mode {
case "percentage":
var value = "n/a"
if let percentage = self.percentage {
value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
}
width = self.drawOneRow(value: value, x: x).rounded(.up)
case "time":
width = self.drawOneRow(
value: Double(self.time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
x: x
).rounded(.up)
case "percentageAndTime":
var value = "n/a"
if let percentage = self.percentage {
value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
}
if self.time > 0 {
width = self.drawTwoRows(
first: value,
second: Double(self.time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
x: x
).rounded(.up)
} else {
width = self.drawOneRow(value: value, x: x).rounded(.up)
}
case "timeAndPercentage":
var value = "n/a"
if let percentage = self.percentage {
value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
}
if self.time > 0 {
width = self.drawTwoRows(
first: Double(self.time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
second: value,
x: x
).rounded(.up)
} else {
width = self.drawOneRow(value: value, x: x).rounded(.up)
}
default: break
}
self.setWidth(width)
}
private func drawOneRow(value: String, x: CGFloat) -> CGFloat {
let attributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 12, weight: .regular),
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
let rowWidth = value.widthOfString(usingFont: .systemFont(ofSize: 12, weight: .regular))
let rect = CGRect(x: x, y: (Constants.Widget.height-12)/2, width: rowWidth, height: 12)
let str = NSAttributedString.init(string: value, attributes: attributes)
str.draw(with: rect)
return rowWidth
}
private func drawTwoRows(first: String, second: String, x: CGFloat) -> CGFloat {
let style = NSMutableParagraphStyle()
style.alignment = .center
let attributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
let rowHeight: CGFloat = self.frame.height / 2
let rowWidth = max(
first.widthOfString(usingFont: .systemFont(ofSize: 9, weight: .regular)),
second.widthOfString(usingFont: .systemFont(ofSize: 9, weight: .regular))
)
var str = NSAttributedString.init(string: first, attributes: attributes)
str.draw(with: CGRect(x: x, y: rowHeight+1, width: rowWidth, height: rowHeight))
str = NSAttributedString.init(string: second, attributes: attributes)
str.draw(with: CGRect(x: x, y: 1, width: rowWidth, height: rowHeight))
return rowWidth
}
public func setValue(percentage: Double? = nil, time: Int? = nil) {
var updated: Bool = false
let timeFormat: String = Store.shared.string(key: "\(self.title)_timeFormat", defaultValue: self.timeFormat)
if self.percentage != percentage {
self.percentage = percentage
updated = true
}
if let time = time, self.time != time {
self.time = time
updated = true
}
if self.timeFormat != timeFormat {
self.timeFormat = timeFormat
updated = true
}
if updated {
self.needsDisplay = true
DispatchQueue.main.async(execute: {
self.display()
})
}
}
// MARK: - Settings
public override func settings() -> NSView {
let view = SettingsContainerView()
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Details"), component: selectView(
action: #selector(self.toggleMode),
items: BatteryInfo,
selected: self.mode
))
]))
return view
}
@objc private func toggleMode(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.mode = key
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_mode", value: key)
self.display()
}
}
================================================
FILE: Kit/Widgets/Label.swift
================================================
//
// Label.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 30/03/2021.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class Label: WidgetWrapper {
private var label: String
internal init(title: String, config: NSDictionary) {
if let title = config["Title"] as? String {
self.label = title
} else {
self.label = title
}
super.init(.label, title: title, frame: CGRect(
x: 0,
y: Constants.Widget.margin.y,
width: 6 + (2*Constants.Widget.margin.x),
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let size: CGSize = CGSize(width: 6, height: self.frame.height / 3)
var margin: CGPoint = CGPoint(x: Constants.Widget.margin.x, y: 0)
let style = NSMutableParagraphStyle()
style.alignment = .center
let stringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .regular),
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
for char in String(self.label.prefix(3)).uppercased().reversed() {
let rect = CGRect(x: margin.x, y: margin.y, width: size.width, height: size.height)
let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes)
str.draw(with: rect)
margin.y += size.height
}
}
}
================================================
FILE: Kit/Widgets/LineChart.swift
================================================
//
// Chart.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 18/04/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class LineChart: WidgetWrapper {
private var labelState: Bool = false
private var boxState: Bool = true
private var frameState: Bool = false
private var valueState: Bool = false
private var valueColorState: Bool = false
private var colorState: SColor = .systemAccent
private var historyCount: Int = 60
private var scaleState: Scale = .none
private var chart: LineChartView = LineChartView(frame: NSRect(
x: 0,
y: 0,
width: 32,
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
), num: 60)
private var colors: [SColor] = SColor.allCases.filter({ $0 != SColor.cluster })
private var _value: Double = 0
private var _pressureLevel: RAMPressure = .normal
private var historyNumbers: [KeyValue_p] = [
KeyValue_t(key: "30", value: "30"),
KeyValue_t(key: "60", value: "60"),
KeyValue_t(key: "90", value: "90"),
KeyValue_t(key: "120", value: "120")
]
private var width: CGFloat {
get {
switch self.historyCount {
case 30:
return 24
case 60:
return 32
case 90:
return 42
case 120:
return 52
default:
return 32
}
}
}
private var boxSettingsView: NSSwitch? = nil
private var frameSettingsView: NSSwitch? = nil
public var NSLabelCharts: [NSAttributedString] = []
public init(title: String, config: NSDictionary?, preview: Bool = false) {
var widgetTitle: String = title
if config != nil {
if let titleFromConfig = config!["Title"] as? String {
widgetTitle = titleFromConfig
}
if let label = config!["Label"] as? Bool {
self.labelState = label
}
if let box = config!["Box"] as? Bool {
self.boxState = box
}
if let value = config!["Value"] as? Bool {
self.valueState = value
}
if let unsupportedColors = config!["Unsupported colors"] as? [String] {
self.colors = self.colors.filter{ !unsupportedColors.contains($0.key) }
}
if let color = config!["Color"] as? String {
if let defaultColor = colors.first(where: { "\($0.self)" == color }) {
self.colorState = defaultColor
}
}
}
super.init(.lineChart, title: widgetTitle, frame: CGRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: 32 + (Constants.Widget.margin.x*2),
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
if !preview {
self.boxState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_box", defaultValue: self.boxState)
self.frameState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_frame", defaultValue: self.frameState)
self.valueState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_value", defaultValue: self.valueState)
self.labelState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState)
self.valueColorState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_valueColor", defaultValue: self.valueColorState)
self.colorState = SColor.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState.key))
self.historyCount = Store.shared.int(key: "\(self.title)_\(self.type.rawValue)_historyCount", defaultValue: self.historyCount)
self.scaleState = Scale.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_scale", defaultValue: self.scaleState.key))
self.chart.setScale(self.scaleState)
self.chart.reinit(self.historyCount)
}
if self.labelState {
self.setFrameSize(NSSize(width: Constants.Widget.width + 6 + (Constants.Widget.margin.x*2), height: self.frame.size.height))
}
if preview {
var list: [DoubleValue] = []
for _ in 0..<16 {
list.append(DoubleValue(Double.random(in: 0..<1)))
}
self.chart.points = list
self._value = 0.38
}
let style = NSMutableParagraphStyle()
style.alignment = .center
let stringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .regular),
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
for char in String(self.title.prefix(3)).uppercased().reversed() {
let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes)
self.NSLabelCharts.append(str)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else { return }
var value: Double = 0
var pressureLevel: RAMPressure = .normal
self.queue.sync {
value = self._value
pressureLevel = self._pressureLevel
}
var width = self.width + (Constants.Widget.margin.x*2)
var x: CGFloat = 0
let lineWidth = 1 / (NSScreen.main?.backingScaleFactor ?? 1)
let offset = lineWidth / 2
var boxSize: CGSize = CGSize(width: self.width - (Constants.Widget.margin.x*2), height: self.frame.size.height)
var color: NSColor = .controlAccentColor
switch self.colorState {
case .systemAccent: color = .controlAccentColor
case .utilization: color = value.usageColor()
case .pressure: color = pressureLevel.pressureColor()
case .monochrome:
if self.boxState {
color = (isDarkMode ? NSColor.black : NSColor.white)
} else {
color = (isDarkMode ? NSColor.white : NSColor.black)
}
default: color = self.colorState.additional as? NSColor ?? .controlAccentColor
}
if self.labelState {
let letterHeight = self.frame.height / 3
let letterWidth: CGFloat = 6.0
var yMargin: CGFloat = 0
for char in self.NSLabelCharts {
let rect = CGRect(x: x, y: yMargin, width: letterWidth, height: letterHeight)
char.draw(with: rect)
yMargin += letterHeight
}
width += letterWidth + Constants.Widget.spacing
x = letterWidth + Constants.Widget.spacing
}
if self.valueState {
let style = NSMutableParagraphStyle()
style.alignment = .right
var valueColor = isDarkMode ? NSColor.white : NSColor.black
if self.valueColorState {
valueColor = color
}
let stringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 8, weight: .regular),
NSAttributedString.Key.foregroundColor: valueColor,
NSAttributedString.Key.paragraphStyle: style
]
let rect = CGRect(x: x+2, y: boxSize.height-7, width: boxSize.width - 2, height: 7)
let str = NSAttributedString.init(string: "\(Int((value.rounded(toPlaces: 2)) * 100))%", attributes: stringAttributes)
str.draw(with: rect)
boxSize.height = offset == 0.5 ? 10 : 9
}
let box = NSBezierPath(roundedRect: NSRect(
x: x+offset,
y: offset,
width: self.width - offset*2,
height: boxSize.height - (offset*2)
), xRadius: 2, yRadius: 2)
if self.boxState {
(isDarkMode ? NSColor.white : NSColor.black).set()
box.stroke()
box.fill()
self.chart.transparent = false
} else if self.frameState {
self.chart.transparent = true
} else {
self.chart.transparent = true
}
context.saveGState()
let chartFrame = NSRect(
x: x+offset+lineWidth,
y: offset,
width: box.bounds.width - (offset*2+lineWidth),
height: box.bounds.height - offset
)
self.chart.color = color
self.chart.setFrameSize(NSSize(width: chartFrame.width, height: chartFrame.height))
self.chart.draw(chartFrame)
context.restoreGState()
if self.boxState || self.frameState {
(isDarkMode ? NSColor.white : NSColor.black).set()
box.lineWidth = lineWidth
box.stroke()
}
self.setWidth(width)
}
public func setValue(_ newValue: Double) {
DispatchQueue.main.async(execute: {
self._value = newValue
self.chart.addValue(newValue)
self.display()
})
}
public func setPressure(_ newPressureLevel: RAMPressure) {
DispatchQueue.main.async(execute: {
guard self._pressureLevel != newPressureLevel else { return }
self._pressureLevel = newPressureLevel
self.display()
})
}
// MARK: - Settings
public override func settings() -> NSView {
let view = SettingsContainerView()
let box = switchView(
action: #selector(self.toggleBox),
state: self.boxState
)
self.boxSettingsView = box
let frame = switchView(
action: #selector(self.toggleFrame),
state: self.frameState
)
self.frameSettingsView = frame
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Label"), component: switchView(
action: #selector(self.toggleLabel),
state: self.labelState
)),
PreferencesRow(localizedString("Value"), component: switchView(
action: #selector(self.toggleValue),
state: self.valueState
)),
PreferencesRow(localizedString("Box"), component: box),
PreferencesRow(localizedString("Frame"), component: frame),
PreferencesRow(localizedString("Color"), component: selectView(
action: #selector(self.toggleColor),
items: self.colors,
selected: self.colorState.key
)),
PreferencesRow(localizedString("Colorize value"), component: switchView(
action: #selector(self.toggleValueColor),
state: self.valueColorState
)),
PreferencesRow(localizedString("Number of reads in the chart"), component: selectView(
action: #selector(self.toggleHistoryCount),
items: self.historyNumbers,
selected: "\(self.historyCount)"
)),
PreferencesRow(localizedString("Scaling"), component: selectView(
action: #selector(self.toggleScale),
items: Scale.allCases.filter({ $0 != .fixed }),
selected: self.scaleState.key
))
]))
return view
}
@objc private func toggleLabel(_ sender: NSControl) {
self.labelState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState)
self.display()
}
@objc private func toggleBox(_ sender: NSControl) {
self.boxState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_box", value: self.boxState)
if self.frameState {
self.frameSettingsView?.state = .off
self.frameState = false
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_frame", value: self.frameState)
}
self.display()
}
@objc private func toggleFrame(_ sender: NSControl) {
self.frameState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_frame", value: self.frameState)
if self.boxState {
self.boxSettingsView?.state = .off
self.boxState = false
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_box", value: self.boxState)
}
self.display()
}
@objc private func toggleValue(_ sender: NSControl) {
self.valueState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_value", value: self.valueState)
self.display()
}
@objc private func toggleColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newColor = SColor.allCases.first(where: { $0.key == key }) {
self.colorState = newColor
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_color", value: key)
self.display()
}
@objc private func toggleValueColor(_ sender: NSControl) {
self.valueColorState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_valueColor", value: self.valueColorState)
self.display()
}
@objc private func toggleHistoryCount(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String, let value = Int(key) else { return }
self.historyCount = value
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_historyCount", value: value)
self.chart.reinit(value)
self.display()
}
@objc private func toggleScale(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String,
let value = Scale.allCases.first(where: { $0.key == key }) else { return }
self.scaleState = value
self.chart.setScale(value)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_scale", value: key)
self.display()
}
}
================================================
FILE: Kit/Widgets/Memory.swift
================================================
//
// Memory.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 30/06/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class MemoryWidget: WidgetWrapper {
private var orderReversedState: Bool = false
private var value: (String, String) = ("0", "0")
private var percentage: Double = 0
private var pressureLevel: RAMPressure = .normal
private var symbolsState: Bool = true
private var colorState: SColor = .monochrome
private let width: CGFloat = 50
public init(title: String, config: NSDictionary?, preview: Bool = false) {
if config != nil {
var configuration = config!
if preview {
if let previewConfig = config!["Preview"] as? NSDictionary {
configuration = previewConfig
if let value = configuration["Value"] as? String {
let values = value.split(separator: ",").map{ (String($0) ) }
if values.count == 2 {
self.value.0 = values[0]
self.value.1 = values[1]
}
}
}
}
}
super.init(.memory, title: title, frame: CGRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: self.width + (Constants.Widget.margin.x*2),
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
if !preview {
self.orderReversedState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_orderReversed", defaultValue: self.orderReversedState)
self.symbolsState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_symbols", defaultValue: self.symbolsState)
self.colorState = SColor.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState.key))
}
if preview {
self.orderReversedState = false
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let letterWidth: CGFloat = 8
let rowHeight: CGFloat = self.frame.height / 2
var width: CGFloat = self.width
var x: CGFloat = 0
let freeY: CGFloat = !self.orderReversedState ? rowHeight+1 : 1
let usedY: CGFloat = !self.orderReversedState ? 1 : rowHeight+1
let style = NSMutableParagraphStyle()
style.alignment = .right
var attributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .light),
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
if self.symbolsState {
var rect = CGRect(x: Constants.Widget.margin.x, y: freeY, width: letterWidth, height: rowHeight)
var str = NSAttributedString.init(string: "F:", attributes: attributes)
str.draw(with: rect)
rect = CGRect(x: Constants.Widget.margin.x, y: usedY, width: letterWidth, height: rowHeight)
str = NSAttributedString.init(string: "U:", attributes: attributes)
str.draw(with: rect)
x = letterWidth + Constants.Widget.spacing*2
width += x
}
var freeColor: NSColor = .controlAccentColor
var usedColor: NSColor = .controlAccentColor
switch self.colorState {
case .systemAccent:
freeColor = .controlAccentColor
usedColor = .controlAccentColor
case .utilization:
freeColor = (1 - self.percentage).usageColor()
usedColor = self.percentage.usageColor()
case .pressure:
usedColor = self.pressureLevel.pressureColor()
freeColor = self.pressureLevel.pressureColor()
case .monochrome:
freeColor = (isDarkMode ? NSColor.white : NSColor.black)
usedColor = (isDarkMode ? NSColor.white : NSColor.black)
default:
freeColor = self.colorState.additional as? NSColor ?? .controlAccentColor
usedColor = self.colorState.additional as? NSColor ?? .controlAccentColor
}
attributes[NSAttributedString.Key.foregroundColor] = freeColor
var rect = CGRect(x: x, y: freeY, width: width - x, height: rowHeight)
var str = NSAttributedString.init(string: self.value.0, attributes: attributes)
str.draw(with: rect)
attributes[NSAttributedString.Key.foregroundColor] = usedColor
rect = CGRect(x: x, y: usedY, width: width - x, height: rowHeight)
str = NSAttributedString.init(string: self.value.1, attributes: attributes)
str.draw(with: rect)
self.setWidth(width + (Constants.Widget.margin.x*2))
}
public func setValue(_ value: (String, String), usedPercentage: Double) {
self.value = value
self.percentage = usedPercentage
DispatchQueue.main.async(execute: {
self.display()
})
}
public func setPressure(_ newPressureLevel: RAMPressure) {
guard self.pressureLevel != newPressureLevel else { return }
self.pressureLevel = newPressureLevel
DispatchQueue.main.async(execute: {
self.display()
})
}
public override func settings() -> NSView {
let view = SettingsContainerView()
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Color"), component: selectView(
action: #selector(self.toggleColor),
items: SColor.allCases.filter({ $0 != .cluster }),
selected: self.colorState.key
)),
PreferencesRow(localizedString("Show symbols"), component: switchView(
action: #selector(self.toggleSymbols),
state: self.symbolsState
)),
PreferencesRow(localizedString("Reverse order"), component: switchView(
action: #selector(self.toggleOrder),
state: self.orderReversedState
))
]))
return view
}
@objc private func toggleOrder(_ sender: NSControl) {
self.orderReversedState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_orderReversed", value: self.orderReversedState)
self.display()
}
@objc private func toggleSymbols(_ sender: NSControl) {
self.symbolsState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_symbols", value: self.symbolsState)
self.display()
}
@objc private func toggleColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newColor = SColor.allCases.first(where: { $0.key == key }) {
self.colorState = newColor
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_color", value: key)
self.display()
}
}
================================================
FILE: Kit/Widgets/Mini.swift
================================================
//
// Mini.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 10/04/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class Mini: WidgetWrapper {
private var labelState: Bool = true
private var colorState: SColor = .monochrome
private var alignmentState: String = "left"
private var colors: [SColor] = SColor.allCases
private var _value: Double = 0
private var _pressureLevel: RAMPressure = .normal
private var _colorZones: colorZones = (0.6, 0.8)
private var _suffix: String = "%"
private var defaultLabel: String
private var _label: String
private var width: CGFloat {
(self.labelState ? 31 : 36) + (2*Constants.Widget.margin.x)
}
private var alignment: NSTextAlignment {
if let alignmentPair = Alignments.first(where: { $0.key == self.alignmentState }) {
return alignmentPair.additional as? NSTextAlignment ?? .left
}
return .left
}
public init(title: String, config: NSDictionary?, preview: Bool = false) {
var widgetTitle: String = title
if config != nil {
var configuration = config!
if preview {
if let previewConfig = config!["Preview"] as? NSDictionary {
configuration = previewConfig
if let value = configuration["Value"] as? String {
self._value = Double(value) ?? 0
}
}
}
if let titleFromConfig = configuration["Title"] as? String {
widgetTitle = titleFromConfig
}
if let label = configuration["Label"] as? Bool {
self.labelState = label
}
if let unsupportedColors = configuration["Unsupported colors"] as? [String] {
self.colors = self.colors.filter{ !unsupportedColors.contains($0.key) }
}
if let color = configuration["Color"] as? String {
if let defaultColor = colors.first(where: { "\($0.self)" == color }) {
self.colorState = defaultColor
}
}
}
self.defaultLabel = widgetTitle
self._label = widgetTitle
super.init(.mini, title: widgetTitle, frame: CGRect(
x: 0,
y: Constants.Widget.margin.y,
width: Constants.Widget.width + (2*Constants.Widget.margin.x),
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
if !preview {
self.colorState = SColor.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState.key))
self.labelState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState)
self.alignmentState = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_alignment", defaultValue: self.alignmentState)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
var value: Double = 0
var pressureLevel: RAMPressure = .normal
var colorZones: colorZones = (0.6, 0.8)
var label: String = ""
var suffix: String = ""
self.queue.sync {
value = self._value
pressureLevel = self._pressureLevel
colorZones = self._colorZones
label = self._label
suffix = self._suffix
}
let valueSize: CGFloat = self.labelState ? 12 : 14
var origin: CGPoint = CGPoint(x: Constants.Widget.margin.x, y: (Constants.Widget.height-valueSize)/2)
let style = NSMutableParagraphStyle()
style.alignment = self.labelState ? self.alignment : .center
if self.labelState {
let style = NSMutableParagraphStyle()
style.alignment = self.alignment
let stringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .light),
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
let rect = CGRect(x: origin.x, y: 12, width: self.width - (Constants.Widget.margin.x*2), height: 7)
let str = NSAttributedString.init(string: label, attributes: stringAttributes)
str.draw(with: rect)
origin.y = 1
}
var color: NSColor = .controlAccentColor
switch self.colorState {
case .systemAccent: color = .controlAccentColor
case .utilization: color = value.usageColor(zones: colorZones, reversed: self.title == "BAT")
case .pressure: color = pressureLevel.pressureColor()
case .monochrome: color = (isDarkMode ? NSColor.white : NSColor.black)
default: color = self.colorState.additional as? NSColor ?? .controlAccentColor
}
let stringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: valueSize, weight: .regular),
NSAttributedString.Key.foregroundColor: color,
NSAttributedString.Key.paragraphStyle: style
]
let rect = CGRect(x: origin.x, y: origin.y, width: self.width - (Constants.Widget.margin.x*2), height: valueSize+1)
let str = NSAttributedString.init(string: "\(Int(value.rounded(toPlaces: 2) * 100))\(suffix)", attributes: stringAttributes)
str.draw(with: rect)
self.setWidth(width)
}
public func setValue(_ newValue: Double) {
guard self._value != newValue else { return }
self._value = newValue
DispatchQueue.main.async(execute: {
self.display()
})
}
public func setPressure(_ newPressureLevel: RAMPressure) {
guard self._pressureLevel != newPressureLevel else { return }
self._pressureLevel = newPressureLevel
DispatchQueue.main.async(execute: {
self.needsDisplay = true
})
}
public func setTitle(_ newTitle: String?) {
var title = self.defaultLabel
if let new = newTitle {
title = new
}
guard self._label != title else { return }
self._label = title
DispatchQueue.main.async(execute: {
self.needsDisplay = true
})
}
public func setColorZones(_ newColorZones: colorZones) {
guard self._colorZones != newColorZones else { return }
self._colorZones = newColorZones
DispatchQueue.main.async(execute: {
self.display()
})
}
public func setSuffix(_ newSuffix: String) {
guard self._suffix != newSuffix else { return }
self._suffix = newSuffix
DispatchQueue.main.async(execute: {
self.display()
})
}
// MARK: - Settings
public override func settings() -> NSView {
let view = SettingsContainerView()
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Label"), component: switchView(
action: #selector(self.toggleLabel),
state: self.labelState
)),
PreferencesRow(localizedString("Color"), component: selectView(
action: #selector(self.toggleColor),
items: self.colors,
selected: self.colorState.key
)),
PreferencesRow(localizedString("Alignment"), component: selectView(
action: #selector(self.toggleAlignment),
items: Alignments,
selected: self.alignmentState
))
]))
return view
}
@objc private func toggleColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newColor = SColor.allCases.first(where: { $0.key == key }) {
self.colorState = newColor
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_color", value: key)
self.display()
}
@objc private func toggleLabel(_ sender: NSControl) {
self.labelState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState)
self.display()
}
@objc private func toggleAlignment(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newAlignment = Alignments.first(where: { $0.key == key }) {
self.alignmentState = newAlignment.key
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_alignment", value: key)
self.display()
}
}
================================================
FILE: Kit/Widgets/NetworkChart.swift
================================================
//
// NetworkChart.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 19/01/2021.
// Using Swift 5.0.
// Running on macOS 11.1.
//
// Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class NetworkChart: WidgetWrapper {
private var boxState: Bool = false
private var frameState: Bool = false
private var labelState: Bool = false
private var historyCount: Int = 60
private var downloadColor: SColor = .secondBlue
private var uploadColor: SColor = .secondRed
private var scaleState: Scale = .linear
private var reverseOrderState: Bool = false
private var points: [(Double, Double)] = Array(repeating: (0, 0), count: 60)
private var width: CGFloat {
get {
switch self.historyCount {
case 30:
return 22
case 60:
return 30
case 90:
return 40
case 120:
return 50
default:
return 30
}
}
}
private var historyNumbers: [KeyValue_p] = [
KeyValue_t(key: "30", value: "30"),
KeyValue_t(key: "60", value: "60"),
KeyValue_t(key: "90", value: "90"),
KeyValue_t(key: "120", value: "120")
]
private var colors: [SColor] = SColor.allCases
private var boxSettingsView: NSSwitch? = nil
private var frameSettingsView: NSSwitch? = nil
public var NSLabelCharts: [NSAttributedString] = []
public init(title: String, config: NSDictionary?, preview: Bool = false) {
var widgetTitle: String = title
if let config = config {
if let titleFromConfig = config["Title"] as? String {
widgetTitle = titleFromConfig
}
if let unsupportedColors = config["Unsupported colors"] as? [String] {
self.colors = self.colors.filter{ !unsupportedColors.contains($0.key) }
}
}
super.init(.networkChart, title: widgetTitle, frame: CGRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: 30 + (2*Constants.Widget.margin.x),
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
if !preview {
self.boxState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_box", defaultValue: self.boxState)
self.frameState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_frame", defaultValue: self.frameState)
self.labelState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState)
self.historyCount = Store.shared.int(key: "\(self.title)_\(self.type.rawValue)_historyCount", defaultValue: self.historyCount)
self.downloadColor = SColor.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_downloadColor", defaultValue: self.downloadColor.key))
self.uploadColor = SColor.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_uploadColor", defaultValue: self.uploadColor.key))
self.scaleState = Scale.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_scale", defaultValue: self.scaleState.key))
self.reverseOrderState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_reverseOrder", defaultValue: self.reverseOrderState)
}
if preview {
var list: [(Double, Double)] = []
for _ in 0..<60 {
list.append((Double.random(in: 0..<23), Double.random(in: 0..<23)))
}
self.points = list
}
let style = NSMutableParagraphStyle()
style.alignment = .center
let stringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .regular),
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
for char in String(self.title.prefix(3)).uppercased().reversed() {
let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes)
self.NSLabelCharts.append(str)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else { return }
var points: [(Double, Double)] = []
var labelState: Bool = false
var boxState: Bool = false
var frameState: Bool = false
var scaleState: Scale = .linear
var reverseOrderState: Bool = false
var originWidth: CGFloat = 0
var labelString: [NSAttributedString] = []
var downloadColor: SColor = .secondBlue
var uploadColor: SColor = .secondRed
self.queue.sync {
points = self.points
labelState = self.labelState
boxState = self.boxState
frameState = self.frameState
scaleState = self.scaleState
reverseOrderState = self.reverseOrderState
labelString = self.NSLabelCharts
originWidth = self.width
downloadColor = self.downloadColor
uploadColor = self.uploadColor
}
let lineWidth = 1 / (NSScreen.main?.backingScaleFactor ?? 1)
let offset = lineWidth / 2
let boxSize: CGSize = CGSize(width: originWidth - (Constants.Widget.margin.x*2), height: self.frame.size.height)
var x: CGFloat = 0
var width = originWidth + (Constants.Widget.margin.x*2)
if labelState {
let letterHeight = self.frame.height / 3
let letterWidth: CGFloat = 6.0
var yMargin: CGFloat = 0
for char in labelString {
let rect = CGRect(x: x, y: yMargin, width: letterWidth, height: letterHeight)
char.draw(with: rect)
yMargin += letterHeight
}
width += letterWidth + Constants.Widget.spacing
x = letterWidth + Constants.Widget.spacing
}
let box = NSBezierPath(roundedRect: NSRect(
x: x + offset,
y: offset,
width: originWidth - offset*2,
height: boxSize.height - (offset*2)
), xRadius: 2, yRadius: 2)
if boxState {
(isDarkMode ? NSColor.white : NSColor.black).set()
box.stroke()
box.fill()
}
context.saveGState()
let chartFrame = NSRect(
x: x+offset+lineWidth,
y: offset,
width: box.bounds.width - (offset*2+lineWidth),
height: box.bounds.height - offset
)
var topMax: Double = (reverseOrderState ? points.map{ $0.1 }.max() : points.map{ $0.0 }.max()) ?? 0
var bottomMax: Double = (reverseOrderState ? points.map{ $0.0 }.max() : points.map{ $0.1 }.max()) ?? 0
if topMax == 0 {
topMax = 1
}
if bottomMax == 0 {
bottomMax = 1
}
let zero: CGFloat = (chartFrame.height/2) + chartFrame.origin.y
let xRatio: CGFloat = (chartFrame.width + (lineWidth*3)) / CGFloat(points.count)
let xCenter: CGFloat = chartFrame.height/2 + chartFrame.origin.y
let columnXPoint = { (point: Int) -> CGFloat in
return (CGFloat(point) * xRatio) + (chartFrame.origin.x - lineWidth)
}
let topYPoint = { (point: Int) -> CGFloat in
let value = reverseOrderState ? points[point].1 : points[point].0
return scaleValue(scale: scaleState, value: value, maxValue: topMax, zeroValue: 256.0, maxHeight: chartFrame.height/2, limit: 1) + xCenter
}
let bottomYPoint = { (point: Int) -> CGFloat in
let value = reverseOrderState ? points[point].0 : points[point].1
return xCenter - scaleValue(scale: scaleState, value: value, maxValue: bottomMax, zeroValue: 256.0, maxHeight: chartFrame.height/2, limit: 1)
}
let topLinePath = NSBezierPath()
topLinePath.move(to: CGPoint(x: columnXPoint(0), y: topYPoint(0)))
let bottomLinePath = NSBezierPath()
bottomLinePath.move(to: CGPoint(x: columnXPoint(0), y: bottomYPoint(0)))
for i in 1..<points.count {
topLinePath.line(to: CGPoint(x: columnXPoint(i), y: topYPoint(i)))
bottomLinePath.line(to: CGPoint(x: columnXPoint(i), y: bottomYPoint(i)))
}
let topColor = (reverseOrderState ? self.uploadColor : downloadColor).additional as? NSColor
let bottomColor = (reverseOrderState ? self.downloadColor : uploadColor).additional as? NSColor
bottomColor?.setStroke()
topLinePath.lineWidth = lineWidth
topLinePath.stroke()
topColor?.setStroke()
bottomLinePath.lineWidth = lineWidth
bottomLinePath.stroke()
context.restoreGState()
context.saveGState()
guard let topUnderLinePath = topLinePath.copy() as? NSBezierPath else { return }
topUnderLinePath.line(to: CGPoint(x: columnXPoint(points.count - 1), y: zero))
topUnderLinePath.line(to: CGPoint(x: columnXPoint(0), y: zero))
topUnderLinePath.close()
topUnderLinePath.addClip()
bottomColor?.withAlphaComponent(0.5).setFill()
let topFillRect = NSRect(x: chartFrame.origin.x - lineWidth, y: chartFrame.origin.y, width: chartFrame.width + (lineWidth*3), height: chartFrame.height)
NSBezierPath(rect: topFillRect).fill()
context.restoreGState()
context.saveGState()
guard let bottomUnderLinePath = bottomLinePath.copy() as? NSBezierPath else { return }
bottomUnderLinePath.line(to: CGPoint(x: columnXPoint(points.count - 1), y: zero))
bottomUnderLinePath.line(to: CGPoint(x: columnXPoint(0), y: zero))
bottomUnderLinePath.close()
bottomUnderLinePath.addClip()
topColor?.withAlphaComponent(0.5).setFill()
let bottomFillRect = NSRect(x: chartFrame.origin.x - lineWidth, y: chartFrame.origin.y, width: chartFrame.width + (lineWidth*3), height: chartFrame.height)
NSBezierPath(rect: bottomFillRect).fill()
context.restoreGState()
if boxState || frameState {
(isDarkMode ? NSColor.white : NSColor.black).set()
box.lineWidth = lineWidth
box.stroke()
}
self.setWidth(width)
}
public func setValue(upload: Double, download: Double) {
DispatchQueue.main.async(execute: {
self.points.remove(at: 0)
self.points.append((upload, download))
if self.window?.isVisible ?? false {
self.display()
}
})
}
// MARK: - Settings
public override func settings() -> NSView {
let view = SettingsContainerView()
let box = switchView(
action: #selector(self.toggleBox),
state: self.boxState
)
self.boxSettingsView = box
let frame = switchView(
action: #selector(self.toggleFrame),
state: self.frameState
)
self.frameSettingsView = frame
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Label"), component: switchView(
action: #selector(self.toggleLabel),
state: self.labelState
)),
PreferencesRow(localizedString("Box"), component: box),
PreferencesRow(localizedString("Frame"), component: frame),
PreferencesRow(localizedString("Reverse order"), component: switchView(
action: #selector(self.toggleReverseOrder),
state: self.reverseOrderState
)),
PreferencesRow(localizedString("Color of download"), component: selectView(
action: #selector(self.toggleDownloadColor),
items: self.colors,
selected: self.downloadColor.key
)),
PreferencesRow(localizedString("Color of upload"), component: selectView(
action: #selector(self.toggleUploadColor),
items: self.colors,
selected: self.uploadColor.key
)),
PreferencesRow(localizedString("Number of reads in the chart"), component: selectView(
action: #selector(self.toggleHistoryCount),
items: self.historyNumbers,
selected: "\(self.historyCount)"
)),
PreferencesRow(localizedString("Scaling"), component: selectView(
action: #selector(self.toggleScale),
items: Scale.allCases.filter({ $0 != .fixed }),
selected: self.scaleState.key
))
]))
return view
}
@objc private func toggleLabel(_ sender: NSControl) {
self.labelState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState)
self.display()
}
@objc private func toggleBox(_ sender: NSControl) {
self.boxState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_box", value: self.boxState)
if self.frameState {
self.frameSettingsView?.state = .off
self.frameState = false
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_frame", value: self.frameState)
}
self.display()
}
@objc private func toggleFrame(_ sender: NSControl) {
self.frameState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_frame", value: self.frameState)
if self.boxState {
self.boxSettingsView?.state = .off
self.boxState = false
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_box", value: self.boxState)
}
self.display()
}
@objc private func toggleHistoryCount(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String, let num = Int(key) else { return }
self.historyCount = num
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_historyCount", value: self.historyCount)
if num < self.points.count {
self.points = Array(self.points.suffix(num))
} else if num > self.points.count {
self.points = Array(repeating: (0, 0), count: num - self.points.count) + self.points
}
self.display()
}
@objc private func toggleDownloadColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newColor = SColor.allCases.first(where: { $0.key == key }) {
self.downloadColor = newColor
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_downloadColor", value: newColor.key)
}
self.display()
}
@objc private func toggleUploadColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newColor = SColor.allCases.first(where: { $0.key == key }) {
self.uploadColor = newColor
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_uploadColor", value: newColor.key)
}
self.display()
}
@objc private func toggleScale(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String,
let value = Scale.allCases.first(where: { $0.key == key }) else { return }
self.scaleState = value
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_scale", value: key)
self.display()
}
@objc private func toggleReverseOrder(_ sender: NSControl) {
self.reverseOrderState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_reverseOrder", value: self.reverseOrderState)
self.display()
}
}
================================================
FILE: Kit/Widgets/PieChart.swift
================================================
//
// PieChart.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 30/11/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class PieChart: WidgetWrapper {
private var labelState: Bool = false
private var monochromeState: Bool = false
private var chart: PieChartView = PieChartView(
frame: NSRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: Constants.Widget.height,
height: Constants.Widget.height
),
segments: [], filled: true, drawValue: false
)
private var labelView: NSView? = nil
private let size: CGFloat = Constants.Widget.height - (Constants.Widget.margin.y*2) + (Constants.Widget.margin.x*2)
public init(title: String, config: NSDictionary?, preview: Bool = false) {
var widgetTitle: String = title
if config != nil {
if let titleFromConfig = config!["Title"] as? String {
widgetTitle = titleFromConfig
}
}
super.init(.pieChart, title: widgetTitle, frame: CGRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: self.size,
height: Constants.Widget.height - (Constants.Widget.margin.y*2)
))
self.canDrawConcurrently = true
if preview {
if self.title == "CPU" {
self.chart.setSegments([
circle_segment(value: 0.16, color: NSColor.systemRed),
circle_segment(value: 0.28, color: NSColor.systemBlue)
])
} else if self.title == "RAM" {
self.chart.setSegments([
circle_segment(value: 0.36, color: NSColor.systemBlue),
circle_segment(value: 0.12, color: NSColor.systemOrange),
circle_segment(value: 0.08, color: NSColor.systemPink)
])
} else if self.title == "Disk" {
self.chart.setSegments([
circle_segment(value: 0.86, color: NSColor.systemBlue)
])
}
} else {
self.labelState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState)
self.monochromeState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_monochrome", defaultValue: self.monochromeState)
}
self.draw()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func draw() {
let x: CGFloat = self.labelState ? 8 + Constants.Widget.spacing : 0
self.labelView = WidgetLabelView(self.title, height: self.frame.height)
self.labelView!.isHidden = !self.labelState
self.addSubview(self.labelView!)
self.addSubview(self.chart)
self.chart.setFrame(NSRect(x: x, y: 0, width: self.frame.size.height, height: self.frame.size.height))
self.setFrameSize(NSSize(width: self.size + x, height: self.frame.size.height))
self.setWidth(self.size + x)
}
public func setValue(_ list: [circle_segment]) {
var segments = list
if self.monochromeState {
for i in 0..<segments.count {
segments[i].color = segments[i].color.grayscaled()
}
}
DispatchQueue.main.async(execute: {
self.chart.setSegments(segments)
})
}
// MARK: - Settings
public override func settings() -> NSView {
let view = SettingsContainerView()
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Label"), component: switchView(
action: #selector(self.toggleLabel),
state: self.labelState
)),
PreferencesRow(localizedString("Monochrome accent"), component: switchView(
action: #selector(self.toggleMonochrome),
state: self.monochromeState
))
]))
return view
}
@objc private func toggleLabel(_ sender: NSControl) {
self.labelState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState)
let x = self.labelState ? 6 + Constants.Widget.spacing : 0
self.labelView!.isHidden = !self.labelState
self.chart.setFrameOrigin(NSPoint(x: x, y: 0))
self.setWidth(self.labelState ? self.size+x : self.size)
}
@objc private func toggleMonochrome(_ sender: NSControl) {
self.monochromeState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_monochrome", value: self.monochromeState)
}
}
================================================
FILE: Kit/Widgets/Speed.swift
================================================
//
// Speed.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 24/05/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class SpeedWidget: WidgetWrapper {
private var icon: String = "dots"
private var valueState: Bool = true
private var unitsState: Bool = true
private var monochromeState: Bool = false
private var valueColorState: String = "none"
private var iconColorState: String = "default"
private var valueAlignmentState: String = "right"
private var modeState: String = "twoRows"
private var iconAlignmentState: String = "left"
private var displayValueState: String = "oi"
private var inputColorState: SColor = .secondBlue
private var outputColorState: SColor = .secondRed
private var symbols: (input: String, output: String) = ("I", "O")
private var words: (input: String, output: String) = ("Input", "Output")
private var inputValue: Int64 = 0
private var outputValue: Int64 = 0
private var width: CGFloat = 58
private var valueColorView: NSPopUpButton? = nil
private var valueAlignmentView: NSPopUpButton? = nil
private var iconAlignmentView: NSPopUpButton? = nil
private var iconColorView: NSPopUpButton? = nil
private var displayModeView: NSPopUpButton? = nil
private var inputColor: (String) -> NSColor {{ state in
if state == "none" { return .textColor }
var color = self.monochromeState ? MonochromeColor.blue : (self.inputColorState.additional as? NSColor ?? NSColor.systemBlue)
if self.inputValue < 1024 {
if state == "transparent" {
color = .clear
} else if state == "default" {
color = .textColor
}
}
return color
}}
private var outputColor: (String) -> NSColor {{ state in
if state == "none" { return .textColor }
var color = self.monochromeState ? MonochromeColor.red : (self.outputColorState.additional as? NSColor ?? NSColor.red)
if self.outputValue < 1024 {
if state == "transparent" {
color = .clear
} else if state == "default" {
color = .textColor
}
}
return color
}}
private var valueAlignment: NSTextAlignment {
get {
if let alignmentPair = Alignments.first(where: { $0.key == self.valueAlignmentState }) {
return alignmentPair.additional as? NSTextAlignment ?? .left
}
return .left
}
}
private var base: DataSizeBase {
DataSizeBase(rawValue: Store.shared.string(key: "\(self.title)_base", defaultValue: "byte")) ?? .byte
}
public init(title: String, config: NSDictionary?, preview: Bool = false) {
let widgetTitle: String = title
if config != nil {
if let symbols = config!["Symbols"] as? NSDictionary {
if let i = symbols["Input"] as? String { self.symbols.input = i }
if let o = symbols["Output"] as? String { self.symbols.output = o }
}
if let icon = config!["Icon"] as? String { self.icon = icon }
if let words = config!["Words"] as? NSDictionary {
if let i = words["Input"] as? String { self.words.input = i }
if let o = words["Output"] as? String { self.words.output = o }
}
}
super.init(.speed, title: widgetTitle, frame: CGRect(
x: 0,
y: Constants.Widget.margin.y,
width: width,
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
if !preview {
self.valueState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_value", defaultValue: self.valueState)
self.icon = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_icon", defaultValue: self.icon)
self.unitsState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_units", defaultValue: self.unitsState)
self.monochromeState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_monochrome", defaultValue: self.monochromeState)
self.valueColorState = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_valueColor", defaultValue: self.valueColorState)
if self.valueColorState == "0" {
self.valueColorState = "none"
}
self.inputColorState = SColor.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_downloadColor", defaultValue: self.inputColorState.key))
self.outputColorState = SColor.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_uploadColor", defaultValue: self.outputColorState.key))
self.valueAlignmentState = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_valueAlignment", defaultValue: self.valueAlignmentState)
self.modeState = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_mode", defaultValue: self.modeState)
self.iconAlignmentState = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_iconAlignment", defaultValue: self.iconAlignmentState)
self.iconColorState = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_iconColor", defaultValue: self.iconColorState)
self.displayValueState = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_displayValue", defaultValue: self.displayValueState)
}
if preview {
self.inputValue = 8947141
self.outputValue = 478678
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
var width: CGFloat = 0
switch self.modeState {
case "oneRow":
width = self.drawOneRow()
case "twoRows":
width = self.drawTwoRows()
default:
width = 0
}
self.setWidth(width)
}
// MARK: - one row
private func drawOneRow() -> CGFloat {
var width: CGFloat = Constants.Widget.margin.x
if self.displayValueState.first == "i" {
width = self.drawRowItem(
initWidth: width,
symbol: self.symbols.input,
iconColor: self.inputColor(self.iconColorState),
value: self.inputValue,
valueColor: self.inputColor(self.valueColorState)
)
} else {
width = self.drawRowItem(
initWidth: width,
symbol: self.symbols.output,
iconColor: self.outputColor(self.iconColorState),
value: self.outputValue,
valueColor: self.outputColor(self.valueColorState)
)
}
if self.displayValueState.count > 1 {
width += Constants.Widget.spacing*3
if self.displayValueState.last == "i" {
width = self.drawRowItem(
initWidth: width,
symbol: self.symbols.input,
iconColor: self.inputColor(self.iconColorState),
value: self.inputValue,
valueColor: self.inputColor(self.valueColorState)
)
} else {
width = self.drawRowItem(
initWidth: width,
symbol: self.symbols.output,
iconColor: self.outputColor(self.iconColorState),
value: self.outputValue,
valueColor: self.outputColor(self.valueColorState)
)
}
}
return width + Constants.Widget.margin.x
}
private func drawRowItem(initWidth: CGFloat, symbol: String, iconColor: NSColor, value: Int64, valueColor: NSColor) -> CGFloat {
var width = initWidth
if self.iconAlignmentState == "left" {
switch self.icon {
case "dots":
width += self.drawDot(CGPoint(x: width, y: 0), color: iconColor)
case "arrows":
width += self.drawArrow(CGPoint(x: width, y: 0), symbol: symbol, color: iconColor)
case "chars":
width += self.drawChar(CGPoint(x: width, y: 0), symbol: symbol, color: iconColor)
default: break
}
width += self.valueState && self.icon != "none" ? 2 : 0
}
if self.valueState {
width += self.drawValue(value, offset: CGPoint(x: width, y: 0), color: valueColor)
}
if self.iconAlignmentState == "right" {
if self.valueState {
width += 2
}
switch self.icon {
case "dots":
width += self.drawDot(CGPoint(x: width, y: 0), color: iconColor)
case "arrows":
width += self.drawArrow(CGPoint(x: width, y: 0), symbol: symbol, color: iconColor)
case "chars":
width += self.drawChar(CGPoint(x: width, y: 0), symbol: symbol, color: iconColor)
default: break
}
}
return width
}
private func drawValue(_ value: Int64, offset: CGPoint, color: NSColor) -> CGFloat {
let rowWidth: CGFloat = self.unitsState ? 58 : 32
let height: CGFloat = self.frame.height
let style = NSMutableParagraphStyle()
style.alignment = self.valueAlignment
let size: CGFloat = 10
let inputStringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 11, weight: .regular),
NSAttributedString.Key.foregroundColor: color,
NSAttributedString.Key.paragraphStyle: style
]
let rect = CGRect(x: offset.x, y: (height-size)/2 + offset.y + 1, width: rowWidth - (Constants.Widget.margin.x*2), height: size)
let value = NSAttributedString.init(
string: Units(bytes: value).getReadableSpeed(base: base, omitUnits: !self.unitsState),
attributes: inputStringAttributes
)
value.draw(with: rect)
return rowWidth
}
private func drawDot(_ offset: CGPoint, color: NSColor) -> CGFloat {
var size: CGFloat = 8
var height: CGFloat = self.frame.height
if self.modeState == "twoRows" {
size = 6
height /= 2
}
var circle = NSBezierPath()
circle = NSBezierPath(ovalIn: CGRect(x: offset.x, y: (height-size)/2 + offset.y, width: size, height: size))
color.set()
circle.fill()
return size
}
private func drawArrow(_ offset: CGPoint, symbol: String, color: NSColor) -> CGFloat {
let height = self.frame.height
let size = height * 0.8
let scaleFactor = NSScreen.main?.backingScaleFactor ?? 1
let lineWidth: CGFloat = 1
let arrowSize: CGFloat = 3 + (scaleFactor/2)
let x = arrowSize + (lineWidth / 2)
let y = (height - size)/2
var start: CGPoint = CGPoint(x: offset.x + x, y: y)
var end: CGPoint = CGPoint(x: offset.x + x, y: size + y)
if symbol == "D" || symbol == "R" {
start = CGPoint(x: offset.x + x, y: size + y)
end = CGPoint(x: offset.x + x, y: y)
} else if symbol == "U" || symbol == "W" {
start = CGPoint(x: offset.x + x, y: y)
end = CGPoint(x: offset.x + x, y: size + y)
}
let arrow = NSBezierPath()
arrow.addArrow(
start: start,
end: end,
pointerLineLength: arrowSize,
arrowAngle: CGFloat(Double.pi / 5)
)
color.set()
arrow.lineWidth = lineWidth
arrow.stroke()
arrow.close()
return arrowSize
}
private func drawChar(_ offset: CGPoint, symbol: String, color: NSColor) -> CGFloat {
let rowHeight: CGFloat = self.frame.height
let height: CGFloat = 10
let inputAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 12, weight: .regular),
NSAttributedString.Key.foregroundColor: color,
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
let rect = CGRect(x: offset.x, y: offset.y + ((rowHeight-height)/2) + 1, width: 10, height: height)
let str = NSAttributedString.init(string: symbol, attributes: inputAttributes)
str.draw(with: rect)
return 10
}
// MARK: - two rows
private func drawTwoRows() -> CGFloat {
var width: CGFloat = 7
var x: CGFloat = 7
if self.iconAlignmentState == "right" {
x = 0
}
if self.icon == "none" {
x = 0
width = 0
}
if self.valueState {
let rowWidth: CGFloat = self.unitsState ? 48 : 30
let rowHeight: CGFloat = self.frame.height / 2
let style = NSMutableParagraphStyle()
style.alignment = self.valueAlignment
let inputStringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .light),
NSAttributedString.Key.foregroundColor: self.inputColor(self.valueColorState),
NSAttributedString.Key.paragraphStyle: style
]
let outputStringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .light),
NSAttributedString.Key.foregroundColor: self.outputColor(self.valueColorState),
NSAttributedString.Key.paragraphStyle: style
]
let inputY: CGFloat = self.displayValueState == "io" ? rowHeight + 1 : 1
let outputY: CGFloat = self.displayValueState == "io" ? 1 : rowHeight + 1
var rect = CGRect(x: Constants.Widget.margin.x + x, y: inputY, width: rowWidth - (Constants.Widget.margin.x*2), height: rowHeight)
let input = NSAttributedString.init(
string: Units(bytes: self.inputValue).getReadableSpeed(base: base, omitUnits: !self.unitsState),
attributes: inputStringAttributes
)
input.draw(with: rect)
rect = CGRect(x: Constants.Widget.margin.x + x, y: outputY, width: rowWidth - (Constants.Widget.margin.x*2), height: rowHeight)
let output = NSAttributedString.init(
string: Units(bytes: self.outputValue).getReadableSpeed(base: base, omitUnits: !self.unitsState),
attributes: outputStringAttributes
)
output.draw(with: rect)
width += rowWidth
}
switch self.icon {
case "dots":
self.drawDots(width)
case "arrows":
self.drawArrows(width)
case "chars":
self.drawChars(width)
default: break
}
return width
}
private func drawDots(_ width: CGFloat) {
let rowHeight: CGFloat = self.frame.height / 2
let size: CGFloat = 6
let y: CGFloat = (rowHeight-size)/2
let x: CGFloat = self.iconAlignmentState == "left" ? Constants.Widget.margin.x : Constants.Widget.margin.x+(width-6)
let inputY: CGFloat = self.displayValueState == "io" ? 10.5 : y-0.2
let outputdY: CGFloat = self.displayValueState == "io" ? y-0.2 : 10.5
var inputCircle = NSBezierPath()
inputCircle = NSBezierPath(ovalIn: CGRect(x: x, y: inputY, width: size, height: size))
self.inputColor(self.iconColorState).set()
inputCircle.fill()
var outputCircle = NSBezierPath()
outputCircle = NSBezierPath(ovalIn: CGRect(x: x, y: outputdY, width: size, height: size))
self.outputColor(self.iconColorState).set()
outputCircle.fill()
}
private func drawArrows(_ width: CGFloat) {
let arrowAngle = CGFloat(Double.pi / 5)
let half = self.frame.size.height / 2
let scaleFactor = NSScreen.main?.backingScaleFactor ?? 1
let lineWidth: CGFloat = 1
let arrowSize: CGFloat = 3 + (scaleFactor/2)
var x = Constants.Widget.margin.x + arrowSize + (lineWidth / 2)
if self.iconAlignmentState == "right" {
x += (width-7)
}
let inputYStart: CGFloat = self.displayValueState == "io" ? self.frame.size.height : half - Constants.Widget.spacing/2
let inputYEnd: CGFloat = self.displayValueState == "io" ? (half + Constants.Widget.spacing/2)+1 : 1
let outputYStart: CGFloat = self.displayValueState == "io" ? 0 : half + Constants.Widget.spacing/2
let uploadYEnd: CGFloat = self.displayValueState == "io" ? (half - Constants.Widget.spacing/2)-1 : self.frame.size.height-1
let inputArrow = NSBezierPath()
inputArrow.addArrow(
start: CGPoint(x: x, y: inputYStart),
end: CGPoint(x: x, y: inputYEnd),
pointerLineLength: arrowSize,
arrowAngle: arrowAngle
)
self.inputColor(self.iconColorState).set()
inputArrow.lineWidth = lineWidth
inputArrow.stroke()
inputArrow.close()
let outputArrow = NSBezierPath()
outputArrow.addArrow(
start: CGPoint(x: x, y: outputYStart),
end: CGPoint(x: x, y: uploadYEnd),
pointerLineLength: arrowSize,
arrowAngle: arrowAngle
)
self.outputColor(self.iconColorState).set()
outputArrow.lineWidth = lineWidth
outputArrow.stroke()
outputArrow.close()
}
private func drawChars(_ width: CGFloat) {
let rowHeight: CGFloat = self.frame.height / 2
let inputY: CGFloat = self.displayValueState == "io" ? rowHeight+1 : 1
let outputY: CGFloat = self.displayValueState == "io" ? 1 : rowHeight+1
let x: CGFloat = self.iconAlignmentState == "left" ? Constants.Widget.margin.x : Constants.Widget.margin.x+(width-6)
let inputAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
NSAttributedString.Key.foregroundColor: self.inputColor(self.iconColorState),
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
var rect = CGRect(x: x, y: inputY, width: 8, height: rowHeight)
var str = NSAttributedString.init(string: self.symbols.input, attributes: inputAttributes)
str.draw(with: rect)
let outputAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
NSAttributedString.Key.foregroundColor: self.outputColor(self.iconColorState),
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
rect = CGRect(x: x, y: outputY, width: 8, height: rowHeight)
str = NSAttributedString.init(string: self.symbols.output, attributes: outputAttributes)
str.draw(with: rect)
}
// MARK: - settings
public override func settings() -> NSView {
let view = SettingsContainerView()
let valueAlignment = selectView(
action: #selector(self.toggleValueAlignment),
items: Alignments,
selected: self.valueAlignmentState
)
valueAlignment.isEnabled = self.valueState
self.valueAlignmentView = valueAlignment
let iconAlignment = selectView(
action: #selector(self.toggleIconAlignment),
items: Alignments.filter({ $0.key != "center" }),
selected: self.iconAlignmentState
)
iconAlignment.isEnabled = self.icon != "none"
self.iconAlignmentView = iconAlignment
let iconColor = selectView(
action: #selector(self.toggleIconColor),
items: SpeedPictogramColor.filter({ $0.key != "none" }),
selected: self.iconColorState
)
iconColor.isEnabled = self.icon != "none"
self.iconColorView = iconColor
let valueColor = selectView(
action: #selector(self.toggleValueColor),
items: SpeedPictogramColor,
selected: self.valueColorState
)
valueColor.isEnabled = self.valueState
self.valueColorView = valueColor
let displayMode = selectView(
action: #selector(self.changeDisplayMode),
items: SensorsWidgetMode.filter({ $0.key == "oneRow" || $0.key == "twoRows"}),
selected: self.modeState
)
displayMode.isEnabled = self.displayValueState.count > 1
self.displayModeView = displayMode
let sensorWidgetValue = SensorsWidgetValue.map { v in
var value = v.value.replacingOccurrences(of: "input", with: localizedString(self.words.input), options: .literal, range: nil)
value = value.replacingOccurrences(of: "output", with: localizedString(self.words.output), options: .literal, range: nil)
return KeyValue_t(key: v.key, value: value)
}
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Value"), component: selectView(
action: #selector(self.changeDisplayValue),
items: sensorWidgetValue,
selected: self.displayValueState
)),
PreferencesRow(localizedString("Display mode"), component: displayMode)
]))
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Pictogram"), component: selectView(
action: #selector(self.toggleIcon),
items: SpeedPictogram,
selected: self.icon
)),
PreferencesRow(localizedString("Colorize"), component: iconColor),
PreferencesRow(localizedString("Alignment"), component: iconAlignment)
]))
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Value"), component: switchView(
action: #selector(self.toggleValue),
state: self.valueState
)),
PreferencesRow(localizedString("Colorize value"), component: valueColor),
PreferencesRow(localizedString("Alignment"), component: valueAlignment),
PreferencesRow(localizedString("Units"), component: switchView(
action: #selector(self.toggleUnits),
state: self.unitsState
))
]))
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Monochrome accent"), component: switchView(
action: #selector(self.toggleMonochrome),
state: self.monochromeState
)),
PreferencesRow(localizedString("Color of download"), component: selectView(
action: #selector(self.toggleInputColor),
items: SColor.allColors,
selected: self.inputColorState.key
)),
PreferencesRow(localizedString("Color of upload"), component: selectView(
action: #selector(self.toggleOutputColor),
items: SColor.allColors,
selected: self.outputColorState.key
))
]))
return view
}
@objc private func changeDisplayValue(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.displayValueState = key
if key.count == 1 {
if self.modeState != "oneRow" {
self.modeState = "oneRow"
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_mode", value: self.modeState)
}
self.displayModeView?.selectItem(at: 0)
}
self.displayModeView?.isEnabled = key.count > 1
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_displayValue", value: key)
self.display()
}
@objc private func changeDisplayMode(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.modeState = key
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_mode", value: key)
self.display()
}
@objc private func toggleValue(_ sender: NSControl) {
self.valueState = controlState(sender)
self.valueColorView?.isEnabled = self.valueState
self.valueAlignmentView?.isEnabled = self.valueState
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_value", value: self.valueState)
self.display()
}
@objc private func toggleUnits(_ sender: NSControl) {
self.unitsState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_units", value: self.unitsState)
self.display()
}
@objc private func toggleIcon(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.icon = key
self.iconColorView?.isEnabled = self.icon != "none"
self.iconAlignmentView?.isEnabled = self.icon != "none"
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_icon", value: key)
self.display()
}
@objc private func toggleMonochrome(_ sender: NSControl) {
self.monochromeState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_monochrome", value: self.monochromeState)
self.display()
}
@objc private func toggleValueColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newColor = SpeedPictogramColor.first(where: { $0.key == key }) {
self.valueColorState = newColor.key
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_valueColor", value: key)
self.display()
}
@objc private func toggleOutputColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String,
let newValue = SColor.allColors.first(where: { $0.key == key }) else {
return
}
self.outputColorState = newValue
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_uploadColor", value: key)
}
@objc private func toggleInputColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String,
let newValue = SColor.allColors.first(where: { $0.key == key }) else {
return
}
self.inputColorState = newValue
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_downloadColor", value: key)
}
public func setValue(input: Int64, output: Int64) {
var updated: Bool = false
if self.inputValue != input {
self.inputValue = abs(input)
updated = true
}
if self.outputValue != output {
self.outputValue = abs(output)
updated = true
}
if updated {
DispatchQueue.main.async(execute: {
self.display()
})
}
}
@objc private func toggleValueAlignment(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newAlignment = Alignments.first(where: { $0.key == key }) {
self.valueAlignmentState = newAlignment.key
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_valueAlignment", value: key)
self.display()
}
@objc private func toggleIconAlignment(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newAlignment = Alignments.first(where: { $0.key == key }) {
self.iconAlignmentState = newAlignment.key
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_iconAlignment", value: key)
self.display()
}
@objc private func toggleIconColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newColor = SpeedPictogramColor.first(where: { $0.key == key }) {
self.iconColorState = newColor.key
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_iconColor", value: key)
self.display()
}
}
================================================
FILE: Kit/Widgets/Stack.swift
================================================
//
// Sensors.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 17/06/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public struct Stack_t: KeyValue_p {
public var key: String
public var value: String
var index: Int {
get { Store.shared.int(key: "stack_\(self.key)_index", defaultValue: -1) }
set { Store.shared.set(key: "stack_\(self.key)_index", value: newValue) }
}
public init(key: String, value: String) {
self.key = key
self.value = value
}
}
public class StackWidget: WidgetWrapper {
private var modeState: StackMode = .auto
private var fixedSizeState: Bool = false
private var monospacedFontState: Bool = false
private var alignmentState: String = "left"
private var values: [Stack_t] = []
private var oneRowWidth: CGFloat = 45
private var twoRowWidth: CGFloat = 32
private let orderTableView: OrderTableView
private var alignment: NSTextAlignment {
if let alignmentPair = Alignments.first(where: { $0.key == self.alignmentState }) {
return alignmentPair.additional as? NSTextAlignment ?? .left
}
return .left
}
public init(title: String, config: NSDictionary?, preview: Bool = false) {
if let config, preview {
if let previewConfig = config["Preview"] as? NSDictionary {
if let value = previewConfig["Values"] as? String {
for (i, value) in value.split(separator: ",").enumerated() {
self.values.append(Stack_t(key: "\(i)", value: String(value)))
}
}
}
}
self.orderTableView = OrderTableView(&self.values)
super.init(.stack, title: title, frame: CGRect(
x: 0,
y: Constants.Widget.margin.y,
width: Constants.Widget.width,
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
if !preview {
self.modeState = StackMode(rawValue: Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_mode", defaultValue: self.modeState.rawValue)) ?? .auto
self.fixedSizeState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_size", defaultValue: self.fixedSizeState)
self.monospacedFontState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_monospacedFont", defaultValue: self.monospacedFontState)
self.alignmentState = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_alignment", defaultValue: self.alignmentState)
}
self.orderTableView.reorderCallback = { [weak self] in
self?.display()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
var values: [Stack_t] = []
var mode: StackMode = .auto
self.queue.sync {
values = self.values
mode = self.modeState
}
guard !values.isEmpty else {
self.setWidth(0)
return
}
let num: Int = Int(round(Double(values.count) / 2))
var totalWidth: CGFloat = Constants.Widget.spacing // opening space
var x: CGFloat = Constants.Widget.spacing
var i = 0
while i < values.count {
switch mode {
case .auto, .twoRows:
let firstElement: Stack_t = values[i]
let secondElement: Stack_t? = values.indices.contains(i+1) ? values[i+1] : nil
var width: CGFloat = 0
if mode == .auto && secondElement == nil {
width += self.drawOneRow(x, firstElement)
} else {
width += self.drawTwoRows(x, firstElement, secondElement)
}
x += width
totalWidth += width
if num != 1 && (i/2) != num {
x += Constants.Widget.spacing
totalWidth += Constants.Widget.spacing
}
i += 1
case .oneRow:
let width = self.drawOneRow(x, values[i])
x += width
totalWidth += width
// add margins between columns
if values.count != 1 && i != values.count {
x += Constants.Widget.spacing
totalWidth += Constants.Widget.spacing
}
}
i += 1
}
totalWidth += Constants.Widget.spacing // closing space
guard abs(self.frame.width - totalWidth) > 2 else { return }
self.setWidth(totalWidth)
}
private func drawOneRow(_ x: CGFloat, _ element: Stack_t) -> CGFloat {
var monospacedFontState: Bool = false
var fixedSizeState: Bool = false
var alignment: NSTextAlignment = .left
self.queue.sync {
monospacedFontState = self.monospacedFontState
fixedSizeState = self.fixedSizeState
alignment = self.alignment
}
var font: NSFont = NSFont.systemFont(ofSize: 13, weight: .regular)
if monospacedFontState {
font = NSFont.monospacedDigitSystemFont(ofSize: 13, weight: .regular)
}
let style = NSMutableParagraphStyle()
style.alignment = alignment
var width: CGFloat = self.oneRowWidth
if !fixedSizeState {
width = element.value.widthOfString(usingFont: font).rounded(.up) + 2
}
let rect = CGRect(x: x, y: (Constants.Widget.height-13)/2, width: width, height: 13)
let str = NSAttributedString.init(string: element.value, attributes: [
NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
])
str.draw(with: rect)
return width
}
private func drawTwoRows(_ x: CGFloat, _ topElement: Stack_t, _ bottomElement: Stack_t?) -> CGFloat {
let rowHeight: CGFloat = self.frame.height / 2
var monospacedFontState: Bool = false
var fixedSizeState: Bool = false
var alignment: NSTextAlignment = .left
self.queue.sync {
monospacedFontState = self.monospacedFontState
fixedSizeState = self.fixedSizeState
alignment = self.alignment
}
var font: NSFont
if monospacedFontState {
font = NSFont.monospacedDigitSystemFont(ofSize: 10, weight: .light)
} else {
font = NSFont.systemFont(ofSize: 10, weight: .light)
}
let style = NSMutableParagraphStyle()
style.alignment = alignment
let attributes = [
NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
var width: CGFloat = self.twoRowWidth
if !fixedSizeState {
let firstRowWidth = topElement.value.widthOfString(usingFont: font)
let secondRowWidth = bottomElement?.value.widthOfString(usingFont: font) ?? 0
width = max(20, max(firstRowWidth, secondRowWidth)).rounded(.up) + 2
}
var rect = CGRect(x: x, y: rowHeight+1, width: width, height: rowHeight)
var str = NSAttributedString.init(string: topElement.value, attributes: attributes)
str.draw(with: rect)
if bottomElement != nil {
rect = CGRect(x: x, y: 1, width: width, height: rowHeight)
str = NSAttributedString.init(string: bottomElement!.value, attributes: attributes)
str.draw(with: rect)
}
return width
}
public func setValues(_ values: [Stack_t]) {
DispatchQueue.main.async(execute: {
var tableNeedsToBeUpdated: Bool = false
values.forEach { (p: Stack_t) in
if let idx = self.values.firstIndex(where: { $0.key == p.key }) {
self.values[idx].value = p.value
return
}
tableNeedsToBeUpdated = true
self.values.append(p)
}
let diff = self.values.filter({ v in values.contains(where: { $0.key == v.key }) })
if diff.count != self.values.count {
tableNeedsToBeUpdated = true
}
self.values = diff.sorted(by: { $0.index < $1.index })
if tableNeedsToBeUpdated {
self.orderTableView.update()
}
self.display()
})
}
// MARK: - Settings
public override func settings() -> NSView {
let view = SettingsContainerView()
var rows = [
PreferencesRow(localizedString("Display mode"), component: selectView(
action: #selector(self.changeDisplayMode),
items: SensorsWidgetMode,
selected: self.modeState.rawValue
)),
PreferencesRow(localizedString("Monospaced font"), component: switchView(
action: #selector(self.toggleMonospacedFont),
state: self.monospacedFontState
)),
PreferencesRow(localizedString("Alignment"), component: selectView(
action: #selector(self.toggleAlignment),
items: Alignments,
selected: self.alignmentState
))
]
if self.title != "Clock" {
rows.append(PreferencesRow(localizedString("Static width"), component: switchView(
action: #selector(self.toggleSize),
state: self.fixedSizeState
)))
}
view.addArrangedSubview(PreferencesSection(rows))
view.addArrangedSubview(self.orderTableView)
return view
}
@objc private func changeDisplayMode(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.modeState = StackMode(rawValue: key) ?? .auto
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_mode", value: key)
self.display()
}
@objc private func toggleSize(_ sender: NSControl) {
self.fixedSizeState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_size", value: self.fixedSizeState)
self.display()
}
@objc private func toggleMonospacedFont(_ sender: NSControl) {
self.monospacedFontState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_monospacedFont", value: self.monospacedFontState)
self.display()
}
@objc private func toggleAlignment(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newAlignment = Alignments.first(where: { $0.key == key }) {
self.alignmentState = newAlignment.key
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_alignment", value: key)
self.display()
}
}
private class OrderTableView: NSView, NSTableViewDelegate, NSTableViewDataSource {
private let scrollView = NSScrollView()
private let tableView = NSTableView()
private var dragDropType = NSPasteboard.PasteboardType(rawValue: "\(Bundle.main.bundleIdentifier!).sensors-row")
fileprivate var reorderCallback: () -> Void = {}
private let list: UnsafeMutablePointer<[Stack_t]>
init(_ list: UnsafeMutablePointer<[Stack_t]>) {
self.list = list
super.init(frame: NSRect.zero)
self.wantsLayer = true
self.layer?.cornerRadius = 3
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.documentView = self.tableView
self.scrollView.hasHorizontalScroller = false
self.scrollView.hasVerticalScroller = true
self.scrollView.autohidesScrollers = true
self.scrollView.backgroundColor = NSColor.clear
self.scrollView.drawsBackground = true
self.tableView.frame = self.scrollView.bounds
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.headerView = nil
self.tableView.backgroundColor = NSColor.clear
self.tableView.columnAutoresizingStyle = .firstColumnOnlyAutoresizingStyle
self.tableView.registerForDraggedTypes([dragDropType])
self.tableView.gridColor = .gridColor
self.tableView.gridStyleMask = [.solidVerticalGridLineMask, .solidHorizontalGridLineMask]
self.tableView.style = .plain
self.tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "name")))
self.addSubview(self.scrollView)
NSLayoutConstraint.activate([
self.scrollView.leftAnchor.constraint(equalTo: self.leftAnchor),
self.scrollView.rightAnchor.constraint(equalTo: self.rightAnchor),
self.scrollView.topAnchor.constraint(equalTo: self.topAnchor),
self.scrollView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
self.heightAnchor.constraint(equalToConstant: 120)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
NotificationCenter.default.removeObserver(self)
}
fileprivate func update() {
self.tableView.reloadData()
}
func numberOfRows(in tableView: NSTableView) -> Int {
return list.pointee.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if !self.list.pointee.indices.contains(row) { return nil }
let item = self.list.pointee[row]
let text: NSTextField = NSTextField()
text.drawsBackground = false
text.isBordered = false
text.isEditable = false
text.isSelectable = false
text.translatesAutoresizingMaskIntoConstraints = false
text.identifier = NSUserInterfaceItemIdentifier(item.key)
switch tableColumn?.identifier.rawValue {
case "name": text.stringValue = item.key
default: break
}
text.sizeToFit()
let cell = NSTableCellView()
cell.addSubview(text)
NSLayoutConstraint.activate([
text.widthAnchor.constraint(equalTo: cell.widthAnchor),
text.centerYAnchor.constraint(equalTo: cell.centerYAnchor)
])
return cell
}
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
let item = NSPasteboardItem()
item.setString(String(row), forType: self.dragDropType)
return item
}
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
if dropOperation == .above {
return .move
}
return []
}
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
var oldIndexes = [Int]()
info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) { dragItem, _, _ in
if let str = (dragItem.item as! NSPasteboardItem).string(forType: self.dragDropType), let index = Int(str) {
oldIndexes.append(index)
}
}
var oldIndexOffset = 0
var newIndexOffset = 0
tableView.beginUpdates()
for oldIndex in oldIndexes {
if oldIndex < row {
let currentIdx = oldIndex + oldIndexOffset
let newIdx = row - 1
self.list.pointee[currentIdx].index = newIdx
self.list.pointee[newIdx].index = currentIdx
oldIndexOffset -= 1
} else {
let currentIdx = oldIndex
let newIdx = row + newIndexOffset
self.list.pointee[currentIdx].index = newIdx
self.list.pointee[newIdx].index = currentIdx
newIndexOffset += 1
}
self.list.pointee = self.list.pointee.sorted(by: { $0.index < $1.index })
self.reorderCallback()
tableView.reloadData()
}
tableView.endUpdates()
return true
}
}
================================================
FILE: Kit/Widgets/State.swift
================================================
//
// State.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 18/09/2022.
// Using Swift 5.0.
// Running on macOS 12.6.
//
// Copyright © 2022 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class StateWidget: WidgetWrapper {
private var activeColorState: SColor = .secondGreen
private var nonactiveColorState: SColor = .secondRed
private var value: Bool = false
private var colors: [SColor] = SColor.allColors
public init(title: String, config: NSDictionary?, preview: Bool = false) {
if config != nil {
var configuration = config!
if preview {
if let previewConfig = config!["Preview"] as? NSDictionary {
configuration = previewConfig
if let value = configuration["Value"] as? Bool {
self.value = value
}
}
}
}
super.init(.state, title: title, frame: CGRect(
x: 0,
y: Constants.Widget.margin.y,
width: 8 + (2*Constants.Widget.margin.x),
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
if !preview {
self.activeColorState = SColor.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_activeColor", defaultValue: self.activeColorState.key))
self.nonactiveColorState = SColor.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_nonactiveColor", defaultValue: self.nonactiveColorState.key))
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let circle = NSBezierPath(ovalIn: CGRect(x: Constants.Widget.margin.x, y: (self.frame.height - 8)/2, width: 8, height: 8))
let color = self.value ? self.activeColorState : self.nonactiveColorState
(color.additional as? NSColor)?.set()
circle.fill()
}
public func setValue(_ value: Bool) {
guard self.value != value else { return }
self.value = value
DispatchQueue.main.async(execute: {
self.display()
})
}
// MARK: - Settings
public override func settings() -> NSView {
let view = SettingsContainerView()
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Active state color"), component: selectView(
action: #selector(self.toggleActiveColor),
items: self.colors,
selected: self.activeColorState.key
)),
PreferencesRow(localizedString("Nonactive state color"), component: selectView(
action: #selector(self.toggleNonactiveColor),
items: self.colors,
selected: self.nonactiveColorState.key
))
]))
return view
}
@objc private func toggleActiveColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newColor = SColor.allCases.first(where: { $0.key == key }) {
self.activeColorState = newColor
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_activeColor", value: key)
self.display()
}
@objc private func toggleNonactiveColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if let newColor = SColor.allCases.first(where: { $0.key == key }) {
self.nonactiveColorState = newColor
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_nonactiveColor", value: key)
self.display()
}
}
================================================
FILE: Kit/Widgets/Tachometer.swift
================================================
//
// Tachometer.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 11/10/2021.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class Tachometer: WidgetWrapper {
private var labelState: Bool = false
private var monochromeState: Bool = false
private var chart: TachometerGraphView = TachometerGraphView(
frame: NSRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: Constants.Widget.height,
height: Constants.Widget.height
), segments: []
)
private var labelView: NSView? = nil
private let size: CGFloat = Constants.Widget.height - (Constants.Widget.margin.y*2) + (Constants.Widget.margin.x*2)
public init(title: String, preview: Bool = false) {
let widgetTitle: String = title
super.init(.tachometer, title: widgetTitle, frame: CGRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: self.size,
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
self.canDrawConcurrently = true
if preview {
self.chart.setSegments([
circle_segment(value: 0.20, color: NSColor.systemRed),
circle_segment(value: 0.57, color: NSColor.systemBlue)
])
} else {
self.labelState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState)
self.monochromeState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_monochrome", defaultValue: self.monochromeState)
}
self.draw()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func draw() {
let x: CGFloat = self.labelState ? 8 + Constants.Widget.spacing : 0
self.labelView = WidgetLabelView(self.title, height: self.frame.height)
self.labelView!.isHidden = !self.labelState
self.addSubview(self.labelView!)
self.addSubview(self.chart)
self.chart.setFrame(NSRect(x: x, y: 0, width: self.frame.size.height, height: self.frame.size.height))
self.setFrameSize(NSSize(width: self.size + x, height: self.frame.size.height))
self.setWidth(self.size + x)
}
public func setValue(_ list: [circle_segment]) {
var segments = list
if self.monochromeState {
for i in 0..<segments.count {
segments[i].color = segments[i].color.grayscaled()
}
}
DispatchQueue.main.async(execute: {
self.chart.setSegments(segments)
})
}
// MARK: - Settings
public override func settings() -> NSView {
let view = SettingsContainerView()
view.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Label"), component: switchView(
action: #selector(self.toggleLabel),
state: self.labelState
)),
PreferencesRow(localizedString("Monochrome accent"), component: switchView(
action: #selector(self.toggleMonochrome),
state: self.monochromeState
))
]))
return view
}
@objc private func toggleLabel(_ sender: NSControl) {
self.labelState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState)
let x = self.labelState ? 6 + Constants.Widget.spacing : 0
self.labelView!.isHidden = !self.labelState
self.chart.setFrameOrigin(NSPoint(x: x, y: 0))
self.setWidth(self.labelState ? self.size+x : self.size)
}
@objc private func toggleMonochrome(_ sender: NSControl) {
self.monochromeState = controlState(sender)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_monochrome", value: self.monochromeState)
}
}
================================================
FILE: Kit/Widgets/Text.swift
================================================
//
// Text.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 08/09/2024
// Using Swift 5.0
// Running on macOS 14.6
//
// Copyright © 2024 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public class TextWidget: WidgetWrapper {
private var value: String = ""
public init(title: String, config: NSDictionary?, preview: Bool = false) {
super.init(.text, title: title, frame: CGRect(
x: 0,
y: Constants.Widget.margin.y,
width: 30 + (2*Constants.Widget.margin.x),
height: Constants.Widget.height - (2*Constants.Widget.margin.y)
))
if preview {
self.value = "Text"
}
self.canDrawConcurrently = true
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
var value: String = ""
self.queue.sync {
value = self.value
}
if value.isEmpty {
self.setWidth(0)
return
}
let valueSize: CGFloat = 12
let style = NSMutableParagraphStyle()
style.alignment = .center
let stringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: valueSize, weight: .regular),
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
let attributedString = NSAttributedString(string: value, attributes: stringAttributes)
let size = attributedString.boundingRect(
with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin, .usesFontLeading]
)
let width = (size.width+Constants.Widget.margin.x*2).roundedUpToNearestTen()
let origin: CGPoint = CGPoint(x: Constants.Widget.margin.x, y: ((Constants.Widget.height-valueSize-1)/2))
let rect = CGRect(x: origin.x, y: origin.y, width: width - (Constants.Widget.margin.x*2), height: valueSize)
attributedString.draw(with: rect)
self.setWidth(width)
}
public func setValue(_ newValue: String) {
guard self.value != newValue else { return }
self.value = newValue
DispatchQueue.main.async(execute: {
self.display()
})
}
static public func parseText(_ raw: String) -> [KeyValue_t] {
var pairs: [KeyValue_t] = []
do {
let regex = try NSRegularExpression(pattern: "(\\$[a-zA-Z0-9_]+)(?:\\.([a-zA-Z0-9_]+))?")
let matches = regex.matches(in: raw, range: NSRange(raw.startIndex..., in: raw))
for match in matches {
if let keyRange = Range(match.range(at: 1), in: raw) {
let key = String(raw[keyRange])
let value: String?
if match.range(at: 2).location != NSNotFound, let valueRange = Range(match.range(at: 2), in: raw) {
value = String(raw[valueRange])
} else {
value = nil
}
pairs.append(KeyValue_t(key: key, value: value ?? ""))
}
}
} catch {
print("Error creating regex: \(error.localizedDescription)")
}
return pairs
}
}
================================================
FILE: Kit/constants.swift
================================================
//
// constants.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 15/04/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
public struct Popup_c_s {
public let width: CGFloat = 264
public let height: CGFloat = 300
public let margins: CGFloat = 8
public let spacing: CGFloat = 2
public let headerHeight: CGFloat = 42
public let separatorHeight: CGFloat = 30
public let portalHeight: CGFloat = 120
}
public struct Settings_c_s {
public let width: CGFloat = 540
public let height: CGFloat = 480
public let margin: CGFloat = 10
public let row: CGFloat = 30
}
public struct Widget_c_s {
public let width: CGFloat = 32
public var height: CGFloat {
get {
let systemHeight = NSApplication.shared.mainMenu?.menuBarHeight
return (systemHeight == 0 ? 22 : systemHeight) ?? 22
}
}
public var margin: CGPoint {
get { CGPoint(x: 0, y: 2) }
}
public let spacing: CGFloat = 2
}
public struct Constants {
public static let Popup: Popup_c_s = Popup_c_s()
public static let Settings: Settings_c_s = Settings_c_s()
public static let Widget: Widget_c_s = Widget_c_s()
public static let defaultProcessIcon = NSWorkspace.shared.icon(forFile: "/bin/bash")
}
public enum ModuleType: Int {
case CPU
case RAM
case GPU
case disk
case sensors
case network
case battery
case bluetooth
case clock
case combined
public var stringValue: String {
switch self {
case .CPU: return "CPU"
case .RAM: return "RAM"
case .GPU: return "GPU"
case .disk: return "Disk"
case .sensors: return "Sensors"
case .network: return "Network"
case .battery: return "Battery"
case .bluetooth: return "Bluetooth"
case .clock: return "Clock"
case .combined: return ""
}
}
}
================================================
FILE: Kit/extensions.swift
================================================
//
// extensions.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 10/04/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
import Carbon
extension String: @retroactive LocalizedError {
public var errorDescription: String? { return self }
public var nilIfEmpty: String? { self.isEmpty ? nil : self }
public var digits: String {
return components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
}
public func widthOfString(usingFont font: NSFont) -> CGFloat {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}
public func condenseWhitespace() -> String {
let components = self.components(separatedBy: .whitespacesAndNewlines)
return components.filter { !$0.isEmpty }.joined(separator: " ")
}
public func findAndCrop(pattern: String) -> (cropped: String, remain: String) {
do {
let regex = try NSRegularExpression(pattern: pattern)
let range = NSRange(self.startIndex..., in: self)
if let match = regex.firstMatch(in: self, options: [], range: range) {
if let range = Range(match.range, in: self) {
let cropped = String(self[range]).trimmingCharacters(in: .whitespaces)
let remaining = self.replacingOccurrences(of: cropped, with: "", options: .regularExpression).trimmingCharacters(in: .whitespaces)
return (cropped, remaining)
}
}
} catch {
print("Error creating regex: \(error.localizedDescription)")
}
return ("", self)
}
public func find(pattern: String) -> String {
do {
let regex = try NSRegularExpression(pattern: pattern)
let stringRange = NSRange(location: 0, length: self.utf16.count)
if let searchRange = regex.firstMatch(in: self, options: [], range: stringRange) {
let start = self.index(self.startIndex, offsetBy: searchRange.range.lowerBound)
let end = self.index(self.startIndex, offsetBy: searchRange.range.upperBound)
let value = String(self[start..<end]).trimmingCharacters(in: .whitespaces)
return value.trimmingCharacters(in: .whitespaces)
}
} catch {}
return ""
}
public var trimmed: String {
var buf = [UInt8]()
var trimming = true
for c in self.utf8 {
if trimming && c < 33 { continue }
trimming = false
buf.append(c)
}
while let last = buf.last, last < 33 {
buf.removeLast()
}
buf.append(0)
return String(cString: buf)
}
public func matches(_ regex: String) -> Bool {
return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
}
public func removedRegexMatches(pattern: String, replaceWith: String = "") -> String {
do {
let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive)
let range = NSRange(location: 0, length: self.count)
return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
} catch {
return self
}
}
func removingWhitespaces() -> String {
return components(separatedBy: .whitespaces).joined()
}
}
public extension DispatchSource.MemoryPressureEvent {
func pressureColor() -> NSColor {
switch self {
case .normal:
return NSColor.systemGreen
case .warning:
return NSColor.systemYellow
case .critical:
return NSColor.systemRed
default:
return .controlAccentColor
}
}
}
public extension Double {
func roundTo(decimalPlaces: Int) -> String {
return NSString(format: "%.\(decimalPlaces)f" as NSString, self) as String
}
func rounded(toPlaces places: Int) -> Double {
let divisor = pow(10.0, Double(places))
return (self * divisor).rounded() / divisor
}
func usageColor(zones: colorZones = (0.6, 0.8), reversed: Bool = false) -> NSColor {
let firstColor: NSColor = NSColor.systemBlue
let secondColor: NSColor = NSColor.orange
let thirdColor: NSColor = NSColor.red
if reversed {
switch self {
case 0...zones.orange:
return thirdColor
case zones.orange...zones.red:
return secondColor
default:
return firstColor
}
} else {
switch self {
case 0...zones.orange:
return firstColor
case zones.orange...zones.red:
return secondColor
default:
return thirdColor
}
}
}
func batteryColor(color: Bool = false, lowPowerMode: Bool? = nil) -> NSColor {
if let mode = lowPowerMode, mode {
return NSColor.systemOrange
}
switch self {
case 0.2...0.4:
if !color {
return NSColor.textColor
}
return NSColor.systemOrange
case 0.4...1:
if self == 1 {
return NSColor.textColor
}
if !color {
return NSColor.textColor
}
return NSColor.systemGreen
default:
return NSColor.systemRed
}
}
func secondsToHoursMinutesSeconds() -> (Int, Int) {
let mins = (self.truncatingRemainder(dividingBy: 3600)) / 60
return (Int(self / 3600), Int(mins))
}
func printSecondsToHoursMinutesSeconds(short: Bool = false) -> String {
let (h, m) = self.secondsToHoursMinutesSeconds()
if self == 0 || h < 0 || m < 0 {
return "n/a"
}
let minutes = m > 9 ? "\(m)" : "0\(m)"
if short {
return "\(h):\(minutes)"
}
if h == 0 {
return "\(minutes)min"
} else if m == 0 {
return "\(h)h"
}
return "\(h)h \(minutes)min"
}
func power(_ unit: String) -> Double {
switch unit {
case "mJ":
return self / 1e3
case "uJ":
return self / 1e6
case "nJ":
return self / 1e9
default:
return 0
}
}
}
public extension NSView {
var isDarkMode: Bool {
switch effectiveAppearance.name {
case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark:
return true
default:
return false
}
}
func toggleSettingRow(title: String, action: Selector, state: Bool) -> NSView {
let view: NSStackView = NSStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.heightAnchor.constraint(equalToConstant: Constants.Settings.row).isActive = true
view.orientation = .horizontal
view.alignment = .centerY
view.distribution = .fill
view.spacing = 0
let titleField: NSTextField = LabelField(frame: NSRect(x: 0, y: 0, width: 0, height: 0), title)
titleField.font = NSFont.systemFont(ofSize: 12, weight: .regular)
titleField.textColor = .textColor
let state: NSControl.StateValue = state ? .on : .off
var toggle: NSControl = NSControl()
if #available(OSX 10.15, *) {
let switchButton = NSSwitch()
switchButton.state = state
switchButton.action = action
switchButton.target = self
toggle = switchButton
} else {
let button: NSButton = NSButton()
button.setButtonType(.switch)
button.state = state
button.title = ""
button.action = action
button.isBordered = false
button.isTransparent = false
button.target = self
button.wantsLayer = true
toggle = button
}
view.addArrangedSubview(titleField)
view.addArrangedSubview(NSView())
view.addArrangedSubview(toggle)
return view
}
func selectSettingsRow(title: String, action: Selector, items: [KeyValue_p], selected: String) -> NSView {
let view = NSStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.heightAnchor.constraint(equalToConstant: Constants.Settings.row).isActive = true
view.orientation = .horizontal
view.alignment = .centerY
view.distribution = .fill
view.spacing = 0
let titleField: NSTextField = LabelField(frame: NSRect(x: 0, y: 0, width: 0, height: 0), title)
titleField.font = NSFont.systemFont(ofSize: 12, weight: .regular)
titleField.textColor = .textColor
let select: NSPopUpButton = selectView(action: action, items: items, selected: selected)
select.sizeToFit()
view.addArrangedSubview(titleField)
view.addArrangedSubview(NSView())
view.addArrangedSubview(select)
return view
}
func selectView(action: Selector, items: [KeyValue_p], selected: String) -> NSPopUpButton {
let select: NSPopUpButton = NSPopUpButton(frame: NSRect(x: 0, y: 4, width: 50, height: 28))
select.target = self
select.action = action
let menu = NSMenu()
items.forEach { (item) in
if item.key.contains("separator") {
menu.addItem(NSMenuItem.separator())
} else {
let interfaceMenu = NSMenuItem(title: localizedString(item.value), action: nil, keyEquivalent: "")
interfaceMenu.representedObject = item.key
menu.addItem(interfaceMenu)
if selected == item.key {
interfaceMenu.state = .on
}
}
}
select.menu = menu
return select
}
func switchView(action: Selector, state: Bool) -> NSSwitch {
let s = NSSwitch()
s.heightAnchor.constraint(equalToConstant: 25).isActive = true
s.controlSize = .mini
s.state = state ? .on : .off
s.action = action
s.target = self
return s
}
func buttonView(_ action: Selector, text: String) -> NSButton {
let button = NSButton()
button.title = text
button.contentTintColor = .labelColor
button.action = action
button.target = self
return button
}
func buttonIconView(_ action: Selector, icon: NSImage, height: CGFloat = 22) -> NSButton {
let button = NSButton()
button.heightAnchor.constraint(equalToConstant: height).isActive = true
button.bezelStyle = .regularSquare
button.translatesAutoresizingMaskIntoConstraints = false
button.imageScaling = .scaleNone
button.image = icon
button.contentTintColor = .labelColor
button.isBordered = false
button.action = action
button.target = self
button.focusRingType = .none
return button
}
func textView(_ value: String, alignment: NSTextAlignment = .left) -> NSTextField {
let field: NSTextField = TextView()
field.font = NSFont.systemFont(ofSize: 13, weight: .regular)
field.stringValue = value
field.isSelectable = true
field.alignment = alignment
return field
}
func sliderView(action: Selector, value: Int, initialValue: String, min: Double = 1, max: Double = 100, valueWidth: CGFloat = 40) -> NSView {
let view: NSStackView = NSStackView()
view.orientation = .horizontal
view.widthAnchor.constraint(equalToConstant: 195).isActive = true
let valueField: NSTextField = LabelField(initialValue)
valueField.font = NSFont.systemFont(ofSize: 12, weight: .regular)
valueField.textColor = .textColor
valueField.alignment = .center
valueField.widthAnchor.constraint(equalToConstant: valueWidth).isActive = true
let slider = NSSlider()
slider.controlSize = .small
slider.minValue = min
slider.maxValue = max
slider.intValue = Int32(value)
slider.target = self
slider.isContinuous = true
slider.action = action
slider.sizeToFit()
view.addArrangedSubview(slider)
view.addArrangedSubview(valueField)
return view
}
}
public class NSButtonWithPadding: NSButton {
public var horizontalPadding: CGFloat = 0
public var verticalPadding: CGFloat = 0
public override var intrinsicContentSize: NSSize {
var size = super.intrinsicContentSize
size.width += self.horizontalPadding
size.height += self.verticalPadding
return size
}
}
public class TextView: NSTextField {
public override init(frame: NSRect = .zero) {
super.init(frame: frame)
self.isEditable = false
self.isSelectable = false
self.isBezeled = false
self.wantsLayer = true
self.textColor = .labelColor
self.backgroundColor = .clear
self.canDrawSubviewsIntoLayer = true
self.alignment = .natural
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
public extension OperatingSystemVersion {
func getFullVersion(separator: String = ".") -> String {
return "\(majorVersion)\(separator)\(minorVersion)\(separator)\(patchVersion)"
}
}
extension URL {
func checkFileExist() -> Bool {
return FileManager.default.fileExists(atPath: self.path)
}
}
public extension NSColor {
func grayscaled() -> NSColor {
guard let space = CGColorSpace(name: CGColorSpace.extendedGray),
let cg = self.cgColor.converted(to: space, intent: .perceptual, options: nil),
let color = NSColor.init(cgColor: cg) else {
return self
}
return color
}
}
public class FlippedStackView: NSStackView {
public override var isFlipped: Bool { return true }
}
open class ScrollableStackView: NSView {
public var stackView: NSStackView = FlippedStackView()
private let clipView: NSClipView = NSClipView()
private let scrollView: NSScrollView = NSScrollView()
public var scrollWidth: CGFloat? {
self.scrollView.verticalScroller?.frame.size.width
}
public init(frame: NSRect = NSRect.zero, orientation: NSUserInterfaceLayoutOrientation = .vertical) {
super.init(frame: frame)
self.clipView.drawsBackground = false
self.stackView.orientation = orientation
self.stackView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
if orientation == .vertical {
self.scrollView.hasVerticalScroller = true
self.scrollView.hasHorizontalScroller = false
self.scrollView.autohidesScrollers = true
self.scrollView.horizontalScrollElasticity = .none
} else {
self.scrollView.hasVerticalScroller = false
self.scrollView.hasHorizontalScroller = true
self.scrollView.autohidesScrollers = true
self.scrollView.verticalScrollElasticity = .none
}
self.scrollView.drawsBackground = false
self.scrollView.contentView = self.clipView
self.scrollView.documentView = self.stackView
self.addSubview(self.scrollView)
NSLayoutConstraint.activate([
self.scrollView.leftAnchor.constraint(equalTo: self.leftAnchor),
self.scrollView.rightAnchor.constraint(equalTo: self.rightAnchor),
self.scrollView.topAnchor.constraint(equalTo: self.topAnchor),
self.scrollView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
self.stackView.leftAnchor.constraint(equalTo: self.clipView.leftAnchor),
self.stackView.topAnchor.constraint(equalTo: self.clipView.topAnchor)
])
if orientation == .vertical {
self.stackView.rightAnchor.constraint(equalTo: self.clipView.rightAnchor).isActive = true
} else {
self.stackView.bottomAnchor.constraint(equalTo: self.clipView.bottomAnchor).isActive = true
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// https://stackoverflow.com/a/54492165
extension NSTextView {
override open func performKeyEquivalent(with event: NSEvent) -> Bool {
let commandKey = NSEvent.ModifierFlags.command.rawValue
let commandShiftKey = NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue
if event.type == NSEvent.EventType.keyDown {
if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey {
switch event.charactersIgnoringModifiers! {
case "x":
if NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: self) { return true }
case "c":
if NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: self) { return true }
case "v":
if NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: self) { return true }
case "z":
if NSApp.sendAction(Selector(("undo:")), to: nil, from: self) { return true }
case "a":
if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self) { return true }
default:
break
}
} else if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector(("redo:")), to: nil, from: self) { return true }
}
}
}
return super.performKeyEquivalent(with: event)
}
}
public extension Data {
var socketAddress: sockaddr {
return withUnsafeBytes { $0.load(as: sockaddr.self) }
}
}
public extension Date {
func convertToTimeZone(_ timeZone: TimeZone) -> Date {
return addingTimeInterval(TimeInterval(timeZone.secondsFromGMT(for: self) - TimeZone.current.secondsFromGMT(for: self)))
}
func currentTimeSeconds() -> Int {
return Int(self.timeIntervalSince1970)
}
}
public extension TimeZone {
init(from: String) {
if let tz = TimeZone(identifier: from) {
self = tz
return
}
if from == "local" {
self = TimeZone.current
return
}
let arr = from.split(separator: ":")
guard !arr.isEmpty else {
self = TimeZone.current
return
}
var secondsFromGMT = 0
if arr.indices.contains(0), let h = Int(arr[0]) {
secondsFromGMT += h*3600
}
if arr.indices.contains(1), let m = Int(arr[1]) {
if secondsFromGMT < 0 {
secondsFromGMT -= m*60
} else {
secondsFromGMT += m*60
}
}
if let tz = TimeZone(secondsFromGMT: secondsFromGMT) {
self = tz
} else {
self = TimeZone.current
}
}
}
extension CGFloat {
func roundedUpToNearestTen() -> CGFloat {
return ceil(self / 10) * 10
}
}
public class KeyboardShartcutView: NSStackView {
private let callback: (_ value: [UInt16]) -> Void
private var startIcon: NSImage { iconFromSymbol(name: "record.circle", scale: .large) }
private var stopIcon: NSImage { iconFromSymbol(name: "stop.circle.fill", scale: .large) }
private var valueField: NSTextField? = nil
private var startButton: NSButton? = nil
private var stopButton: NSButton? = nil
private var recording: Bool = false
private var keyCodes: [UInt16] = []
private var value: [UInt16] = []
private var interaction: Bool = false
public init(callback: @escaping (_ value: [UInt16]) -> Void, value: [UInt16]) {
self.callback = callback
self.value = value
super.init(frame: NSRect.zero)
self.orientation = .horizontal
let stringValue = value.isEmpty ? localizedString("Disabled") : self.parseValue(value)
let valueField: NSTextField = LabelField(stringValue)
valueField.font = NSFont.systemFont(ofSize: 13, weight: .regular)
valueField.textColor = .textColor
valueField.alignment = .center
let startButton = buttonIconView(#selector(self.startListening), icon: self.startIcon, height: 15)
let stopButton = buttonIconView(#selector(self.stopListening), icon: self.stopIcon, height: 15)
self.addArrangedSubview(valueField)
self.addArrangedSubview(startButton)
self.valueField = valueField
self.startButton = startButton
self.stopButton = stopButton
NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .flagsChanged]) { [weak self] event in
self?.handleKeyEvent(event)
return event
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func startListening() {
guard AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary) else { return }
if let btn = self.stopButton {
self.startButton?.removeFromSuperview()
self.addArrangedSubview(btn)
}
self.valueField?.stringValue = localizedString("Listening...")
self.keyCodes = []
self.recording = true
}
@objc private func stopListening() {
if let btn = self.startButton {
self.stopButton?.removeFromSuperview()
self.addArrangedSubview(btn)
}
if self.keyCodes.isEmpty && !self.interaction {
self.value = []
self.valueField?.stringValue = localizedString("Disabled")
}
self.recording = false
self.interaction = false
self.callback(self.value)
}
private func handleKeyEvent(_ event: NSEvent) {
guard self.recording else { return }
self.interaction = true
if event.type == .flagsChanged {
self.keyCodes = []
if event.modifierFlags.contains(.control) { self.keyCodes.append(59) }
if event.modifierFlags.contains(.shift) { self.keyCodes.append(60) }
if event.modifierFlags.contains(.command) { self.keyCodes.append(55) }
if event.modifierFlags.contains(.option) { self.keyCodes.append(58) }
} else if event.type == .keyDown {
self.keyCodes.append(event.keyCode)
self.value = self.keyCodes
}
let list = self.keyCodes.isEmpty ? self.value : self.keyCodes
self.valueField?.stringValue = self.parseValue(list)
}
private func parseValue(_ list: [UInt16]) -> String {
return list.compactMap { self.keyName(virtualKeyCode: $0) }.joined(separator: " + ")
}
private func keyName(virtualKeyCode: UInt16) -> String? {
if virtualKeyCode == 59 {
return "Control"
} else if virtualKeyCode == 60 {
return "Shift"
} else if virtualKeyCode == 55 {
return "Command"
} else if virtualKeyCode == 58 {
return "Option"
}
let maxNameLength = 4
var nameBuffer = [UniChar](repeating: 0, count: maxNameLength)
var nameLength = 0
let modifierKeys = UInt32(alphaLock >> 8) & 0xFF // Caps Lock
var deadKeys: UInt32 = 0
let keyboardType = UInt32(LMGetKbdType())
let source = TISCopyCurrentKeyboardLayoutInputSource().takeRetainedValue()
guard let ptr = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else {
NSLog("Could not get keyboard layout data")
return nil
}
let layoutData = Unmanaged<CFData>.fromOpaque(ptr).takeUnretainedValue() as Data
let osStatus = layoutData.withUnsafeBytes {
UCKeyTranslate($0.bindMemory(to: UCKeyboardLayout.self).baseAddress, virtualKeyCode, UInt16(kUCKeyActionDown),
modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
&deadKeys, maxNameLength, &nameLength, &nameBuffer)
}
guard osStatus == noErr else {
NSLog("Code: 0x%04X Status: %+i", virtualKeyCode, osStatus)
return nil
}
return String(utf16CodeUnits: nameBuffer, count: nameLength)
}
}
================================================
FILE: Kit/helpers.swift
================================================
//
// helpers.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 29/09/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
// swiftlint:disable file_length
import Cocoa
import ServiceManagement
import UserNotifications
import WebKit
import Metal
public struct LaunchAtLogin {
private static let id = "\(Bundle.main.bundleIdentifier!).LaunchAtLogin"
public static var isEnabled: Bool {
get {
if #available(macOS 13, *) {
return isEnabledNext
} else {
return isEnabledLegacy
}
}
set {
if #available(macOS 13, *) {
isEnabledNext = newValue
} else {
isEnabledLegacy = newValue
}
}
}
private static var isEnabledLegacy: Bool {
get {
guard let jobs = (LaunchAtLogin.self as DeprecationWarningWorkaround.Type).jobsDict else {
return false
}
let job = jobs.first { $0["Label"] as! String == id }
return job?["OnDemand"] as? Bool ?? false
}
set {
SMLoginItemSetEnabled(id as CFString, newValue)
}
}
@available(macOS 13, *)
private static var isEnabledNext: Bool {
get { SMAppService.mainApp.status == .enabled }
set {
do {
if newValue {
if SMAppService.mainApp.status == .enabled {
try? SMAppService.mainApp.unregister()
}
try SMAppService.mainApp.register()
} else {
try SMAppService.mainApp.unregister()
}
} catch {
print("failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)")
}
}
}
public static func migrate() {
guard #available(macOS 13, *), !Store.shared.exist(key: "LaunchAtLoginNext") else {
return
}
Store.shared.set(key: "LaunchAtLoginNext", value: true)
isEnabledNext = isEnabledLegacy
isEnabledLegacy = false
try? SMAppService.loginItem(identifier: id).unregister()
}
}
private protocol DeprecationWarningWorkaround {
static var jobsDict: [[String: AnyObject]]? { get }
}
extension LaunchAtLogin: DeprecationWarningWorkaround {
@available(*, deprecated)
static var jobsDict: [[String: AnyObject]]? {
SMCopyAllJobDictionaries(kSMDomainUserLaunchd)?.takeRetainedValue() as? [[String: AnyObject]]
}
}
public protocol KeyValue_p {
var key: String { get }
var value: String { get }
}
public struct KeyValue_t: KeyValue_p, Codable {
public let key: String
public let value: String
public let additional: Any?
private enum CodingKeys: String, CodingKey {
case key, value
}
public init(key: String, value: String, additional: Any? = nil) {
self.key = key
self.value = value
self.additional = additional
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.key = try container.decode(String.self, forKey: .key)
self.value = try container.decode(String.self, forKey: .value)
self.additional = nil
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(key, forKey: .key)
try container.encode(value, forKey: .value)
}
}
public struct Units {
public let bytes: Int64
public init(bytes: Int64) {
self.bytes = bytes
}
public var kilobytes: Double {
return Double(bytes) / 1_000
}
public var megabytes: Double {
return kilobytes / 1_000
}
public var gigabytes: Double {
return megabytes / 1_000
}
public var terabytes: Double {
return gigabytes / 1_000
}
public func getReadableTuple(base: DataSizeBase = .byte) -> (String, String) {
let stringBase = base == .byte ? "B" : "b"
let multiplier: Double = base == .byte ? 1 : 8
switch bytes {
case 0..<1_000:
return ("0", "K\(stringBase)/s")
case 1_000..<(1_000 * 1_000):
return (String(format: "%.0f", kilobytes*multiplier), "K\(stringBase)/s")
case 1_000..<(1_000 * 1_000 * 100):
return (String(format: "%.1f", megabytes*multiplier), "M\(stringBase)/s")
case (1_000 * 1_000 * 100)..<(1_000 * 1_000 * 1_000):
return (String(format: "%.0f", megabytes*multiplier), "M\(stringBase)/s")
case (1_000 * 1_000 * 1_000)...Int64.max:
return (String(format: "%.1f", gigabytes*multiplier), "G\(stringBase)/s")
default:
return (String(format: "%.0f", kilobytes*multiplier), "K\(stringBase)B/s")
}
}
public func getReadableSpeed(base: DataSizeBase = .byte, omitUnits: Bool = false) -> String {
let stringBase = base == .byte ? "B" : "b"
let multiplier: Double = base == .byte ? 1 : 8
switch bytes*Int64(multiplier) {
case 0..<1_000:
let unit = omitUnits ? "" : " K\(stringBase)/s"
return "0\(unit)"
case 1_000..<(1_000 * 1_000):
let unit = omitUnits ? "" : " K\(stringBase)/s"
return String(format: "%.0f\(unit)", kilobytes*multiplier)
case 1_000..<(1_000 * 1_000 * 100):
let unit = omitUnits ? "" : " M\(stringBase)/s"
return String(format: "%.1f\(unit)", megabytes*multiplier)
case (1_000 * 1_000 * 100)..<(1_000 * 1_000 * 1_000):
let unit = omitUnits ? "" : " M\(stringBase)/s"
return String(format: "%.0f\(unit)", megabytes*multiplier)
case (1_000 * 1_000 * 1_000)...Int64.max:
let unit = omitUnits ? "" : " G\(stringBase)/s"
return String(format: "%.1f\(unit)", gigabytes*multiplier)
default:
let unit = omitUnits ? "" : " K\(stringBase)/s"
return String(format: "%.0f\(unit)", kilobytes*multiplier)
}
}
public func getReadableMemory(style: ByteCountFormatter.CountStyle = .file) -> String {
let formatter: ByteCountFormatter = ByteCountFormatter()
formatter.countStyle = style
formatter.includesUnit = true
formatter.isAdaptive = true
var value = formatter.string(fromByteCount: Int64(self.bytes))
if let idx = value.lastIndex(of: ",") {
value.replaceSubrange(idx...idx, with: ".")
}
return value
}
public func toUnit(_ unit: SizeUnit) -> Double {
switch unit {
case .KB: return self.kilobytes
case .MB: return self.megabytes
case .GB: return self.gigabytes
case .TB: return self.terabytes
default: return Double(self.bytes)
}
}
}
public struct DiskSize {
public let value: Int64
public init(_ size: Int64) {
self.value = size
}
public var kilobytes: Double {
return Double(value) / 1_000
}
public var megabytes: Double {
return kilobytes / 1_000
}
public var gigabytes: Double {
return megabytes / 1_000
}
public var terabytes: Double {
return gigabytes / 1_000
}
gitextract_0i6vpw9i/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ └── bug_report.md │ └── workflows/ │ ├── build.yaml │ ├── i18n.yaml │ └── linter.yaml ├── .gitignore ├── .swiftlint.yml ├── Kit/ │ ├── Supporting Files/ │ │ ├── Assets.xcassets/ │ │ │ ├── Contents.json │ │ │ ├── calendar.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── cancel.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── chart.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── close.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── refresh.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── settings.imageset/ │ │ │ │ └── Contents.json │ │ │ └── tune.imageset/ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── Kit.h │ ├── Widgets/ │ │ ├── BarChart.swift │ │ ├── Battery.swift │ │ ├── Label.swift │ │ ├── LineChart.swift │ │ ├── Memory.swift │ │ ├── Mini.swift │ │ ├── NetworkChart.swift │ │ ├── PieChart.swift │ │ ├── Speed.swift │ │ ├── Stack.swift │ │ ├── State.swift │ │ ├── Tachometer.swift │ │ └── Text.swift │ ├── constants.swift │ ├── extensions.swift │ ├── helpers.swift │ ├── lldb/ │ │ ├── LICENSE.txt │ │ ├── include/ │ │ │ ├── c.h │ │ │ ├── cache.h │ │ │ ├── comparator.h │ │ │ ├── db.h │ │ │ ├── dumpfile.h │ │ │ ├── env.h │ │ │ ├── export.h │ │ │ ├── filter_policy.h │ │ │ ├── iterator.h │ │ │ ├── options.h │ │ │ ├── slice.h │ │ │ ├── status.h │ │ │ ├── table.h │ │ │ ├── table_builder.h │ │ │ └── write_batch.h │ │ ├── libleveldb.a │ │ ├── lldb.h │ │ └── lldb.m │ ├── module/ │ │ ├── module.swift │ │ ├── notifications.swift │ │ ├── popup.swift │ │ ├── portal.swift │ │ ├── reader.swift │ │ ├── widget.swift │ │ └── window.swift │ ├── plugins/ │ │ ├── Charts.swift │ │ ├── DB.swift │ │ ├── Logger.swift │ │ ├── Reachability.swift │ │ ├── Remote.swift │ │ ├── Repeater.swift │ │ ├── Store.swift │ │ ├── SystemKit.swift │ │ └── Updater.swift │ ├── process.swift │ ├── scripts/ │ │ ├── SMJobBlessUtil.py │ │ ├── changelog.py │ │ ├── i18n.py │ │ ├── uninstall.sh │ │ └── updater.sh │ └── types.swift ├── LICENSE ├── LaunchAtLogin/ │ ├── Info.plist │ ├── LaunchAtLogin.entitlements │ └── main.swift ├── Makefile ├── Modules/ │ ├── Battery/ │ │ ├── Info.plist │ │ ├── config.plist │ │ ├── main.swift │ │ ├── notifications.swift │ │ ├── popup.swift │ │ ├── portal.swift │ │ ├── readers.swift │ │ └── settings.swift │ ├── Bluetooth/ │ │ ├── Info.plist │ │ ├── config.plist │ │ ├── main.swift │ │ ├── notifications.swift │ │ ├── popup.swift │ │ ├── readers.swift │ │ └── settings.swift │ ├── CPU/ │ │ ├── Info.plist │ │ ├── bridge.h │ │ ├── config.plist │ │ ├── main.swift │ │ ├── notifications.swift │ │ ├── popup.swift │ │ ├── portal.swift │ │ ├── readers.swift │ │ ├── settings.swift │ │ └── widget.swift │ ├── Clock/ │ │ ├── config.plist │ │ ├── main.swift │ │ ├── popup.swift │ │ ├── portal.swift │ │ ├── reader.swift │ │ └── settings.swift │ ├── Disk/ │ │ ├── Info.plist │ │ ├── config.plist │ │ ├── header.h │ │ ├── main.swift │ │ ├── notifications.swift │ │ ├── popup.swift │ │ ├── portal.swift │ │ ├── readers.swift │ │ ├── settings.swift │ │ └── widget.swift │ ├── GPU/ │ │ ├── Info.plist │ │ ├── config.plist │ │ ├── main.swift │ │ ├── notifications.swift │ │ ├── popup.swift │ │ ├── portal.swift │ │ ├── reader.swift │ │ ├── settings.swift │ │ └── widget.swift │ ├── Net/ │ │ ├── Info.plist │ │ ├── config.plist │ │ ├── main.swift │ │ ├── notifications.swift │ │ ├── popup.swift │ │ ├── portal.swift │ │ ├── readers.swift │ │ ├── settings.swift │ │ └── widget.swift │ ├── RAM/ │ │ ├── Info.plist │ │ ├── config.plist │ │ ├── main.swift │ │ ├── notifications.swift │ │ ├── popup.swift │ │ ├── portal.swift │ │ ├── readers.swift │ │ ├── settings.swift │ │ └── widget.swift │ └── Sensors/ │ ├── Info.plist │ ├── bridge.h │ ├── config.plist │ ├── main.swift │ ├── notifications.swift │ ├── popup.swift │ ├── portal.swift │ ├── reader.m │ ├── readers.swift │ ├── settings.swift │ └── values.swift ├── README.md ├── SMC/ │ ├── Helper/ │ │ ├── Info.plist │ │ ├── Launchd.plist │ │ ├── main.swift │ │ └── protocol.swift │ ├── Makefile │ ├── main.swift │ └── smc.swift ├── Stats/ │ ├── AppDelegate.swift │ ├── Supporting Files/ │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── ac_unit.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── apps.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bug.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── devices/ │ │ │ │ ├── Contents.json │ │ │ │ ├── imac.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── imacPro.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── macMini.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── macMini2020.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── macMini2024.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── macPro.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── macPro2019.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── macStudio.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── macbookAir.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── macbookAir4thGen.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── macbookNeo.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── macbookPro.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── macbookPro5thGen.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── donate.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── high-battery.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── low-battery.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── pause.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── power.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── record.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── resume.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── settings.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── stop.imageset/ │ │ │ │ └── Contents.json │ │ │ └── support/ │ │ │ ├── Contents.json │ │ │ ├── github.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ko-fi.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── patreon.imageset/ │ │ │ │ └── Contents.json │ │ │ └── paypal.imageset/ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── Stats/ │ │ │ └── en.xcloc/ │ │ │ ├── Localized Contents/ │ │ │ │ └── en.xliff │ │ │ ├── Source Contents/ │ │ │ │ ├── LaunchAtLogin/ │ │ │ │ │ └── en.lproj/ │ │ │ │ │ └── InfoPlist.strings │ │ │ │ ├── ModuleKit/ │ │ │ │ │ └── Supporting Files/ │ │ │ │ │ └── en.lproj/ │ │ │ │ │ └── InfoPlist.strings │ │ │ │ ├── Modules/ │ │ │ │ │ ├── Battery/ │ │ │ │ │ │ └── en.lproj/ │ │ │ │ │ │ └── InfoPlist.strings │ │ │ │ │ ├── CPU/ │ │ │ │ │ │ └── en.lproj/ │ │ │ │ │ │ └── InfoPlist.strings │ │ │ │ │ ├── Disk/ │ │ │ │ │ │ └── en.lproj/ │ │ │ │ │ │ └── InfoPlist.strings │ │ │ │ │ ├── GPU/ │ │ │ │ │ │ └── en.lproj/ │ │ │ │ │ │ └── InfoPlist.strings │ │ │ │ │ ├── Memory/ │ │ │ │ │ │ └── en.lproj/ │ │ │ │ │ │ └── InfoPlist.strings │ │ │ │ │ ├── Net/ │ │ │ │ │ │ └── en.lproj/ │ │ │ │ │ │ └── InfoPlist.strings │ │ │ │ │ └── Sensors/ │ │ │ │ │ └── en.lproj/ │ │ │ │ │ └── InfoPlist.strings │ │ │ │ ├── Stats/ │ │ │ │ │ └── Supporting Files/ │ │ │ │ │ └── en.lproj/ │ │ │ │ │ ├── InfoPlist.strings │ │ │ │ │ └── Localizable.strings │ │ │ │ └── StatsKit/ │ │ │ │ └── en.lproj/ │ │ │ │ └── InfoPlist.strings │ │ │ └── contents.json │ │ ├── Stats.entitlements │ │ ├── ar.lproj/ │ │ │ └── Localizable.strings │ │ ├── bg.lproj/ │ │ │ └── Localizable.strings │ │ ├── ca.lproj/ │ │ │ └── Localizable.strings │ │ ├── cs.lproj/ │ │ │ └── Localizable.strings │ │ ├── da.lproj/ │ │ │ └── Localizable.strings │ │ ├── de.lproj/ │ │ │ └── Localizable.strings │ │ ├── el.lproj/ │ │ │ └── Localizable.strings │ │ ├── en-AU.lproj/ │ │ │ └── Localizable.strings │ │ ├── en-GB.lproj/ │ │ │ └── Localizable.strings │ │ ├── en.lproj/ │ │ │ └── Localizable.strings │ │ ├── es.lproj/ │ │ │ └── Localizable.strings │ │ ├── et.lproj/ │ │ │ └── Localizable.strings │ │ ├── fa.lproj/ │ │ │ └── Localizable.strings │ │ ├── fi.lproj/ │ │ │ └── Localizable.strings │ │ ├── fr.lproj/ │ │ │ └── Localizable.strings │ │ ├── he.lproj/ │ │ │ └── Localizable.strings │ │ ├── hi.lproj/ │ │ │ └── Localizable.strings │ │ ├── hr.lproj/ │ │ │ └── Localizable.strings │ │ ├── hu.lproj/ │ │ │ └── Localizable.strings │ │ ├── id.lproj/ │ │ │ └── Localizable.strings │ │ ├── it.lproj/ │ │ │ └── Localizable.strings │ │ ├── ja.lproj/ │ │ │ └── Localizable.strings │ │ ├── ko.lproj/ │ │ │ └── Localizable.strings │ │ ├── menus.psd │ │ ├── nb.lproj/ │ │ │ └── Localizable.strings │ │ ├── nl.lproj/ │ │ │ └── Localizable.strings │ │ ├── pl.lproj/ │ │ │ └── Localizable.strings │ │ ├── popups.psd │ │ ├── pt-BR.lproj/ │ │ │ └── Localizable.strings │ │ ├── pt-PT.lproj/ │ │ │ └── Localizable.strings │ │ ├── ro.lproj/ │ │ │ └── Localizable.strings │ │ ├── ru.lproj/ │ │ │ └── Localizable.strings │ │ ├── sk.lproj/ │ │ │ └── Localizable.strings │ │ ├── sl.lproj/ │ │ │ └── Localizable.strings │ │ ├── sv.lproj/ │ │ │ └── Localizable.strings │ │ ├── th.lproj/ │ │ │ └── Localizable.strings │ │ ├── tr.lproj/ │ │ │ └── Localizable.strings │ │ ├── uk.lproj/ │ │ │ └── Localizable.strings │ │ ├── vi.lproj/ │ │ │ └── Localizable.strings │ │ ├── zh-Hans.lproj/ │ │ │ └── Localizable.strings │ │ └── zh-Hant.lproj/ │ │ └── Localizable.strings │ ├── Views/ │ │ ├── AppSettings.swift │ │ ├── CombinedView.swift │ │ ├── Dashboard.swift │ │ ├── Settings.swift │ │ ├── Setup.swift │ │ ├── Support.swift │ │ └── Update.swift │ └── helpers.swift ├── Stats.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata/ │ └── xcschemes/ │ ├── SMC.xcscheme │ ├── Stats.xcscheme │ └── WidgetsExtension.xcscheme ├── Tests/ │ ├── Info.plist │ └── RAM.swift ├── Widgets/ │ ├── Supporting Files/ │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── WidgetBackground.colorset/ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── Widgets.entitlements │ ├── UnitedWidget.swift │ └── widgets.swift └── exportOptions.plist
SYMBOL INDEX (73 symbols across 20 files)
FILE: Kit/lldb/include/c.h
type leveldb_t (line 55) | typedef struct leveldb_t leveldb_t;
type leveldb_cache_t (line 56) | typedef struct leveldb_cache_t leveldb_cache_t;
type leveldb_comparator_t (line 57) | typedef struct leveldb_comparator_t leveldb_comparator_t;
type leveldb_env_t (line 58) | typedef struct leveldb_env_t leveldb_env_t;
type leveldb_filelock_t (line 59) | typedef struct leveldb_filelock_t leveldb_filelock_t;
type leveldb_filterpolicy_t (line 60) | typedef struct leveldb_filterpolicy_t leveldb_filterpolicy_t;
type leveldb_iterator_t (line 61) | typedef struct leveldb_iterator_t leveldb_iterator_t;
type leveldb_logger_t (line 62) | typedef struct leveldb_logger_t leveldb_logger_t;
type leveldb_options_t (line 63) | typedef struct leveldb_options_t leveldb_options_t;
type leveldb_randomfile_t (line 64) | typedef struct leveldb_randomfile_t leveldb_randomfile_t;
type leveldb_readoptions_t (line 65) | typedef struct leveldb_readoptions_t leveldb_readoptions_t;
type leveldb_seqfile_t (line 66) | typedef struct leveldb_seqfile_t leveldb_seqfile_t;
type leveldb_snapshot_t (line 67) | typedef struct leveldb_snapshot_t leveldb_snapshot_t;
type leveldb_writablefile_t (line 68) | typedef struct leveldb_writablefile_t leveldb_writablefile_t;
type leveldb_writebatch_t (line 69) | typedef struct leveldb_writebatch_t leveldb_writebatch_t;
type leveldb_writeoptions_t (line 70) | typedef struct leveldb_writeoptions_t leveldb_writeoptions_t;
FILE: Kit/lldb/include/cache.h
function namespace (line 26) | namespace leveldb {
FILE: Kit/lldb/include/comparator.h
function namespace (line 12) | namespace leveldb {
FILE: Kit/lldb/include/db.h
function namespace (line 15) | namespace leveldb {
FILE: Kit/lldb/include/dumpfile.h
function namespace (line 14) | namespace leveldb {
FILE: Kit/lldb/include/env.h
function namespace (line 42) | namespace leveldb {
FILE: Kit/lldb/include/filter_policy.h
function namespace (line 23) | namespace leveldb {
FILE: Kit/lldb/include/iterator.h
type CleanupNode (line 86) | struct CleanupNode {
FILE: Kit/lldb/include/options.h
function namespace (line 12) | namespace leveldb {
FILE: Kit/lldb/include/slice.h
function namespace (line 25) | namespace leveldb {
function compare (line 100) | inline int Slice::compare(const Slice& b) const {
FILE: Kit/lldb/include/status.h
function namespace (line 22) | namespace leveldb {
FILE: Kit/lldb/include/table.h
function namespace (line 13) | namespace leveldb {
FILE: Kit/lldb/include/table_builder.h
function namespace (line 22) | namespace leveldb {
FILE: Kit/lldb/include/write_batch.h
function namespace (line 29) | namespace leveldb {
FILE: Kit/scripts/SMJobBlessUtil.py
class UsageException (line 62) | class UsageException (Exception):
class CheckException (line 69) | class CheckException (Exception):
method __init__ (line 74) | def __init__(self, message, path=None):
function checkCodeSignature (line 78) | def checkCodeSignature(programPath, programType):
function readDesignatedRequirement (line 100) | def readDesignatedRequirement(programPath, programType):
function readInfoPlistFromPath (line 120) | def readInfoPlistFromPath(infoPath):
function readPlistFromToolSection (line 131) | def readPlistFromToolSection(toolPath, segmentName, sectionName):
function checkStep1 (line 196) | def checkStep1(appPath):
function checkStep2 (line 230) | def checkStep2(appPath, toolPathList):
function checkStep3 (line 272) | def checkStep3(appPath, toolPathList):
function checkStep4 (line 304) | def checkStep4(appPath, toolPathList):
function checkStep5 (line 317) | def checkStep5(appPath):
function check (line 321) | def check(appPath):
function setreq (line 336) | def setreq(appPath, appInfoPlistPath, toolInfoPlistPaths):
function main (line 421) | def main():
FILE: Kit/scripts/changelog.py
function last_release_tag (line 5) | def last_release_tag():
function git_hash (line 11) | def git_hash(tag):
class Changelog (line 17) | class Changelog:
method generate (line 22) | def generate(self):
method commits (line 43) | def commits(self, first_commit):
FILE: Kit/scripts/i18n.py
function dictionary (line 14) | def dictionary(lines):
class i18n (line 28) | class i18n:
method __init__ (line 31) | def __init__(self):
method en_file (line 36) | def en_file(self):
method check (line 43) | def check(self):
method fix (line 63) | def fix(self):
method _normalize_lang_code (line 84) | def _normalize_lang_code(self, code):
method _extract_translation (line 90) | def _extract_translation(self, raw, fallback):
method _lang_name_from_code (line 149) | def _lang_name_from_code(self, code):
method _script_hint (line 164) | def _script_hint(self, lang_code):
method _ollama_translate (line 181) | def _ollama_translate(self, text, target_lang, model="translategemma:4...
method _line_authors (line 211) | def _line_authors(self, file_path):
method _my_git_author (line 220) | def _my_git_author(self):
method _strings_escape (line 230) | def _strings_escape(self, value):
method translate (line 235) | def translate(self, model="translategemma:4b", accept=False):
FILE: Modules/CPU/bridge.h
type IOReportSubscriptionRef (line 17) | struct IOReportSubscriptionRef
FILE: Modules/Disk/header.h
type nvme_smart_log (line 15) | struct nvme_smart_log {
type IONVMeSMARTInterface (line 39) | typedef struct IONVMeSMARTInterface {
FILE: Modules/Sensors/bridge.h
type __IOHIDEvent (line 17) | struct __IOHIDEvent
type __IOHIDServiceClient (line 18) | struct __IOHIDServiceClient
type IOReportSubscriptionRef (line 19) | struct IOReportSubscriptionRef
type IOHIDFloat (line 21) | typedef double IOHIDFloat;
type IOHIDFloat (line 23) | typedef float IOHIDFloat;
Condensed preview — 283 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,771K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 122,
"preview": "github: [exelban]\npatreon: exelban\nko_fi: exelban\ncustom: [\"https://www.paypal.com/donate?hosted_button_id=3DS5JHDBATMTC"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 289,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
},
{
"path": ".github/workflows/build.yaml",
"chars": 380,
"preview": "name: build\n\non:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\n\nconcurrency:\n group"
},
{
"path": ".github/workflows/i18n.yaml",
"chars": 357,
"preview": "name: i18n check\n\non:\n push:\n paths:\n - '.github/workflows/i18n.yaml'\n - '**/*.strings'\n pull_request:\n "
},
{
"path": ".github/workflows/linter.yaml",
"chars": 378,
"preview": "name: Linter\n\non:\n push:\n paths:\n - '.github/workflows/linter.yaml'\n - '.swiftlint.yml'\n - '**/*.swif"
},
{
"path": ".gitignore",
"chars": 129,
"preview": ".DS_Store\n\nPods\nCarthage\nbuild\nxcuserdata\n\nStats.dmg\nStats.app\ncreate-dmg\ndSYMs.zip\nStats.dmg.zip\nSMC/smc\n\nCartfile.reso"
},
{
"path": ".swiftlint.yml",
"chars": 1108,
"preview": "disabled_rules:\n - force_cast # todo\n - type_name # todo\n - cyclomatic_complexity # todo\n - trailing_whitespace\n - "
},
{
"path": "Kit/Supporting Files/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Kit/Supporting Files/Assets.xcassets/calendar.imageset/Contents.json",
"chars": 532,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_calendar_month_black_24pt_1x.png\",\n \"idiom\" : \"universal\",\n "
},
{
"path": "Kit/Supporting Files/Assets.xcassets/cancel.imageset/Contents.json",
"chars": 502,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"outline_close_white_12pt_1x.png\",\n \"idiom\" : \"universal\",\n \"scale\" "
},
{
"path": "Kit/Supporting Files/Assets.xcassets/chart.imageset/Contents.json",
"chars": 553,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_insert_chart_outlined_white_24pt_1x.png\",\n \"idiom\" : \"universal"
},
{
"path": "Kit/Supporting Files/Assets.xcassets/close.imageset/Contents.json",
"chars": 508,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_cancel_white_24pt_1x.png\",\n \"idiom\" : \"universal\",\n \"scale"
},
{
"path": "Kit/Supporting Files/Assets.xcassets/refresh.imageset/Contents.json",
"chars": 508,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"outline_refresh_black_18pt_1x.png\",\n \"idiom\" : \"universal\",\n \"scale"
},
{
"path": "Kit/Supporting Files/Assets.xcassets/settings.imageset/Contents.json",
"chars": 514,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_settings_black_24pt_1x.png\",\n \"idiom\" : \"universal\",\n \"sca"
},
{
"path": "Kit/Supporting Files/Assets.xcassets/tune.imageset/Contents.json",
"chars": 499,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"outline_tune_black_18pt_1x.png\",\n \"idiom\" : \"universal\",\n \"scale\" :"
},
{
"path": "Kit/Supporting Files/Info.plist",
"chars": 864,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Kit/Supporting Files/Kit.h",
"chars": 412,
"preview": "//\n// Kit.h\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 05/02/2024\n// Using Swift 5.0\n// Running on macOS 14.3\n//\n"
},
{
"path": "Kit/Widgets/BarChart.swift",
"chars": 12279,
"preview": "//\n// BarChart.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 26/04/2020.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Kit/Widgets/Battery.swift",
"chars": 27095,
"preview": "//\n// Battery.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 06/06/2020.\n// Using Swift 5.0.\n// Running on macO"
},
{
"path": "Kit/Widgets/Label.swift",
"chars": 1864,
"preview": "//\n// Label.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 30/03/2021.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Kit/Widgets/LineChart.swift",
"chars": 14874,
"preview": "//\n// Chart.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 18/04/2020.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Kit/Widgets/Memory.swift",
"chars": 7472,
"preview": "//\n// Memory.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 30/06/2020.\n// Using Swift 5.0.\n// Running on macOS"
},
{
"path": "Kit/Widgets/Mini.swift",
"chars": 9142,
"preview": "//\n// Mini.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 10/04/2020.\n// Using Swift 5.0.\n// Running on macOS 1"
},
{
"path": "Kit/Widgets/NetworkChart.swift",
"chars": 16567,
"preview": "//\n// NetworkChart.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 19/01/2021.\n// Using Swift 5.0.\n// Running on"
},
{
"path": "Kit/Widgets/PieChart.swift",
"chars": 4979,
"preview": "//\n// PieChart.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 30/11/2020.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Kit/Widgets/Speed.swift",
"chars": 29094,
"preview": "//\n// Speed.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 24/05/2020.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Kit/Widgets/Stack.swift",
"chars": 17296,
"preview": "//\n// Sensors.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 17/06/2020.\n// Using Swift 5.0.\n// Running on macO"
},
{
"path": "Kit/Widgets/State.swift",
"chars": 3927,
"preview": "//\n// State.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 18/09/2022.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Kit/Widgets/Tachometer.swift",
"chars": 4192,
"preview": "//\n// Tachometer.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 11/10/2021.\n// Using Swift 5.0.\n// Running on m"
},
{
"path": "Kit/Widgets/Text.swift",
"chars": 3512,
"preview": "//\n// Text.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 08/09/2024\n// Using Swift 5.0\n// Running on macOS 14."
},
{
"path": "Kit/constants.swift",
"chars": 2002,
"preview": "//\n// constants.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 15/04/2020.\n// Using Swift 5.0.\n// Running on ma"
},
{
"path": "Kit/extensions.swift",
"chars": 25790,
"preview": "//\n// extensions.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 10/04/2020.\n// Using Swift 5.0.\n// Running on m"
},
{
"path": "Kit/helpers.swift",
"chars": 64390,
"preview": "//\n// helpers.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 29/09/2020.\n// Using Swift 5.0.\n// Running on macO"
},
{
"path": "Kit/lldb/LICENSE.txt",
"chars": 1484,
"preview": "Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or"
},
{
"path": "Kit/lldb/include/c.h",
"chars": 12037,
"preview": "/* Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n Use of this source code is governed by a BSD-style lic"
},
{
"path": "Kit/lldb/include/cache.h",
"chars": 3993,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/comparator.h",
"chars": 2417,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/db.h",
"chars": 6779,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/dumpfile.h",
"chars": 927,
"preview": "// Copyright (c) 2014 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/env.h",
"chars": 16024,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/export.h",
"chars": 911,
"preview": "// Copyright (c) 2017 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/filter_policy.h",
"chars": 3013,
"preview": "// Copyright (c) 2012 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/iterator.h",
"chars": 3977,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/options.h",
"chars": 7515,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/slice.h",
"chars": 3245,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/status.h",
"chars": 3945,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/table.h",
"chars": 2998,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/table_builder.h",
"chars": 3417,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/include/write_batch.h",
"chars": 2519,
"preview": "// Copyright (c) 2011 The LevelDB Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style li"
},
{
"path": "Kit/lldb/lldb.h",
"chars": 588,
"preview": "//\n// lldb.h\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 03/02/2024\n// Using Swift 5.0\n// Running on macOS 14.3\n//"
},
{
"path": "Kit/lldb/lldb.m",
"chars": 3977,
"preview": "//\n// lldb.m\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 03/02/2024\n// Using Swift 5.0\n// Running on macOS 14.3\n//"
},
{
"path": "Kit/module/module.swift",
"chars": 13858,
"preview": "//\n// module.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 09/04/2020.\n// Copyright © 2020 Serhiy Mytrovtsiy. A"
},
{
"path": "Kit/module/notifications.swift",
"chars": 3122,
"preview": "//\n// notifications.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 04/12/2023\n// Using Swift 5.0\n// Running on "
},
{
"path": "Kit/module/popup.swift",
"chars": 16302,
"preview": "//\n// popup.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 11/04/2020.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Kit/module/portal.swift",
"chars": 3506,
"preview": "//\n// portal.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 17/02/2023\n// Using Swift 5.0\n// Running on macOS 1"
},
{
"path": "Kit/module/reader.swift",
"chars": 7867,
"preview": "//\n// reader.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 10/04/2020.\n// Using Swift 5.0.\n// Running on macOS"
},
{
"path": "Kit/module/widget.swift",
"chars": 21541,
"preview": "//\n// widget.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 10/04/2020.\n// Using Swift 5.0.\n// Running on macOS"
},
{
"path": "Kit/module/window.swift",
"chars": 25910,
"preview": "//\n// settings.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 13/04/2020.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Kit/plugins/Charts.swift",
"chars": 40474,
"preview": "//\n// Chart.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 17/04/2020.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Kit/plugins/DB.swift",
"chars": 3680,
"preview": "//\n// DB.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 03/02/2024\n// Using Swift 5.0\n// Running on macOS 14.3\n"
},
{
"path": "Kit/plugins/Logger.swift",
"chars": 5447,
"preview": "//\n// Logger.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 24/06/2021.\n// Using Swift 5.0.\n// Running on macOS"
},
{
"path": "Kit/plugins/Reachability.swift",
"chars": 3927,
"preview": "//\n// Reachability.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 15/10/2021.\n// Using Swift 5.0.\n// Running on"
},
{
"path": "Kit/plugins/Remote.swift",
"chars": 40697,
"preview": "//\n// Remote.swift\n// Stats\n//\n// Created by Serhiy Mytrovtsiy on 16/03/2025\n// Using Swift 6.0\n// Running on macOS"
},
{
"path": "Kit/plugins/Repeater.swift",
"chars": 1689,
"preview": "//\n// Repeater.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 27/06/2022.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Kit/plugins/Store.swift",
"chars": 4147,
"preview": "//\n// store.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 10/04/2020.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Kit/plugins/SystemKit.swift",
"chars": 44182,
"preview": "//\n// SystemKit.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 13/04/2020.\n// Using Swift 5.0.\n// Running on ma"
},
{
"path": "Kit/plugins/Updater.swift",
"chars": 10138,
"preview": "//\n// Updater.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 14/04/2020.\n// Using Swift 5.0.\n// Running on macO"
},
{
"path": "Kit/process.swift",
"chars": 9633,
"preview": "//\n// process.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 05/01/2024\n// Using Swift 5.0\n// Running on macOS "
},
{
"path": "Kit/scripts/SMJobBlessUtil.py",
"chars": 19400,
"preview": "#! /usr/bin/python3\n#\n# File: SMJobBlessUtil.py\n#\n# Contains: Tool for checking and correcting apps that use"
},
{
"path": "Kit/scripts/changelog.py",
"chars": 2018,
"preview": "import subprocess\nimport re\n\n\ndef last_release_tag():\n cmd = \"git describe --abbrev=0 --tags\"\n output = subprocess"
},
{
"path": "Kit/scripts/i18n.py",
"chars": 13188,
"preview": "import os\nimport sys\nimport json\nimport urllib.request\nimport subprocess\nimport unicodedata\n\ntry:\n import langcodes\ne"
},
{
"path": "Kit/scripts/uninstall.sh",
"chars": 271,
"preview": "#! /bin/sh\n\nsudo launchctl unload /Library/LaunchDaemons/eu.exelban.Stats.SMC.Helper.plist\nsudo rm /Library/LaunchDaemon"
},
{
"path": "Kit/scripts/updater.sh",
"chars": 1000,
"preview": "#!/bin/bash\n\nDMG_PATH=\"$HOME/Downloads/Stats.dmg\"\nMOUNT_PATH=\"/tmp/Stats\"\nAPPLICATION_PATH=\"/Applications/\"\n\nSTEP=\"\"\n\nwh"
},
{
"path": "Kit/types.swift",
"chars": 18858,
"preview": "//\n// types.swift\n// Kit\n//\n// Created by Serhiy Mytrovtsiy on 10/04/2021.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "LICENSE",
"chars": 1074,
"preview": "MIT License\n\nCopyright (c) 2019 Serhiy Mytrovtsiy\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "LaunchAtLogin/Info.plist",
"chars": 1025,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "LaunchAtLogin/LaunchAtLogin.entitlements",
"chars": 240,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "LaunchAtLogin/main.swift",
"chars": 782,
"preview": "//\n// main.swift\n// LaunchAtLogin\n//\n// Created by Serhiy Mytrovtsiy on 08/04/2020.\n// Copyright © 2020 Serhiy Mytro"
},
{
"path": "Makefile",
"chars": 3719,
"preview": "APP = Stats\nBUNDLE_ID = eu.exelban.$(APP)\n\nBUILD_PATH = $(PWD)/build\nAPP_PATH = \"$(BUILD_PATH)/$(APP).app\"\nZIP_PATH = \"$"
},
{
"path": "Modules/Battery/Info.plist",
"chars": 864,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Battery/config.plist",
"chars": 1662,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Battery/main.swift",
"chars": 4577,
"preview": "//\n// main.swift\n// Battery\n//\n// Created by Serhiy Mytrovtsiy on 06/06/2020.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Modules/Battery/notifications.swift",
"chars": 4105,
"preview": "//\n// notifications.swift\n// Battery\n//\n// Created by Serhiy Mytrovtsiy on 17/12/2023\n// Using Swift 5.0\n// Running"
},
{
"path": "Modules/Battery/popup.swift",
"chars": 19216,
"preview": "//\n// popup.swift\n// Battery\n//\n// Created by Serhiy Mytrovtsiy on 06/06/2020.\n// Using Swift 5.0.\n// Running on ma"
},
{
"path": "Modules/Battery/portal.swift",
"chars": 4314,
"preview": "//\n// portal.swift\n// Battery\n//\n// Created by Serhiy Mytrovtsiy on 16/03/2023\n// Using Swift 5.0\n// Running on mac"
},
{
"path": "Modules/Battery/readers.swift",
"chars": 8731,
"preview": "//\n// readers.swift\n// Battery\n//\n// Created by Serhiy Mytrovtsiy on 06/06/2020.\n// Using Swift 5.0.\n// Running on "
},
{
"path": "Modules/Battery/settings.swift",
"chars": 2629,
"preview": "//\n// settings.swift\n// Battery\n//\n// Created by Serhiy Mytrovtsiy on 15/07/2020.\n// Using Swift 5.0.\n// Running on"
},
{
"path": "Modules/Bluetooth/Info.plist",
"chars": 864,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Bluetooth/config.plist",
"chars": 909,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Bluetooth/main.swift",
"chars": 4423,
"preview": "//\n// main.swift\n// Bluetooth\n//\n// Created by Serhiy Mytrovtsiy on 08/06/2021.\n// Using Swift 5.0.\n// Running on m"
},
{
"path": "Modules/Bluetooth/notifications.swift",
"chars": 2962,
"preview": "//\n// notifications.swift\n// Bluetooth\n//\n// Created by Serhiy Mytrovtsiy on 24/06/2024\n// Using Swift 5.0\n// Runni"
},
{
"path": "Modules/Bluetooth/popup.swift",
"chars": 5184,
"preview": "//\n// popup.swift\n// Bluetooth\n//\n// Created by Serhiy Mytrovtsiy on 22/06/2021.\n// Using Swift 5.0.\n// Running on "
},
{
"path": "Modules/Bluetooth/readers.swift",
"chars": 19738,
"preview": "//\n// readers.swift\n// Bluetooth\n//\n// Created by Serhiy Mytrovtsiy on 08/06/2021.\n// Using Swift 5.0.\n// Running o"
},
{
"path": "Modules/Bluetooth/settings.swift",
"chars": 2516,
"preview": "//\n// settings.swift\n// Bluetooth\n//\n// Created by Serhiy Mytrovtsiy on 07/07/2021.\n// Using Swift 5.0.\n// Running "
},
{
"path": "Modules/CPU/Info.plist",
"chars": 912,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/CPU/bridge.h",
"chars": 1316,
"preview": "//\n// bridge.h\n// Stats\n//\n// Created by Serhiy Mytrovtsiy on 17/12/2024\n// Using Swift 6.0\n// Running on macOS 15."
},
{
"path": "Modules/CPU/config.plist",
"chars": 1744,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/CPU/main.swift",
"chars": 9097,
"preview": "//\n// main.swift\n// CPU\n//\n// Created by Serhiy Mytrovtsiy on 09/04/2020.\n// Copyright © 2020 Serhiy Mytrovtsiy. All"
},
{
"path": "Modules/CPU/notifications.swift",
"chars": 10618,
"preview": "//\n// notifications.swift\n// CPU\n//\n// Created by Serhiy Mytrovtsiy on 04/12/2023\n// Using Swift 5.0\n// Running on "
},
{
"path": "Modules/CPU/popup.swift",
"chars": 31782,
"preview": "//\n// popup.swift\n// CPU\n//\n// Created by Serhiy Mytrovtsiy on 15/04/2020.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Modules/CPU/portal.swift",
"chars": 7945,
"preview": "//\n// portal.swift\n// CPU\n//\n// Created by Serhiy Mytrovtsiy on 17/02/2023\n// Using Swift 5.0\n// Running on macOS 1"
},
{
"path": "Modules/CPU/readers.swift",
"chars": 23354,
"preview": "//\n// readers.swift\n// CPU\n//\n// Created by Serhiy Mytrovtsiy on 10/04/2020.\n// Using Swift 5.0.\n// Running on macO"
},
{
"path": "Modules/CPU/settings.swift",
"chars": 8848,
"preview": "//\n// Settings.swift\n// CPU\n//\n// Created by Serhiy Mytrovtsiy on 18/04/2020.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Modules/CPU/widget.swift",
"chars": 5201,
"preview": "//\n// widget.swift\n// CPU\n//\n// Created by Serhiy Mytrovtsiy on 01/07/2024\n// Using Swift 5.0\n// Running on macOS 1"
},
{
"path": "Modules/Clock/config.plist",
"chars": 827,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Clock/main.swift",
"chars": 5711,
"preview": "//\n// main.swift\n// Clock\n//\n// Created by Serhiy Mytrovtsiy on 23/03/2023\n// Using Swift 5.0\n// Running on macOS 1"
},
{
"path": "Modules/Clock/popup.swift",
"chars": 32663,
"preview": "//\n// popup.swift\n// Clock\n//\n// Created by Serhiy Mytrovtsiy on 24/03/2023\n// Using Swift 5.0\n// Running on macOS "
},
{
"path": "Modules/Clock/portal.swift",
"chars": 3184,
"preview": "//\n// portal.swift\n// Clock\n//\n// Created by Serhiy Mytrovtsiy on 28/12/2023\n// Using Swift 5.0\n// Running on macOS"
},
{
"path": "Modules/Clock/reader.swift",
"chars": 4492,
"preview": "//\n// reader.swift\n// Stats\n//\n// Created by Serhiy Mytrovtsiy on 05/03/2026\n// Using Swift 6.0\n// Running on macOS"
},
{
"path": "Modules/Clock/settings.swift",
"chars": 11292,
"preview": "//\n// settings.swift\n// Clock\n//\n// Created by Serhiy Mytrovtsiy on 24/03/2023\n// Using Swift 5.0\n// Running on mac"
},
{
"path": "Modules/Disk/Info.plist",
"chars": 864,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Disk/config.plist",
"chars": 2752,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Disk/header.h",
"chars": 1127,
"preview": "//\n// Header.h\n// Disk\n//\n// Created by Serhiy Mytrovtsiy on 25/03/2023\n// Using Swift 5.0\n// Running on macOS 13.2"
},
{
"path": "Modules/Disk/main.swift",
"chars": 12413,
"preview": "//\n// main.swift\n// Disk\n//\n// Created by Serhiy Mytrovtsiy on 07/05/2020.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Modules/Disk/notifications.swift",
"chars": 2614,
"preview": "//\n// notifications.swift\n// Disk\n//\n// Created by Serhiy Mytrovtsiy on 05/12/2023\n// Using Swift 5.0\n// Running on"
},
{
"path": "Modules/Disk/popup.swift",
"chars": 35308,
"preview": "//\n// popup.swift\n// Disk\n//\n// Created by Serhiy Mytrovtsiy on 11/05/2020.\n// Using Swift 5.0.\n// Running on macOS"
},
{
"path": "Modules/Disk/portal.swift",
"chars": 4519,
"preview": "//\n// portal.swift\n// Disk\n//\n// Created by Serhiy Mytrovtsiy on 20/02/2023\n// Using Swift 5.0\n// Running on macOS "
},
{
"path": "Modules/Disk/readers.swift",
"chars": 21325,
"preview": "//\n// readers.swift\n// Disk\n//\n// Created by Serhiy Mytrovtsiy on 07/05/2020.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Modules/Disk/settings.swift",
"chars": 8738,
"preview": "//\n// settings.swift\n// Disk\n//\n// Created by Serhiy Mytrovtsiy on 12/05/2020.\n// Using Swift 5.0.\n// Running on ma"
},
{
"path": "Modules/Disk/widget.swift",
"chars": 4642,
"preview": "//\n// widget.swift\n// Disk\n//\n// Created by Serhiy Mytrovtsiy on 16/07/2024\n// Using Swift 5.0\n// Running on macOS "
},
{
"path": "Modules/GPU/Info.plist",
"chars": 864,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/GPU/config.plist",
"chars": 1581,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/GPU/main.swift",
"chars": 6695,
"preview": "//\n// main.swift\n// GPU\n//\n// Created by Serhiy Mytrovtsiy on 17/08/2020.\n// Using Swift 5.0.\n// Running on macOS 1"
},
{
"path": "Modules/GPU/notifications.swift",
"chars": 2487,
"preview": "//\n// notifications.swift\n// GPU\n//\n// Created by Serhiy Mytrovtsiy on 05/12/2023\n// Using Swift 5.0\n// Running on "
},
{
"path": "Modules/GPU/popup.swift",
"chars": 18376,
"preview": "//\n// popup.swift\n// GPU\n//\n// Created by Serhiy Mytrovtsiy on 17/08/2020.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Modules/GPU/portal.swift",
"chars": 3288,
"preview": "//\n// portal.swift\n// GPU\n//\n// Created by Serhiy Mytrovtsiy on 18/02/2023\n// Using Swift 5.0\n// Running on macOS 1"
},
{
"path": "Modules/GPU/reader.swift",
"chars": 7853,
"preview": "//\n// reader.swift\n// GPU\n//\n// Created by Serhiy Mytrovtsiy on 17/08/2020.\n// Using Swift 5.0.\n// Running on macOS"
},
{
"path": "Modules/GPU/settings.swift",
"chars": 4705,
"preview": "//\n// settings.swift\n// GPU\n//\n// Created by Serhiy Mytrovtsiy on 17/08/2020.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Modules/GPU/widget.swift",
"chars": 4738,
"preview": "//\n// widget.swift\n// GPU\n//\n// Created by Serhiy Mytrovtsiy on 17/07/2024\n// Using Swift 5.0\n// Running on macOS 1"
},
{
"path": "Modules/Net/Info.plist",
"chars": 864,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Net/config.plist",
"chars": 1636,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Net/main.swift",
"chars": 15920,
"preview": "//\n// main.swift\n// Net\n//\n// Created by Serhiy Mytrovtsiy on 24/05/2020.\n// Using Swift 5.0.\n// Running on macOS 1"
},
{
"path": "Modules/Net/notifications.swift",
"chars": 9426,
"preview": "//\n// notifications.swift\n// Net\n//\n// Created by Serhiy Mytrovtsiy on 25/01/2025\n// Using Swift 6.0\n// Running on "
},
{
"path": "Modules/Net/popup.swift",
"chars": 45104,
"preview": "//\n// popup.swift\n// Net\n//\n// Created by Serhiy Mytrovtsiy on 24/05/2020.\n// Using Swift 5.0.\n// Running on macOS "
},
{
"path": "Modules/Net/portal.swift",
"chars": 6040,
"preview": "//\n// portal.swift\n// Net\n//\n// Created by Serhiy Mytrovtsiy on 18/02/2023\n// Using Swift 5.0\n// Running on macOS 1"
},
{
"path": "Modules/Net/readers.swift",
"chars": 40371,
"preview": "//\n// readers.swift\n// Net\n//\n// Created by Serhiy Mytrovtsiy on 24/05/2020.\n// Using Swift 5.0.\n// Running on macO"
},
{
"path": "Modules/Net/settings.swift",
"chars": 20438,
"preview": "//\n// settings.swift\n// Net\n//\n// Created by Serhiy Mytrovtsiy on 06/07/2020.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Modules/Net/widget.swift",
"chars": 5558,
"preview": "//\n// widget.swift\n// Net\n//\n// Created by Serhiy Mytrovtsiy on 30/07/2024\n// Using Swift 5.0\n// Running on macOS 1"
},
{
"path": "Modules/RAM/Info.plist",
"chars": 864,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/RAM/config.plist",
"chars": 2019,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/RAM/main.swift",
"chars": 10088,
"preview": "//\n// main.swift\n// Memory\n//\n// Created by Serhiy Mytrovtsiy on 12/04/2020.\n// Using Swift 5.0.\n// Running on macO"
},
{
"path": "Modules/RAM/notifications.swift",
"chars": 9385,
"preview": "//\n// notifications.swift\n// RAM\n//\n// Created by Serhiy Mytrovtsiy on 05/12/2023\n// Using Swift 5.0\n// Running on "
},
{
"path": "Modules/RAM/popup.swift",
"chars": 24920,
"preview": "//\n// popup.swift\n// Memory\n//\n// Created by Serhiy Mytrovtsiy on 18/04/2020.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Modules/RAM/portal.swift",
"chars": 5537,
"preview": "//\n// portal.swift\n// RAM\n//\n// Created by Serhiy Mytrovtsiy on 17/02/2023\n// Using Swift 5.0\n// Running on macOS 1"
},
{
"path": "Modules/RAM/readers.swift",
"chars": 10259,
"preview": "//\n// readers.swift\n// Memory\n//\n// Created by Serhiy Mytrovtsiy on 12/04/2020.\n// Using Swift 5.0.\n// Running on m"
},
{
"path": "Modules/RAM/settings.swift",
"chars": 8494,
"preview": "//\n// settings.swift\n// Memory\n//\n// Created by Serhiy Mytrovtsiy on 11/07/2020.\n// Using Swift 5.0.\n// Running on "
},
{
"path": "Modules/RAM/widget.swift",
"chars": 5379,
"preview": "//\n// widget.swift\n// RAM\n//\n// Created by Serhiy Mytrovtsiy on 03/07/2024\n// Using Swift 5.0\n// Running on macOS 1"
},
{
"path": "Modules/Sensors/Info.plist",
"chars": 864,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Sensors/bridge.h",
"chars": 2319,
"preview": "//\n// bridge.h\n// Stats\n//\n// Created by Serhiy Mytrovtsiy on 30/03/2021.\n// Using Swift 5.0.\n// Running on macOS 1"
},
{
"path": "Modules/Sensors/config.plist",
"chars": 1586,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Modules/Sensors/main.swift",
"chars": 6233,
"preview": "//\n// main.swift\n// Sensors\n//\n// Created by Serhiy Mytrovtsiy on 17/06/2020.\n// Using Swift 5.0.\n// Running on mac"
},
{
"path": "Modules/Sensors/notifications.swift",
"chars": 3539,
"preview": "//\n// notifications.swift\n// Sensors\n//\n// Created by Serhiy Mytrovtsiy on 05/12/2023\n// Using Swift 5.0\n// Running"
},
{
"path": "Modules/Sensors/popup.swift",
"chars": 45836,
"preview": "//\n// popup.swift\n// Sensors\n//\n// Created by Serhiy Mytrovtsiy on 22/06/2020.\n// Using Swift 5.0.\n// Running on ma"
},
{
"path": "Modules/Sensors/portal.swift",
"chars": 3157,
"preview": "//\n// portal.swift\n// Sensors\n//\n// Created by Serhiy Mytrovtsiy on 14/01/2024\n// Using Swift 5.0\n// Running on mac"
},
{
"path": "Modules/Sensors/reader.m",
"chars": 1484,
"preview": "//\n// reader.m\n// Sensors\n//\n// Created by Serhiy Mytrovtsiy on 06/05/2021.\n// Using Swift 5.0.\n// Running on macOS"
},
{
"path": "Modules/Sensors/readers.swift",
"chars": 25000,
"preview": "//\n// readers.swift\n// Sensors\n//\n// Created by Serhiy Mytrovtsiy on 17/06/2020.\n// Using Swift 5.0.\n// Running on "
},
{
"path": "Modules/Sensors/settings.swift",
"chars": 8908,
"preview": "//\n// settings.swift\n// Sensors\n//\n// Created by Serhiy Mytrovtsiy on 23/06/2020.\n// Using Swift 5.0.\n// Running on"
},
{
"path": "Modules/Sensors/values.swift",
"chars": 33490,
"preview": "//\n// values.swift\n// Sensors\n//\n// Created by Serhiy Mytrovtsiy on 17/06/2020.\n// Using Swift 5.0.\n// Running on m"
},
{
"path": "README.md",
"chars": 7583,
"preview": "# Stats\n\n<a href=\"https://github.com/exelban/stats/releases\"><p align=\"center\"><img src=\"https://github.com/exelban/stat"
},
{
"path": "SMC/Helper/Info.plist",
"chars": 889,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "SMC/Helper/Launchd.plist",
"chars": 345,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "SMC/Helper/main.swift",
"chars": 10843,
"preview": "//\n// main.swift\n// Helper\n//\n// Created by Serhiy Mytrovtsiy on 17/11/2022\n// Using Swift 5.0\n// Running on macOS "
},
{
"path": "SMC/Helper/protocol.swift",
"chars": 696,
"preview": "//\n// protocol.swift\n// Helper\n//\n// Created by Serhiy Mytrovtsiy on 17/11/2022\n// Using Swift 5.0\n// Running on ma"
},
{
"path": "SMC/Makefile",
"chars": 337,
"preview": ".PHONY: build\n.SILENT: build\n\nbuild:\n\trm -rf ./build\n\n\txcodebuild \\\n\t\t-project ../Stats.xcodeproj \\\n \t\t-scheme SMC \\\n "
},
{
"path": "SMC/main.swift",
"chars": 5200,
"preview": "//\n// main.swift\n// SMC\n//\n// Created by Serhiy Mytrovtsiy on 25/05/2021.\n// Using Swift 5.0.\n// Running on macOS 1"
},
{
"path": "SMC/smc.swift",
"chars": 25580,
"preview": "//\n// smc.swift\n// SMC\n//\n// Created by Serhiy Mytrovtsiy on 25/05/2021.\n// Using Swift 5.0.\n// Running on macOS 10"
},
{
"path": "Stats/AppDelegate.swift",
"chars": 4211,
"preview": "//\n// AppDelegate.swift\n// Stats\n//\n// Created by Serhiy Mytrovtsiy on 28.05.2019.\n// Copyright © 2019 Serhiy Mytrov"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1299,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"icon_16x16.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\",\n \"size\" : "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/ac_unit.imageset/Contents.json",
"chars": 508,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"outline_ac_unit_black_18pt_1x.png\",\n \"idiom\" : \"universal\",\n \"scale"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/apps.imageset/Contents.json",
"chars": 502,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_apps_white_24pt_1x.png\",\n \"idiom\" : \"universal\",\n \"scale\" "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/bug.imageset/Contents.json",
"chars": 520,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_bug_report_white_24pt_1x.png\",\n \"idiom\" : \"universal\",\n \"s"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/Contents.json",
"chars": 302,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"imac.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/Contents.json",
"chars": 305,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"imacPro.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macMini.imageset/Contents.json",
"chars": 305,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"macMini.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macMini2020.imageset/Contents.json",
"chars": 309,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"macMini2020.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macMini2024.imageset/Contents.json",
"chars": 309,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"macMini2024.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macPro.imageset/Contents.json",
"chars": 304,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"macPro.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macPro2019.imageset/Contents.json",
"chars": 310,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"mac pro 2019.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macStudio.imageset/Contents.json",
"chars": 307,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"macStudio.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/Contents.json",
"chars": 308,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"macbookAir.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macbookAir4thGen.imageset/Contents.json",
"chars": 308,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"macbookAir.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macbookNeo.imageset/Contents.json",
"chars": 308,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"macbookNeo.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macbookPro.imageset/Contents.json",
"chars": 308,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"macbookPro.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/devices/macbookPro5thGen.imageset/Contents.json",
"chars": 308,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"macbookPro.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/donate.imageset/Contents.json",
"chars": 448,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"donate@1x.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/high-battery.imageset/Contents.json",
"chars": 310,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"high-battery.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/low-battery.imageset/Contents.json",
"chars": 309,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"low-battery.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/pause.imageset/Contents.json",
"chars": 502,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"outline_pause_white_24pt_1x.png\",\n \"idiom\" : \"universal\",\n \"scale\" "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/power.imageset/Contents.json",
"chars": 544,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_power_settings_new_white_24pt_1x.png\",\n \"idiom\" : \"universal\",\n"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/record.imageset/Contents.json",
"chars": 550,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_radio_button_checked_black_20pt_1x.png\",\n \"idiom\" : \"universal\""
},
{
"path": "Stats/Supporting Files/Assets.xcassets/resume.imageset/Contents.json",
"chars": 520,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_play_arrow_white_24pt_1x.png\",\n \"idiom\" : \"universal\",\n \"s"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/settings.imageset/Contents.json",
"chars": 514,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_settings_white_24pt_1x.png\",\n \"idiom\" : \"universal\",\n \"sca"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/stop.imageset/Contents.json",
"chars": 523,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"baseline_stop_circle_black_20pt_1x.png\",\n \"idiom\" : \"universal\",\n \""
},
{
"path": "Stats/Supporting Files/Assets.xcassets/support/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/support/github.imageset/Contents.json",
"chars": 373,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"github.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/support/ko-fi.imageset/Contents.json",
"chars": 303,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"ko-fi.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n "
},
{
"path": "Stats/Supporting Files/Assets.xcassets/support/patreon.imageset/Contents.json",
"chars": 305,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"patreon.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n"
},
{
"path": "Stats/Supporting Files/Assets.xcassets/support/paypal.imageset/Contents.json",
"chars": 304,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"paypal.png\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n "
}
]
// ... and 83 more files (download for full content)
About this extraction
This page contains the full source code of the exelban/stats GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 283 files (17.7 MB), approximately 660.7k tokens, and a symbol index with 73 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.