From 71207f29fcc960284335f6c895a0e69a76b83287 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Tue, 16 Jul 2024 20:17:43 +0200 Subject: [PATCH] feat: added macOS widget for Disk module --- Modules/Disk/main.swift | 8 +++ Modules/Disk/widget.swift | 106 ++++++++++++++++++++++++++++++++ Modules/GPU/widget.swift | 12 ++++ Stats.xcodeproj/project.pbxproj | 32 ++-------- Widgets/widgets.swift | 2 + 5 files changed, 132 insertions(+), 28 deletions(-) create mode 100644 Modules/Disk/widget.swift create mode 100644 Modules/GPU/widget.swift diff --git a/Modules/Disk/main.swift b/Modules/Disk/main.swift index 1f7746c3..a4fc1b05 100644 --- a/Modules/Disk/main.swift +++ b/Modules/Disk/main.swift @@ -11,6 +11,7 @@ import Cocoa import Kit +import WidgetKit public struct stats: Codable { var read: Int64 = 0 @@ -190,6 +191,7 @@ public class Disk: Module { private var processReader: ProcessReader? private var selectedDisk: String = "" + private var userDefaults: UserDefaults? = UserDefaults(suiteName: "eu.exelban.Stats.widgets") public init() { super.init( @@ -266,6 +268,12 @@ public class Disk: Module { default: break } } + + if #available(macOS 11.0, *) { + guard let blobData = try? JSONEncoder().encode(d) else { return } + self.userDefaults?.set(blobData, forKey: "Disk@CapacityReader") + WidgetCenter.shared.reloadTimelines(ofKind: Disk_entry.kind) + } } private func activityCallback(_ value: Disks) { diff --git a/Modules/Disk/widget.swift b/Modules/Disk/widget.swift new file mode 100644 index 00000000..0853e887 --- /dev/null +++ b/Modules/Disk/widget.swift @@ -0,0 +1,106 @@ +// +// widget.swift +// Disk +// +// Created by Serhiy Mytrovtsiy on 16/07/2024 +// Using Swift 5.0 +// Running on macOS 14.5 +// +// Copyright © 2024 Serhiy Mytrovtsiy. All rights reserved. +// + +import SwiftUI +import WidgetKit +import Charts +import Kit + +public struct Disk_entry: TimelineEntry { + public static let kind = "DiskWidget" + public static var snapshot: Disk_entry = Disk_entry() + + public var date: Date { + Calendar.current.date(byAdding: .second, value: 5, to: Date())! + } + public var value: drive? = nil +} + +@available(macOS 11.0, *) +public struct Provider: TimelineProvider { + public typealias Entry = Disk_entry + + private let userDefaults: UserDefaults? = UserDefaults(suiteName: "eu.exelban.Stats.widgets") + + public func placeholder(in context: Context) -> Disk_entry { + Disk_entry() + } + + public func getSnapshot(in context: Context, completion: @escaping (Disk_entry) -> Void) { + completion(Disk_entry.snapshot) + } + + public func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + var entry = Disk_entry() + if let raw = userDefaults?.data(forKey: "Disk@CapacityReader"), let load = try? JSONDecoder().decode(drive.self, from: raw) { + entry.value = load + } + let entries: [Disk_entry] = [entry] + completion(Timeline(entries: entries, policy: .atEnd)) + } +} + +@available(macOS 14.0, *) +public struct DiskWidget: Widget { + var usedColor: Color = Color(nsColor: NSColor.systemBlue) + var freeColor: Color = Color(nsColor: NSColor.lightGray) + + public init() {} + + public var body: some WidgetConfiguration { + StaticConfiguration(kind: Disk_entry.kind, provider: Provider()) { entry in + VStack(spacing: 10) { + if let value = entry.value { + HStack { + Chart { + SectorMark(angle: .value(localizedString("Used"), (100*(value.size-value.free))/value.size), innerRadius: .ratio(0.8)).foregroundStyle(self.usedColor) + SectorMark(angle: .value(localizedString("Free"), (100*value.free)/value.size), innerRadius: .ratio(0.8)).foregroundStyle(self.freeColor) + } + .frame(maxWidth: .infinity, maxHeight: 84) + .chartLegend(.hidden) + .chartBackground { chartProxy in + GeometryReader { geometry in + if let anchor = chartProxy.plotFrame { + let frame = geometry[anchor] + Text("\(Int((100*(value.size-value.free))/value.size))%") + .font(.system(size: 16, weight: .regular)) + .position(x: frame.midX, y: frame.midY) + } + } + } + } + VStack(spacing: 3) { + HStack { + Rectangle().fill(self.usedColor).frame(width: 12, height: 12).cornerRadius(2) + Text(localizedString("Used")).font(.system(size: 12, weight: .regular)).foregroundColor(.secondary) + Spacer() + Text(DiskSize(value.size - value.free).getReadableMemory()) + } + HStack { + Rectangle().fill(self.freeColor).frame(width: 12, height: 12).cornerRadius(2) + Text(localizedString("Free")).font(.system(size: 12, weight: .regular)).foregroundColor(.secondary) + Spacer() + Text(DiskSize(value.free).getReadableMemory()) + } + } + } else { + Text("No data") + } + } + .containerBackground(for: .widget) { + Color.clear + } + } + .configurationDisplayName("Disk widget") + .description("Displays Disk stats") + .supportedFamilies([.systemSmall]) + } +} diff --git a/Modules/GPU/widget.swift b/Modules/GPU/widget.swift new file mode 100644 index 00000000..361ebaf2 --- /dev/null +++ b/Modules/GPU/widget.swift @@ -0,0 +1,12 @@ +// +// widget.swift +// GPU +// +// Created by Serhiy Mytrovtsiy on 16/07/2024 +// Using Swift 5.0 +// Running on macOS 14.5 +// +// Copyright © 2024 Serhiy Mytrovtsiy. All rights reserved. +// + +import Foundation diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 4da12210..ba6510e9 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 5C044F7A2B3DE6F3005F6951 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C044F792B3DE6F3005F6951 /* portal.swift */; }; 5C0A2A8A292A5B4D009B4C1F /* SMJobBlessUtil.py in Resources */ = {isa = PBXBuildFile; fileRef = 5C0A2A89292A5B4D009B4C1F /* SMJobBlessUtil.py */; }; + 5C0A9CA22C467AA300EE6A89 /* widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0A9CA12C467AA300EE6A89 /* widget.swift */; }; 5C21D80B296C7B81005BA16D /* CombinedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C21D80A296C7B81005BA16D /* CombinedView.swift */; }; 5C2229A329CCB3C400F00E69 /* Clock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C22299D29CCB3C400F00E69 /* Clock.framework */; }; 5C2229A429CCB3C400F00E69 /* Clock.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5C22299D29CCB3C400F00E69 /* Clock.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -45,8 +46,6 @@ 5C4E8BC72B6EF98800F148B6 /* DB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4E8BC62B6EF98800F148B6 /* DB.swift */; }; 5C4E8BE92B71031A00F148B6 /* Kit.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C4E8BE82B7102A700F148B6 /* Kit.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5C5647F82A3F6B100098FFE9 /* Telemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5647F72A3F6B100098FFE9 /* Telemetry.swift */; }; - 5C60A4A32C32CE0600E02734 /* CPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A97CECA2537331B00742D8F /* CPU.framework */; }; - 5C60A4A42C32CE0600E02734 /* CPU.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A97CECA2537331B00742D8F /* CPU.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5C621D822B4770D6004ED7AF /* process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C621D812B4770D6004ED7AF /* process.swift */; }; 5C7C1DF42C29A3A00060387D /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C1DF32C29A3A00060387D /* notifications.swift */; }; 5C8E001029269C7F0027C75A /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; }; @@ -206,13 +205,6 @@ remoteGlobalIDString = 9A2846F62666A9CC00EC1F6D; remoteInfo = Kit; }; - 5C60A4A52C32CE0600E02734 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 9A1410ED229E721100D29793 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 9A97CEC92537331B00742D8F; - remoteInfo = CPU; - }; 5CE7E79A2C318513006BC92C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9A1410ED229E721100D29793 /* Project object */; @@ -349,17 +341,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 5C60A4A72C32CE0600E02734 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 5C60A4A42C32CE0600E02734 /* CPU.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; 5CE7E79D2C318513006BC92C /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -455,6 +436,7 @@ 4921436D25319699000A1C47 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; 5C044F792B3DE6F3005F6951 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; 5C0A2A89292A5B4D009B4C1F /* SMJobBlessUtil.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = SMJobBlessUtil.py; sourceTree = ""; }; + 5C0A9CA12C467AA300EE6A89 /* widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = widget.swift; sourceTree = ""; }; 5C0E550A2B5D545A00FFF1FB /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; 5C21D80A296C7B81005BA16D /* CombinedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedView.swift; sourceTree = ""; }; 5C22299D29CCB3C400F00E69 /* Clock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Clock.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -680,7 +662,6 @@ files = ( 5CE7E78E2C318512006BC92C /* SwiftUI.framework in Frameworks */, 5CE7E78C2C318512006BC92C /* WidgetKit.framework in Frameworks */, - 5C60A4A32C32CE0600E02734 /* CPU.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1217,6 +1198,7 @@ 5C23BC0F29A3B5AE00DBA990 /* portal.swift */, 9AB7FD7B246B48DB00387FDA /* settings.swift */, 5CF221182B1F8B90006C583F /* notifications.swift */, + 5C0A9CA12C467AA300EE6A89 /* widget.swift */, 9AF9EE0524648751005D2270 /* Info.plist */, 9AF9EE12246492E8005D2270 /* config.plist */, 5C2229BB29CF66B100F00E69 /* header.h */, @@ -1343,12 +1325,10 @@ 5CE7E7862C318512006BC92C /* Sources */, 5CE7E7872C318512006BC92C /* Frameworks */, 5CE7E7882C318512006BC92C /* Resources */, - 5C60A4A72C32CE0600E02734 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( - 5C60A4A62C32CE0600E02734 /* PBXTargetDependency */, ); name = WidgetsExtension; productName = WidgetsExtension; @@ -2152,6 +2132,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5C0A9CA22C467AA300EE6A89 /* widget.swift in Sources */, 9A5AF11B2469CE9B00684737 /* popup.swift in Sources */, 9AF9EE1124648ADC005D2270 /* readers.swift in Sources */, 5C23BC1029A3B5AE00DBA990 /* portal.swift in Sources */, @@ -2174,11 +2155,6 @@ target = 9A2846F62666A9CC00EC1F6D /* Kit */; targetProxy = 5C2229B229CDFBF600F00E69 /* PBXContainerItemProxy */; }; - 5C60A4A62C32CE0600E02734 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 9A97CEC92537331B00742D8F /* CPU */; - targetProxy = 5C60A4A52C32CE0600E02734 /* PBXContainerItemProxy */; - }; 5CE7E79B2C318513006BC92C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5CE7E7892C318512006BC92C /* WidgetsExtension */; diff --git a/Widgets/widgets.swift b/Widgets/widgets.swift index cc71c870..1939556a 100644 --- a/Widgets/widgets.swift +++ b/Widgets/widgets.swift @@ -13,11 +13,13 @@ import SwiftUI import CPU import RAM +import Disk @main struct WidgetsBundle: WidgetBundle { var body: some Widget { CPUWidget() RAMWidget() + DiskWidget() } }