This complements README.md and the developer index for anyone
keeping the project healthy between releases.
All platforms (Windows, Linux, macOS) use
~=Path.home()as the base, so paths are identical relative to the home directory. On Windows~resolves toC:\Users\<name>.Windows path consistency (fixed in 2.0) — an earlier version of
utils/device_remote_icon.pycomputed the icon cache base as%LOCALAPPDATA%\netneighbor\cache\on Windows, whileutils/app_logging.pyandutils/discovery_cache.pyboth usedPath.home() / ".cache" / "netneighbor". This caused icon payload files and the index to land inC:\Users\<name>\AppData\Local\netneighbor\cache\remote_icons\instead ofC:\Users\<name>\.cache\netneighbor\remote_icons\like everything else. The fix was to remove thesys.platform == "win32"branch from_netneighbor_cache_dir()indevice_remote_icon.pyso all three modules resolve to the same~/.cache/netneighbor/base. Users who ran the old version on Windows may have orphaned files in%LOCALAPPDATA%\netneighbor\cache\— those can be deleted; the app will re-fetch icons into the correct location automatically.
| Path | Content |
|---|---|
~/.config/netneighbor/ui_prefs.json |
UI state + per-device overrides + rules (not a full device DB). Tray-related booleans include close_to_tray (default true), start_minimized_to_tray, start_at_login (writes XDG autostart when enabled). custom_command_template (Tools → External applications) for Run custom command; connect_command_templates overrides Open per URL scheme; device_commands stores per-device connection command lists from the Options tab (see COMMUNITY_OVERRIDES.md). |
~/.config/autostart/io.esp3d.netneighbor.desktop |
Present when Start NetNeighbor when logging in is checked in View → Preferences; standard XDG autostart entry. Exec= appends --start-minimized-to-tray (tray-only login start) while the menu .desktop from packaging stays without that flag. Implemented in utils/session_autostart.py. |
~/.cache/netneighbor/discovery-cache.json |
Volatile discovery cache (last_seen_overrides, monitored snapshots metadata, SSDP XML/profile cache). Safe to delete; app rebuilds it. |
~/.config/netneighbor/logging.json |
Per-area log levels (default, app, ssdp, mdns). Created with defaults on first run if missing. |
~/.cache/netneighbor/netneighbor.log |
Log file when file logging is enabled (see app.py). |
~/.cache/netneighbor/remote_icons/ |
On-disk cache for device-provided icons fetched from SSDP/mDNS URLs (normalized). Primary file per URL: {SHA256(url)}.payload raw HTTP body (decoded with GdkPixbuf on load — avoids flaky savev). The digest uses a canonical URL after rewriting typical LAN hosts (*.local, *.lan, single-label names) to the device IP (stable fetch + hash), then normalizing (lowercased scheme/host, trailing FQDN dot stripped, default HTTP/HTTPS ports omitted). Disk lookup also tries legacy explicit :80 / :443 variants so filenames from older canonicalization rules still resolve. A second lookup tries the canonical raw URL (pre-IP rewrite) for older payloads. HTTPS to *.local / *.lan or RFC1918 hosts uses a relaxed TLS context: certificate verification is disabled (self-signed printer certs) and SECLEVEL=0 is set to allow legacy cipher suites and short keys that older embedded firmware (HP, Canon, Lexmark…) may require. Standard public HTTPS URLs go through the system default SSL context with full verification. Legacy .png files from older releases are still read if present. Clearing the folder forces a fresh download; if icons look wrong after a firmware change, clear remote_icon_index.json (see next row) as well. |
~/.cache/netneighbor/remote_icon_index.json |
Host → icon cache index (version 2 JSON): per IPv4/IPv6 key stores canonical_url, payload_sha256 (same stem as remote_icons/{sha256}.payload), and updated_at. Lets the UI load the correct cached icon on cold start without waiting for every mDNS TXT variant. Delete this file to drop remembered host→URL mappings (payload files remain until removed manually). |
~/.config/netneighbor_icon_packs/ |
User-installed icon packs (one subfolder per pack). Intentionally outside ~/.config/netneighbor/ so Reset all application data does not wipe them. Each subfolder may contain iconpack.json (name, owner, version, license, repository fields; optional) and icon files under {N}x{N}/ or flat {N}/ subdirectories. Active pack ID stored in ui_prefs.json under icon_pack. See utils/icon_packs.py. |
~/.config/netneighbor/discovery.json |
Discovery toggles + startup refresh schedule. Top-level mdns / ssdp with enabled, rules, optional query: SSDP interval_seconds (periodic M-SEARCH cadence, default 60), mx_seconds (M-SEARCH MX max wait, clamped 5–6, default 5), descriptor_http_min_interval_seconds (minimum gap between HTTP GETs for descriptor URLs whose host is the same IP (or same hostname if not numeric); avoids duplicate fetches when anticipatory XML and SSDP LOCATION arrive close together; 0 disables; default 5, max 120); mDNS enumeration_timeout_seconds (DNS-SD type scan, 2–3 s, default 2.5), enumeration_interval_seconds (repeat scan, default 240), service_info_timeout_ms (get_service_info, 2000–3000 ms, default 2500). merge.protocol_order: ordered protocol source ids (default ssdp, mdns) — earlier = stronger for live-row tie-breaks. merge.information_precedence: ordered roles (user_override, ssdp_live, ssdp_profile_cache, mdns) — must list all four exactly once to customize; defaults favour user prefs, then live SSDP, then disk-cache hints on mDNS, then raw mDNS (see utils/discovery_config.py). startup_refresh_seconds at root (comma-separated string or JSON array). |
- Root logging is configured in
app.py(console + optional rotating file under cache dir). logging.json: keysssdpandmdnsapply both to the protocol module (discovery.ssdp,discovery.mdns) and to manager-side lines for that source (discovery.manager.ssdp,discovery.manager.mdns), including “Device added/updated”, location resolution (Location: …), and device appearance (type, bundled icon filename,user_location). Setappto DEBUG forui.device_listmessages (remote vs bundled icon path, GTK theme fallback).- Special levels
NONE,OFF,DISABLED,SILENTturn off that area (no INFO/DEBUG/WARNING from those loggers). NETNEIGHBOR_LOG_LEVELstill overrides the root/default level when set.- Other useful loggers:
discovery.manager(start/stop, publishing at DEBUG),ui.
- Tray:
ui/systray.py—NetNeighborTray(QSystemTrayIcon). Tray menu: Show window / Hide window / Quit. Left-click toggles window visibility. Created inapp_qt.pywhenQSystemTrayIcon.isSystemTrayAvailable(). On Linux this requires a desktop environment with a system tray (most include one). - Icons: canonical vector logo —
assets/svg/netneighbor_icon.svg. The installed.debcopies the SVG into/usr/share/icons/hicolor/scalable/apps/asio.esp3d.netneighbor.svg, and the tray SVG asio.esp3d.netneighbor-tray.svg. - Close-to-tray:
closeEventinui/main_window.pycallsevent.ignore()+self.hide()whenclose_to_traypref isTrueand a tray is available; otherwise normal close. - Minimize to tray:
changeEventinterceptsWindowStateChange+isMinimized(); schedulesself.hide()viaQTimer.singleShot(0, …). - CLI
--start-minimized-to-tray: when set and tray is available,window.show()is skipped so the app starts invisible. - F11 fullscreen: handled via
keyPressEventinui/main_window.py. - Open on PCs: Open / double-click uses
resolve_connect_target— HTTP(S) first, then SMB / FTP / SSH / SFTP / Telnet from merged mDNS or explicit device URLs; typecomputerwith no other target openssmb://toward the host IP. The resulting URI is opened vialaunch_connect_for_uri(utils/connect_launcher.py) — empty per-scheme template in View → Preferences… → Applications keeps the system default. If no target exists, Open is inactive.
NetNeighbor supports swappable icon packs for device-type icons. The active pack is selected in Preferences → General → Icon pack and stored as icon_pack in ui_prefs.json.
- Resolution:
utils/qt_device_icons.py(_bundled_freedesktop_qicon) checks the active pack first, then falls back to the built-inassets/icons/netneighbor/. Supports both{N}x{N}/and flat{N}/directory naming. - User packs:
~/.config/netneighbor_icon_packs/{pack_id}/— survives Reset. Addiconpack.jsonfor display name; otherwise the directory name is shown. - Cache: changing the pack invalidates the module-level cache (
utils/icon_packs.invalidate_icon_pack_cache) andQPixmapCacheso the view rebuilds immediately.
assets/icons/netneighbor/ contains PNG fallback icons for ~1 000
freedesktop.org
icon names, supplied at multiple pixel resolutions. The source tree retains all
resolutions (~151 MB total) for archival — they are available for future UI changes or
higher-DPI presets without requiring a new source checkout.
Packages ship only the 5 sizes actually used by the Qt icon-view presets:
| Folder | UI preset | HiDPI equivalent |
|---|---|---|
16/ |
Small (16 px) | — |
32/ |
Medium (32 px) | Small @2× |
48/ |
Large (48 px) | Medium @2× |
96/ |
Extra large (96 px) | Large @2× |
256/ |
— | high-DPI launcher / app icon |
All other resolution directories (24, 64, 128, …) are stripped during the packaging
build step (packaging/linux/build_*.sh, packaging/windows/build.ps1), saving
~143 MB per artifact.
bundled_freedesktop_png_side_sizes() in ui/icons.py discovers the available sizes
dynamically at runtime by scanning for {N}x{N} subdirectories, so trimming the
directory has no effect on the runtime code path — it simply resolves to a smaller set.
User packs that use flat {N}/ directories are handled by scan_pack_sizes() / find_icon_in_pack() in utils/icon_packs.py.
- Rules tuning: shipped
config/ssdp_rules.json/config/mdns_rules.json; user overlays in~/.config/netneighbor/—COMMUNITY_OVERRIDES.md. - Icons / types:
config/device_types.jsonandCONTRIBUTING_ICONS.md; optional user merge from~/.config/netneighbor/device_types.json(COMMUNITY_OVERRIDES.md). Icons loaded from SSDP/mDNS URLs are persisted under~/.cache/netneighbor/remote_icons/with aremote_icon_index.jsonhost map (see table above). - Translations:
app/locale/*/LC_MESSAGES/netneighbor.po, compile withmsgfmt; seeI18N.mdfor full workflow.
DiscoveryManager.register_presence_transition_hook/unregister_presence_transition_hook(discovery/manager.py): optional observers for"online"/"offline"transitions per device identity row (not wired in core UI). Callbacks run on the same thread as the discovery update — useGLib.idle_addbefore touching GTK. Failures are logged and do not stop discovery. Planned use: future plugins / alerting. Extension code may importDiscoveryManager,PresenceTransitionHook, andPresenceTransitionKindfrom thediscoverypackage (from discovery import …).
- Confirm only one app instance (SSDP bind + multicast).
- Check firewall allows UDP 1900 inbound / multicast.
- Inspect SSDP details in the UI for
LOCATION,USN, XML presence. - Compare logs for “XML fetched” vs failures (timeouts, parse errors).
- Location looks like a URL /
description.xml: should no longer happen; if prefs were polluted earlier, open Location presets or editui_prefs.json— invalid URL-like presets are stripped on load.utils/location_label.pydocuments the rejection rules. - Wrong or delayed device icon: confirm
remote_icons/*.payloadandremote_icon_index.jsontogether; bumpmdns/appto DEBUG to trace aggregate + icon load path. - Device details (first tab): duplicate Last seen rows are collapsed to the latest timestamp; bundled WSD/wsdd/NMB-only rows omit legacy Discovery / WSD XAddrs / NetBIOS registration lines (overview + protocol fields stay minimal). IPv6 (link-local) in the overview only appears when a
fe80::address is inferred from device IP or WSD/wsdd payloads — many Windows PCs only advertise IPv4 in WSD URLs, so an empty IPv6 line there is expected.
Archived in archive/CHANGELOG.md (up to 0.8.0). New release notes go in the repository changelog or release tags.
| Document | When to add |
|---|---|
TESTING.md |
Once you have automated tests or a repeatable manual checklist per release. |
PACKAGING.md |
When AppImage / .deb pipeline is scripted (commands, deps, smoke test). |
TROUBLESHOOTING.md |
User-facing FAQ distilled from GitHub issues (short). |
Architecture Decision Records (ADR): optional short files under docs/adr/ for major choices
(why GTK3, why in-memory store first, etc.) if the team grows.
-
VERSIONbumped. -
README.mdandUSER_DOCUMENTATION.mdaligned with new behavior. -
docs/operations/MAINTENANCE.md,docs/operations/PACKAGING.md, anddocs/contributing/COMMUNITY_OVERRIDES.mdupdated if behavior or deps changed. -
docs/ROADMAP.mdupdated (move done items, add new known issues). - Translation catalogs merged (
msgmerge) and.mofiles compiled. - Smoke run: start app, discovery, details, tray hide/show, CLI
--start-minimized-to-tray, optional autostart toggle, monitored device restart. - Test Open / double-click with Override and Additional device commands.
- Packaging smoke:
./packaging/linux/build_deb.sh, install in VM, verify icons +Recommends(tray +nmblookup).