Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ATTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ For text this project did not write — trade account and character names, which
Cyrillic, Hangul or CJK, none of which Fontin covers — a fallback face is loaded from **whatever
the operating system already ships**. Nothing is bundled for it and nothing is redistributed.

## Font Awesome Free

The buttons a word does not fit on are drawn in **[Font Awesome Free](https://fontawesome.com)**
Solid, by Fonticons, Inc. Only the codepoints actually used are bundled — `scripts/fetch-glyphs.sh`
subsets the release down to those and no more, and
[assets/fonts/README.md](assets/fonts/README.md) says which and why. The website's navigation
icons are the same set, inlined into `site/style.css` rather than fetched from anywhere.

Icons are **CC BY 4.0** and the fonts are **SIL OFL 1.1**; both permit redistribution with
attribution, which this section is. The full text is fetched to
`assets/fonts/FontAwesome-LICENSE.txt`.

## Icon

`assets/popc_icon.png` and the `.ico` generated from it are part of this repository and covered by
Expand Down
13 changes: 12 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,15 @@ and the Save row sit outside it and stay put when they do — a form whose Save
is the failure that shape exists to prevent. Adding a section needs no new number; adding one that
overflows 720 needs this one raised.

**The two paths the dialog names go through `path_line()`** — the config file beside Save, and the
debug log under Diagnostics. Both are drawn through `paths::display_path`, which folds the home
directory back into `~` or `%APPDATA%`, and both open their **folder** on a click
(`paths::file_url` → `SDL_OpenURL`). Three reasons, and they all point the same way: the line has
one line of room and an absolute path does not reliably fit it; this dialog is the one people
screenshot, and an absolute path names the person who took it; and a `.json` or a `.log` is a file
a desktop may have no handler for, while its folder is a thing every file manager opens. The whole
path is still in the hover, which is where the shortening gives back what it took.

**The look is `src/ui/theme.cpp`** — the game's palette and control shapes, sampled off its Options
dialog: near-black frames under hairline brown borders, headings and the title in the small-caps
face, the left column tinted where the value beside it is not, and orange for check marks, slider
Expand Down Expand Up @@ -344,7 +353,9 @@ appears while the game is the window in front). `PPC_DEV_UPDATE_URL=<url>` point
at a `latest.json` of your own, which is the only way to see its three notice surfaces before a
release publishes one — see [updater.md](updater.md). `PPC_REPORT_URL=<url>` points the bug
reporter at a relay of your own, which is how its two outcomes are seen without posting into the
real channel — see [reporting.md](reporting.md).
real channel — see [reporting.md](reporting.md). `PPC_DEV_ANON=1` replaces every seller's handle
in the results with its position for the whole run — the masking a bug-report capture already
does for one frame, held on, because `scripts/capture-screenshots.sh` publishes what it takes.

