Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a55ea883a1 | ||
|
|
3e066f991f | ||
|
|
3752419075 |
No files matched your search
@@ -45,7 +45,6 @@ jobs:
|
|||||||
deb: true
|
deb: true
|
||||||
deb_arch: arm64
|
deb_arch: arm64
|
||||||
pkg_arch: aarch64
|
pkg_arch: aarch64
|
||||||
gnu_target: aarch64-unknown-linux-gnu
|
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
asset: tsunagi-macos-aarch64
|
asset: tsunagi-macos-aarch64
|
||||||
@@ -111,11 +110,19 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
export RUSTFLAGS="${RUSTFLAGS:-} -A linker_messages"
|
|
||||||
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
||||||
rustup target add "${{ matrix.gnu_target }}"
|
if [[ -n "${{ matrix.gnu_target }}" ]]; then
|
||||||
cargo zigbuild --release --locked \
|
# tray-icon links GTK and libappindicator through pkg-config, which
|
||||||
--target "${{ matrix.gnu_target }}.2.28" --bin tsunagi-tray
|
# has no cross story under zig, so the Linux GUI is built natively
|
||||||
|
# (x86_64 only) against the runner's glibc.
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends \
|
||||||
|
libgtk-3-dev libayatana-appindicator3-dev libxdo-dev
|
||||||
|
cargo build --release --locked \
|
||||||
|
--target "${{ matrix.gnu_target }}" --bin tsunagi-tray
|
||||||
|
else
|
||||||
|
echo "no GUI build for ${{ matrix.target }}"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
cargo build --release --locked --target "${{ matrix.target }}" --bin tsunagi-tray
|
cargo build --release --locked --target "${{ matrix.target }}" --bin tsunagi-tray
|
||||||
fi
|
fi
|
||||||
@@ -133,11 +140,13 @@ jobs:
|
|||||||
[[ "${{ runner.os }}" == "Windows" ]] && bin="tsunagi.exe"
|
[[ "${{ runner.os }}" == "Windows" ]] && bin="tsunagi.exe"
|
||||||
cp "target/${{ matrix.target }}/release/${bin}" "$stage/"
|
cp "target/${{ matrix.target }}/release/${bin}" "$stage/"
|
||||||
|
|
||||||
# The tray GUI alongside it: glibc build on Linux, native elsewhere.
|
# The tray GUI alongside it, where it was built (Linux: x86_64 only).
|
||||||
tray="tsunagi-tray"
|
tray="tsunagi-tray"
|
||||||
[[ "${{ runner.os }}" == "Windows" ]] && tray="tsunagi-tray.exe"
|
[[ "${{ runner.os }}" == "Windows" ]] && tray="tsunagi-tray.exe"
|
||||||
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
||||||
cp "target/${{ matrix.gnu_target }}/release/${tray}" "$stage/"
|
if [[ -n "${{ matrix.gnu_target }}" ]]; then
|
||||||
|
cp "target/${{ matrix.gnu_target }}/release/${tray}" "$stage/"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
cp "target/${{ matrix.target }}/release/${tray}" "$stage/"
|
cp "target/${{ matrix.target }}/release/${tray}" "$stage/"
|
||||||
fi
|
fi
|
||||||
@@ -201,7 +210,7 @@ jobs:
|
|||||||
# The GUI variants: the same agent plus the tray binary and a desktop
|
# The GUI variants: the same agent plus the tray binary and a desktop
|
||||||
# launcher, named tsunagi-gui (conflicting with the headless packages).
|
# launcher, named tsunagi-gui (conflicting with the headless packages).
|
||||||
- name: Build the GUI packages
|
- name: Build the GUI packages
|
||||||
if: matrix.deb
|
if: matrix.gnu_target
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ jobs:
|
|||||||
deb: true
|
deb: true
|
||||||
deb_arch: arm64
|
deb_arch: arm64
|
||||||
pkg_arch: aarch64
|
pkg_arch: aarch64
|
||||||
gnu_target: aarch64-unknown-linux-gnu
|
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
asset: tsunagi-macos-aarch64
|
asset: tsunagi-macos-aarch64
|
||||||
@@ -113,11 +112,19 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
export RUSTFLAGS="${RUSTFLAGS:-} -A linker_messages"
|
|
||||||
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
||||||
rustup target add "${{ matrix.gnu_target }}"
|
if [[ -n "${{ matrix.gnu_target }}" ]]; then
|
||||||
cargo zigbuild --release --locked \
|
# tray-icon links GTK and libappindicator through pkg-config, which
|
||||||
--target "${{ matrix.gnu_target }}.2.28" --bin tsunagi-tray
|
# has no cross story under zig, so the Linux GUI is built natively
|
||||||
|
# (x86_64 only) against the runner's glibc.
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends \
|
||||||
|
libgtk-3-dev libayatana-appindicator3-dev libxdo-dev
|
||||||
|
cargo build --release --locked \
|
||||||
|
--target "${{ matrix.gnu_target }}" --bin tsunagi-tray
|
||||||
|
else
|
||||||
|
echo "no GUI build for ${{ matrix.target }}"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
cargo build --release --locked --target "${{ matrix.target }}" --bin tsunagi-tray
|
cargo build --release --locked --target "${{ matrix.target }}" --bin tsunagi-tray
|
||||||
fi
|
fi
|
||||||
@@ -135,11 +142,13 @@ jobs:
|
|||||||
[[ "${{ runner.os }}" == "Windows" ]] && bin="tsunagi.exe"
|
[[ "${{ runner.os }}" == "Windows" ]] && bin="tsunagi.exe"
|
||||||
cp "target/${{ matrix.target }}/release/${bin}" "$stage/"
|
cp "target/${{ matrix.target }}/release/${bin}" "$stage/"
|
||||||
|
|
||||||
# The tray GUI alongside it: glibc build on Linux, native elsewhere.
|
# The tray GUI alongside it, where it was built (Linux: x86_64 only).
|
||||||
tray="tsunagi-tray"
|
tray="tsunagi-tray"
|
||||||
[[ "${{ runner.os }}" == "Windows" ]] && tray="tsunagi-tray.exe"
|
[[ "${{ runner.os }}" == "Windows" ]] && tray="tsunagi-tray.exe"
|
||||||
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
||||||
cp "target/${{ matrix.gnu_target }}/release/${tray}" "$stage/"
|
if [[ -n "${{ matrix.gnu_target }}" ]]; then
|
||||||
|
cp "target/${{ matrix.gnu_target }}/release/${tray}" "$stage/"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
cp "target/${{ matrix.target }}/release/${tray}" "$stage/"
|
cp "target/${{ matrix.target }}/release/${tray}" "$stage/"
|
||||||
fi
|
fi
|
||||||
@@ -203,7 +212,7 @@ jobs:
|
|||||||
# The GUI variants: the same agent plus the tray binary and a desktop
|
# The GUI variants: the same agent plus the tray binary and a desktop
|
||||||
# launcher, named tsunagi-gui (conflicting with the headless packages).
|
# launcher, named tsunagi-gui (conflicting with the headless packages).
|
||||||
- name: Build the GUI packages
|
- name: Build the GUI packages
|
||||||
if: matrix.deb
|
if: matrix.gnu_target
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|||||||
Generated
+1
@@ -6329,6 +6329,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui-phosphor",
|
"egui-phosphor",
|
||||||
|
"gtk",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ tracing.workspace = true
|
|||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
egui-phosphor = "0.9"
|
egui-phosphor = "0.9"
|
||||||
|
|
||||||
|
# Linux only: tray-icon (already GTK-based there) needs the app to initialise
|
||||||
|
# GTK and run its main loop, which eframe/winit do not do.
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
gtk = "0.18"
|
||||||
|
|
||||||
# macOS only: set the app's activation policy to Accessory so it lives in the
|
# macOS only: set the app's activation policy to Accessory so it lives in the
|
||||||
# menu bar with no Dock icon. Pinned to eframe's winit so the types unify.
|
# menu bar with no Dock icon. Pinned to eframe's winit so the types unify.
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use tray_icon::menu::{Menu, MenuEvent, MenuItem, PredefinedMenuItem};
|
use tray_icon::menu::{Menu, MenuEvent, MenuId, MenuItem, PredefinedMenuItem};
|
||||||
use tray_icon::{Icon, TrayIcon, TrayIconBuilder};
|
use tray_icon::{Icon, TrayIcon, TrayIconBuilder};
|
||||||
|
|
||||||
/// What the user picked from the tray menu.
|
/// What the user picked from the tray menu.
|
||||||
@@ -22,34 +22,81 @@ pub(crate) enum Action {
|
|||||||
/// The live tray icon plus the channel of menu actions.
|
/// The live tray icon plus the channel of menu actions.
|
||||||
pub(crate) struct Tray {
|
pub(crate) struct Tray {
|
||||||
/// Kept alive for as long as the app runs; dropping it removes the icon.
|
/// Kept alive for as long as the app runs; dropping it removes the icon.
|
||||||
|
/// On Linux the icon lives on the GTK thread instead (it is not `Send`).
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
_icon: TrayIcon,
|
_icon: TrayIcon,
|
||||||
actions: mpsc::Receiver<Action>,
|
actions: mpsc::Receiver<Action>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A built tray icon and the ids of its menu items.
|
||||||
|
struct Built {
|
||||||
|
icon: TrayIcon,
|
||||||
|
open: MenuId,
|
||||||
|
quit: MenuId,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build() -> Result<Built, String> {
|
||||||
|
let open = MenuItem::new("Open tsunagi", true, None);
|
||||||
|
let quit = MenuItem::new("Quit", true, None);
|
||||||
|
let menu = Menu::new();
|
||||||
|
menu.append(&open).map_err(|err| err.to_string())?;
|
||||||
|
menu.append(&PredefinedMenuItem::separator())
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
menu.append(&quit).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let icon = TrayIconBuilder::new()
|
||||||
|
.with_tooltip("tsunagi")
|
||||||
|
.with_menu(Box::new(menu))
|
||||||
|
.with_icon(make_icon()?)
|
||||||
|
.build()
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
Ok(Built {
|
||||||
|
icon,
|
||||||
|
open: open.id().clone(),
|
||||||
|
quit: quit.id().clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl Tray {
|
impl Tray {
|
||||||
/// Builds the tray and starts reading its menu events.
|
/// Builds the tray and starts reading its menu events.
|
||||||
///
|
///
|
||||||
/// Created on the main thread (required on macOS); `ctx` is used to wake the
|
/// Created on the main thread on macOS and Windows (required on macOS). On
|
||||||
/// UI when a menu item is chosen.
|
/// Linux the tray needs GTK, which eframe/winit never initialise, so it is
|
||||||
|
/// built on a dedicated thread that initialises GTK and runs its main loop.
|
||||||
|
/// `ctx` is used to wake the UI when a menu item is chosen.
|
||||||
pub(crate) fn new(ctx: egui::Context) -> Result<Self, String> {
|
pub(crate) fn new(ctx: egui::Context) -> Result<Self, String> {
|
||||||
let open = MenuItem::new("Open tsunagi", true, None);
|
#[cfg(target_os = "linux")]
|
||||||
let quit = MenuItem::new("Quit", true, None);
|
let (open_id, quit_id) = {
|
||||||
let menu = Menu::new();
|
let (ready_tx, ready_rx) = mpsc::sync_channel(1);
|
||||||
menu.append(&open).map_err(|err| err.to_string())?;
|
std::thread::Builder::new()
|
||||||
menu.append(&PredefinedMenuItem::separator())
|
.name("tray-gtk".into())
|
||||||
.map_err(|err| err.to_string())?;
|
.spawn(move || {
|
||||||
menu.append(&quit).map_err(|err| err.to_string())?;
|
if let Err(err) = gtk::init() {
|
||||||
|
let _ = ready_tx.send(Err(format!("cannot initialise GTK: {err}")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match build() {
|
||||||
|
Ok(built) => {
|
||||||
|
let _ = ready_tx.send(Ok((built.open, built.quit)));
|
||||||
|
gtk::main();
|
||||||
|
drop(built.icon);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let _ = ready_tx.send(Err(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
ready_rx
|
||||||
|
.recv()
|
||||||
|
.map_err(|_| "the tray thread exited before it was ready".to_string())??
|
||||||
|
};
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
let (icon, open_id, quit_id) = {
|
||||||
|
let built = build()?;
|
||||||
|
(built.icon, built.open, built.quit)
|
||||||
|
};
|
||||||
|
|
||||||
let icon = make_icon()?;
|
|
||||||
let tray = TrayIconBuilder::new()
|
|
||||||
.with_tooltip("tsunagi")
|
|
||||||
.with_menu(Box::new(menu))
|
|
||||||
.with_icon(icon)
|
|
||||||
.build()
|
|
||||||
.map_err(|err| err.to_string())?;
|
|
||||||
|
|
||||||
let open_id = open.id().clone();
|
|
||||||
let quit_id = quit.id().clone();
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
// The global menu-event channel is fed by the OS event loop.
|
// The global menu-event channel is fed by the OS event loop.
|
||||||
@@ -69,7 +116,8 @@ impl Tray {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
_icon: tray,
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
_icon: icon,
|
||||||
actions: rx,
|
actions: rx,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user