Merge pull request #14 from theta42/feat/tray-icons
feat(icons): clean tray badge set + Start menu/installer icon
This commit is contained in:
@@ -5,6 +5,13 @@ All notable changes to the `theta-agent` daemon will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **New tray icon set** (`cmd/icon-gen`, generated `cmd/theta-agent-tray/icons.go`) — the state badges (Red/Yellow/Green/Blue) are now a rounded-square badge with a subtle vertical gradient and a crisp white theta glyph, rendered at 256px with 4x4 supersampling instead of the old flat 48px circle. Windows gets a proper multi-size ICO (16/24/32/48/64/128/256) built with an exact box filter (was nearest-neighbour over three sizes), so the tray/taskbar icon is sharp at every DPI.
|
||||||
|
- **Start menu / installer icon** — `installer/windows/theta-agent.ico` (multi-size, Blue badge) is bundled by the installer and used for the Start menu "Theta Agent Tray" shortcut, the setup.exe's own icon (`SetupIconFile`), and the uninstaller's display icon.
|
||||||
|
- Removed the dead duplicate icon byte arrays in the root package's `tray_icons.go` (nothing referenced them; the tray binary carries its own copy).
|
||||||
|
|
||||||
## [v2.2.0] - 2026-08-10
|
## [v2.2.0] - 2026-08-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -0,0 +1,327 @@
|
|||||||
|
// Command icon-gen renders the theta-agent tray icon set (Red/Yellow/Green/
|
||||||
|
// Blue state badges) and writes:
|
||||||
|
//
|
||||||
|
// - <tray-dir>/icons.go — the Go source with the embedded 256x256 PNG byte
|
||||||
|
// arrays the tray binary compiles in (gofmt-formatted).
|
||||||
|
// - <tray-dir>/icon-*.png — a preview of each color, for eyeballing.
|
||||||
|
// - <ico-path> — the multi-size Windows .ico (16..256) of the Blue badge,
|
||||||
|
// used as the installer's own icon, the Start menu shortcut icon, and the
|
||||||
|
// uninstaller display icon.
|
||||||
|
//
|
||||||
|
// Design: a rounded-square badge in the state color with a subtle vertical
|
||||||
|
// gradient, and a white theta (ring + horizontal bar) glyph knocked out of it.
|
||||||
|
// Rendered at 256px with 4x4 supersampling so the edges are crisp at every
|
||||||
|
// downscaled size. Pure stdlib; run with:
|
||||||
|
//
|
||||||
|
// go run ./cmd/icon-gen cmd/theta-agent-tray installer/windows/theta-agent.ico
|
||||||
|
//
|
||||||
|
// The generated icons.go and theta-agent.ico must not be edited by hand.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const size = 256
|
||||||
|
|
||||||
|
type badgeColor struct {
|
||||||
|
name string
|
||||||
|
base color.RGBA
|
||||||
|
}
|
||||||
|
|
||||||
|
var palette = []badgeColor{
|
||||||
|
{"Red", color.RGBA{0xEF, 0x44, 0x44, 0xFF}},
|
||||||
|
{"Yellow", color.RGBA{0xEA, 0xB3, 0x08, 0xFF}},
|
||||||
|
{"Green", color.RGBA{0x22, 0xC5, 0x5E, 0xFF}},
|
||||||
|
{"Blue", color.RGBA{0x3B, 0x82, 0xF6, 0xFF}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 3 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: go run ./cmd/icon-gen <tray-package-dir> <ico-output-path>")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
dir := os.Args[1]
|
||||||
|
icoPath := os.Args[2]
|
||||||
|
|
||||||
|
// The Blue badge doubles as the product/app icon (Start menu shortcut,
|
||||||
|
// installer, uninstaller).
|
||||||
|
var blue *image.RGBA
|
||||||
|
|
||||||
|
var src strings.Builder
|
||||||
|
src.WriteString("// Code generated by cmd/icon-gen; DO NOT EDIT.\n")
|
||||||
|
src.WriteString("\npackage main\n\n")
|
||||||
|
src.WriteString("// Theta Agent tray state badges, 256x256 PNG.\n")
|
||||||
|
src.WriteString("var (\n")
|
||||||
|
for _, c := range palette {
|
||||||
|
img := renderBadge(c.base)
|
||||||
|
preview := filepath.Join(dir, "icon-"+strings.ToLower(c.name)+".png")
|
||||||
|
writePNG(img, preview)
|
||||||
|
if c.name == "Blue" {
|
||||||
|
blue = img
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := png.Encode(&buf, img); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "encode:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
src.WriteString(fmt.Sprintf("\ticon%s = %s\n", c.name, goBytes(buf.Bytes())))
|
||||||
|
}
|
||||||
|
src.WriteString(")\n")
|
||||||
|
|
||||||
|
out := filepath.Join(dir, "icons.go")
|
||||||
|
formatted, err := format.Source([]byte(src.String()))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "format:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(out, formatted, 0644); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "write:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("wrote", out)
|
||||||
|
|
||||||
|
if err := writeICO(blue, icoPath); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "ico:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("wrote", icoPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderBadge draws the state badge at `size` with 4x4 supersampling.
|
||||||
|
func renderBadge(base color.RGBA) *image.RGBA {
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, size, size))
|
||||||
|
white := color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}
|
||||||
|
top := mix(base, white, 0.16)
|
||||||
|
bottom := mix(base, color.RGBA{0, 0, 0, 0xFF}, 0.20)
|
||||||
|
|
||||||
|
const margin = 8.0
|
||||||
|
const corner = 52.0
|
||||||
|
cx, cy := size/2.0, size/2.0
|
||||||
|
half := (float64(size) - 2*margin) / 2
|
||||||
|
|
||||||
|
// Theta glyph geometry (ring + horizontal bar, bar extends past the ring).
|
||||||
|
const ringOuter = 80.0
|
||||||
|
const ringInner = 56.0
|
||||||
|
const barHalf = 12.0
|
||||||
|
const barLen = 86.0
|
||||||
|
|
||||||
|
const ss = 4 // supersample factor
|
||||||
|
for py := 0; py < size; py++ {
|
||||||
|
for px := 0; px < size; px++ {
|
||||||
|
var accR, accG, accB, accA float64
|
||||||
|
for sy := 0; sy < ss; sy++ {
|
||||||
|
for sx := 0; sx < ss; sx++ {
|
||||||
|
x := float64(px) + (float64(sx)+0.5)/ss
|
||||||
|
y := float64(py) + (float64(sy)+0.5)/ss
|
||||||
|
if !inRoundedRect(x, y, cx, cy, half, corner) {
|
||||||
|
continue // transparent outside the badge
|
||||||
|
}
|
||||||
|
t := clamp01((y - (cy - half)) / (2 * half))
|
||||||
|
col := lerp(top, bottom, t)
|
||||||
|
if inTheta(x, y, cx, cy, ringOuter, ringInner, barHalf, barLen) {
|
||||||
|
col = white
|
||||||
|
}
|
||||||
|
accR += float64(col.R)
|
||||||
|
accG += float64(col.G)
|
||||||
|
accB += float64(col.B)
|
||||||
|
accA += 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n := float64(ss * ss)
|
||||||
|
img.SetRGBA(px, py, color.RGBA{
|
||||||
|
R: uint8(accR / n),
|
||||||
|
G: uint8(accG / n),
|
||||||
|
B: uint8(accB / n),
|
||||||
|
A: uint8(accA / n),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
func inRoundedRect(x, y, cx, cy, half, r float64) bool {
|
||||||
|
dx := math.Abs(x-cx) - (half - r)
|
||||||
|
dy := math.Abs(y-cy) - (half - r)
|
||||||
|
if dx <= 0 && dy <= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return dx*dx+dy*dy <= r*r
|
||||||
|
}
|
||||||
|
|
||||||
|
func inTheta(x, y, cx, cy, ringOuter, ringInner, barHalf, barLen float64) bool {
|
||||||
|
d := math.Hypot(x-cx, y-cy)
|
||||||
|
inRing := d >= ringInner && d <= ringOuter
|
||||||
|
inBar := math.Abs(y-cy) <= barHalf && math.Abs(x-cx) <= barLen
|
||||||
|
return inRing || inBar
|
||||||
|
}
|
||||||
|
|
||||||
|
func mix(a, b color.RGBA, t float64) color.RGBA {
|
||||||
|
return color.RGBA{
|
||||||
|
R: uint8(float64(a.R) + (float64(b.R)-float64(a.R))*t),
|
||||||
|
G: uint8(float64(a.G) + (float64(b.G)-float64(a.G))*t),
|
||||||
|
B: uint8(float64(a.B) + (float64(b.B)-float64(a.B))*t),
|
||||||
|
A: 0xFF,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lerp(a, b color.RGBA, t float64) color.RGBA {
|
||||||
|
return mix(a, b, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clamp01(v float64) float64 {
|
||||||
|
if v < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if v > 1 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePNG(img *image.RGBA, path string) {
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "create:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if err := png.Encode(f, img); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "encode:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// icoSizes are the entries embedded in the .ico. The 256px source is an
|
||||||
|
// integer multiple of each, so every entry is an exact box-filter downscale.
|
||||||
|
var icoSizes = []int{16, 24, 32, 48, 64, 128, 256}
|
||||||
|
|
||||||
|
// writeICO writes a multi-size Windows .ico (classic BMP XOR+AND entries, the
|
||||||
|
// format LoadImage has always supported) from the 256px source image.
|
||||||
|
func writeICO(src *image.RGBA, path string) error {
|
||||||
|
sizes := icoSizes
|
||||||
|
var dir bytes.Buffer
|
||||||
|
var payload bytes.Buffer
|
||||||
|
offset := 6 + len(sizes)*16
|
||||||
|
|
||||||
|
dir.Write([]byte{0, 0, 1, 0, byte(len(sizes)), 0}) // ICONDIR
|
||||||
|
|
||||||
|
for _, s := range sizes {
|
||||||
|
bmp := toDIB(scaleBox(src, s))
|
||||||
|
w, h := byte(s), byte(s)
|
||||||
|
if s >= 256 {
|
||||||
|
w, h = 0, 0
|
||||||
|
}
|
||||||
|
dir.Write([]byte{w, h, 0, 0, 1, 0, 32, 0})
|
||||||
|
dir.Write(u32le(len(bmp)))
|
||||||
|
dir.Write(u32le(offset + payload.Len()))
|
||||||
|
payload.Write(bmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := f.Write(dir.Bytes()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.Write(payload.Bytes())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// scaleBox resizes src to w x w with an exact box (area-average) filter. Only
|
||||||
|
// correct when src's dimensions are an integer multiple of w — 256 is for
|
||||||
|
// every icoSizes entry — so no interpolation blur is introduced.
|
||||||
|
func scaleBox(src *image.RGBA, w int) *image.RGBA {
|
||||||
|
b := src.Bounds()
|
||||||
|
sw, sh := b.Dx(), b.Dy()
|
||||||
|
fx := sw / w
|
||||||
|
fy := sh / w
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, w, w))
|
||||||
|
for y := 0; y < w; y++ {
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
var r, g, bl, a int64
|
||||||
|
for sy := 0; sy < fy; sy++ {
|
||||||
|
yy := b.Min.Y + y*fy + sy
|
||||||
|
for sx := 0; sx < fx; sx++ {
|
||||||
|
xx := b.Min.X + x*fx + sx
|
||||||
|
cr, cg, cb, ca := src.At(xx, yy).RGBA()
|
||||||
|
r += int64(cr >> 8)
|
||||||
|
g += int64(cg >> 8)
|
||||||
|
bl += int64(cb >> 8)
|
||||||
|
a += int64(ca >> 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n := int64(fx * fy)
|
||||||
|
dst.SetRGBA(x, y, color.RGBA{
|
||||||
|
R: uint8(r / n),
|
||||||
|
G: uint8(g / n),
|
||||||
|
B: uint8(bl / n),
|
||||||
|
A: uint8(a / n),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// toDIB encodes an image as a 32-bit bottom-up DIB with an all-transparent
|
||||||
|
// AND mask — the classic icon bitmap entry.
|
||||||
|
func toDIB(img *image.RGBA) []byte {
|
||||||
|
b := img.Bounds()
|
||||||
|
w, h := b.Dx(), b.Dy()
|
||||||
|
andRow := ((w + 31) / 32) * 4
|
||||||
|
|
||||||
|
dib := make([]byte, 0, 40+w*h*4+andRow*h)
|
||||||
|
dib = append(dib, u32le(40)...) // biSize
|
||||||
|
dib = append(dib, u32le(w)...) // biWidth
|
||||||
|
dib = append(dib, u32le(h*2)...) // biHeight (XOR + AND)
|
||||||
|
dib = append(dib, u16le(1)...) // biPlanes
|
||||||
|
dib = append(dib, u16le(32)...) // biBitCount
|
||||||
|
dib = append(dib, 0, 0, 0, 0) // biCompression = 0 (BI_RGB)
|
||||||
|
dib = append(dib, u32le(w*h*4+andRow*h)...)
|
||||||
|
dib = append(dib, make([]byte, 16)...) // remaining header fields
|
||||||
|
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
srcY := b.Min.Y + (h - 1 - y) // DIB rows are bottom-up
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
r, g, bl, a := img.At(b.Min.X+x, srcY).RGBA()
|
||||||
|
dib = append(dib, byte(bl>>8), byte(g>>8), byte(r>>8), byte(a>>8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dib = append(dib, make([]byte, andRow*h)...) // AND mask: all zero
|
||||||
|
return dib
|
||||||
|
}
|
||||||
|
|
||||||
|
func u16le(v int) []byte {
|
||||||
|
return []byte{byte(v), byte(v >> 8)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func u32le(v int) []byte {
|
||||||
|
var b [4]byte
|
||||||
|
binary.LittleEndian.PutUint32(b[:], uint32(v))
|
||||||
|
return b[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func goBytes(b []byte) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("[]byte{")
|
||||||
|
for i, x := range b {
|
||||||
|
if i%12 == 0 {
|
||||||
|
sb.WriteString("\n\t\t")
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("0x%02x, ", x))
|
||||||
|
}
|
||||||
|
sb.WriteString("\n\t}")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -23,8 +23,8 @@ func TestPNGToIco(t *testing.T) {
|
|||||||
t.Errorf("type must be 1 (icon)")
|
t.Errorf("type must be 1 (icon)")
|
||||||
}
|
}
|
||||||
count := int(ico[4]) | int(ico[5])<<8
|
count := int(ico[4]) | int(ico[5])<<8
|
||||||
if count != 3 {
|
if count != len(iconSizes) {
|
||||||
t.Fatalf("expected 3 icon entries, got %d", count)
|
t.Fatalf("expected %d icon entries, got %d", len(iconSizes), count)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each ICONDIRENTRY: valid size, planes=1, bpp=32, DIB with BITMAPINFOHEADER.
|
// Each ICONDIRENTRY: valid size, planes=1, bpp=32, DIB with BITMAPINFOHEADER.
|
||||||
@@ -38,6 +38,9 @@ func TestPNGToIco(t *testing.T) {
|
|||||||
if h == 0 {
|
if h == 0 {
|
||||||
h = 256
|
h = 256
|
||||||
}
|
}
|
||||||
|
if w != iconSizes[i] || h != iconSizes[i] {
|
||||||
|
t.Errorf("entry %d: encoded size %dx%d, want %dx%d", i, w, h, iconSizes[i], iconSizes[i])
|
||||||
|
}
|
||||||
planes := int(ico[e+4]) | int(ico[e+5])<<8
|
planes := int(ico[e+4]) | int(ico[e+5])<<8
|
||||||
bpp := int(ico[e+6]) | int(ico[e+7])<<8
|
bpp := int(ico[e+6]) | int(ico[e+7])<<8
|
||||||
size := int(ico[e+8]) | int(ico[e+9])<<8 | int(ico[e+10])<<16 | int(ico[e+11])<<24
|
size := int(ico[e+8]) | int(ico[e+9])<<8 | int(ico[e+10])<<16 | int(ico[e+11])<<24
|
||||||
|
|||||||
@@ -43,10 +43,12 @@ ArchitecturesAllowed=x64compatible
|
|||||||
ArchitecturesInstallIn64BitMode=x64compatible
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
OutputDir={#AgentDir}
|
OutputDir={#AgentDir}
|
||||||
OutputBaseFilename=theta-agent-{#MyAppVersion}-windows-amd64-setup
|
OutputBaseFilename=theta-agent-{#MyAppVersion}-windows-amd64-setup
|
||||||
|
SetupIconFile=theta-agent.ico
|
||||||
Compression=lzma2
|
Compression=lzma2
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
WizardStyle=modern
|
WizardStyle=modern
|
||||||
UninstallDisplayName={#MyAppName}
|
UninstallDisplayName={#MyAppName}
|
||||||
|
UninstallDisplayIcon={app}\theta-agent.ico
|
||||||
CloseApplications=no
|
CloseApplications=no
|
||||||
MinVersion=10.0.17763
|
MinVersion=10.0.17763
|
||||||
|
|
||||||
@@ -69,15 +71,19 @@ Source: "{#AgentDir}\theta-agent-helper-windows-amd64.exe"; DestDir: "{app}"; Fl
|
|||||||
; driver is signed; no signature phone-home).
|
; driver is signed; no signature phone-home).
|
||||||
Source: "{#VendorDir}\wireguard-amd64-0.5.3.msi"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
Source: "{#VendorDir}\wireguard-amd64-0.5.3.msi"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
||||||
|
|
||||||
|
; Product icon (Start menu shortcut, uninstaller display icon). Generated by
|
||||||
|
; cmd/icon-gen from the same badge the tray uses.
|
||||||
|
Source: "theta-agent.ico"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
|
||||||
; OpenCredential credential provider installer (BSD-3 pGina fork) + the VC++
|
; OpenCredential credential provider installer (BSD-3 pGina fork) + the VC++
|
||||||
; runtime it needs. Both install silently at [Run].
|
; runtime it needs. Both install silently at [Run].
|
||||||
Source: "{#VendorDir}\OpenCredentialInstaller-1.0.0.0.exe"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
Source: "{#VendorDir}\OpenCredentialInstaller-1.0.0.0.exe"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
||||||
Source: "{#VendorDir}\vc_redist.x64.exe"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
Source: "{#VendorDir}\vc_redist.x64.exe"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{group}\Theta Agent Tray"; Filename: "{app}\tray\theta-agent-tray-windows-amd64.exe"; Comment: "Theta Agent status tray"
|
Name: "{group}\Theta Agent Tray"; Filename: "{app}\tray\theta-agent-tray-windows-amd64.exe"; IconFilename: "{app}\theta-agent.ico"; Comment: "Theta Agent status tray"
|
||||||
Name: "{group}\Open Agent Config"; Filename: "notepad.exe"; Parameters: "{commonappdata}\Theta42\agent.yml"; Comment: "Open the agent configuration file"
|
Name: "{group}\Open Agent Config"; Filename: "notepad.exe"; Parameters: "{commonappdata}\Theta42\agent.yml"; Comment: "Open the agent configuration file"
|
||||||
Name: "{group}\Uninstall Theta Agent"; Filename: "{uninstallexe}"
|
Name: "{group}\Uninstall Theta Agent"; Filename: "{uninstallexe}"; IconFilename: "{app}\theta-agent.ico"
|
||||||
|
|
||||||
[Registry]
|
[Registry]
|
||||||
; Start the tray for every interactive logon.
|
; Start the tray for every interactive logon.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 364 KiB |
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user