The Windows binary is **GUI-subsystem** (`WIN32_EXECUTABLE`, entered at `WinMain` in `src/main.cpp`):
a console-subsystem build pops a console window beside an application whose whole UI is an overlay
Expand Down
4 changes: 3 additions & 1 deletion docs/reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ resize the dialog brings must not land inside one.
the user's own, on the row `account_name` marks as theirs. What a maintainer needs off the
picture is that these are twenty different sellers and which row is which; the handles are
somebody else's name and are worth nothing in a bug report. Prices, ages and the counts stay,
because those are what a mispricing is read against.
because those are what a mispricing is read against. The predicate is `App::mask_sellers`, not
`report_capture_pending` directly: `PPC_DEV_ANON` holds the same masking on for a whole run,
which is what the website's screenshots are taken with, and both exist for the same reason.
- **No button shows its tooltip.** The cursor is on the report button when it is pressed, so
without this every capture carries "Report a Bug" hovering over the panel — a picture of the act
of reporting rather than of the thing being reported.
Expand Down
12 changes: 7 additions & 5 deletions scripts/build-site.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ PAGES=(
# rather than a row of broken images. No `|` or `;` inside the copy — they are the separators.
SHOTS=(
"panel-rare.png|The Price Check Window|Just one hotkey away;Rebuilt item preview;Modifier parsing including Rank, Tier and Ranges;Pre-selected modifiers based on strategies, but you decide what to search by"
"listing-hover.png|See what you are comparing to|Item previews straight from the live trade site results;Rendered exactly the way your own item is;See the asking price and the gold fee as a bonus"
"unique-reference.png|A going rate, not just a list of prices|Whenever available, see the current price recorded by poe.ninja;See which way the price is going, with a week of history behind it;Recognises variable modifiers (can roll n of m modifiers)"
"listing-hover.png|See what you are comparing to|Item previews straight from the live trade site results;Rendered exactly the way your own item is;See the asking price and the gold fee as a bonus;Whenever available, the going rate poe.ninja records, and which way it is heading with a week of history behind it;Recognises variable modifiers (can roll n of m modifiers)"
"map.png|Every kind of item asks its own question|Different item, different strategy;Smart search for maps based on Tier, Quantity, More X modifiers…;Custom strategy for Blighted, Blight-ravaged, Corrupted or Influenced maps;Valdo map reward and Void matching"
"currency-exchange.png|Faustus does the work, you should see it|See how an item tradeable on the currency exchange actually trades;See the average price, volume, maximums and minimums for the stated period;Straight from GGG, published once each hour has closed;Every hour ever published* is crawled, going all the way back to Settlers, so no item that has ever traded there is missed;* Only to know what trades there at all — the prices you see are always the single hour named under the table"
"unidentified-picker.png|It knows what it cannot know|Identify an unidentified unique from a list;Pictures included! (unless they aren't);The trade site search matches the Identified/Unidentified status, so the listings are the same product you are holding;Careful: poe.ninja shows you the identified price — don't forget to search!;Not a gambler? Never turn a Watcher's Eye into a 5c item*;* Without checking what the unidentified ones are selling for first!"
"report.png|Something priced wrong? Say so from where it happened|Bug reporting with zero friction;Sends what the tool actually used to identify the item, which is the thing a maintainer has to see;As anonymous as it can be made — every account name is scrubbed out of the trade results before the picture is taken;Everything you see is what will be sent. Nothing more. No fingerprint, no identifying information, just what matters for fixing the problem;A screenshot sometimes tells the rest of the story, so you get the preview and the choice of whether to attach it"
"settings.png|Some Assembly Required|You should probably tell the tool the league you play in;Adjustable range matching including open-sided matching;Want a pleasant surprise? Set up your own account name;The tool will spot your own listings in the results and highlight them"
)

Expand Down Expand Up @@ -107,13 +107,15 @@ inject() {
{ print }'
}

# The class is the page's own name, which is what carries the glyph: `.nav-<page>` in the
# stylesheet holds the icon, and a page listed here without a rule there simply goes without one.
nav=""
for p in "${PAGES[@]}"; do
IFS='|' read -r _ out title <<<"$p"
case $out in about.html|roadmap.html|privacy.html|attribution.html|contact.html) ;; *) continue ;; esac
nav+="<a href=\"$out\">$title</a>"
nav+="<a class=\"nav-${out%.html}\" href=\"$out\">$title</a>"
done
nav+="<a href=\"https://github.com/$REPO\">GitHub</a>"
nav+="<a class=\"nav-github\" href=\"https://github.com/$REPO\">GitHub</a>"

