mirror of
https://github.com/tiennm99/claude-code-usage-bubble.git
synced 2026-10-21 22:16:30 +00:00
build: scaffold the Tauri v2 crate
Turn the package into a lib plus thin bin on Tauri v2 with a root-level tauri.conf.json, capabilities, generated icons (keeping the hand-tuned icon.ico) and a placeholder bubble page. The Win32 modules leave the module tree until each one is ported; their sources stay on disk. tauri-build now embeds the icon, a VERSIONINFO from the Cargo version and a PerMonitorV2 manifest, replacing res/icon.rc and its stale 0.1.0.
This commit is contained in:
1 parent
bb09765be5
commit
f64d81e6a1
18 files changed
+4656
-415
No files matched your search
@@ -1 +1,3 @@
|
||||
/target
|
||||
/node_modules
|
||||
/gen
|
||||
@@ -0,0 +1,6 @@
|
||||
ui/
|
||||
tests/
|
||||
scripts/
|
||||
plans/
|
||||
docs/
|
||||
node_modules/
|
||||
Generated
+4421
-287
File diff suppressed because it is too large.
Load diff
+33
-17
@@ -3,11 +3,23 @@ name = "claude-code-usage-bubble"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
description = "Floating bubble showing Claude Code and Codex usage on Windows"
|
||||
description = "Floating bubble showing Claude Code and Codex usage"
|
||||
homepage = "https://github.com/tiennm99/claude-code-usage-bubble"
|
||||
repository = "https://github.com/tiennm99/claude-code-usage-bubble"
|
||||
|
||||
[lib]
|
||||
name = "claude_code_usage_bubble_lib"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "claude-code-usage-bubble"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = ["tray-icon", "macos-private-api"] }
|
||||
tauri-plugin-single-instance = "2"
|
||||
tauri-plugin-autostart = "2"
|
||||
ureq = "3"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
dirs = "6"
|
||||
@@ -18,27 +30,31 @@ tiny-skia = "0.11"
|
||||
sha2 = "0.10"
|
||||
self-replace = "1.5"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.58"
|
||||
features = [
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
x11rb = "0.14"
|
||||
libloading = "0.9"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
# Keep the major in step with the `windows` crate tauri pulls
|
||||
# (`cargo tree -i windows`), so only one copy is compiled.
|
||||
windows = { version = "0.62", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_System_Registry",
|
||||
"Win32_System_Threading",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_Security",
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_Networking_WinHttp",
|
||||
]
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
] }
|
||||
# Same major as tauri's; used for the WebView2 runtime check.
|
||||
webview2-com = "0.39"
|
||||
|
||||
[build-dependencies]
|
||||
embed-resource = "3"
|
||||
tauri-build = { version = "2", features = [] }
|
||||
serde_json = "1"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
|
||||
@@ -1,16 +1,37 @@
|
||||
// Compiles `res/icon.rc` into the binary as Windows PE resources.
|
||||
// `tauri-build` validates `tauri.conf.json` against the enabled crate
|
||||
// features and generates the capability schemas under `gen/`. On Windows it
|
||||
// also embeds the icon, a VERSIONINFO and the application manifest below.
|
||||
//
|
||||
// `embed-resource` shells out to `rc.exe` (when a Windows SDK is on PATH) or
|
||||
// `windres` (MinGW) to produce a .res object that the linker bakes into the
|
||||
// .exe alongside our Rust code. The .rc file references `src/icons/icon.ico`
|
||||
// via a relative path; keep that path stable.
|
||||
// `tauri.conf.json` has no `version`, so `Cargo.toml` stays the only version
|
||||
// source. `tauri-build` writes VERSIONINFO only from the config version, so
|
||||
// the Cargo version is merged in through `TAURI_CONFIG`, which `try_build`
|
||||
// reads after the config file. Any `TAURI_CONFIG` passed by the CLI
|
||||
// (`--config`) is kept.
|
||||
//
|
||||
// The manifest keeps tauri's default Common-Controls v6 dependency and adds
|
||||
// PerMonitorV2 DPI awareness, so the process is DPI aware before any window
|
||||
// exists instead of relying on a runtime call.
|
||||
|
||||
fn main() {
|
||||
// `compile` returns a `CompilationResult` annotated `#[must_use]` on
|
||||
// embed-resource 3.x. `manifest_optional()` collapses Linux/Mac no-op
|
||||
// cases (where there's no `rc.exe`) into Ok while still surfacing real
|
||||
// failures on Windows.
|
||||
embed_resource::compile("res/icon.rc", embed_resource::NONE)
|
||||
.manifest_optional()
|
||||
.expect("Failed to compile Windows resources");
|
||||
inject_cargo_version();
|
||||
|
||||
let windows = tauri_build::WindowsAttributes::new()
|
||||
.app_manifest(include_str!("res/windows-app-manifest.xml"));
|
||||
tauri_build::try_build(tauri_build::Attributes::new().windows_attributes(windows))
|
||||
.expect("failed to run the tauri build script");
|
||||
}
|
||||
|
||||
fn inject_cargo_version() {
|
||||
let mut config = match std::env::var("TAURI_CONFIG") {
|
||||
Ok(raw) => serde_json::from_str::<serde_json::Value>(&raw)
|
||||
.expect("TAURI_CONFIG must be a JSON object"),
|
||||
Err(_) => serde_json::json!({}),
|
||||
};
|
||||
let object = config
|
||||
.as_object_mut()
|
||||
.expect("TAURI_CONFIG must be a JSON object");
|
||||
object
|
||||
.entry("version")
|
||||
.or_insert_with(|| env!("CARGO_PKG_VERSION").into());
|
||||
std::env::set_var("TAURI_CONFIG", config.to_string());
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Core IPC and window dragging for the bubble and panel windows. Plugins are driven from Rust only.",
|
||||
"windows": ["bubble-*", "panel"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:allow-start-dragging"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 515 B |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
-32
@@ -1,32 +0,0 @@
|
||||
#include <winver.h>
|
||||
|
||||
1 ICON "..\\src\\icons\\icon.ico"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 0,1,0,0
|
||||
PRODUCTVERSION 0,1,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
FILEFLAGS 0x0L
|
||||
FILEOS 0x40004L
|
||||
FILETYPE 0x1L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "tiennm99\0"
|
||||
VALUE "FileDescription", "Claude Code Usage Bubble\0"
|
||||
VALUE "FileVersion", "0.1.0\0"
|
||||
VALUE "InternalName", "ClaudeCodeUsageBubble\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2026 tiennm99\0"
|
||||
VALUE "OriginalFilename", "claude-code-usage-bubble.exe\0"
|
||||
VALUE "ProductName", "Claude Code Usage Bubble\0"
|
||||
VALUE "ProductVersion", "0.1.0\0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,20 @@
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
//! Claude Code Usage Bubble on Tauri v2.
|
||||
//!
|
||||
//! The binary in `src/main.rs` only calls [`run`]. Every module is `pub` so
|
||||
//! that items wired up incrementally do not trip `dead_code`.
|
||||
|
||||
pub mod diag;
|
||||
|
||||
use tauri::{RunEvent, WebviewUrl, WebviewWindowBuilder, WindowEvent};
|
||||
|
||||
/// Label of the scaffold window that proves the webview pipeline works.
|
||||
const TEST_WINDOW_LABEL: &str = "bubble-test";
|
||||
|
||||
/// Starts the application and blocks until it exits.
|
||||
pub fn run() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let diagnose_enabled = args.iter().any(|a| a == "--diagnose");
|
||||
match diag::init(diagnose_enabled) {
|
||||
Ok(Some(path)) => log::info!("startup args={args:?} log_path={}", path.display()),
|
||||
Ok(None) => {}
|
||||
Err(err) => eprintln!("diagnostic log could not be opened: {err}"),
|
||||
}
|
||||
|
||||
let app = tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
let window = WebviewWindowBuilder::new(
|
||||
app,
|
||||
TEST_WINDOW_LABEL,
|
||||
WebviewUrl::App("bubble.html".into()),
|
||||
)
|
||||
.title("Claude Code Usage Bubble")
|
||||
.inner_size(220.0, 66.0)
|
||||
.transparent(true)
|
||||
.decorations(false)
|
||||
.always_on_top(true)
|
||||
.skip_taskbar(true)
|
||||
.resizable(false)
|
||||
.build()?;
|
||||
|
||||
// The process otherwise outlives its windows (see the exit
|
||||
// handler below), so closing the only window must exit
|
||||
// explicitly.
|
||||
let handle = app.handle().clone();
|
||||
window.on_window_event(move |event| {
|
||||
if let WindowEvent::CloseRequested { .. } = event {
|
||||
handle.exit(0);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.build(tauri::generate_context!());
|
||||
|
||||
let app = match app {
|
||||
Ok(app) => app,
|
||||
Err(err) => {
|
||||
log::error!("tauri builder failed: {err}");
|
||||
eprintln!("Claude Code Usage Bubble failed to start: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
app.run(|_app, event| {
|
||||
// Destroying the last window must not end the process; only an
|
||||
// explicit `exit` (which carries an exit code) does.
|
||||
if let RunEvent::ExitRequested {
|
||||
code: None, api, ..
|
||||
} = event
|
||||
{
|
||||
api.prevent_exit();
|
||||
}
|
||||
});
|
||||
}
|
||||
+4
-67
@@ -1,70 +1,7 @@
|
||||
#![windows_subsystem = "windows"]
|
||||
// Several modules (creds, usage, update, os::dpi, …) expose API that the
|
||||
// app surface doesn't fully call yet — they're scaffolding for in-progress
|
||||
// port phases. Allow at the crate root rather than scattering attributes.
|
||||
#![allow(dead_code)]
|
||||
|
||||
// Original infrastructure.
|
||||
mod creds;
|
||||
mod diag;
|
||||
mod i18n;
|
||||
mod net;
|
||||
mod os;
|
||||
mod tray;
|
||||
mod update;
|
||||
mod usage;
|
||||
mod usage_color;
|
||||
|
||||
// Application surface.
|
||||
mod app;
|
||||
mod bubble;
|
||||
mod panel;
|
||||
mod settings;
|
||||
// Release builds use the GUI subsystem so no console window opens with the
|
||||
// app. Debug builds keep the console for log output.
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let diagnose_enabled = args.iter().any(|a| a == "--diagnose");
|
||||
if diagnose_enabled {
|
||||
if let Ok(Some(path)) = diag::init(true) {
|
||||
log::info!("startup args={args:?} log_path={}", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(exit_code) = update::run_cli(&args) {
|
||||
if diagnose_enabled {
|
||||
log::info!("cli mode exited with code {exit_code}");
|
||||
}
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
let wait_pid = args
|
||||
.iter()
|
||||
.position(|a| a == "--wait-pid")
|
||||
.and_then(|i| args.get(i + 1))
|
||||
.and_then(|s| s.parse::<u32>().ok());
|
||||
if let Some(pid) = wait_pid {
|
||||
if diagnose_enabled {
|
||||
log::info!("waiting up to 5s for parent pid {pid} to exit");
|
||||
}
|
||||
update::handoff::wait_for_parent_exit(pid, 5_000);
|
||||
}
|
||||
|
||||
let updated_to = args
|
||||
.iter()
|
||||
.position(|a| a == "--updated-to")
|
||||
.and_then(|i| args.get(i + 1))
|
||||
.cloned();
|
||||
|
||||
if diagnose_enabled {
|
||||
log::info!("entering app::run (wait_pid={wait_pid:?} updated_to={updated_to:?})");
|
||||
}
|
||||
app::run(AppArgs {
|
||||
wait_pid_present: wait_pid.is_some(),
|
||||
updated_to,
|
||||
});
|
||||
}
|
||||
|
||||
pub struct AppArgs {
|
||||
pub wait_pid_present: bool,
|
||||
pub updated_to: Option<String>,
|
||||
claude_code_usage_bubble_lib::run();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Claude Code Usage Bubble",
|
||||
"mainBinaryName": "claude-code-usage-bubble",
|
||||
"identifier": "io.github.tiennm99.claudecodeusagebubble",
|
||||
"build": {
|
||||
"frontendDist": "ui"
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
"macOSPrivateApi": true,
|
||||
"windows": [],
|
||||
"security": {
|
||||
"csp": "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src ipc: http://ipc.localhost"
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": ["appimage", "deb"],
|
||||
"createUpdaterArtifacts": false,
|
||||
"copyright": "Copyright (C) 2026 tiennm99",
|
||||
"publisher": "tiennm99",
|
||||
"icon": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Claude Code Usage Bubble</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pill {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
border-radius: 9999px;
|
||||
background: #1f1f1f;
|
||||
color: #f0f0f0;
|
||||
font: 600 14px "Segoe UI", system-ui, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="pill">Claude Code Usage Bubble</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user