mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-05-08 15:18:05 +03:00
macos: run as LSUIElement menubar app with NSStatusItem
Ship Lan Mouse on macOS as an accessory app (no Dock icon, no main window on launch) with a status-bar item for show/quit. Closing the window hides it instead of quitting so the menu bar stays the primary surface. - build-aux/macos-lsui-element.plist: LSUIElement=true plus NSInputMonitoringUsageDescription and NSAppleEventsUsageDescription (merged into Info.plist via cargo-bundle's osx_info_plist_exts). - lan-mouse-gtk/src/macos_status_item.rs: NSStatusItem setup via raw objc_msgSend FFI. Loads a bundled 22pt PNG as a template image so it auto-tints for light/dark menu bars. - scripts/makeicns.sh: emit Contents/Resources/menubar-template.png from the existing SVG. - scripts/copy-macos-dylib.sh: flatten cargo-bundle's preserved target/ subdir under Resources so NSBundle pathForResource: finds the template image. - lan-mouse-gtk/src/lib.rs: register the new modules, set up a Cmd+Q-wired quit action, configure bundle env vars (schemas, XDG_DATA_DIRS, GTK_DATA_PREFIX) when running from inside the .app, and filter the known upstream Gtk theme-parser warning spam. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
Ferdinand Schober
parent
c40e10505b
commit
903b0504e0
@@ -43,6 +43,9 @@ bundle_path=$(dirname "$(dirname "$(dirname "$exec_path")")")
|
||||
# Path to the Frameworks directory
|
||||
fwks_path="$bundle_path/Contents/Frameworks"
|
||||
mkdir -p "$fwks_path"
|
||||
# Path to bundled GTK/GSettings data
|
||||
resources_path="$bundle_path/Contents/Resources"
|
||||
share_path="$resources_path/share"
|
||||
|
||||
# Copy and fix references for a binary (executable or dylib)
|
||||
#
|
||||
@@ -58,6 +61,10 @@ fix_references() {
|
||||
libs=$(otool -L "$bin" | awk -v homebrew="$homebrew_path" '$0 ~ homebrew {print $1}')
|
||||
|
||||
echo "$libs" | while IFS= read -r old_path; do
|
||||
if [ -z "$old_path" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local base_name="$(basename "$old_path")"
|
||||
local dest="$fwks_path/$base_name"
|
||||
|
||||
@@ -81,6 +88,42 @@ fix_references() {
|
||||
|
||||
fix_references "$exec_path"
|
||||
|
||||
copy_runtime_data() {
|
||||
mkdir -p "$share_path"
|
||||
|
||||
if [ -d "$homebrew_path/share/glib-2.0/schemas" ]; then
|
||||
mkdir -p "$share_path/glib-2.0"
|
||||
rm -rf "$share_path/glib-2.0/schemas"
|
||||
cp -RL "$homebrew_path/share/glib-2.0/schemas" "$share_path/glib-2.0/schemas"
|
||||
if command -v glib-compile-schemas >/dev/null 2>&1; then
|
||||
glib-compile-schemas "$share_path/glib-2.0/schemas"
|
||||
elif [ -x "$homebrew_path/bin/glib-compile-schemas" ]; then
|
||||
"$homebrew_path/bin/glib-compile-schemas" "$share_path/glib-2.0/schemas"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d "$homebrew_path/share/gtk-4.0" ]; then
|
||||
rm -rf "$share_path/gtk-4.0"
|
||||
cp -RL "$homebrew_path/share/gtk-4.0" "$share_path/gtk-4.0"
|
||||
fi
|
||||
|
||||
if [ -d "$homebrew_path/share/icons/Adwaita" ]; then
|
||||
mkdir -p "$share_path/icons"
|
||||
rm -rf "$share_path/icons/Adwaita"
|
||||
cp -RL "$homebrew_path/share/icons/Adwaita" "$share_path/icons/Adwaita"
|
||||
fi
|
||||
}
|
||||
|
||||
copy_runtime_data
|
||||
|
||||
# cargo-bundle preserves the source path under Contents/Resources (so
|
||||
# `target/menubar-template.png` lands at `Resources/target/...`). Flatten it
|
||||
# so NSBundle pathForResource: finds the file at the Resources root.
|
||||
if [ -f "$resources_path/target/menubar-template.png" ]; then
|
||||
mv "$resources_path/target/menubar-template.png" "$resources_path/menubar-template.png"
|
||||
rmdir "$resources_path/target" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Ensure the main executable has our Frameworks path in its RPATH
|
||||
if ! otool -l "$exec_path" | grep -q "@executable_path/../Frameworks"; then
|
||||
echo "Adding RPATH to $exec_path"
|
||||
|
||||
@@ -67,12 +67,13 @@ magick -size ${CANVAS}x${CANVAS} xc:none \
|
||||
# 3) Composite the artwork onto the background, centered inside the content area.
|
||||
magick "$workdir/background.png" \
|
||||
"$workdir/content.png" -geometry +${CONTENT_OFFSET}+${CONTENT_OFFSET} -composite \
|
||||
"$workdir/icon-1024.png"
|
||||
-colorspace sRGB -type TrueColorAlpha PNG32:"$workdir/icon-1024.png"
|
||||
|
||||
# 4) Generate each iconset size from the master so all sizes share the same
|
||||
# squircle proportions and look consistent at every resolution.
|
||||
for size in 1024 512 256 128 64 32 16; do
|
||||
magick "$workdir/icon-1024.png" -resize ${size}x${size} "$workdir/${size}.png"
|
||||
magick "$workdir/icon-1024.png" -resize ${size}x${size} \
|
||||
-colorspace sRGB -type TrueColorAlpha PNG32:"$workdir/${size}.png"
|
||||
done
|
||||
|
||||
cp "$workdir/1024.png" "$iconset"/icon_512x512@2x.png
|
||||
@@ -86,4 +87,51 @@ cp "$workdir/32.png" "$iconset"/icon_32x32.png
|
||||
cp "$workdir/32.png" "$iconset"/icon_16x16@2x.png
|
||||
cp "$workdir/16.png" "$iconset"/icon_16x16.png
|
||||
|
||||
iconutil -c icns "$iconset" -o "$icns"
|
||||
mkdir -p "$(dirname "$icns")"
|
||||
|
||||
# Menu bar template icon: flatten all RGB channels to 0 (black) while keeping
|
||||
# alpha so the artwork reads as a clean silhouette. NSStatusBarButton tints
|
||||
# template images to match the menu bar appearance in light and dark modes.
|
||||
menubar_template="$(dirname "$icns")/menubar-template.png"
|
||||
rsvg-convert -w 44 -h 44 "$svg" -o "$workdir/menubar-44.png"
|
||||
magick "$workdir/menubar-44.png" -channel RGB -evaluate set 0 +channel \
|
||||
"$menubar_template"
|
||||
|
||||
if ! iconutil -c icns "$iconset" -o "$icns"; then
|
||||
if ! command -v perl >/dev/null 2>&1; then
|
||||
echo "iconutil failed and perl is not available for the fallback icns writer" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "iconutil rejected the iconset; writing icns directly" >&2
|
||||
perl - "$icns" "$iconset" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my ($icns, $iconset) = @ARGV;
|
||||
my @icons = (
|
||||
[ 'icp4', "$iconset/icon_16x16.png" ],
|
||||
[ 'ic11', "$iconset/icon_16x16\@2x.png" ],
|
||||
[ 'icp5', "$iconset/icon_32x32.png" ],
|
||||
[ 'ic12', "$iconset/icon_32x32\@2x.png" ],
|
||||
[ 'ic07', "$iconset/icon_128x128.png" ],
|
||||
[ 'ic13', "$iconset/icon_128x128\@2x.png" ],
|
||||
[ 'ic08', "$iconset/icon_256x256.png" ],
|
||||
[ 'ic14', "$iconset/icon_256x256\@2x.png" ],
|
||||
[ 'ic09', "$iconset/icon_512x512.png" ],
|
||||
[ 'ic10', "$iconset/icon_512x512\@2x.png" ],
|
||||
);
|
||||
|
||||
my $body = '';
|
||||
for my $icon (@icons) {
|
||||
my ($type, $path) = @$icon;
|
||||
open my $fh, '<:raw', $path or die "$path: $!";
|
||||
local $/;
|
||||
my $png = <$fh>;
|
||||
$body .= $type . pack('N', length($png) + 8) . $png;
|
||||
}
|
||||
|
||||
open my $out, '>:raw', $icns or die "$icns: $!";
|
||||
print {$out} 'icns' . pack('N', length($body) + 8) . $body;
|
||||
PERL
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user