docs_list=""
for p in "${PAGES[@]}"; do
Expand Down Expand Up @@ -210,7 +212,7 @@ done
if [ -n "$gallery" ]; then
{
printf '<section class="shots" id="screenshots">\n'
printf '<h2>What it looks like</h2>\n'
printf '<h2>Gallery</h2>\n'
printf '<div class="strip" tabindex="0" role="region" aria-label="Screenshots">%s</div>\n' "$gallery"
printf '<nav class="dots" aria-label="Jump to a screenshot"><span>Scroll, swipe or pick one</span>%s</nav>\n' "$dots"
printf '</section>\n'
Expand Down
112 changes: 93 additions & 19 deletions scripts/capture-screenshots.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,29 @@ cd "$(dirname "$0")/.."

DISP=:99
GEOM=2560x1440x24
HEIGHT=980 # every shot is cropped to this so the gallery reads as one set
HEIGHT=980 # the tallest a shot may be, so no one slide towers over the gallery
SETTLE=16 # data bundle, poe.ninja overview, exchange digest, trade search, CDN icons
PARK=20,1400 # somewhere no window is, so nothing is left hovered when the shutter goes
CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/PathOfPriceCheck/config.json"

# The update check is pointed at a closed port for the run: this build is whatever is in the tree
# and is therefore usually behind the published release, and a screenshot selling the tool should
# not open on its own "a new version is out" banner. A failed check is silent by design.
NOUPDATE=http://127.0.0.1:9/latest.json

# name | item file | hover x,y on the virtual screen (optional)
#
# The hover coordinate is what makes the seller's item appear beside a listing row, which has no
# other way of being photographed. It is in *screen* space, so it moves whenever the panel width
# or the screen geometry does — re-derive it from a first capture rather than trusting it.
# A shot that needs more than one pointer move says so in `stage` below instead.
SHOTS=(
"panel-rare|tests/data/examples/item_6.txt|200,1300"
"unique-reference|tests/data/examples/item_5.txt|"
"listing-hover|tests/data/examples/item_5.txt|1323,486"
"map|tests/data/items/map-rare-t16-corrupted.txt|"
"currency-exchange|tests/data/items/currency-chaos-stack.txt|"
"unidentified-picker|tests/data/items/unique-unidentified-jewel.txt|"
"report|tests/data/examples/item_5.txt|"
"settings||200,1300" # no item: PPC_DEV_OVERLAY alone opens Settings
)

Expand All @@ -54,29 +61,97 @@ restore() {
}
trap restore EXIT

# `account_name` is blanked for the run because the Settings shot draws the field: what belongs on
# the published picture is the placeholder a new user sees, not the maintainer's handle. Sellers'
# handles are somebody else's name again and are masked by `PPC_DEV_ANON` below.
cp "$CONFIG" "$work/config.json"
jq '.auto_search = true | .debug_log = false' "$work/config.json" >"$CONFIG"
jq '.auto_search = true | .debug_log = false | .account_name = ""' "$work/config.json" >"$CONFIG"

# A plain XWarpPointer moves the pointer without generating the motion a widget reacts to, so
# hovering has to go through XTest — the same extension the app's own copy injection uses.
cat >"$work/warp.c" <<'EOF'
# hovering has to go through XTest — the same extension the app's own copy injection uses. The
# same helper clicks and types, which is how a shot of a dialog gets taken at all: there is no
# hotkey onto the bug reporter, and a report with an empty description photographs as a form
# nobody filled in.
#
# xdo <display> move <x> <y> xdo <display> click <x> <y> xdo <display> type <text>
cat >"$work/xdo.c" <<'EOF'
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <X11/keysym.h>
#include <X11/extensions/XTest.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void move(Display* d, int x, int y) {
XTestFakeMotionEvent(d, 0, x - 8, y - 8, 0); XFlush(d); usleep(120000);
XTestFakeMotionEvent(d, 0, x, y, 0); XFlush(d); usleep(120000);
XTestFakeMotionEvent(d, 0, x, y + 1, 0); XFlush(d); usleep(120000);
}

// ASCII is its own keysym over the printable range, so the layout lookup is the whole of it:
// find the keycode carrying that keysym and note whether it sits in the shifted slot.
static void type_char(Display* d, char c) {
KeySym ks = (c == '\n') ? XK_Return : (KeySym)(unsigned char)c;
KeyCode kc = XKeysymToKeycode(d, ks);
if (!kc) return;
int shift = XkbKeycodeToKeysym(d, kc, 0, 0) != ks && XkbKeycodeToKeysym(d, kc, 0, 1) == ks;
KeyCode sh = XKeysymToKeycode(d, XK_Shift_L);
if (shift) XTestFakeKeyEvent(d, sh, True, 0);
XTestFakeKeyEvent(d, kc, True, 0);
XTestFakeKeyEvent(d, kc, False, 0);
if (shift) XTestFakeKeyEvent(d, sh, False, 0);
XFlush(d);
usleep(12000);
}

