From 376a64c42d9ed1a07ab8eb60b062f5f79fba28bf Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Fri, 5 Jul 2024 19:47:18 +0200 Subject: [PATCH] feat: added widget for the RAM module --- Modules/RAM/main.swift | 9 +++ Modules/RAM/widget.swift | 111 ++++++++++++++++++++++++++++++++ Stats.xcodeproj/project.pbxproj | 6 +- Widgets/widgets.swift | 2 + 4 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 Modules/RAM/widget.swift diff --git a/Modules/RAM/main.swift b/Modules/RAM/main.swift index ea7b6e97..69663adf 100644 --- a/Modules/RAM/main.swift +++ b/Modules/RAM/main.swift @@ -11,6 +11,7 @@ import Cocoa import Kit +import WidgetKit public struct RAM_Usage: Codable { var total: Double @@ -81,6 +82,8 @@ public class RAM: Module { return color.additional as! NSColor } + private var userDefaults: UserDefaults? = UserDefaults(suiteName: "eu.exelban.Stats.widgets") + public init() { self.settingsView = Settings(.RAM) self.popupView = Popup(.RAM) @@ -132,6 +135,12 @@ public class RAM: Module { self.portalView.callback(value) self.notificationsView.loadCallback(value) + if #available(macOS 11.0, *) { + guard let blobData = try? JSONEncoder().encode(value) else { return } + self.userDefaults?.set(blobData, forKey: "RAM@UsageReader") + WidgetCenter.shared.reloadTimelines(ofKind: RAM_entry.kind) + } + let total: Double = value.total == 0 ? 1 : value.total self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: SWidget) in switch w.item { diff --git a/Modules/RAM/widget.swift b/Modules/RAM/widget.swift new file mode 100644 index 00000000..68612a4a --- /dev/null +++ b/Modules/RAM/widget.swift @@ -0,0 +1,111 @@ +// +// widget.swift +// RAM +// +// Created by Serhiy Mytrovtsiy on 03/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 RAM_entry: TimelineEntry { + public static let kind = "RAMWidget" + public static var snapshot: RAM_entry = RAM_entry() + + public var date: Date { + Calendar.current.date(byAdding: .second, value: 5, to: Date())! + } + public var value: RAM_Usage? = nil +} + +@available(macOS 11.0, *) +public struct Provider: TimelineProvider { + public typealias Entry = RAM_entry + + private let userDefaults: UserDefaults? = UserDefaults(suiteName: "eu.exelban.Stats.widgets") + + public func placeholder(in context: Context) -> RAM_entry { + RAM_entry() + } + + public func getSnapshot(in context: Context, completion: @escaping (RAM_entry) -> Void) { + completion(RAM_entry.snapshot) + } + + public func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + var entry = RAM_entry() + if let raw = userDefaults?.data(forKey: "RAM@UsageReader"), let load = try? JSONDecoder().decode(RAM_Usage.self, from: raw) { + entry.value = load + } + let entries: [RAM_entry] = [entry] + completion(Timeline(entries: entries, policy: .atEnd)) + } +} + +@available(macOS 14.0, *) +public struct RAMWidget: Widget { + var usedColor: Color = Color(nsColor: NSColor.systemBlue) + var freeColor: Color = Color(nsColor: NSColor.lightGray) + + public init() {} + + public var body: some WidgetConfiguration { + StaticConfiguration(kind: RAM_entry.kind, provider: Provider()) { entry in + VStack(spacing: 10) { + if let value = entry.value { + HStack { + Chart { + SectorMark(angle: .value(localizedString("Used"), value.used/value.total), innerRadius: .ratio(0.8)).foregroundStyle(self.usedColor) + SectorMark(angle: .value(localizedString("Free"), 1-(value.used/value.total)), 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((value.used/value.total)*100))%") + .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(Units(bytes: Int64(value.used)).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(Units(bytes: Int64(value.free)).getReadableMemory()) + } + HStack { + Text(localizedString("Pressure level")).font(.system(size: 12, weight: .regular)).foregroundColor(.secondary) + Spacer() + Text("\(value.rawPressureLevel)") + } + } + } else { + Text("No data") + } + } + .containerBackground(for: .widget) { + Color.clear + } + } + .configurationDisplayName("RAM widget") + .description("Displays RAM stats") + .supportedFamilies([.systemSmall]) + } +} diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index bc3c66b0..bd7d21f4 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 5C7C1DF42C29A3A00060387D /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C1DF32C29A3A00060387D /* notifications.swift */; }; 5C8E001029269C7F0027C75A /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; }; 5CA518382B543FE600EBCCC4 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA518372B543FE600EBCCC4 /* portal.swift */; }; + 5CB3878A2C35A7110030459D /* widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB387892C35A7110030459D /* widget.swift */; }; 5CD342F42B2F2FB700225631 /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CD342F32B2F2FB700225631 /* notifications.swift */; }; 5CE7E78C2C318512006BC92C /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE7E78B2C318512006BC92C /* WidgetKit.framework */; }; 5CE7E78E2C318512006BC92C /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE7E78D2C318512006BC92C /* SwiftUI.framework */; }; @@ -122,7 +123,6 @@ 9A46BF8A266D7D00001A1117 /* smc in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9ADE6FD8265D032100D2FBA8 /* smc */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 9A46C05F266D85F8001A1117 /* smc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE7038265D059000D2FBA8 /* smc.swift */; }; 9A46C06B266D8602001A1117 /* smc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE7038265D059000D2FBA8 /* smc.swift */; }; - 9A46C077266D8606001A1117 /* smc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE7038265D059000D2FBA8 /* smc.swift */; }; 9A53EBF924EAFA5200648841 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A53EBF824EAFA5200648841 /* settings.swift */; }; 9A53EBFB24EB041E00648841 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A53EBFA24EB041E00648841 /* popup.swift */; }; 9A58DE9E24B363D800716A9F /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58DE9D24B363D800716A9F /* popup.swift */; }; @@ -496,6 +496,7 @@ 5C7C1DF32C29A3A00060387D /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; }; 5C9F90A02A76B30500D41748 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; }; 5CA518372B543FE600EBCCC4 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; + 5CB387892C35A7110030459D /* widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = widget.swift; sourceTree = ""; }; 5CD342F32B2F2FB700225631 /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; }; 5CE7E78A2C318512006BC92C /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 5CE7E78B2C318512006BC92C /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; @@ -1072,6 +1073,7 @@ 5C23BC0729A03D1200DBA990 /* portal.swift */, 9A953A1324B9D22D0038EF4B /* settings.swift */, 5CF221162B1F4ACB006C583F /* notifications.swift */, + 5CB387892C35A7110030459D /* widget.swift */, 9A81C7592449A41400825D92 /* Info.plist */, 9AF9EE192464A7B3005D2270 /* config.plist */, ); @@ -2042,6 +2044,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5CB3878A2C35A7110030459D /* widget.swift in Sources */, 9A81C76A2449A43600825D92 /* readers.swift in Sources */, 9AA64260244B274200416A33 /* popup.swift in Sources */, 5C23BC0829A03D1200DBA990 /* portal.swift in Sources */, @@ -2069,7 +2072,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 9A46C077266D8606001A1117 /* smc.swift in Sources */, 9A97CEF1253733D200742D8F /* readers.swift in Sources */, 9A97CEF6253733E400742D8F /* popup.swift in Sources */, 5C3068732C32E83200B05EFA /* widget.swift in Sources */, diff --git a/Widgets/widgets.swift b/Widgets/widgets.swift index 0cf53dc2..cc71c870 100644 --- a/Widgets/widgets.swift +++ b/Widgets/widgets.swift @@ -12,10 +12,12 @@ import SwiftUI import CPU +import RAM @main struct WidgetsBundle: WidgetBundle { var body: some Widget { CPUWidget() + RAMWidget() } }