Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v5.12.0 #238

Merged
merged 22 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c126c7d
Add support for Linear
starbugs Mar 20, 2024
56e58cf
Merge pull request #233 from wakatime/feature/linear
alanhamlett Mar 20, 2024
6a9faa6
Attempt to modify stack view to scroll properly (not happy with result)
starbugs Mar 22, 2024
767a828
Rewrite to outline view to implement proper scrolling for monitored a…
starbugs Mar 22, 2024
1f81292
Fix bug showing wrong monitoring state due to race with `MonitoringMa…
starbugs Mar 22, 2024
c5cd950
Fix left padding
starbugs Mar 22, 2024
7a8e3f0
Fix missing support for SetApp bundle IDs
starbugs Mar 25, 2024
25b3d8f
Disable monitored apps outline view row selection
starbugs Mar 25, 2024
6c76bd4
Refactor setAppBundleId
starbugs Mar 25, 2024
38e7fb7
Merge pull request #235 from wakatime/feature/scroll-monitored-apps
alanhamlett Mar 25, 2024
f7e6665
Add browser activity filter
starbugs Mar 21, 2024
c407ef7
Rename blacklist/whitelist to denylist/allowlist
starbugs Mar 22, 2024
4f317d6
Allow regular expression in URL filter lists
starbugs Mar 25, 2024
f297e8d
Disable formatting for filter text view
starbugs Mar 25, 2024
e329702
Filter pattern against plain address from browser address field also
starbugs Mar 25, 2024
1ded43b
Merge pull request #234 from wakatime/feature/browser-activity-filter
starbugs Mar 25, 2024
99d10af
Filter pattern against plain address from browser address field also
starbugs Mar 25, 2024
b369100
Fix preset URLs and strings for browser filters
starbugs Mar 25, 2024
f449dce
Also check allowList without protocol part
alanhamlett Mar 25, 2024
bcd4bd0
Merge pull request #237 from wakatime/feature/browser-activity-filter…
alanhamlett Mar 25, 2024
16e1707
Prevent upgrading wakatime-cli when built from source
alanhamlett Mar 25, 2024
d976d1c
Merge pull request #239 from wakatime/misc/disable-updates
alanhamlett Mar 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions WakaTime/Extensions/AXUIElementExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ extension AXUIElement {
// swiftlint:enable force_cast
}

var nextSibling: AXUIElement? {
guard let parentChildren = self.parent?.children, let currentIndex = parentChildren.firstIndex(of: self) else { return nil }
let nextIndex = currentIndex + 1
guard parentChildren.indices.contains(nextIndex) else { return nil }
return parentChildren[nextIndex]
}

var previousSibling: AXUIElement? {
guard let parentChildren = self.parent?.children, let currentIndex = parentChildren.firstIndex(of: self) else { return nil }
let previousIndex = currentIndex - 1
guard parentChildren.indices.contains(previousIndex) else { return nil }
return parentChildren[previousIndex]
}

var id: String? {
guard let ref = getValue(for: kAXIdentifierAttribute) else { return nil }
// swiftlint:disable force_cast
Expand Down Expand Up @@ -60,7 +74,7 @@ extension AXUIElement {
// swiftlint:enable force_cast
}

func project(for app: MonitoredApp) -> String? {
func address(for app: MonitoredApp) -> String? {
var address: String?
switch app {
case .brave:
Expand All @@ -72,6 +86,10 @@ extension AXUIElement {
case .firefox:
let addressField = findAddressField()
address = addressField?.value
case .linear:
let projectLabel = firstDescendantWhere { $0.value == "Project" }
let projectButton = projectLabel?.nextSibling?.firstDescendantWhere { $0.role == kAXButtonRole }
return projectButton?.rawTitle
case .safari:
let addressField = elementById(identifier: "WEB_BROWSER_ADDRESS_AND_SEARCH_FIELD")
address = addressField?.value
Expand All @@ -80,12 +98,12 @@ extension AXUIElement {
address = addressField?.value
default: return nil
}
return address
}

if let address {
return extractProjectName(from: address)
}

return nil
func project(for app: MonitoredApp) -> String? {
guard let address = address(for: app) else { return nil }
return extractProjectName(from: address)
}

// swiftlint:disable cyclomatic_complexity
Expand Down
13 changes: 10 additions & 3 deletions WakaTime/Extensions/NSRunningApplicationExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ enum MonitoredApp: String, CaseIterable {
MonitoredApp.slack.rawValue,
]

static let browserAppIds = [
MonitoredApp.arcbrowser.rawValue,
MonitoredApp.brave.rawValue,
MonitoredApp.chrome.rawValue,
MonitoredApp.firefox.rawValue,
MonitoredApp.safari.rawValue,
MonitoredApp.safaripreview.rawValue,
]

// list apps which are enabled by default on first run
static let defaultEnabledApps = [
MonitoredApp.canva.rawValue,
Expand All @@ -57,9 +66,7 @@ enum MonitoredApp: String, CaseIterable {
]

// list apps which we aren't yet able to track, so they're hidden from the Monitored Apps menu
static let unsupportedAppIds = [
MonitoredApp.linear.rawValue,
]
static let unsupportedAppIds = [String]()
}

extension NSRunningApplication {
Expand Down
15 changes: 15 additions & 0 deletions WakaTime/Extensions/StringExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

extension String {
func matchesRegex(_ pattern: String) -> Bool {
if let regex = try? NSRegularExpression(pattern: pattern) {
let range = NSRange(location: 0, length: self.utf16.count)
return regex.firstMatch(in: self, options: [], range: range) != nil
}
return false
}

func trim() -> String {
self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
}
6 changes: 6 additions & 0 deletions WakaTime/Helpers/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ class Dependencies {
}
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let output = String(decoding: outputData, as: UTF8.self)

// disable updating wakatime-cli when it was built from source
if output.trim() == "<local-build>" {
return true
}

let version: String?
if let regex = try? NSRegularExpression(pattern: "([0-9]+\\.[0-9]+\\.[0-9]+)"),
let match = regex.firstMatch(in: output, range: NSRange(output.startIndex..., in: output)),
Expand Down
46 changes: 46 additions & 0 deletions WakaTime/Helpers/FilterManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Cocoa

class FilterManager {
static func filterBrowsedSites(app: NSRunningApplication, monitoredApp: MonitoredApp, activeWindow: AXUIElement) -> Bool {
guard MonitoringManager.isAppBrowser(app) else { return true }

if let address = activeWindow.address(for: monitoredApp) {
let patterns = Self.parseList(PropertiesManager.currentFilterList)
if patterns.isEmpty { return true }

// Create scheme-prefixed address versions to allow regular expressions
// that incorporate a scheme to match
let httpAddress = "http://" + address
let httpsAddress = "https://" + address

switch PropertiesManager.filterType {
case .denylist:
for pattern in patterns {
if address.matchesRegex(pattern) || httpAddress.matchesRegex(pattern) || httpsAddress.matchesRegex(pattern) {
// Address matches a pattern on the denylist. Filter the site out.
return false
}
}
case .allowlist:
let addressMatchesAllowlist = patterns.contains { pattern in
address.matchesRegex(pattern) || httpAddress.matchesRegex(pattern) || httpsAddress.matchesRegex(pattern)
}
// If none of the patterns on the allowlist match the given address, filter the site out
if !addressMatchesAllowlist {
return false
}
}
}

// The given address passed all filters and will be included
return true
}

private static func parseList(_ listString: String) -> [String] {
Self.sanitizeList(listString.components(separatedBy: "\n"))
}

private static func sanitizeList(_ urls: [String]) -> [String] {
urls.map { $0.trimmingCharacters(in: CharacterSet.whitespaces) }
}
}
20 changes: 20 additions & 0 deletions WakaTime/Helpers/MonitoringManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ class MonitoringManager {
return bundleId == MonitoredApp.xcode.rawValue
}

static func isAppBrowser(for bundleId: String) -> Bool {
MonitoredApp.browserAppIds.contains(bundleId)
}

static func isAppBrowser(_ app: NSRunningApplication) -> Bool {
guard let bundleId = app.bundleIdentifier else { return false }

return isAppBrowser(for: bundleId)
}

static func heartbeatData(_ app: NSRunningApplication) -> HeartbeatData? {
let pid = app.processIdentifier

Expand All @@ -60,6 +70,15 @@ class MonitoringManager {
let title = activeWindow.title(for: monitoredApp)
else { return nil }

// For browser apps, filter out deny/allowlisted sites and use the predefined project
// from the allowlist if applicable.
let included = FilterManager.filterBrowsedSites(
app: app,
monitoredApp: monitoredApp,
activeWindow: activeWindow
)
guard included else { return nil }

let project = activeWindow.project(for: monitoredApp)

switch monitoredApp {
Expand Down Expand Up @@ -103,6 +122,7 @@ class MonitoringManager {
case .linear:
return HeartbeatData(
entity: title,
project: project,
category: .planning)
case .notes:
if activeWindow.rawTitle == "Notes" {
Expand Down
65 changes: 65 additions & 0 deletions WakaTime/Helpers/PropertiesManager.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import Foundation

class PropertiesManager {
enum FilterType: String {
case denylist
case allowlist
}

enum Keys: String {
case shouldLaunchOnLogin = "launch_on_login"
case shouldLogToFile = "log_to_file"
case shouldAutomaticallyDownloadUpdates = "should_automatically_download_updates"
case hasLaunchedBefore = "has_launched_before"
case filterType = "filter_type"
case denylist = "denylist"
case allowlist = "allowlist"
}

static var shouldLaunchOnLogin: Bool {
Expand Down Expand Up @@ -71,4 +79,61 @@ class PropertiesManager {
UserDefaults.standard.synchronize()
}
}

static var filterType: FilterType {
get {
guard let filterTypeString = UserDefaults.standard.string(forKey: Keys.filterType.rawValue) else {
return .allowlist
}

return FilterType(rawValue: filterTypeString) ?? .denylist
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: Keys.filterType.rawValue)
UserDefaults.standard.synchronize()
}
}

static var denylist: String {
get {
guard let denylist = UserDefaults.standard.string(forKey: Keys.denylist.rawValue) else {
return ""
}

return denylist
}
set {
UserDefaults.standard.set(newValue, forKey: Keys.denylist.rawValue)
UserDefaults.standard.synchronize()
}
}

static var allowlist: String {
get {
guard let allowlist = UserDefaults.standard.string(forKey: Keys.allowlist.rawValue) else {
return
"https?://(\\w\\.)*github\\.com/\n" +
"https?://(\\w\\.)*gitlab\\.com/\n" +
"^stackoverflow\\.com/\n" +
"^docs\\.python\\.org/\n" +
"https?://(\\w\\.)*golang\\.org/\n" +
"https?://(\\w\\.)*go\\.dev/\n" +
"https?://(\\w\\.)*npmjs\\.com/\n" +
"https?//localhost[:\\d+]?/"
}

return allowlist
}
set {
UserDefaults.standard.set(newValue, forKey: Keys.allowlist.rawValue)
UserDefaults.standard.synchronize()
}
}

static var currentFilterList: String {
switch Self.filterType {
case .denylist: return Self.denylist
case .allowlist: return Self.allowlist
}
}
}
Loading
Loading