5 Commits

Author SHA1 Message Date
5310b98542 Feat: produce an .msi for windows CI
Some checks failed
CI / check (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Failing after 1m27s
Deploy Website / deploy (push) Has been skipped
CI / check (macos-14, aarch64-apple-darwin) (push) Has been cancelled
CI / check (windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
2026-02-28 03:33:54 +01:00
7099501130 Feat: overhaul to produce .dmg and .app on macOS build script 2026-02-28 03:15:51 +01:00
730ddfb716 Feat: add slicing words 2026-02-28 02:37:09 +01:00
0a186f774c Feat: more mouse support 2026-02-28 02:26:33 +01:00
25a5c77344 Feat: fixes 2026-02-27 14:39:42 +01:00
20 changed files with 446 additions and 28 deletions

View File

@@ -1,5 +1,10 @@
# Uncomment to use local doux for development
paths = ["/Users/bubo/doux"]
[alias]
xtask = "run --package xtask --release --"
[target.x86_64-pc-windows-gnu]
rustflags = ["-C", "link-args=-lstdc++ -lws2_32 -liphlpapi -lwinmm"]
rustflags = [
"-C", "link-args=-Wl,-Bstatic -lstdc++ -lgcc -lgcc_eh -lpthread -Wl,-Bdynamic -lmingwex -lmsvcrt -lws2_32 -liphlpapi -lwinmm -lole32 -loleaut32 -luuid -lkernel32",
]

View File

@@ -141,6 +141,21 @@ jobs:
name: ${{ matrix.artifact }}-desktop
path: target/${{ matrix.target }}/release/cagire-desktop.exe
- name: Install cargo-wix (Windows)
if: runner.os == 'Windows'
run: cargo install cargo-wix
- name: Build MSI installer (Windows)
if: runner.os == 'Windows'
run: cargo wix --no-build --nocapture -C -p -C x64
- name: Upload MSI installer (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}-msi
path: target/wix/*.msi
- name: Upload CLAP artifact
uses: actions/upload-artifact@v4
with:
@@ -266,6 +281,18 @@ jobs:
-output cagire-plugins.vst3/Contents/MacOS/cagire-plugins
lipo -info cagire-plugins.vst3/Contents/MacOS/cagire-plugins
- uses: actions/checkout@v4
with:
sparse-checkout: |
assets/DMG-README.txt
scripts/make-dmg.sh
clean: false
- name: Create DMG
run: |
chmod +x scripts/make-dmg.sh
scripts/make-dmg.sh Cagire.app .
- name: Build .pkg installer
run: |
VERSION="${GITHUB_REF_NAME#v}"
@@ -303,6 +330,12 @@ jobs:
name: cagire-macos-universal-vst3
path: cagire-plugins.vst3
- name: Upload DMG
uses: actions/upload-artifact@v4
with:
name: cagire-macos-universal-dmg
path: Cagire-*.dmg
- name: Upload .pkg installer
uses: actions/upload-artifact@v4
with:
@@ -328,7 +361,9 @@ jobs:
mkdir -p release
for dir in artifacts/*/; do
name=$(basename "$dir")
if [[ "$name" == "cagire-macos-universal-pkg" ]]; then
if [[ "$name" == "cagire-macos-universal-dmg" ]]; then
cp "$dir"/*.dmg release/
elif [[ "$name" == "cagire-macos-universal-pkg" ]]; then
cp "$dir"/*.pkg release/
elif [[ "$name" == "cagire-macos-universal-desktop" ]]; then
cp "$dir/Cagire.app.zip" "release/cagire-macos-universal-desktop.app.zip"
@@ -344,6 +379,8 @@ jobs:
elif [[ "$name" == *-vst3 ]]; then
base="${name%-vst3}"
cd "$dir" && zip -r "../../release/${base}-vst3.zip" cagire-plugins.vst3 && cd ../..
elif [[ "$name" == *-msi ]]; then
cp "$dir"/*.msi release/
elif [[ "$name" == *-appimage ]]; then
cp "$dir"/*.AppImage release/
elif [[ "$name" == *-desktop ]]; then

4
Cargo.lock generated
View File

@@ -1809,8 +1809,8 @@ dependencies = [
[[package]]
name = "doux"
version = "0.0.4"
source = "git+https://github.com/sova-org/doux#ba475601a448f36d4755eb1fdcaa629987485892"
version = "0.0.5"
source = "git+https://github.com/sova-org/doux#886702b4fe937d26ed681a2f6d7626d26d6890d0"
dependencies = [
"arc-swap",
"clap",

19
assets/DMG-README.txt Normal file
View File

@@ -0,0 +1,19 @@
Cagire - A Forth-based music sequencer
Made by BuboBubo and his friends
======================================
Installation
------------
Drag Cagire.app into the Applications folder.
Unquarantine
------------
Since this app is not signed with an Apple Developer certificate,
macOS will block it from running. To fix this, open Terminal and run:
xattr -cr /Applications/Cagire.app
Support
-------
If you enjoy Cagire, consider supporting development:
https://ko-fi.com/raphaelbubo

View File

@@ -4,10 +4,13 @@ fn main() {
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os == "windows" {
// rusty_link's build.rs only links stdc++ on linux — add it for windows too
println!("cargo:rustc-link-lib=stdc++");
// C++ runtime (stdc++, gcc, gcc_eh, pthread) linked statically via .cargo/config.toml
// using -Wl,-Bstatic. Only Windows system DLLs go here.
println!("cargo:rustc-link-lib=ws2_32");
println!("cargo:rustc-link-lib=iphlpapi");
println!("cargo:rustc-link-lib=winmm");
println!("cargo:rustc-link-lib=ole32");
println!("cargo:rustc-link-lib=oleaut32");
}
#[cfg(windows)]

View File

@@ -58,7 +58,7 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
"nand" => Op::Nand,
"nor" => Op::Nor,
"ifelse" => Op::IfElse,
"pick" => Op::Pick,
"select" => Op::Pick,
"sound" => Op::NewCmd,
"." => Op::Emit,
"rand" => Op::Rand(None),

View File

@@ -507,12 +507,12 @@ pub(super) const WORDS: &[Word] = &[
varargs: false,
},
Word {
name: "pick",
name: "select",
aliases: &[],
category: "Logic",
stack: "(..quots n --)",
desc: "Execute nth quotation (0-indexed)",
example: "{ 1 } { 2 } { 3 } 2 pick => 3",
example: "{ 1 } { 2 } { 3 } 2 select => 3",
compile: Simple,
varargs: true,
},

View File

@@ -186,6 +186,26 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: true,
},
Word {
name: "slice",
aliases: &[],
category: "Sample",
stack: "(v.. --)",
desc: "Divide sample into N equal slices",
example: r#""break" s 8 slice 3 pick ."#,
compile: Param,
varargs: true,
},
Word {
name: "pick",
aliases: &[],
category: "Sample",
stack: "(v.. --)",
desc: "Select which slice to play (0-indexed, wraps)",
example: r#""break" s 8 slice 3 pick ."#,
compile: Param,
varargs: true,
},
Word {
name: "voice",
aliases: &[],

View File

@@ -45,6 +45,8 @@ snare sound 0.5 speed . ( play snare at half speed )
| `n` | 0+ | Sample index within a folder (wraps around) |
| `begin` | 0-1 | Playback start position |
| `end` | 0-1 | Playback end position |
| `slice` | 1+ | Divide sample into N equal slices |
| `pick` | 0+ | Select which slice to play (0-indexed, wraps) |
| `speed` | any | Playback speed multiplier |
| `freq` | Hz | Base frequency for pitch tracking |
| `fit` | seconds | Stretch/compress sample to fit duration |
@@ -62,6 +64,21 @@ kick sound 0.5 end . ( play first half )
If begin is greater than end, they swap automatically.
## Slice and Pick
For evenly-spaced slicing, `slice` divides the sample into N equal parts and `pick` selects which one (0-indexed, wraps around).
```forth
break sound 8 slice 3 pick . ( play the 4th eighth of the sample )
break sound 16 slice step pick . ( scan through 16 slices by step )
```
Combine with `fit` to time-stretch each slice to a target duration. `fit` accounts for the sliced range automatically.
```forth
break sound 4 slice 2 pick 1 loop . ( quarter of the sample, fitted to 1 beat )
```
## Speed and Pitch
The `speed` parameter affects both tempo and pitch. A speed of 2 plays twice as fast and an octave higher.

View File

@@ -63,12 +63,12 @@ Reads naturally: "c3 or c4, depending on the coin."
tri s c4 note 0.2 decay . ;; loud during fills, quiet otherwise
```
## pick
## select
Choose the nth option from a list of quotations:
```forth
{ c4 } { e4 } { g4 } { b4 } iter 4 mod pick
{ c4 } { e4 } { g4 } { b4 } iter 4 mod select
note sine s 0.5 decay .
```

View File

@@ -229,6 +229,14 @@ bundle_plugins_native() {
cargo xtask bundle "$PLUGIN_NAME" --release $tf
}
bundle_desktop_native() {
local platform="$1"
local tf
tf=$(target_flag "$platform")
# shellcheck disable=SC2086
cargo bundle --release --features desktop --bin cagire-desktop $tf
}
bundle_plugins_cross() {
local platform="$1"
local rd
@@ -300,6 +308,25 @@ copy_artifacts() {
local dst="$OUT/cagire-desktop-${os}-${arch}${suffix}"
cp "$src" "$dst"
echo " cagire-desktop -> $dst"
# macOS .app bundle
if [[ "$os" == "macos" ]]; then
local app_src="$rd/bundle/osx/Cagire.app"
if [[ -d "$app_src" ]]; then
local app_dst="$OUT/Cagire-${arch}.app"
rm -rf "$app_dst"
cp -R "$app_src" "$app_dst"
echo " Cagire.app -> $app_dst"
scripts/make-dmg.sh "$app_dst" "$OUT"
fi
fi
fi
# MSI installer for Windows targets
if [[ "$os" == "windows" ]] && command -v cargo-wix &>/dev/null; then
echo " Building MSI installer..."
cargo wix --no-build --nocapture -C -p -C x64
cp target/wix/*.msi "$OUT/" 2>/dev/null && echo " MSI -> $OUT/" || true
fi
# AppImage for Linux targets
@@ -407,6 +434,10 @@ for platform in "${selected_platforms[@]}"; do
if $build_desktop; then
echo " -> cagire-desktop"
build_binary "$platform" --features desktop --bin cagire-desktop
if ! is_cross_target "$platform"; then
echo " -> bundling cagire-desktop .app"
bundle_desktop_native "$platform"
fi
fi
if $build_plugins; then

View File

@@ -7,7 +7,13 @@ RUN apt-get update && \
libclang-dev \
mingw-w64-tools \
mingw-w64-x86-64-dev \
g++-mingw-w64-x86-64 \
&& rm -rf /var/lib/apt/lists/* \
&& ln -sf windows.h /usr/x86_64-w64-mingw32/include/Windows.h \
&& ln -sf winsock2.h /usr/x86_64-w64-mingw32/include/WinSock2.h \
&& ln -sf ws2tcpip.h /usr/x86_64-w64-mingw32/include/WS2tcpip.h
&& ln -sf ws2tcpip.h /usr/x86_64-w64-mingw32/include/WS2tcpip.h \
&& GCCDIR=$(ls -d /usr/lib/gcc/x86_64-w64-mingw32/*-posix 2>/dev/null | head -1) \
&& ln -sf "$GCCDIR/libstdc++.a" /usr/x86_64-w64-mingw32/lib/libstdc++.a \
&& ln -sf "$GCCDIR/libgcc.a" /usr/x86_64-w64-mingw32/lib/libgcc.a \
&& rm -f /usr/x86_64-w64-mingw32/lib/libstdc++.dll.a \
&& rm -f /usr/lib/gcc/x86_64-w64-mingw32/*/libstdc++.dll.a

52
scripts/make-dmg.sh Executable file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail
# Usage: scripts/make-dmg.sh <app-path> <output-dir>
# Produces a .dmg from a macOS .app bundle using only hdiutil.
if [[ $# -ne 2 ]]; then
echo "Usage: $0 <app-path> <output-dir>"
exit 1
fi
APP_PATH="$1"
OUTDIR="$2"
REPO_ROOT="$(git rev-parse --show-toplevel)"
if [[ ! -d "$APP_PATH" ]]; then
echo "ERROR: $APP_PATH is not a directory"
exit 1
fi
LIPO_OUTPUT=$(lipo -info "$APP_PATH/Contents/MacOS/cagire-desktop" 2>/dev/null)
if [[ -z "$LIPO_OUTPUT" ]]; then
echo "ERROR: could not determine architecture from $APP_PATH"
exit 1
fi
if echo "$LIPO_OUTPUT" | grep -q "Architectures in the fat file"; then
ARCH="universal"
else
ARCH=$(echo "$LIPO_OUTPUT" | awk '{print $NF}')
case "$ARCH" in
arm64) ARCH="aarch64" ;;
esac
fi
STAGING="$(mktemp -d)"
trap 'rm -rf "$STAGING"' EXIT
cp -R "$APP_PATH" "$STAGING/Cagire.app"
ln -s /Applications "$STAGING/Applications"
cp "$REPO_ROOT/assets/DMG-README.txt" "$STAGING/README.txt"
DMG_NAME="Cagire-${ARCH}.dmg"
mkdir -p "$OUTDIR"
hdiutil create -volname "Cagire" \
-srcfolder "$STAGING" \
-ov -format UDZO \
"$OUTDIR/$DMG_NAME"
echo " DMG -> $OUTDIR/$DMG_NAME"

View File

@@ -394,6 +394,7 @@ impl eframe::App for CagireDesktop {
self.app.flush_queued_changes(&sequencer.cmd_tx);
self.app.flush_dirty_patterns(&sequencer.cmd_tx);
self.app.flush_dirty_script(&sequencer.cmd_tx);
while let Ok(midi_cmd) = self.midi_rx.try_recv() {
match midi_cmd {

View File

@@ -1070,7 +1070,7 @@ impl SequencerState {
}
let script_frontier = if self.script_frontier < 0.0 {
frontier.max(0.0)
frontier
} else {
self.script_frontier
};

View File

@@ -6,8 +6,8 @@ use ratatui::layout::{Constraint, Layout, Rect};
use crate::commands::AppCommand;
use crate::page::Page;
use crate::state::{
DeviceKind, DictFocus, EngineSection, HelpFocus, MinimapMode, Modal, OptionsFocus,
PatternsColumn, SettingKind,
DeviceKind, DictFocus, EditorTarget, EngineSection, HelpFocus, MinimapMode, Modal,
OptionsFocus, PatternsColumn, SettingKind,
};
use crate::views::{dict_view, engine_view, help_view, main_view, patterns_view, script_view};
@@ -94,14 +94,22 @@ fn handle_editor_drag(ctx: &mut InputContext, col: u16, row: u16, term: Rect) {
}
}
fn handle_editor_mouse(ctx: &mut InputContext, col: u16, row: u16, term: Rect, dragging: bool) {
// Reconstruct editor area (mirrors render_modal_editor / ModalFrame::render_centered)
fn editor_modal_rect(term: Rect) -> Rect {
let width = (term.width * 80 / 100).max(40);
let height = (term.height * 60 / 100).max(10);
let modal_w = width.min(term.width.saturating_sub(4));
let modal_h = height.min(term.height.saturating_sub(4));
let mx = term.x + (term.width.saturating_sub(modal_w)) / 2;
let my = term.y + (term.height.saturating_sub(modal_h)) / 2;
Rect::new(mx, my, modal_w, modal_h)
}
fn handle_editor_mouse(ctx: &mut InputContext, col: u16, row: u16, term: Rect, dragging: bool) {
let modal = editor_modal_rect(term);
let mx = modal.x;
let my = modal.y;
let modal_w = modal.width;
let modal_h = modal.height;
// inner = area inside 1-cell border
let inner_x = mx + 1;
let inner_y = my + 1;
@@ -180,6 +188,18 @@ fn handle_scroll(ctx: &mut InputContext, col: u16, row: u16, term: Rect, up: boo
return;
}
if matches!(ctx.app.ui.modal, Modal::Editor) {
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
let code = if up { KeyCode::Up } else { KeyCode::Down };
for _ in 0..3 {
ctx.app
.editor_ctx
.editor
.input(KeyEvent::new(code, KeyModifiers::empty()));
}
return;
}
if !matches!(ctx.app.ui.modal, Modal::None) {
return;
}
@@ -1057,13 +1077,28 @@ fn handle_engine_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect, k
fn handle_modal_click(ctx: &mut InputContext, col: u16, row: u16, term: Rect) {
match &ctx.app.ui.modal {
Modal::Editor => {
let modal_area = editor_modal_rect(term);
if contains(modal_area, col, row) {
handle_editor_mouse(ctx, col, row, term, false);
} else {
match ctx.app.editor_ctx.target {
EditorTarget::Step => {
ctx.dispatch(AppCommand::SaveEditorToStep);
ctx.dispatch(AppCommand::CompileCurrentStep);
}
EditorTarget::Prelude => {
ctx.dispatch(AppCommand::SavePrelude);
ctx.dispatch(AppCommand::EvaluatePrelude);
ctx.dispatch(AppCommand::ClosePreludeEditor);
}
}
ctx.dispatch(AppCommand::CloseModal);
}
}
Modal::Confirm { .. } => {
handle_confirm_click(ctx, col, row, term);
}
Modal::KeybindingsHelp { .. } => {
// Click outside keybindings help to dismiss
let padded = padded(term);
let width = (padded.width * 80 / 100).clamp(60, 100);
let height = (padded.height * 80 / 100).max(15);
@@ -1073,7 +1108,20 @@ fn handle_modal_click(ctx: &mut InputContext, col: u16, row: u16, term: Rect) {
}
}
_ => {
// For other modals, don't dismiss on click (they have their own input)
let (w, h) = match &ctx.app.ui.modal {
Modal::PatternProps { .. } => (50, 18),
Modal::EuclideanDistribution { .. } => (50, 11),
Modal::Onboarding { .. } => (57, 20),
Modal::FileBrowser(_) | Modal::AddSamplePath(_) => (60, 18),
Modal::Rename { .. } => (40, 5),
Modal::SetPattern { .. } | Modal::SetScript { .. } => (45, 5),
Modal::SetTempo(_) | Modal::JumpToStep(_) => (30, 5),
_ => return,
};
let modal_area = centered_rect(term, w, h);
if !contains(modal_area, col, row) {
ctx.dispatch(AppCommand::CloseModal);
}
}
}
}

View File

@@ -196,21 +196,21 @@ fn ifelse_false() {
}
#[test]
fn pick_first() {
expect_int("{ 10 } { 20 } { 30 } 0 pick", 10);
fn select_first() {
expect_int("{ 10 } { 20 } { 30 } 0 select", 10);
}
#[test]
fn pick_second() {
expect_int("{ 10 } { 20 } { 30 } 1 pick", 20);
fn select_second() {
expect_int("{ 10 } { 20 } { 30 } 1 select", 20);
}
#[test]
fn pick_third() {
expect_int("{ 10 } { 20 } { 30 } 2 pick", 30);
fn select_third() {
expect_int("{ 10 } { 20 } { 30 } 2 select", 30);
}
#[test]
fn pick_preserves_stack() {
expect_int("5 { 10 } { 20 } 0 pick +", 15);
fn select_preserves_stack() {
expect_int("5 { 10 } { 20 } 0 select +", 15);
}

View File

@@ -255,3 +255,16 @@ fn all_replaces_previous_global() {
assert_eq!(outputs.len(), 1);
assert!(outputs[0].contains("lpf/2000"), "latest lpf should be 2000: {}", outputs[0]);
}
#[test]
fn slice_param() {
let outputs = expect_outputs(r#""break" s 8 slice ."#, 1);
assert!(outputs[0].contains("slice/8"));
}
#[test]
fn pick_param() {
let outputs = expect_outputs(r#""break" s 8 slice 3 pick ."#, 1);
assert!(outputs[0].contains("slice/8"));
assert!(outputs[0].contains("pick/3"));
}

20
wix/License.rtf Normal file
View File

@@ -0,0 +1,20 @@
{\rtf1\ansi\deff0\nouicompat{\fonttbl{\f0\fswiss\fcharset0 Helvetica;}}
{\*\generator Msftedit 5.41.21.2510;}\viewkind4\uc1
\pard\sa200\sl276\slmult1\f0\fs20\lang9
CAGIRE - Forth-based Music Sequencer\par
Copyright (c) 2025 Rapha\"el Forment\par
\par
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.\par
\par
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.\par
\par
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see https://www.gnu.org/licenses/.\par
}

146
wix/main.wxs Normal file
View File

@@ -0,0 +1,146 @@
<?xml version='1.0' encoding='windows-1252'?>
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = intel64 ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Product
Id='*'
Name='Cagire'
UpgradeCode='F2A3D4E5-6B7C-8D9E-0F1A-2B3C4D5E6F7A'
Manufacturer='Raphael Forment'
Language='1033'
Codepage='1252'
Version='$(var.Version)'>
<Package Id='*'
Keywords='Installer'
Description='Cagire - Forth-based music sequencer'
Manufacturer='Raphael Forment'
InstallerVersion='450'
Languages='1033'
Compressed='yes'
InstallScope='perMachine'
SummaryCodepage='1252'
/>
<MajorUpgrade
Schedule='afterInstallInitialize'
DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>
<Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1'/>
<Property Id='DiskPrompt' Value='Cagire Installation'/>
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
<Directory Id='APPLICATIONFOLDER' Name='Cagire'>
<Component Id='CagireCLI' Guid='A1B2C3D4-E5F6-7890-ABCD-EF1234567890' Win64='yes'>
<File
Id='CagireEXE'
Name='cagire.exe'
DiskId='1'
Source='$(var.CargoTargetBinDir)\cagire.exe'
KeyPath='yes'/>
</Component>
<Component Id='CagireDesktop' Guid='B2C3D4E5-F6A7-8901-BCDE-F12345678901' Win64='yes'>
<File
Id='CagireDesktopEXE'
Name='cagire-desktop.exe'
DiskId='1'
Source='$(var.CargoTargetBinDir)\cagire-desktop.exe'
KeyPath='yes'/>
</Component>
<Component Id='PathEntry' Guid='C3D4E5F6-A7B8-9012-CDEF-123456789012' Win64='yes' KeyPath='yes'>
<Environment
Id='PATH'
Name='PATH'
Value='[APPLICATIONFOLDER]'
Permanent='no'
Part='last'
Action='set'
System='yes'/>
</Component>
</Directory>
</Directory>
<Directory Id='ProgramMenuFolder'>
<Directory Id='ApplicationProgramsFolder' Name='Cagire'>
<Component Id='StartMenuShortcut' Guid='D4E5F6A7-B8C9-0123-DEFA-234567890123' Win64='yes'>
<Shortcut
Id='CagireDesktopShortcut'
Name='Cagire'
Description='Forth-based music sequencer'
Target='[APPLICATIONFOLDER]cagire-desktop.exe'
WorkingDirectory='APPLICATIONFOLDER'
Icon='CagireIcon.exe'/>
<RemoveFolder Id='CleanUpShortcutFolder' On='uninstall'/>
<RegistryValue
Root='HKCU'
Key='Software\Cagire'
Name='installed'
Type='integer'
Value='1'
KeyPath='yes'/>
</Component>
</Directory>
</Directory>
</Directory>
<Feature
Id='Binaries'
Title='Application'
Description='Installs Cagire CLI and Desktop binaries.'
Level='1'
ConfigurableDirectory='APPLICATIONFOLDER'
AllowAdvertise='no'
Display='expand'
Absent='disallow'>
<ComponentRef Id='CagireCLI'/>
<ComponentRef Id='CagireDesktop'/>
<Feature
Id='Environment'
Title='PATH Environment Variable'
Description='Add the install location to the PATH system environment variable. This allows the cagire CLI to be called from any location.'
Level='1'
Absent='allow'>
<ComponentRef Id='PathEntry'/>
</Feature>
</Feature>
<Feature
Id='StartMenu'
Title='Start Menu Shortcut'
Description='Add a Cagire shortcut to the Start Menu.'
Level='1'
Absent='allow'>
<ComponentRef Id='StartMenuShortcut'/>
</Feature>
<SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize'/>
<Icon Id='CagireIcon.exe' SourceFile='assets\Cagire.ico'/>
<Property Id='ARPPRODUCTICON' Value='CagireIcon.exe'/>
<Property Id='ARPHELPLINK' Value='https://cagire.raphaelforment.fr'/>
<Property Id='ARPURLINFOABOUT' Value='https://github.com/Bubobubobubobubo/cagire'/>
<UI>
<UIRef Id='WixUI_FeatureTree'/>
<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>
<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>
</UI>
<WixVariable Id='WixUILicenseRtf' Value='wix\License.rtf'/>
</Product>
</Wix>