|
| 1 | +#!/usr/bin/env bash |
| 2 | +# core_tray_icon_events_test_linux.sh — the Linux tray icon's menu export and click events, |
| 3 | +# driven the way the shell drives them: over D-Bus. A StatusNotifierItem is clicked by the |
| 4 | +# host calling its methods (gnome-shell's AppIndicator extension: Activate on a double |
| 5 | +# click, the menu on a single one; KDE: Activate on a click, ContextMenu on a right click), |
| 6 | +# so calling those methods is the click, minus the pointer the input driver may not send |
| 7 | +# to the top bar. Checks that the menu is exported for the host to show, that the host's |
| 8 | +# dbusmenu events come back as menu events (opened, closed, an item's click), that each |
| 9 | +# call reaches the app as the event it stands for, and that the calls with no event yet |
| 10 | +# (SecondaryActivate, Scroll) report nothing. |
| 11 | +# |
| 12 | +# $R linux run tools/gui/build_core_example_linux.sh tray_icon_example |
| 13 | +# $R linux desktop tools/gui/core_tray_icon_events_test_linux.sh 60 |
| 14 | +set -uo pipefail |
| 15 | +source "$(dirname "$0")/env.sh" |
| 16 | +exe="$REMOTE_SCRATCH/core-build/examples/tray_icon_example/tray_icon_example" |
| 17 | +log="$REMOTE_SCRATCH/tray_icon_events.log" |
| 18 | +failures=0 |
| 19 | + |
| 20 | +"$exe" > "$log" 2>&1 & |
| 21 | +pid=$! |
| 22 | +trap 'kill $pid 2>/dev/null' EXIT |
| 23 | + |
| 24 | +item="" |
| 25 | +for _ in $(seq 20); do |
| 26 | + item=$(gdbus call --session --dest org.kde.StatusNotifierWatcher \ |
| 27 | + --object-path /StatusNotifierWatcher \ |
| 28 | + --method org.freedesktop.DBus.Properties.Get \ |
| 29 | + org.kde.StatusNotifierWatcher RegisteredStatusNotifierItems 2>/dev/null \ |
| 30 | + | grep -o "org\.kde\.StatusNotifierItem-$pid-[0-9]*" | head -1) |
| 31 | + [ -n "$item" ] && break |
| 32 | + sleep 0.5 |
| 33 | +done |
| 34 | +if [ -z "$item" ]; then |
| 35 | + echo "FAIL the shell lists the icon" |
| 36 | + exit 1 |
| 37 | +fi |
| 38 | +echo "PASS the shell lists the icon ($item)" |
| 39 | + |
| 40 | +call() { |
| 41 | + gdbus call --session --dest "$item" --object-path /StatusNotifierItem \ |
| 42 | + --method "org.kde.StatusNotifierItem.$1" "${@:2}" > /dev/null |
| 43 | +} |
| 44 | + |
| 45 | +# The events the app printed since the last step, as a space-separated list in $got. |
| 46 | +seen=0 |
| 47 | +events() { |
| 48 | + sleep 0.8 # well past the double-click time, so steps do not pair up |
| 49 | + local all |
| 50 | + all=$(grep -o 'TRAY ICON [A-Z]* CLICKED' "$log" | awk '{print $3}') |
| 51 | + got=$(echo "$all" | tail -n +$((seen + 1)) | paste -sd' ' -) |
| 52 | + seen=$(echo "$all" | grep -c .) |
| 53 | +} |
| 54 | + |
| 55 | +check() { # check <what> <expected> <got> |
| 56 | + if [ "$2" = "$3" ]; then |
| 57 | + echo "PASS $1: ${3:-nothing}" |
| 58 | + else |
| 59 | + echo "FAIL $1: expected '${2:-nothing}', got '${3:-nothing}'" |
| 60 | + failures=$((failures + 1)) |
| 61 | + fi |
| 62 | +} |
| 63 | + |
| 64 | +# The example sets a menu with the RightClicked trigger: exported for the host to show on |
| 65 | +# a secondary click (GNOME shows an empty one without it), but not as ItemIsMenu. |
| 66 | +prop() { |
| 67 | + gdbus call --session --dest "$item" --object-path /StatusNotifierItem \ |
| 68 | + --method org.freedesktop.DBus.Properties.Get org.kde.StatusNotifierItem "$1" |
| 69 | +} |
| 70 | +check "the menu is exported" "(<objectpath '/StatusNotifierItem/Menu'>,)" "$(prop Menu)" |
| 71 | +check "ItemIsMenu is off" "(<false>,)" "$(prop ItemIsMenu)" |
| 72 | +layout=$(gdbus call --session --dest "$item" --object-path /StatusNotifierItem/Menu \ |
| 73 | + --method com.canonical.dbusmenu.GetLayout -- 0 -1 '[]') |
| 74 | +for label in 'Settings...' 'About' 'Exit'; do |
| 75 | + case "$layout" in |
| 76 | + *"'label': <'$label'>"*) check "the menu has \"$label\"" "yes" "yes" ;; |
| 77 | + *) check "the menu has \"$label\"" "yes" "no" ;; |
| 78 | + esac |
| 79 | +done |
| 80 | +# gnome-shell's extension reads WindowId as an int32 and warns about anything else. |
| 81 | +check "WindowId is an int32" "(<0>,)" "$(prop WindowId)" |
| 82 | + |
| 83 | +# The host shows the exported menu itself and tells the app through dbusmenu events. |
| 84 | +menu_event() { # menu_event <id> <opened|closed|clicked> |
| 85 | + gdbus call --session --dest "$item" --object-path /StatusNotifierItem/Menu \ |
| 86 | + --method com.canonical.dbusmenu.Event -- "$1" "$2" '<0>' 0 > /dev/null |
| 87 | +} |
| 88 | +menu_lines() { # the number of lines the app printed so far that match $1 |
| 89 | + sleep 0.5 |
| 90 | + grep -c "$1" "$log" |
| 91 | +} |
| 92 | +menu_event 0 opened; menu_event 0 opened |
| 93 | +check "\"opened\" on the root is one MenuOpened, however often it comes" "1" "$(menu_lines 'Tray menu opened')" |
| 94 | +settings=$(echo "$layout" | grep -o "([0-9]*, {[^}]*'label': <'Settings...'>" | grep -o '^([0-9]*' | tr -d '(') |
| 95 | +menu_event "${settings:-0}" clicked |
| 96 | +check "\"clicked\" on an item is its click" "1" "$(menu_lines 'Settings clicked from context menu')" |
| 97 | +menu_event 0 closed; menu_event 0 closed |
| 98 | +check "\"closed\" on the root is one MenuClosed" "1" "$(menu_lines 'Tray menu closed')" |
| 99 | + |
| 100 | +# gdbus is not gnome-shell: these are the calls of any other host (KDE). gnome-shell's |
| 101 | +# Activate is a double click, which only a real click in the top bar can show. |
| 102 | +call Activate 10 10 |
| 103 | +events |
| 104 | +check "Activate is a click" "LEFT" "$got" |
| 105 | + |
| 106 | +call Activate 10 10; call Activate 10 10 |
| 107 | +events |
| 108 | +check "two quick Activates are a click, then a double click" "LEFT DOUBLE" "$got" |
| 109 | + |
| 110 | +call Activate 10 10; call Activate 10 10; call Activate 10 10 |
| 111 | +events |
| 112 | +check "a third quick Activate starts a new pair" "LEFT DOUBLE LEFT" "$got" |
| 113 | + |
| 114 | +call Activate 10 10; sleep 1; call Activate 10 10 |
| 115 | +events |
| 116 | +check "two slow Activates are two clicks" "LEFT LEFT" "$got" |
| 117 | + |
| 118 | +call ContextMenu 10 10 |
| 119 | +events |
| 120 | +check "ContextMenu is a right click" "RIGHT" "$got" |
| 121 | + |
| 122 | +call SecondaryActivate 10 10; call Scroll 1 vertical |
| 123 | +events |
| 124 | +check "SecondaryActivate and Scroll report nothing" "" "$got" |
| 125 | + |
| 126 | +if ! kill -0 $pid 2>/dev/null; then |
| 127 | + echo "FAIL the app is still running" |
| 128 | + failures=$((failures + 1)) |
| 129 | +fi |
| 130 | + |
| 131 | +echo "$failures failure(s)" |
| 132 | +exit $((failures > 0)) |
0 commit comments