int main(int argc, char** argv) {
if (argc < 4) return 2;
Display* d = XOpenDisplay(argv[1]);
if (!d) return 1;
int x = atoi(argv[2]), y = atoi(argv[3]);
XTestFakeMotionEvent(d, 0, x - 8, y - 8, 0); XFlush(d); usleep(120000);
XTestFakeMotionEvent(d, 0, x, y, 0); XFlush(d); usleep(120000);
XTestFakeMotionEvent(d, 0, x, y + 1, 0); XFlush(d);
const char* what = argv[2];
if (!strcmp(what, "type")) {
for (const char* p = argv[3]; *p; ++p) type_char(d, *p);
} else {
if (argc < 5) return 2;
move(d, atoi(argv[3]), atoi(argv[4]));
if (!strcmp(what, "click")) {
XTestFakeButtonEvent(d, 1, True, 0); XFlush(d); usleep(80000);
XTestFakeButtonEvent(d, 1, False, 0); XFlush(d);
}
}
XCloseDisplay(d);
return 0;
}
EOF
cc -O1 -o "$work/warp" "$work/warp.c" -lX11 -lXtst || die "cannot build the pointer helper"
cc -O1 -o "$work/xdo" "$work/xdo.c" -lX11 -lXtst || die "cannot build the pointer helper"

move() { "$work/xdo" $DISP move "${1%,*}" "${1#*,}"; }
click() { "$work/xdo" $DISP click "${1%,*}" "${1#*,}"; sleep "${2:-1}"; }
type_in() { "$work/xdo" $DISP type "$1"; sleep 0.5; }

# Everything a shot needs on screen beyond opening on an item. Coordinates are the virtual
# screen's, and the dialog ones are stable because it is a fixed size centred on it — 940x660 at
# 810,390 — so they only move when that size does.
#
# The report shot is the one that is *staged* rather than photographed as found: a description
# typed in, and the consent box ticked so the picture shows what ticking it attaches. Everything
# the dialog draws is still the app's own — the masked seller names in the preview included.
stage() {
case $1 in
report)
click 1590,368 2 # the report glyph on the results toolbar, which opens the dialog
click 1210,490 # into the description box
type_in "The reference row says 142 chaos but every listing is 2 divine - is it reading the wrong variant?"
click 1315,1022 # tick "Attach the screenshot"
;;
*) return 0 ;;
esac
# Parked afterwards: the pointer is sitting on whatever it last pressed, and a tooltip that
# happens to be up is a picture of the click rather than of the dialog.
move "$PARK"
sleep 1.5
}

mkdir -p site/img
for s in "${SHOTS[@]}"; do
Expand All @@ -88,19 +163,17 @@ for s in "${SHOTS[@]}"; do
Xvfb $DISP -screen 0 $GEOM -nolisten tcp >"$work/xvfb.log" 2>&1 &
sleep 1.5

if [ -n "$item" ]; then
DISPLAY=$DISP PPC_DEV_OVERLAY=1 PPC_DEV_ITEM="$item" \
./build/PathOfPriceCheck >"$work/app.log" 2>&1 &
else
DISPLAY=$DISP PPC_DEV_OVERLAY=1 ./build/PathOfPriceCheck >"$work/app.log" 2>&1 &
fi
vars=(DISPLAY=$DISP PPC_DEV_OVERLAY=1 PPC_DEV_UPDATE_URL=$NOUPDATE PPC_DEV_ANON=1)
[ -n "$item" ] && vars+=(PPC_DEV_ITEM="$item") # without one, PPC_DEV_OVERLAY opens Settings
env "${vars[@]}" ./build/PathOfPriceCheck >"$work/app.log" 2>&1 &
app=$!
sleep $SETTLE

