Skip to content

Commit 173a903

Browse files
joa-quimclaude
andcommitted
Desktop shortcut + self-locating iview_app launcher
Squashed the whole iteration history of the desktop-launcher work into one commit. Net result: * iview_app.vbs: self-locating launcher. Uses its own folder when run in place; when detached (e.g. a Desktop shortcut) it finds the live install -- honoring JULIA_DEPOT_PATH, checking the `] dev` location first, then scanning the `] add` content-hashed depot -- in a single julia.exe launch. Passes --project only when the target has its own Manifest.toml; forwards dropped files as arguments; self-diagnosing error message. * Desktop shortcut: created by InteractiveGMT's __init__ (_ensure_desktop_shortcut) on the first `using`, so it works for a `] dev` install too (`] dev` never runs the deps/build.jl build hook). Cheap isfile guard: cscript is spawned only when the .lnk is missing, and it self-heals if deleted. * make_desktop_shortcut.vbs: writes exactly ONE .lnk, pointing straight at pkgRoot's own iview_app.vbs and igmt.ico -- never copies files to the Desktop, never targets desktop\iview_app.vbs. Forces the local %USERPROFILE%\Desktop (creating it if missing), never the OneDrive-redirected Desktop. Normalizes a "/"-separated pkgRoot to "\". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6772fa3 commit 173a903

4 files changed

Lines changed: 195 additions & 4 deletions

File tree

deps/build.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ function fetch_and_extract(url::String, dest::String)
6464
rm(zip; force=true)
6565
end
6666

