Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8242a64a5d | ||
|
|
355cf0ded1 | ||
|
|
fa5551f272 | ||
|
|
b6eaa8d3c4 | ||
|
|
1af81e5b4a | ||
|
|
05714ef69e |
19
CHANGELOG.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.1] – 2026-01-28
|
||||||
|
Fixed:
|
||||||
|
- Crash on macOS 10.13 if path contains space character
|
||||||
|
- Larger status icon glyph
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.0] – 2026-01-27
|
||||||
|
Initial release
|
||||||
|
|
||||||
|
|
||||||
|
[1.0.1]: https://github.com/relikd/Menuscript/compare/v1.0.0...v1.0.1
|
||||||
|
[1.0.0]: https://github.com/relikd/Menuscript/compare/1eca425c5f453bc0ed47f780d491656549d3ab53...v1.0.0
|
||||||
9
Makefile
@@ -11,7 +11,7 @@ HAS_SIGN_IDENTITY=$(shell security find-identity -v -p codesigning | grep -q "Ap
|
|||||||
|
|
||||||
|
|
||||||
Menuscript.app: SDK_PATH=$(shell xcrun --show-sdk-path --sdk macosx)
|
Menuscript.app: SDK_PATH=$(shell xcrun --show-sdk-path --sdk macosx)
|
||||||
Menuscript.app: src/* examples/*
|
Menuscript.app: src/* res/**
|
||||||
@mkdir -p Menuscript.app/Contents/MacOS/
|
@mkdir -p Menuscript.app/Contents/MacOS/
|
||||||
swiftc ${CFLAGS} src/main.swift -target x86_64-apple-macos10.13 \
|
swiftc ${CFLAGS} src/main.swift -target x86_64-apple-macos10.13 \
|
||||||
-emit-executable -sdk ${SDK_PATH} -o bin_x64
|
-emit-executable -sdk ${SDK_PATH} -o bin_x64
|
||||||
@@ -20,11 +20,10 @@ Menuscript.app: src/* examples/*
|
|||||||
lipo -create bin_x64 bin_arm64 -o Menuscript.app/Contents/MacOS/Menuscript
|
lipo -create bin_x64 bin_arm64 -o Menuscript.app/Contents/MacOS/Menuscript
|
||||||
@rm bin_x64 bin_arm64
|
@rm bin_x64 bin_arm64
|
||||||
@echo 'APPL????' > Menuscript.app/Contents/PkgInfo
|
@echo 'APPL????' > Menuscript.app/Contents/PkgInfo
|
||||||
@mkdir -p Menuscript.app/Contents/Resources/
|
|
||||||
@cp src/AppIcon.icns Menuscript.app/Contents/Resources/AppIcon.icns
|
|
||||||
@rm -rf Menuscript.app/Contents/Resources/examples/
|
|
||||||
@cp -R examples/ Menuscript.app/Contents/Resources/examples/
|
|
||||||
@cp src/Info.plist Menuscript.app/Contents/Info.plist
|
@cp src/Info.plist Menuscript.app/Contents/Info.plist
|
||||||
|
@find res -name .DS_Store -delete
|
||||||
|
@rm -rf Menuscript.app/Contents/Resources/
|
||||||
|
@cp -R res/ Menuscript.app/Contents/Resources/
|
||||||
@touch Menuscript.app
|
@touch Menuscript.app
|
||||||
@echo
|
@echo
|
||||||
ifeq ($(HAS_SIGN_IDENTITY),1)
|
ifeq ($(HAS_SIGN_IDENTITY),1)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ A menu bar script executor.
|
|||||||
|
|
||||||
Menuscript adds a status bar menu to call custom (user defined) scripts.
|
Menuscript adds a status bar menu to call custom (user defined) scripts.
|
||||||
The app reads the content of a directory and adds all executable files to the status menu.
|
The app reads the content of a directory and adds all executable files to the status menu.
|
||||||
The screenshot above represents the content of the [examples](examples/) directory.
|
The screenshot above represents the content of the [res/examples](res/examples/) directory.
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<rect id="blue" x="500" width="600" height="470" fill="url(#lg2)"/>
|
<rect id="blue" x="500" width="600" height="470" fill="url(#lg2)"/>
|
||||||
<rect id="blue_line" x="500" y="470" width="600" height="30" fill="#2847C5"/>
|
<rect id="blue_line" x="500" y="470" width="600" height="30" fill="#2847C5"/>
|
||||||
<g transform="translate(562.5,62)scale(0.35)">
|
<g transform="translate(562.5,62)scale(0.35)">
|
||||||
<path id="_x2318_" d="M335,335m-145,0a145,145,0,1,1,145,-145v620a145,145,0,1,1,-145,-145h620a145,145,0,1,1,-145,145v-620a145,145,0,1,1,145,145Z" stroke="#fff" stroke-width="90" fill="transparent"/>
|
<path id="_x2318_" d="M335,335m-145,0a145,145,0,1,1,145,-145v620a145,145,0,1,1,-145,-145h620a145,145,0,1,1,-145,145v-620a145,145,0,1,1,145,145Z" stroke="#fff" stroke-width="90" fill="none"/>
|
||||||
</g>
|
</g>
|
||||||
<text x="75" y="840">run.sh</text>
|
<text x="75" y="840">run.sh</text>
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -21,7 +21,7 @@
|
|||||||
<rect id="blue" x="512" width="402" height="331" fill="url(#lg2)"/>
|
<rect id="blue" x="512" width="402" height="331" fill="url(#lg2)"/>
|
||||||
<rect id="blue_line" x="512" y="331" width="402" height="20" fill="#2847C5"/>
|
<rect id="blue_line" x="512" y="331" width="402" height="20" fill="#2847C5"/>
|
||||||
<g transform="translate(587,40) scale(0.251)">
|
<g transform="translate(587,40) scale(0.251)">
|
||||||
<path id="_x2318_" d="M335,335m-145,0a145,145,0,1,1,145,-145v620a145,145,0,1,1,-145,-145h620a145,145,0,1,1,-145,145v-620a145,145,0,1,1,145,145Z" stroke="#fff" stroke-width="90" fill="transparent"/>
|
<path id="_x2318_" d="M335,335m-145,0a145,145,0,1,1,145,-145v620a145,145,0,1,1,-145,-145h620a145,145,0,1,1,-145,145v-620a145,145,0,1,1,145,145Z" stroke="#fff" stroke-width="90" fill="none"/>
|
||||||
</g>
|
</g>
|
||||||
<text x="72" y="590">run.sh</text>
|
<text x="72" y="590">run.sh</text>
|
||||||
<rect id="sep" y="672" width="1024" height="20" fill="#ccc"/>
|
<rect id="sep" y="672" width="1024" height="20" fill="#ccc"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
3
res/status.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
|
||||||
|
<path id="_x2318_" d="M335,335m-145,0a145,145,0,1,1,145,-145v620a145,145,0,1,1,-145,-145h620a145,145,0,1,1,-145,145v-620a145,145,0,1,1,145,145Z" stroke="black" stroke-width="90" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 263 B |
@@ -15,9 +15,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.0</string>
|
<string>1.0.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>2</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>10.13</string>
|
<string>10.13</string>
|
||||||
<key>LSBackgroundOnly</key>
|
<key>LSBackgroundOnly</key>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env swift
|
#!/usr/bin/env swift
|
||||||
import AppKit
|
import AppKit
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
class FlippedView: NSView {
|
class FlippedView: NSView {
|
||||||
override var isFlipped: Bool { true }
|
override var isFlipped: Bool { true }
|
||||||
@@ -21,12 +20,22 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|||||||
|
|
||||||
private func initStatusIcon() {
|
private func initStatusIcon() {
|
||||||
self.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
self.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||||
self.statusItem.button?.title = "⌘"
|
if let img = statusIcon() {
|
||||||
// self.statusItem.button?.image = NSImage.statusIcon
|
self.statusItem.button?.image = img
|
||||||
|
} else {
|
||||||
|
self.statusItem.button?.title = "⌘" // cant load svg (<10.15)
|
||||||
|
}
|
||||||
self.statusItem.menu = NSMenu(title: resolvedStorageURL().path)
|
self.statusItem.menu = NSMenu(title: resolvedStorageURL().path)
|
||||||
self.statusItem.menu?.delegate = self
|
self.statusItem.menu?.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func statusIcon() -> NSImage? {
|
||||||
|
let img = NSImage(contentsOf: resFile("status", "svg"))
|
||||||
|
img?.isTemplate = true
|
||||||
|
img?.size = NSMakeSize(14, 14)
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
func menuDidClose(_ menu: NSMenu) {
|
func menuDidClose(_ menu: NSMenu) {
|
||||||
if menu == self.statusItem.menu {
|
if menu == self.statusItem.menu {
|
||||||
self.statusItem.menu = NSMenu(title: menu.title)
|
self.statusItem.menu = NSMenu(title: menu.title)
|
||||||
@@ -67,17 +76,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|||||||
(sender.representedObject as! Entry).run()
|
(sender.representedObject as! Entry).run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Helper
|
||||||
|
|
||||||
|
private func resFile(_ name: String, _ ext: String?) -> URL {
|
||||||
|
// if run via .app bundle
|
||||||
|
return Bundle.main.url(forResource: name, withExtension: ext)
|
||||||
|
// if calling swift directly
|
||||||
|
?? URL(fileURLWithPath: #file + "/../../res/" + name + (ext == nil ? "" : ("." + ext!)))
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Manage storage path
|
// MARK: - Manage storage path
|
||||||
|
|
||||||
private func resolvedStorageURL() -> URL {
|
private func resolvedStorageURL() -> URL {
|
||||||
userStorageURL()
|
userStorageURL() ?? resFile("examples", nil)
|
||||||
// if run via .app bundle
|
|
||||||
?? Bundle.main.url(forResource: "examples", withExtension: nil)
|
|
||||||
// if calling swift directly
|
|
||||||
?? URL(string: "file://" + FileManager.default.currentDirectoryPath + "/" + #file)!
|
|
||||||
.deletingLastPathComponent()
|
|
||||||
.deletingLastPathComponent()
|
|
||||||
.appendingPathComponent("examples")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func userStorageURL() -> URL? {
|
private func userStorageURL() -> URL? {
|
||||||
@@ -205,7 +216,7 @@ struct Entry {
|
|||||||
var rv: [Entry] = []
|
var rv: [Entry] = []
|
||||||
for url
|
for url
|
||||||
in (try? FileManager.default.contentsOfDirectory(
|
in (try? FileManager.default.contentsOfDirectory(
|
||||||
at: URL(string: path)!,
|
at: URL(fileURLWithPath: path),
|
||||||
includingPropertiesForKeys: [.isDirectoryKey, .isExecutableKey])) ?? []
|
includingPropertiesForKeys: [.isDirectoryKey, .isExecutableKey])) ?? []
|
||||||
{
|
{
|
||||||
if url.hasDirectoryPath || FileManager.default.isExecutableFile(atPath: url.path) {
|
if url.hasDirectoryPath || FileManager.default.isExecutableFile(atPath: url.path) {
|
||||||
@@ -267,7 +278,7 @@ struct Entry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Entry : Comparable {
|
extension Entry: Comparable {
|
||||||
static func < (lhs: Entry, rhs: Entry) -> Bool {
|
static func < (lhs: Entry, rhs: Entry) -> Bool {
|
||||||
return (lhs.order, lhs.title) < (rhs.order, rhs.title)
|
return (lhs.order, lhs.title) < (rhs.order, rhs.title)
|
||||||
}
|
}
|
||||||
|
|||||||