if [ -n "$hover" ]; then
"$work/warp" $DISP "${hover%,*}" "${hover#*,}"
move "$hover"
sleep 2.5
fi
stage "$name"

DISPLAY=$DISP import -window root "$work/raw.png" 2>/dev/null
kill $app 2>/dev/null; sleep 1; kill -9 $app 2>/dev/null
Expand All @@ -110,5 +183,6 @@ for s in "${SHOTS[@]}"; do
identify -format '%wx%h\n' "site/img/$name.png"
done

echo "capture: site/img/ updated — check each one before committing (a listing carries the"
echo " seller's handle, and your own is tinted green if account_name is set)"
echo "capture: site/img/ updated — check each one before committing. Handles are masked by"
echo " PPC_DEV_ANON and the account field is blanked, so what to look for is a name"
echo " that got through anyway, and anything else on screen that names this machine."
45 changes: 28 additions & 17 deletions site/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,31 @@ The overlay is drawn on a **virtual X display**, which is what makes this reprod
nothing appears on screen, the system-wide hotkey grabs land on `:99` where they bother nobody,
and the screen is black everywhere the overlay did not paint, so trimming leaves the panel and
the item beside it and nothing else. `PPC_DEV_ITEM` opens the panel on a captured clipboard with
no game running; the one shot that cannot be staged that way — the seller's item drawn beside a
hovered listing — is taken by moving the pointer over a row with `XTestFakeMotionEvent`, since a
plain `XWarpPointer` moves the pointer without generating the motion a widget reacts to.

Every shot is cropped to a **common 980px height**, which is what the Settings dialog measures
and comfortably clears the tallest panel. That also crops away the footer, which on a machine
with the debug log on draws the check id and is not worth publishing.

The run does two things worth knowing. It **spends real trade API requests**, one search per
item, because a screenshot of an empty results table sells nothing. And it overrides
`auto_search` and `debug_log` for the duration and puts the config back afterwards — without
that the Settings shot shows this machine's state and its home directory rather than what a new
user sees.

Check each one before committing: a listing row carries the **seller's** handle (public on the
trade site anyway), and **your own** listings are tinted green with `(you)` whenever
`account_name` is filled in.
no game running.

What that cannot open, the pointer does, through `XTestFakeMotionEvent` — a plain `XWarpPointer`
moves the pointer without generating the motion a widget reacts to. The same helper clicks and
types, and `stage` in the script is where a shot says what it needs beyond an item: the seller's
item drawn beside a hovered listing is a move, and the bug reporter is a click onto its glyph, a
description typed into the box and the consent ticked, because a report with an empty description
photographs as a form nobody filled in. Its coordinates are the **virtual screen's** and they move
whenever the panel width or the screen geometry does — re-derive them from a capture rather than
trusting them.

No shot is taller than **980px**, which comfortably clears the tallest panel. That also crops away
the panel footer, which on a machine with the debug log on draws the check id and is not worth
publishing. Dialogs come out at their own size — the bug reporter is a fixed 940×660.

**Nobody's name is published.** `PPC_DEV_ANON` holds on the same masking a bug-report capture
does, so every seller is `seller 1`, `seller 2` — and `account_name` is blanked in the config for
the run, so the Settings shot draws the placeholder a new user sees rather than the maintainer's
handle. The config is put back afterwards, along with the `auto_search` and `debug_log` overrides
that make the run look like a fresh install rather than this machine. The update check is pointed
at a closed port for the same reason: the build in the tree is usually behind the published
release, and a screenshot selling the tool should not open on a "new version" banner.

Still check each one before committing. The masking covers the results table, and what it cannot
cover is anything else that happens to name this machine — a path, a league, a window title.

The run **spends real trade API requests**, one search per item, because a screenshot of an empty
results table sells nothing.
Binary file modified site/img/currency-exchange.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/img/listing-hover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/img/map.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/img/panel-rare.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added site/img/report.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/img/settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/img/unidentified-picker.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed site/img/unique-reference.png
Binary file not shown.
Loading