From 4314de533c1f3444fa6a28057af2b58310a7e7dc Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Fri, 31 Mar 2023 20:17:36 +0200 Subject: [PATCH] feat: added Clock module (#929) --- Kit/extensions.swift | 16 ++ Modules/Clock/config.plist | 36 +++++ Modules/Clock/main.swift | 133 ++++++++++++++++ Modules/Clock/popup.swift | 215 +++++++++++++++++++++++++ Modules/Clock/settings.swift | 267 ++++++++++++++++++++++++++++++++ Modules/Disk/header.h | 12 ++ Stats.xcodeproj/project.pbxproj | 199 ++++++++++++++++++++++++ Stats/AppDelegate.swift | 4 +- 8 files changed, 881 insertions(+), 1 deletion(-) create mode 100644 Modules/Clock/config.plist create mode 100644 Modules/Clock/main.swift create mode 100644 Modules/Clock/popup.swift create mode 100644 Modules/Clock/settings.swift create mode 100644 Modules/Disk/header.h diff --git a/Kit/extensions.swift b/Kit/extensions.swift index 0c2d7466..e708f738 100644 --- a/Kit/extensions.swift +++ b/Kit/extensions.swift @@ -593,3 +593,19 @@ public extension Data { return withUnsafeBytes { $0.load(as: sockaddr_in.self) } } } + +public extension Date { + func convertToTimeZone(_ timeZone: TimeZone) -> Date { + return addingTimeInterval(TimeInterval(timeZone.secondsFromGMT(for: self) - TimeZone.current.secondsFromGMT(for: self))) + } +} + +public extension TimeZone { + init(fromUTC: String) { + if let utc = Int(fromUTC), let tz = TimeZone(secondsFromGMT: utc*3600) { + self = tz + } else { + self = TimeZone.current + } + } +} diff --git a/Modules/Clock/config.plist b/Modules/Clock/config.plist new file mode 100644 index 00000000..ed007513 --- /dev/null +++ b/Modules/Clock/config.plist @@ -0,0 +1,36 @@ + + + + + Name + Clock + State + + Symbol + clock.fill + Widgets + + label + + Default + + Title + CLK + Order + 0 + + sensors + + Default + + Preview + + Values + 23.03.2023 13:45 + + Order + 1 + + + + diff --git a/Modules/Clock/main.swift b/Modules/Clock/main.swift new file mode 100644 index 00000000..ce7dd125 --- /dev/null +++ b/Modules/Clock/main.swift @@ -0,0 +1,133 @@ +// +// main.swift +// Clock +// +// Created by Serhiy Mytrovtsiy on 23/03/2023 +// Using Swift 5.0 +// Running on macOS 13.2 +// +// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved. +// + +import Foundation +import Kit + +public struct Clock_t: Codable { + public var id: String = UUID().uuidString + public var enabled: Bool = true + + public var name: String + public var format: String + public var tz: String + + public var value: Date? = nil + + public func formatted() -> String { + let formatter = DateFormatter() + formatter.dateFormat = self.format + formatter.timeZone = TimeZone(fromUTC: self.tz) + return formatter.string(from: self.value ?? Date()) + } +} + +internal class ClockReader: Reader { + public override func read() { + self.callback(Date()) + } +} + +public class Clock: Module { + private let popupView: Popup = Popup() + private let settingsView: Settings = Settings() + + private var reader: ClockReader = ClockReader() + + private var list: [Clock_t] { + if let objects = Store.shared.data(key: "\(Clock.title)_list") { + let decoder = JSONDecoder() + if let objectsDecoded = try? decoder.decode(Array.self, from: objects) as [Clock_t] { + return objectsDecoded + } + } + return [Clock.local] + } + + public init() { + super.init( + popup: self.popupView, + settings: self.settingsView + ) + guard self.available else { return } + + self.reader.callbackHandler = { [unowned self] value in + guard let value else { return } + self.callback(value) + } + + self.addReader(self.reader) + self.reader.readyCallback = { [unowned self] in + self.readyHandler() + } + } + + private func callback(_ value: Date) { + var clocks: [Clock_t] = self.list.filter({ $0.enabled }) + var list: [Stack_t] = [] + + for (i, c) in clocks.enumerated() { + clocks[i].value = value + list.append(Stack_t(key: c.name, value: clocks[i].formatted())) + } + + DispatchQueue.main.async(execute: { + self.popupView.callback(clocks) + }) + + self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: Widget) in + switch w.item { + case let widget as SensorsWidget: widget.setValues(list) + default: break + } + } + } +} + +extension Clock { + static let title: String = "Clock" + static var local: Clock_t { + Clock_t(name: localizedString("Local time"), format: "YYYY-MM-DD HH:mm:ss", tz: "local") + } + static var zones: [KeyValue_t] { + [ + KeyValue_t(key: "local", value: "Local"), + KeyValue_t(key: "separator", value: "separator"), + KeyValue_t(key: "-12", value: "UTC-12:00"), + KeyValue_t(key: "-11", value: "UTC-11:00"), + KeyValue_t(key: "-10", value: "UTC-10:00"), + KeyValue_t(key: "-9", value: "UTC-9:00"), + KeyValue_t(key: "-8", value: "UTC-8:00"), + KeyValue_t(key: "-7", value: "UTC-7:00"), + KeyValue_t(key: "-6", value: "UTC-6:00"), + KeyValue_t(key: "-5", value: "UTC-5:00"), + KeyValue_t(key: "-4", value: "UTC-4:00"), + KeyValue_t(key: "-3", value: "UTC-3:00"), + KeyValue_t(key: "-2", value: "UTC-2:00"), + KeyValue_t(key: "-1", value: "UTC-1:00"), + KeyValue_t(key: "0", value: "UTC"), + KeyValue_t(key: "1", value: "UTC+1:00"), + KeyValue_t(key: "2", value: "UTC+2:00"), + KeyValue_t(key: "3", value: "UTC+3:00"), + KeyValue_t(key: "4", value: "UTC+4:00"), + KeyValue_t(key: "5", value: "UTC+5:00"), + KeyValue_t(key: "6", value: "UTC+6:00"), + KeyValue_t(key: "7", value: "UTC+7:00"), + KeyValue_t(key: "8", value: "UTC+8:00"), + KeyValue_t(key: "9", value: "UTC+9:00"), + KeyValue_t(key: "10", value: "UTC+10:00"), + KeyValue_t(key: "11", value: "UTC+11:00"), + KeyValue_t(key: "12", value: "UTC+12:00"), + KeyValue_t(key: "13", value: "UTC+13:00"), + KeyValue_t(key: "14", value: "UTC+14:00") + ] + } +} diff --git a/Modules/Clock/popup.swift b/Modules/Clock/popup.swift new file mode 100644 index 00000000..f349de7e --- /dev/null +++ b/Modules/Clock/popup.swift @@ -0,0 +1,215 @@ +// +// popup.swift +// Clock +// +// Created by Serhiy Mytrovtsiy on 24/03/2023 +// Using Swift 5.0 +// Running on macOS 13.2 +// +// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import Kit + +internal class Popup: PopupWrapper { + public init() { + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) + + self.orientation = .vertical + self.spacing = Constants.Popup.margins + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + internal func callback(_ list: [Clock_t]) { + defer { + let h = self.arrangedSubviews.map({ $0.bounds.height + self.spacing }).reduce(0, +) - self.spacing + if h > 0 && self.frame.size.height != h { + self.setFrameSize(NSSize(width: self.frame.width, height: h)) + self.sizeCallback?(self.frame.size) + } + } + + var views = self.subviews.filter{ $0 is ClockView }.compactMap{ $0 as? ClockView } + if list.count < views.count && !views.isEmpty { + views.forEach{ $0.removeFromSuperview() } + views = [] + } + + list.forEach { (c: Clock_t) in + if let view = views.first(where: { $0.clock.id == c.id }) { + view.update(c) + } else { + self.addArrangedSubview(ClockView(width: self.frame.width, clock: c)) + } + } + } +} + +private class ClockView: NSStackView { + public var clock: Clock_t + + open override var intrinsicContentSize: CGSize { + return CGSize(width: self.bounds.width, height: self.bounds.height) + } + + private var ready: Bool = false + + private let clockView: ClockChart = ClockChart() + private let nameField: NSTextField = TextView() + private let timeField: NSTextField = TextView() + + init(width: CGFloat, clock: Clock_t) { + self.clock = clock + + super.init(frame: NSRect(x: 0, y: 0, width: width, height: 44)) + + self.orientation = .horizontal + self.spacing = 5 + self.edgeInsets = NSEdgeInsets( + top: 5, + left: 5, + bottom: 5, + right: 5 + ) + self.wantsLayer = true + self.layer?.cornerRadius = 2 + + self.clockView.widthAnchor.constraint(equalToConstant: 34).isActive = true + + let container: NSStackView = NSStackView() + container.orientation = .vertical + container.spacing = 2 + container.distribution = .fillEqually + container.alignment = .left + + self.nameField.font = NSFont.systemFont(ofSize: 11, weight: .light) + self.setTZ() + self.nameField.cell?.truncatesLastVisibleLine = true + + self.timeField.font = NSFont.systemFont(ofSize: 13, weight: .regular) + self.timeField.stringValue = clock.formatted() + self.timeField.cell?.truncatesLastVisibleLine = true + + container.addArrangedSubview(self.nameField) + container.addArrangedSubview(self.timeField) + + self.addArrangedSubview(self.clockView) + self.addArrangedSubview(container) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateLayer() { + self.layer?.backgroundColor = isDarkMode ? NSColor(hexString: "#111111", alpha: 0.25).cgColor : NSColor(hexString: "#f5f5f5", alpha: 1).cgColor + } + + private func setTZ() { + self.nameField.stringValue = "\(self.clock.name)" + if let tz = Clock.zones.first(where: { $0.key == self.clock.tz }) { + self.nameField.stringValue += " (\(tz.value))" + } + } + + public func update(_ newClock: Clock_t) { + if self.clock.tz != newClock.tz { + self.clock = newClock + self.setTZ() + } + + if (self.window?.isVisible ?? false) || !self.ready { + self.timeField.stringValue = newClock.formatted() + if let value = newClock.value { + self.clockView.setValue(value.convertToTimeZone(TimeZone(fromUTC: newClock.tz))) + } + self.ready = true + } + } +} + +private class ClockChart: NSView { + private var color: NSColor = Color.systemAccent.additional as! NSColor + + private let calendar = Calendar.current + private var hour: Int! + private var minute: Int! + private var second: Int! + + private let hourLayer = CALayer() + private let minuteLayer = CALayer() + private let secondsLayer = CALayer() + private let pinLayer = CAShapeLayer() + + override init(frame: CGRect = NSRect.zero) { + super.init(frame: frame) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + guard (self.hour != nil), (self.minute != nil), (self.second != nil) else { return } + + let context = NSGraphicsContext.current!.cgContext + context.saveGState() + context.setFillColor(self.color.cgColor) + context.fillEllipse(in: dirtyRect) + context.restoreGState() + + let anchor = CGPoint(x: 0.5, y: 0) + let center = CGPoint(x: dirtyRect.size.width / 2, y: dirtyRect.size.height / 2) + + let hourAngle: CGFloat = CGFloat(Double(hour) * (360.0 / 12.0)) + CGFloat(Double(minute) * (1.0 / 60.0) * (360.0 / 12.0)) + let minuteAngle: CGFloat = CGFloat(minute) * CGFloat(360.0 / 60.0) + let secondsAngle: CGFloat = CGFloat(self.second) * CGFloat(360.0 / 60.0) + + self.hourLayer.backgroundColor = NSColor.white.cgColor + self.hourLayer.anchorPoint = anchor + self.hourLayer.position = center + self.hourLayer.bounds = CGRect(x: 0, y: 0, width: 3, height: dirtyRect.size.width / 2 - 7) + self.hourLayer.transform = CATransform3DMakeRotation(-hourAngle / 180 * CGFloat(Double.pi), 0, 0, 1) + self.layer?.addSublayer(self.hourLayer) + + self.minuteLayer.backgroundColor = NSColor.white.cgColor + self.minuteLayer.anchorPoint = anchor + self.minuteLayer.position = center + self.minuteLayer.bounds = CGRect(x: 0, y: 0, width: 2, height: dirtyRect.size.width / 2 - 4) + self.minuteLayer.transform = CATransform3DMakeRotation(-minuteAngle / 180 * CGFloat(Double.pi), 0, 0, 1) + self.layer?.addSublayer(self.minuteLayer) + + self.secondsLayer.backgroundColor = NSColor.red.cgColor + self.secondsLayer.anchorPoint = anchor + self.secondsLayer.position = center + self.secondsLayer.bounds = CGRect(x: 0, y: 0, width: 1, height: dirtyRect.size.width / 2 - 2) + self.secondsLayer.transform = CATransform3DMakeRotation(-secondsAngle / 180 * CGFloat(Double.pi), 0, 0, 1) + self.layer?.addSublayer(self.secondsLayer) + + self.pinLayer.fillColor = NSColor.white.cgColor + self.pinLayer.anchorPoint = anchor + self.pinLayer.path = CGMutablePath(roundedRect: CGRect( + x: center.x - 3 / 2, + y: center.y - 3 / 2, + width: 3, + height: 3 + ), cornerWidth: 4, cornerHeight: 4, transform: nil) + self.layer?.addSublayer(self.pinLayer) + } + + public func setValue(_ value: Date) { + self.hour = self.calendar.component(.hour, from: value) + self.minute = self.calendar.component(.minute, from: value) + self.second = self.calendar.component(.second, from: value) + + DispatchQueue.main.async(execute: { + self.display() + }) + } +} diff --git a/Modules/Clock/settings.swift b/Modules/Clock/settings.swift new file mode 100644 index 00000000..ae9545c5 --- /dev/null +++ b/Modules/Clock/settings.swift @@ -0,0 +1,267 @@ +// +// settings.swift +// Clock +// +// Created by Serhiy Mytrovtsiy on 24/03/2023 +// Using Swift 5.0 +// Running on macOS 13.2 +// +// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import Kit + +let nameColumnID = NSUserInterfaceItemIdentifier(rawValue: "name") +let formatColumnID = NSUserInterfaceItemIdentifier(rawValue: "format") +let tzColumnID = NSUserInterfaceItemIdentifier(rawValue: "tz") +let statusColumnID = NSUserInterfaceItemIdentifier(rawValue: "status") + +internal class Settings: NSStackView, Settings_v, NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate { + public var callback: (() -> Void) = {} + + private var list: [Clock_t] { + get { + if let objects = Store.shared.data(key: "\(Clock.title)_list") { + let decoder = JSONDecoder() + if let objectsDecoded = try? decoder.decode(Array.self, from: objects) as [Clock_t] { + return objectsDecoded + } + } + return [Clock.local] + } + set { + if newValue.isEmpty { + Store.shared.remove("\(Clock.title)_list") + } else { + let encoder = JSONEncoder() + if let encoded = try? encoder.encode(newValue){ + Store.shared.set(key: "\(Clock.title)_list", value: encoded) + } + } + } + } + + private var selectedRow: Int = -1 + + private let scrollView = NSScrollView() + private let tableView = NSTableView() + private var footerView: NSStackView? = nil + private var deleteButton: NSButton? = nil + + public init() { + super.init(frame: NSRect.zero) + + self.orientation = .vertical + self.distribution = .gravityAreas + self.edgeInsets = NSEdgeInsets( + top: Constants.Settings.margin, + left: Constants.Settings.margin, + bottom: Constants.Settings.margin, + right: Constants.Settings.margin + ) + self.spacing = 0 + + 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.allowsMultipleSelection = false + self.tableView.focusRingType = .none + self.tableView.gridColor = .gridColor + self.tableView.columnAutoresizingStyle = .firstColumnOnlyAutoresizingStyle + self.tableView.allowsColumnResizing = false + self.tableView.gridStyleMask = [.solidVerticalGridLineMask, .solidHorizontalGridLineMask] + self.tableView.usesAlternatingRowBackgroundColors = true + if #available(macOS 11.0, *) { + self.tableView.style = .plain + } + self.tableView.rowHeight = 27 + + let nameColumn = NSTableColumn(identifier: nameColumnID) + nameColumn.headerCell.title = localizedString("Name") + nameColumn.headerCell.alignment = .center + let formatColumn = NSTableColumn(identifier: formatColumnID) + formatColumn.headerCell.title = localizedString("Format") + formatColumn.headerCell.alignment = .center + formatColumn.width = 160 + let tzColumn = NSTableColumn(identifier: tzColumnID) + tzColumn.headerCell.title = localizedString("Time zone") + tzColumn.headerCell.alignment = .center + tzColumn.width = 132 + let statusColumn = NSTableColumn(identifier: statusColumnID) + statusColumn.headerCell.title = "" + statusColumn.width = 16 + + self.tableView.addTableColumn(nameColumn) + self.tableView.addTableColumn(formatColumn) + self.tableView.addTableColumn(tzColumn) + self.tableView.addTableColumn(statusColumn) + + let separator = NSBox() + separator.boxType = .separator + + self.addArrangedSubview(self.scrollView) + self.addArrangedSubview(separator) + self.addArrangedSubview(self.footer()) + + NSLayoutConstraint.activate([ + self.scrollView.heightAnchor.constraint(equalToConstant: 278) + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func footer() -> NSView { + let view = NSStackView() + view.heightAnchor.constraint(equalToConstant: 27).isActive = true + view.spacing = 0 + view.orientation = .horizontal + + var addButton: NSButton { + let btn = NSButton() + btn.widthAnchor.constraint(equalToConstant: 27).isActive = true + btn.heightAnchor.constraint(equalToConstant: 27).isActive = true + btn.bezelStyle = .regularSquare + btn.translatesAutoresizingMaskIntoConstraints = false + if #available(macOS 11.0, *) { + btn.image = iconFromSymbol(name: "plus", scale: .large) + } else { + btn.title = "Add" + } + btn.isBordered = false + btn.action = #selector(self.addNewClock) + btn.target = self + btn.toolTip = localizedString("Add new clock") + btn.focusRingType = .none + return btn + } + var deleteButton: NSButton { + let btn = NSButton() + btn.widthAnchor.constraint(equalToConstant: 27).isActive = true + btn.heightAnchor.constraint(equalToConstant: 27).isActive = true + btn.bezelStyle = .regularSquare + btn.translatesAutoresizingMaskIntoConstraints = false + if #available(macOS 11.0, *) { + btn.image = iconFromSymbol(name: "minus", scale: .large) + } else { + btn.title = "Add" + } + btn.isBordered = false + btn.action = #selector(self.deleteClock) + btn.target = self + btn.toolTip = localizedString("Delete selected clock") + btn.focusRingType = .none + return btn + } + self.deleteButton = deleteButton + + view.addArrangedSubview(addButton) + view.addArrangedSubview(NSView()) + + self.footerView = view + return view + } + + func load(widgets: [Kit.widget_t]) { + self.tableView.reloadData() + } + + func numberOfRows(in tableView: NSTableView) -> Int { + return self.list.count + } + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + guard let id = tableColumn?.identifier else { return nil } + let cell = NSTableCellView() + let item = self.list[row] + + switch id { + case nameColumnID, formatColumnID: + let text: NSTextField = NSTextField() + text.identifier = id + text.drawsBackground = false + text.isBordered = false + text.sizeToFit() + text.delegate = self + text.stringValue = id == nameColumnID ? item.name : item.format + text.translatesAutoresizingMaskIntoConstraints = false + + cell.addSubview(text) + text.widthAnchor.constraint(equalTo: cell.widthAnchor).isActive = true + text.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = true + case tzColumnID: + let select: NSPopUpButton = selectView(action: #selector(self.toggleTZ), items: Clock.zones, selected: item.tz) + select.identifier = NSUserInterfaceItemIdentifier("\(row)") + select.sizeToFit() + cell.addSubview(select) + case statusColumnID: + let button: NSButton = NSButton(frame: NSRect(x: 0, y: 5, width: 10, height: 10)) + button.identifier = NSUserInterfaceItemIdentifier("\(row)") + button.setButtonType(.switch) + button.state = item.enabled ? .on : .off + button.action = #selector(self.toggleClock) + button.title = "" + button.isBordered = false + button.isTransparent = false + button.target = self + button.sizeToFit() + cell.addSubview(button) + default: break + } + + return cell + } + + func controlTextDidChange(_ notification: Notification) { + if let textField = notification.object as? NSTextField, let id = textField.identifier { + let i = self.tableView.selectedRow + switch id { + case nameColumnID: + self.list[i].name = textField.stringValue + case formatColumnID: + self.list[i].format = textField.stringValue + default: return + } + } + } + + func tableViewSelectionDidChange(_ notification: Notification) { + if self.tableView.selectedRow == -1 { + self.deleteButton?.removeFromSuperview() + } else { + if let btn = self.deleteButton { + self.footerView?.addArrangedSubview(btn) + } + } + } + + @objc private func toggleTZ(_ sender: NSMenuItem) { + guard let key = sender.representedObject as? String, let id = sender.identifier, let i = Int(id.rawValue) else { return } + self.list[i].tz = key + } + + @objc private func toggleClock(_ sender: NSButton) { + guard let id = sender.identifier, let i = Int(id.rawValue) else { return } + self.list[i].enabled = sender.state == NSControl.StateValue.on + } + + @objc private func addNewClock(_ sender: Any) { + self.list.append(Clock_t(name: "Clock \(self.list.count)", format: Clock.local.format, tz: Clock.local.tz)) + self.tableView.reloadData() + } + @objc private func deleteClock(_ sender: Any) { + guard self.tableView.selectedRow != -1 else { return } + self.list.remove(at: self.tableView.selectedRow) + self.tableView.reloadData() + self.deleteButton?.removeFromSuperview() + } +} diff --git a/Modules/Disk/header.h b/Modules/Disk/header.h new file mode 100644 index 00000000..92c3bdde --- /dev/null +++ b/Modules/Disk/header.h @@ -0,0 +1,12 @@ +// +// Header.h +// Disk +// +// Created by Serhiy Mytrovtsiy on 25/03/2023 +// Using Swift 5.0 +// Running on macOS 13.2 +// +// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved. +// + +#include diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 34679b8f..89e58ca8 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -9,6 +9,14 @@ /* Begin PBXBuildFile section */ 5C0A2A8A292A5B4D009B4C1F /* SMJobBlessUtil.py in Resources */ = {isa = PBXBuildFile; fileRef = 5C0A2A89292A5B4D009B4C1F /* SMJobBlessUtil.py */; }; 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, ); }; }; + 5C2229A929CCB41900F00E69 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2229A829CCB41900F00E69 /* main.swift */; }; + 5C2229AB29CCB53E00F00E69 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5C2229AA29CCB53E00F00E69 /* config.plist */; }; + 5C2229AF29CDC08700F00E69 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2229AE29CDC08700F00E69 /* settings.swift */; }; + 5C2229B029CDFBF600F00E69 /* Kit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A2846F72666A9CC00EC1F6D /* Kit.framework */; }; + 5C2229B829CE3F3300F00E69 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2229B729CE3F3300F00E69 /* popup.swift */; }; + 5C2229BD29CF685A00F00E69 /* header.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C2229BB29CF66B100F00E69 /* header.h */; }; 5C23BC0229A0102500DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0129A0102500DBA990 /* portal.swift */; }; 5C23BC0429A014AC00DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0329A014AC00DBA990 /* portal.swift */; }; 5C23BC0829A03D1200DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0729A03D1200DBA990 /* portal.swift */; }; @@ -145,6 +153,20 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 5C2229A129CCB3C400F00E69 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5C22299C29CCB3C400F00E69; + remoteInfo = Clock; + }; + 5C2229B229CDFBF600F00E69 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A2846F62666A9CC00EC1F6D; + remoteInfo = Kit; + }; 9A11AAD4266FD77F000C1C05 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9A1410ED229E721100D29793 /* Project object */; @@ -310,6 +332,7 @@ dstSubfolderSpec = 10; files = ( 9AF9EE0A24648751005D2270 /* Disk.framework in Embed Frameworks */, + 5C2229A429CCB3C400F00E69 /* Clock.framework in Embed Frameworks */, 9A81C75E2449A41400825D92 /* RAM.framework in Embed Frameworks */, 9AE29ADD249A50350071B02D /* Sensors.framework in Embed Frameworks */, 9A11AAD7266FD77F000C1C05 /* Bluetooth.framework in Embed Frameworks */, @@ -355,6 +378,12 @@ 4921436D25319699000A1C47 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; 5C0A2A89292A5B4D009B4C1F /* SMJobBlessUtil.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = SMJobBlessUtil.py; 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; }; + 5C2229A829CCB41900F00E69 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 5C2229AA29CCB53E00F00E69 /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; }; + 5C2229AE29CDC08700F00E69 /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; }; + 5C2229B729CE3F3300F00E69 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; + 5C2229BB29CF66B100F00E69 /* header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = header.h; sourceTree = ""; }; 5C23BC0129A0102500DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; 5C23BC0329A014AC00DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; 5C23BC0729A03D1200DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; @@ -516,6 +545,14 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 5C22299A29CCB3C400F00E69 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C2229B029CDFBF600F00E69 /* Kit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5CFE492429264DF1000F2856 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -536,6 +573,7 @@ buildActionMask = 2147483647; files = ( 9AF9EE0924648751005D2270 /* Disk.framework in Frameworks */, + 5C2229A329CCB3C400F00E69 /* Clock.framework in Frameworks */, 9AE29ADC249A50350071B02D /* Sensors.framework in Frameworks */, 9ABFF8FD248BEBCB00C9041A /* Battery.framework in Frameworks */, 9A11AAD6266FD77F000C1C05 /* Bluetooth.framework in Frameworks */, @@ -635,6 +673,17 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5C22299E29CCB3C400F00E69 /* Clock */ = { + isa = PBXGroup; + children = ( + 5C2229A829CCB41900F00E69 /* main.swift */, + 5C2229B729CE3F3300F00E69 /* popup.swift */, + 5C2229AE29CDC08700F00E69 /* settings.swift */, + 5C2229AA29CCB53E00F00E69 /* config.plist */, + ); + path = Clock; + sourceTree = ""; + }; 5CFE492829264DF1000F2856 /* Helper */ = { isa = PBXGroup; children = ( @@ -690,6 +739,7 @@ 9A11AACF266FD77F000C1C05 /* Bluetooth.framework */, 9AAC5E2A280ACC120043D892 /* Tests.xctest */, 5CFE492729264DF1000F2856 /* eu.exelban.Stats.SMC.Helper */, + 5C22299D29CCB3C400F00E69 /* Clock.framework */, ); name = Products; sourceTree = ""; @@ -909,6 +959,7 @@ 9A3E17CD247A94AF00449CD1 /* Net */, 9ABFF8F7248BEBCB00C9041A /* Battery */, 9A11AAD0266FD77F000C1C05 /* Bluetooth */, + 5C22299E29CCB3C400F00E69 /* Clock */, ); path = Modules; sourceTree = ""; @@ -963,6 +1014,7 @@ 9AB7FD7B246B48DB00387FDA /* settings.swift */, 9AF9EE0524648751005D2270 /* Info.plist */, 9AF9EE12246492E8005D2270 /* config.plist */, + 5C2229BB29CF66B100F00E69 /* header.h */, ); path = Disk; sourceTree = ""; @@ -970,6 +1022,13 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 5C22299829CCB3C400F00E69 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9A11AACA266FD77F000C1C05 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -1030,12 +1089,32 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 5C2229BD29CF685A00F00E69 /* header.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 5C22299C29CCB3C400F00E69 /* Clock */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5C2229A529CCB3C400F00E69 /* Build configuration list for PBXNativeTarget "Clock" */; + buildPhases = ( + 5C22299829CCB3C400F00E69 /* Headers */, + 5C22299929CCB3C400F00E69 /* Sources */, + 5C22299A29CCB3C400F00E69 /* Frameworks */, + 5C22299B29CCB3C400F00E69 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5C2229B329CDFBF600F00E69 /* PBXTargetDependency */, + ); + name = Clock; + productName = Clock; + productReference = 5C22299D29CCB3C400F00E69 /* Clock.framework */; + productType = "com.apple.product-type.framework"; + }; 5CFE492629264DF1000F2856 /* Helper */ = { isa = PBXNativeTarget; buildConfigurationList = 5CFE492D29264DF1000F2856 /* Build configuration list for PBXNativeTarget "Helper" */; @@ -1098,6 +1177,7 @@ 9A97CED02537331B00742D8F /* PBXTargetDependency */, 9A2846FD2666A9CC00EC1F6D /* PBXTargetDependency */, 9A11AAD5266FD77F000C1C05 /* PBXTargetDependency */, + 5C2229A229CCB3C400F00E69 /* PBXTargetDependency */, ); name = Stats; packageProductDependencies = ( @@ -1325,6 +1405,10 @@ LastUpgradeCheck = 1420; ORGANIZATIONNAME = "Serhiy Mytrovtsiy"; TargetAttributes = { + 5C22299C29CCB3C400F00E69 = { + CreatedOnToolsVersion = 14.2; + LastSwiftMigration = 1420; + }; 5CFE492629264DF1000F2856 = { CreatedOnToolsVersion = 14.1; }; @@ -1447,6 +1531,7 @@ 9ABFF8F5248BEBCB00C9041A /* Battery */, 9AE29AD4249A50350071B02D /* Sensors */, 9A11AACE266FD77F000C1C05 /* Bluetooth */, + 5C22299C29CCB3C400F00E69 /* Clock */, 9AAC5E29280ACC120043D892 /* Tests */, 5CFE492629264DF1000F2856 /* Helper */, ); @@ -1454,6 +1539,14 @@ /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 5C22299B29CCB3C400F00E69 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C2229AB29CCB53E00F00E69 /* config.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9A11AACD266FD77F000C1C05 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1578,6 +1671,16 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 5C22299929CCB3C400F00E69 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C2229AF29CDC08700F00E69 /* settings.swift in Sources */, + 5C2229B829CE3F3300F00E69 /* popup.swift in Sources */, + 5C2229A929CCB41900F00E69 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5CFE492329264DF1000F2856 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1767,6 +1870,16 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 5C2229A229CCB3C400F00E69 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5C22299C29CCB3C400F00E69 /* Clock */; + targetProxy = 5C2229A129CCB3C400F00E69 /* PBXContainerItemProxy */; + }; + 5C2229B329CDFBF600F00E69 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A2846F62666A9CC00EC1F6D /* Kit */; + targetProxy = 5C2229B229CDFBF600F00E69 /* PBXContainerItemProxy */; + }; 9A11AAD5266FD77F000C1C05 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 9A11AACE266FD77F000C1C05 /* Bluetooth */; @@ -1903,6 +2016,81 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 5C2229A629CCB3C400F00E69 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved."; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Clock; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 5C2229A729CCB3C400F00E69 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved."; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Clock; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; 5CFE492B29264DF1000F2856 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2908,6 +3096,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = Modules/Disk/header.h; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -2943,6 +3132,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = Modules/Disk/header.h; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -2952,6 +3142,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 5C2229A529CCB3C400F00E69 /* Build configuration list for PBXNativeTarget "Clock" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5C2229A629CCB3C400F00E69 /* Debug */, + 5C2229A729CCB3C400F00E69 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 5CFE492D29264DF1000F2856 /* Build configuration list for PBXNativeTarget "Helper" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Stats/AppDelegate.swift b/Stats/AppDelegate.swift index 84e43504..1e2dd543 100755 --- a/Stats/AppDelegate.swift +++ b/Stats/AppDelegate.swift @@ -19,6 +19,7 @@ import Battery import Sensors import GPU import Bluetooth +import Clock let updater = Updater(github: "exelban/stats", url: "https://api.serhiy.io/v1/stats/release/latest") var modules: [Module] = [ @@ -29,7 +30,8 @@ var modules: [Module] = [ Sensors(), Network(), Battery(), - Bluetooth() + Bluetooth(), + Clock() ] @main