67+
# The Desktop shortcut is NOT created here. `] dev` never runs this build hook, so it can't be the
68+
# thing that makes the icon for a dev install -- InteractiveGMT's __init__ (_ensure_desktop_shortcut)
69+
# owns that instead, firing on the first `using` for dev and add alike.
70+
6771
function main()
6872
if !isfile(MARKER)
6973
# First install EVER on this machine: full runtime bundle, pinned to a coarse,
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
' Creates a real Desktop .lnk shortcut, with the iGMT icon. Two ways to run:
2+
' 1. From deps/build.jl, automatically, on every `Pkg.build("InteractiveGMT")` -- passes the
3+
' package root as WScript.Arguments(0), so no Julia lookup subprocess needed and no
4+
' interactive MsgBox popups (a build should never block on a dialog).
5+
' 2. Standalone, by hand, with no arguments -- looks up the current InteractiveGMT install via
6+
' Julia (Base.find_package) and reports success/failure with a MsgBox.
7+
'
8+
' EXACTLY ONE icon, always. It points STRAIGHT at pkgRoot's own iview_app.vbs and igmt.ico -- we
9+
' NEVER copy those files to the Desktop and NEVER point the shortcut at a Desktop copy. (An earlier
10+
' version had a second "copy to Desktop for `] add` installs" branch; it littered the Desktop with
11+
' iview_app.vbs + igmt.ico and pointed the .lnk at desktop\iview_app.vbs -- forbidden. Removed.)
12+
' iview_app.vbs is already self-locating, so even for a hash-changing `] add` install the direct
13+
' target stays valid enough; the icon path is the only thing that could go stale, and that is an
14+
' acceptable trade against ever cluttering the Desktop.
15+
Dim fso : Set fso = CreateObject("Scripting.FileSystemObject")
16+
Dim sh : Set sh = CreateObject("WScript.Shell")
17+
18+
Dim calledFromBuild : calledFromBuild = (WScript.Arguments.Count > 0)
19+
Dim pkgRoot
20+
21+
If calledFromBuild Then
22+
pkgRoot = WScript.Arguments(0)
23+
Else
24+
Dim tmp : tmp = sh.ExpandEnvironmentStrings("%TEMP%") & "\igmt_locate_" & CStr(Timer) & ".jl"
25+
Dim f : Set f = fso.CreateTextFile(tmp, True)
26+
f.WriteLine "p = Base.find_package(" & Chr(34) & "InteractiveGMT" & Chr(34) & ")"
27+
f.WriteLine "print(p === nothing ? " & Chr(34) & Chr(34) & " : dirname(dirname(p)))"
28+
f.Close
29+
30+
Dim ex : Set ex = sh.Exec("julia --startup-file=no " & Chr(34) & tmp & Chr(34))
31+
pkgRoot = Trim(ex.StdOut.ReadAll())
32+
fso.DeleteFile tmp, True
33+
34+
If pkgRoot = "" Then
35+
MsgBox "Could not locate InteractiveGMT." & vbCrLf & _
36+
"Is it added ('] add https://github.com/GenericMappingTools/InteractiveGMT') or dev'd in Julia's default environment?", _
37+
vbExclamation, "iGMT"
38+
WScript.Quit 1
39+
End If
40+
End If
41+
42+
' NEVER sh.SpecialFolders("Desktop") -- on a machine with OneDrive "Known Folder Move" that call
43+
' returns the OneDrive-synced Desktop (here: "OneDrive - Universidade do Algarve\Ambiente de
44+
' Trabalho"), and NO shortcut of ours may ever land in OneDrive. Force the real local Desktop,
45+
' %USERPROFILE%\Desktop. Once Desktop has been redirected that folder may not exist, so create it
46+
' first -- otherwise CreateShortcut(...).Save fails with "Unable to save shortcut".
47+
Dim desktop : desktop = sh.ExpandEnvironmentStrings("%USERPROFILE%") & "\Desktop"
48+
If Not fso.FolderExists(desktop) Then fso.CreateFolder desktop
49+
50+
' EXACTLY ONE icon, and it points STRAIGHT at the real source files in pkgRoot -- never a copy on
51+
' the Desktop, never a shortcut whose target is desktop\iview_app.vbs. (An earlier version copied
52+
' the .vbs+.ico to the Desktop for `add` installs and pointed the .lnk at those copies; that is
53+
' forbidden -- it littered the Desktop with extra files and a wrong-target shortcut.) So: delete
54+
' any stray copies a previous run may have dropped, then create the single .lnk -> source.
55+
If fso.FileExists(desktop & "\iview_app.vbs") Then fso.DeleteFile desktop & "\iview_app.vbs", True
56+
If fso.FileExists(desktop & "\igmt.ico") Then fso.DeleteFile desktop & "\igmt.ico", True
57+
58+
' Normalize any forward slashes -- callers sometimes pass a "/"-separated pkgRoot, and VBScript's
59+
' GetParentFolderName/GetFileName only ever split on "\". A "/"-path would otherwise build broken
60+
' targets like ".../InteractiveGMT/iview_app.vbs" that the rest of this script mishandles.
61+
pkgRoot = Replace(pkgRoot, "/", "\")
62+
If Right(pkgRoot, 1) = "\" Then pkgRoot = Left(pkgRoot, Len(pkgRoot) - 1)
63+
64+
Dim link : Set link = sh.CreateShortcut(desktop & "\iGMT.lnk")
65+
link.TargetPath = pkgRoot & "\iview_app.vbs"
66+
link.IconLocation = pkgRoot & "\igmt.ico"
67+
link.WindowStyle = 7
68+
link.Save
69+
70+
If Not calledFromBuild Then
71+
MsgBox "iGMT Desktop shortcut created (or refreshed). You can run this setup script again anytime -- it's idempotent.", vbInformation, "iGMT"
72+
End If

iview_app.vbs

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,104 @@
22
' STARTUPINFO SW_HIDE, which the Qt GUI window would inherit and stay invisible. iview_app.jl
33
' hides its own console on startup, so only the viewer window remains.
44
'
5+
' Path resolution, fast path first:
6+
' 1. If iview_app.jl sits right next to this script (running in place -- a dev checkout, or
7+
' inside a live Pkg package folder), use this script's own folder. Zero overhead, exactly
8+
' one julia.exe launch, same speed as always.
9+
' 2. Only if this is a DETACHED copy (e.g. sitting on your Desktop, so iview_app.jl is NOT next
10+
' to it) does it look for a live install, checking BOTH possible locations:
11+
' a. `] dev`-installed: a FIXED directory (<depot>\dev\InteractiveGMT), no scanning needed.
12+
' b. `] add`-installed: a content-hashed folder (<depot>\packages\InteractiveGMT\<hash>)
13+
' that gets a NEW hash on every Pkg.update -- scan for the newest-modified one.
14+
' This is a plain filesystem check, not a Julia subprocess -- still only one julia.exe
15+
' launch total, never two.
16+
'
17+
' (An earlier version asked Julia itself, via a second julia.exe subprocess, to resolve the path
18+
' on every launch -- correct, but doubled startup time. Don't reintroduce that.)
19+
'
520
' Files dropped ONTO the desktop shortcut arrive here as WScript.Arguments; we forward each path
621
' to Julia, and iview_app.jl opens it (otherwise, with no args, it opens an empty launcher).
7-
' sh.Run """C:\programs\Julia-1.10\bin\julia.exe"" --project=""C:\Users\j\.julia\dev\InteractiveGMT"" ""C:\Users\j\.julia\dev\InteractiveGMT\iview_app.jl""" & extra, 7, False
8-
Dim sh : Set sh = CreateObject("WScript.Shell")
22+
Dim fso : Set fso = CreateObject("Scripting.FileSystemObject")
23+
Dim sh : Set sh = CreateObject("WScript.Shell")
24+
25+
Dim here : here = fso.GetParentFolderName(WScript.ScriptFullName)
26+
27+
Dim devPath, depot, diag, subFoldersScanned
28+
devPath = "" : depot = "" : diag = ""
29+
subFoldersScanned = 0
30+
31+
If Not fso.FileExists(here & "\iview_app.jl") Then
32+
' Detached copy -- find the live InteractiveGMT install.
33+
'
34+
' Depot root: respect JULIA_DEPOT_PATH if set (Julia checks this env var itself; a
35+
' customized depot location is real, not hypothetical -- this same machine already turned
36+
' out to have Desktop silently redirected by OneDrive). ExpandEnvironmentStrings returns the
37+
' literal "%JULIA_DEPOT_PATH%" string unexpanded when the var isn't set, which is how we
38+
' detect "not set" here. Windows uses ";" to separate multiple depot paths -- take the first.
39+
Dim depotRoot : depotRoot = sh.ExpandEnvironmentStrings("%JULIA_DEPOT_PATH%")
40+
If depotRoot = "%JULIA_DEPOT_PATH%" Then
41+
depotRoot = sh.ExpandEnvironmentStrings("%USERPROFILE%") & "\.julia"
42+
ElseIf InStr(depotRoot, ";") > 0 Then
43+
depotRoot = Left(depotRoot, InStr(depotRoot, ";") - 1)
44+
End If
45+
here = ""
46+
47+
' 1. `] dev` install -- fixed location, check directly first, no scanning.
48+
devPath = depotRoot & "\dev\InteractiveGMT"
49+
If fso.FileExists(devPath & "\iview_app.jl") Then
50+
here = devPath
51+
Else
52+
' 2. `] add` install -- content-hashed depot, scan for the newest match.
53+
depot = depotRoot & "\packages\InteractiveGMT"
54+
If fso.FolderExists(depot) Then
55+
Dim subFolder, best
56+
best = -1
57+
For Each subFolder In fso.GetFolder(depot).SubFolders
58+
subFoldersScanned = subFoldersScanned + 1
59+
If fso.FileExists(subFolder.Path & "\iview_app.jl") Then
60+
If CDbl(subFolder.DateLastModified) > best Then
61+
best = CDbl(subFolder.DateLastModified)
62+
here = subFolder.Path
63+
End If
64+
End If
65+
Next
66+
End If
67+
End If
68+
End If
69+
70+
If here = "" Then
71+
' Report exactly what was checked instead of a bare "not found" -- no more guessing/back
72+
' and forth needed to diagnose why.
73+
diag = "Checked dev install: " & devPath & " (not found)" & vbCrLf
74+
If Not fso.FolderExists(depot) Then
75+
diag = diag & "Checked add install: depot folder does not exist: " & depot
76+
Else
77+
diag = diag & "Checked add install: depot folder exists (" & depot & ")," & vbCrLf & _
78+
"scanned " & subFoldersScanned & " subfolder(s), none contained iview_app.jl."
79+
End If
80+
MsgBox "Could not locate InteractiveGMT." & vbCrLf & vbCrLf & diag & vbCrLf & vbCrLf & _
81+
"Is it added ('] add https://github.com/GenericMappingTools/InteractiveGMT') or dev'd in Julia's default environment?", _
82+
vbExclamation, "iGMT"
83+
WScript.Quit 1
84+
End If
85+
86+
' --project=<here> ONLY if <here> has its OWN Manifest.toml (a `] dev`-instantiated checkout --
87+
' this dev checkout has one). A plain `Pkg.add`-installed copy in the depot has NO Manifest of
88+
' its own -- the resolved dependency graph lives only in your DEFAULT environment
89+
' (~/.julia/environments/v1.10/), where `] add` put it. Pointing --project straight at a bare,
90+
' Manifest-less package folder forces Julia into an empty/unresolved environment and it tries to
91+
' re-resolve + precompile the entire dependency tree from scratch, which fails ("Failed to
92+
' precompile InteractiveGMT..."). Launch bare (no --project) in that case instead, so Julia uses
93+
' your normal default environment where `Pkg.add` already resolved everything correctly -- same
94+
' as what a plain interactive `using InteractiveGMT` does.
95+
Dim projectArg : projectArg = ""
96+
If fso.FileExists(here & "\Manifest.toml") Then
97+
projectArg = "--project=" & Chr(34) & here & Chr(34) & " "
98+
End If
99+
9100
Dim extra : extra = ""
10101
Dim i
11102
For i = 0 To WScript.Arguments.Count - 1
12-
extra = extra & " """ & WScript.Arguments(i) & """"
103+
extra = extra & " " & Chr(34) & WScript.Arguments(i) & Chr(34)
13104
Next
14-
sh.Run """C:\programs\Julia-1.10\bin\julia.exe"" ""C:\Users\j\.julia\dev\InteractiveGMT\iview_app.jl""" & extra, 7, False
105+
sh.Run "julia " & projectArg & Chr(34) & here & "\iview_app.jl" & Chr(34) & extra, 7, False

src/InteractiveGMT.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,30 @@ function __init__()
128128
catch e
129129
@warn "InteractiveGMT: the Qt+VTK viewer DLL could not be loaded; build it with deps/build.bat (Windows only). Viewer calls will error until then." exception=(e,)
130130
end
131+
132+
# Desktop shortcut (Windows). `] dev` never runs deps/build.jl, so the build hook can't be what
133+
# puts the icon there — this is. Cheap isfile check every load; only spawns cscript when the
134+
# .lnk is actually missing, so it costs nothing after the first launch and self-heals if the
135+
# user deletes it. Non-fatal.
136+
try
137+
_ensure_desktop_shortcut()
138+
catch e
139+
@warn "InteractiveGMT: could not create the Desktop shortcut (non-fatal)." exception=(e,)
140+
end
141+
end
142+
143+
# See __init__. The heavy install-shape logic (dev = point at source, add = copy to a stable home)
144+
# lives in deps/installer/make_desktop_shortcut.vbs; here we only decide WHETHER to (re)run it.
145+
function _ensure_desktop_shortcut()
146+
Sys.iswindows() || return
147+
desktop = joinpath(get(ENV, "USERPROFILE", homedir()), "Desktop")
148+
isdir(desktop) || return
149+
isfile(joinpath(desktop, "iGMT.lnk")) && return
150+
pkgroot = normpath(joinpath(@__DIR__, ".."))
151+
vbs = joinpath(pkgroot, "deps", "installer", "make_desktop_shortcut.vbs")
152+
isfile(vbs) || return
153+
cscript = joinpath(get(ENV, "SystemRoot", "C:\\Windows"), "System32", "cscript.exe")
154+
run(`$cscript //nologo $vbs $pkgroot`)
131155
end
132156

133157
end # module InteractiveGMT

0 commit comments

Comments
 (0)