From 051b4179715a0cf62f27aa86690c3507c3fd8533 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 10 May 2023 21:56:59 +0100 Subject: [PATCH 1/5] widhid: don't check clkinfo focus on drag ... because we've already disabled those handlers --- apps/widhid/wid.js | 2 -- apps/widhid/wid.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/apps/widhid/wid.js b/apps/widhid/wid.js index ed1e78e76..92a1e2483 100644 --- a/apps/widhid/wid.js +++ b/apps/widhid/wid.js @@ -19,8 +19,6 @@ } }); var onDrag = (function (e) { - if (Bangle.CLKINFO_FOCUS) - return; if (e.b === 0) { var wasDragging = dragging; dragging = false; diff --git a/apps/widhid/wid.ts b/apps/widhid/wid.ts index 6b5e38855..a02e4f1aa 100644 --- a/apps/widhid/wid.ts +++ b/apps/widhid/wid.ts @@ -23,8 +23,6 @@ }) satisfies SwipeCallback; const onDrag = (e => { - if((Bangle as BangleExt).CLKINFO_FOCUS) return; - if(e.b === 0){ // released const wasDragging = dragging; From a9affac57ecdb99f73e260bd73f505e8d55ad812 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 10 May 2023 21:57:33 +0100 Subject: [PATCH 2/5] widhid: disable (temporarily) if a menu's shown --- apps/widhid/wid.js | 14 ++++++++++++++ apps/widhid/wid.ts | 24 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/apps/widhid/wid.js b/apps/widhid/wid.js index 92a1e2483..38af50995 100644 --- a/apps/widhid/wid.js +++ b/apps/widhid/wid.js @@ -10,9 +10,23 @@ var dragging = false; var activeTimeout; var waitForRelease = true; + var menuShown = 0; + var origShowMenu = E.showMenu; + E.showMenu = (function (menu) { + menuShown++; + var origSetUI = Bangle.setUI; + Bangle.setUI = (function (mode, cb) { + menuShown--; + Bangle.setUI = origSetUI; + return origSetUI(mode, cb); + }); + return origShowMenu(menu); + }); var onSwipe = (function (_lr, ud) { if (Bangle.CLKINFO_FOCUS) return; + if (menuShown) + return; if (!activeTimeout && ud > 0) { listen(); Bangle.buzz(20); diff --git a/apps/widhid/wid.ts b/apps/widhid/wid.ts index a02e4f1aa..bd031c1cc 100644 --- a/apps/widhid/wid.ts +++ b/apps/widhid/wid.ts @@ -12,9 +12,33 @@ let dragging = false; let activeTimeout: number | undefined; let waitForRelease = true; + let menuShown = 0; + + // If the user shows a menu, we want to temporarily disable ourselves + // We can detect showing of a menu by overriding E.showMenu + // to detect hiding of a menu, we hook setUI, since all menus + // either show other menus, load() or (eventually) call it + // (I hope) + // + // Alternatively we could watch for when Bangle.dragHandler and + // Bangle.swipeHandler get removed from Bangle["#on"] + const origShowMenu = E.showMenu; + E.showMenu = ((menu: Menu): MenuInstance => { + menuShown++; + + const origSetUI = Bangle.setUI; + Bangle.setUI = ((mode: unknown, cb: () => void) => { + menuShown--; + Bangle.setUI = origSetUI; + return origSetUI(mode as any, cb); + }) as any; + + return origShowMenu(menu); + }) as any; const onSwipe = ((_lr, ud) => { if((Bangle as BangleExt).CLKINFO_FOCUS) return; + if(menuShown) return; if(!activeTimeout && ud! > 0){ listen(); From 9a3ac3bc7a7d08a75905d92a788ed918dd8a9054 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 10 May 2023 23:13:19 +0100 Subject: [PATCH 3/5] widhid: better clash detection --- apps/widhid/wid.js | 47 ++++++++++++++++++++--------------- apps/widhid/wid.ts | 62 +++++++++++++++++++++++++--------------------- 2 files changed, 61 insertions(+), 48 deletions(-) diff --git a/apps/widhid/wid.js b/apps/widhid/wid.js index 38af50995..b361b6453 100644 --- a/apps/widhid/wid.js +++ b/apps/widhid/wid.js @@ -10,24 +10,29 @@ var dragging = false; var activeTimeout; var waitForRelease = true; - var menuShown = 0; - var origShowMenu = E.showMenu; - E.showMenu = (function (menu) { - menuShown++; - var origSetUI = Bangle.setUI; - Bangle.setUI = (function (mode, cb) { - menuShown--; - Bangle.setUI = origSetUI; - return origSetUI(mode, cb); - }); - return origShowMenu(menu); - }); - var onSwipe = (function (_lr, ud) { + var mayInterceptSwipe = function () { if (Bangle.CLKINFO_FOCUS) - return; - if (menuShown) - return; - if (!activeTimeout && ud > 0) { + return 0; + if (Bangle.CLOCK) + return 1; + var swipes = Bangle["#onswipe"]; + if (typeof swipes === "function") { + if (swipes !== onSwipe) + return swipes.length > 1; + } + else if (swipes) { + for (var _i = 0, swipes_1 = swipes; _i < swipes_1.length; _i++) { + var handler = swipes_1[_i]; + if (handler !== onSwipe && (handler === null || handler === void 0 ? void 0 : handler.length) > 1) + return 0; + } + } + if (Bangle["#ondrag"]) + return 0; + return 1; + }; + var onSwipe = (function (_lr, ud) { + if (ud > 0 && !activeTimeout && mayInterceptSwipe()) { listen(); Bangle.buzz(20); } @@ -153,13 +158,14 @@ stroke: null, }; var suspendOthers = function () { - for (var event in touchEvents) { + for (var event_ in touchEvents) { + var event = event_; var handlers = Bangle["#on".concat(event)]; if (!handlers) continue; var newEvents = void 0; if (handlers instanceof Array) - newEvents = handlers.slice(); + newEvents = handlers.filter(function (f) { return f; }); else newEvents = [handlers]; for (var _i = 0, newEvents_1 = newEvents; _i < newEvents_1.length; _i++) { @@ -170,7 +176,8 @@ } }; var resumeOthers = function () { - for (var event in touchEvents) { + for (var event_ in touchEvents) { + var event = event_; var handlers = touchEvents[event]; touchEvents[event] = null; if (handlers) diff --git a/apps/widhid/wid.ts b/apps/widhid/wid.ts index bd031c1cc..a85725569 100644 --- a/apps/widhid/wid.ts +++ b/apps/widhid/wid.ts @@ -1,4 +1,9 @@ (() => { + type BangleEventKeys = "tap" | "gesture" | "aiGesture" | "swipe" | "touch" | "drag" | "stroke"; + type BangleEvents = { + [key in BangleEventKeys as `#on${key}`]?: Handler | (Handler | undefined)[] + }; + const settings: Settings = require("Storage").readJSON("setting.json", true) || { HID: false } as Settings; if (settings.HID !== "kbmedia") { console.log("widhid: can't enable, HID setting isn't \"kbmedia\""); @@ -12,35 +17,35 @@ let dragging = false; let activeTimeout: number | undefined; let waitForRelease = true; - let menuShown = 0; // If the user shows a menu, we want to temporarily disable ourselves - // We can detect showing of a menu by overriding E.showMenu - // to detect hiding of a menu, we hook setUI, since all menus - // either show other menus, load() or (eventually) call it - // (I hope) // - // Alternatively we could watch for when Bangle.dragHandler and - // Bangle.swipeHandler get removed from Bangle["#on"] - const origShowMenu = E.showMenu; - E.showMenu = ((menu: Menu): MenuInstance => { - menuShown++; + // We could detect showing of a menu by overriding E.showMenu + // and to detect hiding of a menu, we hook setUI + // + // Perhaps easier to check Bangle.swipeHandler - set by setUI, + // called by E.showMenu + const mayInterceptSwipe = () => { + if((Bangle as BangleExt).CLKINFO_FOCUS) return 0; + if(Bangle.CLOCK) return 1; - const origSetUI = Bangle.setUI; - Bangle.setUI = ((mode: unknown, cb: () => void) => { - menuShown--; - Bangle.setUI = origSetUI; - return origSetUI(mode as any, cb); - }) as any; + const swipes = (Bangle as BangleEvents)["#onswipe"]; + if(typeof swipes === "function"){ + if(swipes !== onSwipe) + return swipes.length > 1; // second argument is up/down + }else if(swipes){ + for(const handler of swipes) + if(handler !== onSwipe && handler?.length > 1) + return 0; + } - return origShowMenu(menu); - }) as any; + if((Bangle as BangleEvents)["#ondrag"]) return 0; + return 1; + }; const onSwipe = ((_lr, ud) => { - if((Bangle as BangleExt).CLKINFO_FOCUS) return; - if(menuShown) return; - - if(!activeTimeout && ud! > 0){ + // do these checks in order of cheapness + if(ud! > 0 && !activeTimeout && mayInterceptSwipe()){ listen(); Bangle.buzz(20); } @@ -174,7 +179,7 @@ // disable event handlers type Handler = () => void; const touchEvents: { - [key: string]: null | Handler[] + [key in BangleEventKeys]: null | Handler[] } = { tap: null, gesture: null, @@ -186,15 +191,15 @@ }; const suspendOthers = () => { - for(const event in touchEvents){ - const handlers: Handler[] | Handler | undefined - = (Bangle as any)[`#on${event}`]; + for(const event_ in touchEvents){ + const event = event_ as BangleEventKeys; + const handlers = (Bangle as BangleEvents)[`#on${event}`]; if(!handlers) continue; let newEvents; if(handlers instanceof Array) - newEvents = handlers.slice(); + newEvents = handlers.filter(f=>f) as Handler[]; else newEvents = [handlers /* single fn */]; @@ -205,7 +210,8 @@ } }; const resumeOthers = () => { - for(const event in touchEvents){ + for(const event_ in touchEvents){ + const event = event_ as BangleEventKeys; const handlers = touchEvents[event]; touchEvents[event] = null; From b12fa582158d478e3949506cfb93d1c72b8cc94f Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 10 May 2023 23:38:43 +0100 Subject: [PATCH 4/5] bump version --- apps/widhid/ChangeLog | 3 +++ apps/widhid/metadata.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 apps/widhid/ChangeLog diff --git a/apps/widhid/ChangeLog b/apps/widhid/ChangeLog new file mode 100644 index 000000000..a6ed84766 --- /dev/null +++ b/apps/widhid/ChangeLog @@ -0,0 +1,3 @@ +0.01: New widget - music control via a swipe +0.02: Improve interactivity - avoid responding to swipes when a menu or + launcher is active. diff --git a/apps/widhid/metadata.json b/apps/widhid/metadata.json index 10e75fadc..84334636b 100644 --- a/apps/widhid/metadata.json +++ b/apps/widhid/metadata.json @@ -2,7 +2,7 @@ "id": "widhid", "name": "Bluetooth Music Swipe Control Widget", "shortName": "BLE Swipe Widget", - "version": "0.01", + "version": "0.02", "description": "Based on Swipe Bluetooth Music Controls (based on Bluetooth Music Controls). Swipe down to enable, then swipe up/down for volume, left/right for previous and next and tap for play/pause. Enable HID in settings, pair with your phone/computer, then use this widget to control music from your watch!", "icon": "icon.png", "readme": "README.md", From 109fd078f41a5b465042ffd132c06287caef4efd Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sat, 13 May 2023 08:44:50 +0100 Subject: [PATCH 5/5] popcon: use E.stopEventPropagation() for the drag handler --- apps/widhid/README.md | 1 + apps/widhid/wid.js | 71 ++------------------------------- apps/widhid/wid.ts | 93 +++++-------------------------------------- 3 files changed, 14 insertions(+), 151 deletions(-) diff --git a/apps/widhid/README.md b/apps/widhid/README.md index 7651d74eb..652a2ed49 100644 --- a/apps/widhid/README.md +++ b/apps/widhid/README.md @@ -13,6 +13,7 @@ Swipe down to enable - note the icon changes from blue to orange, indicating it' All other watch interaction is disabled for 3 seconds, to prevent clashing taps/drags - this period is extended as you continue to alter the volume, play/pause and jump between tracks. +Requires espruino firmware > 2v17 to avoid event handler clashes. # Setup / Technical details diff --git a/apps/widhid/wid.js b/apps/widhid/wid.js index b361b6453..dd411513e 100644 --- a/apps/widhid/wid.js +++ b/apps/widhid/wid.js @@ -10,34 +10,14 @@ var dragging = false; var activeTimeout; var waitForRelease = true; - var mayInterceptSwipe = function () { - if (Bangle.CLKINFO_FOCUS) - return 0; - if (Bangle.CLOCK) - return 1; - var swipes = Bangle["#onswipe"]; - if (typeof swipes === "function") { - if (swipes !== onSwipe) - return swipes.length > 1; - } - else if (swipes) { - for (var _i = 0, swipes_1 = swipes; _i < swipes_1.length; _i++) { - var handler = swipes_1[_i]; - if (handler !== onSwipe && (handler === null || handler === void 0 ? void 0 : handler.length) > 1) - return 0; - } - } - if (Bangle["#ondrag"]) - return 0; - return 1; - }; var onSwipe = (function (_lr, ud) { - if (ud > 0 && !activeTimeout && mayInterceptSwipe()) { + if (ud > 0 && !activeTimeout && !Bangle.CLKINFO_FOCUS) { listen(); Bangle.buzz(20); } }); var onDrag = (function (e) { + E.stopEventPropagation && E.stopEventPropagation(); if (e.b === 0) { var wasDragging = dragging; dragging = false; @@ -99,9 +79,9 @@ var listen = function () { var wasActive = !!activeTimeout; if (!wasActive) { - suspendOthers(); waitForRelease = true; Bangle.on("drag", onDrag); + Bangle["#ondrag"] = [onDrag].concat(Bangle["#ondrag"].filter(function (f) { return f !== onDrag; })); redraw(); } if (activeTimeout) @@ -109,7 +89,6 @@ activeTimeout = setTimeout(function () { activeTimeout = undefined; Bangle.removeListener("drag", onDrag); - resumeOthers(); redraw(); }, 3000); }; @@ -148,48 +127,4 @@ var toggle = function () { return sendHid(0x10); }; var up = function () { return sendHid(0x40); }; var down = function () { return sendHid(0x80); }; - var touchEvents = { - tap: null, - gesture: null, - aiGesture: null, - swipe: null, - touch: null, - drag: null, - stroke: null, - }; - var suspendOthers = function () { - for (var event_ in touchEvents) { - var event = event_; - var handlers = Bangle["#on".concat(event)]; - if (!handlers) - continue; - var newEvents = void 0; - if (handlers instanceof Array) - newEvents = handlers.filter(function (f) { return f; }); - else - newEvents = [handlers]; - for (var _i = 0, newEvents_1 = newEvents; _i < newEvents_1.length; _i++) { - var handler = newEvents_1[_i]; - Bangle.removeListener(event, handler); - } - touchEvents[event] = newEvents; - } - }; - var resumeOthers = function () { - for (var event_ in touchEvents) { - var event = event_; - var handlers = touchEvents[event]; - touchEvents[event] = null; - if (handlers) - for (var _i = 0, handlers_1 = handlers; _i < handlers_1.length; _i++) { - var handler = handlers_1[_i]; - try { - Bangle.on(event, handler); - } - catch (e) { - console.log("couldn't restore \"".concat(event, "\" handler:"), e); - } - } - } - }; })(); diff --git a/apps/widhid/wid.ts b/apps/widhid/wid.ts index a85725569..3291c188d 100644 --- a/apps/widhid/wid.ts +++ b/apps/widhid/wid.ts @@ -1,9 +1,4 @@ (() => { - type BangleEventKeys = "tap" | "gesture" | "aiGesture" | "swipe" | "touch" | "drag" | "stroke"; - type BangleEvents = { - [key in BangleEventKeys as `#on${key}`]?: Handler | (Handler | undefined)[] - }; - const settings: Settings = require("Storage").readJSON("setting.json", true) || { HID: false } as Settings; if (settings.HID !== "kbmedia") { console.log("widhid: can't enable, HID setting isn't \"kbmedia\""); @@ -18,40 +13,18 @@ let activeTimeout: number | undefined; let waitForRelease = true; - // If the user shows a menu, we want to temporarily disable ourselves - // - // We could detect showing of a menu by overriding E.showMenu - // and to detect hiding of a menu, we hook setUI - // - // Perhaps easier to check Bangle.swipeHandler - set by setUI, - // called by E.showMenu - const mayInterceptSwipe = () => { - if((Bangle as BangleExt).CLKINFO_FOCUS) return 0; - if(Bangle.CLOCK) return 1; - - const swipes = (Bangle as BangleEvents)["#onswipe"]; - if(typeof swipes === "function"){ - if(swipes !== onSwipe) - return swipes.length > 1; // second argument is up/down - }else if(swipes){ - for(const handler of swipes) - if(handler !== onSwipe && handler?.length > 1) - return 0; - } - - if((Bangle as BangleEvents)["#ondrag"]) return 0; - return 1; - }; - const onSwipe = ((_lr, ud) => { // do these checks in order of cheapness - if(ud! > 0 && !activeTimeout && mayInterceptSwipe()){ + if(ud! > 0 && !activeTimeout && !(Bangle as BangleExt).CLKINFO_FOCUS){ listen(); Bangle.buzz(20); } }) satisfies SwipeCallback; const onDrag = (e => { + // Espruino/35c8cb9be11 + (E as any).stopEventPropagation && (E as any).stopEventPropagation(); + if(e.b === 0){ // released const wasDragging = dragging; @@ -108,9 +81,14 @@ const listen = () => { const wasActive = !!activeTimeout; if(!wasActive){ - suspendOthers(); waitForRelease = true; // wait for first touch up before accepting gestures + Bangle.on("drag", onDrag); + // move our drag to the start of the event listener array + (Bangle as any)["#ondrag"] = [onDrag].concat( + (Bangle as any)["#ondrag"].filter((f: unknown) => f !== onDrag) + ); + redraw(); } @@ -119,7 +97,6 @@ activeTimeout = undefined; Bangle.removeListener("drag", onDrag); - resumeOthers(); redraw(); }, 3000); @@ -174,54 +151,4 @@ const toggle = () => /*DEBUG ? console.log("toggle") : */ sendHid(0x10); const up = () => /*DEBUG ? console.log("up") : */ sendHid(0x40); const down = () => /*DEBUG ? console.log("down") : */ sendHid(0x80); - - // similarly to the lightswitch app, we tangle with the listener arrays to - // disable event handlers - type Handler = () => void; - const touchEvents: { - [key in BangleEventKeys]: null | Handler[] - } = { - tap: null, - gesture: null, - aiGesture: null, - swipe: null, - touch: null, - drag: null, - stroke: null, - }; - - const suspendOthers = () => { - for(const event_ in touchEvents){ - const event = event_ as BangleEventKeys; - const handlers = (Bangle as BangleEvents)[`#on${event}`]; - - if(!handlers) continue; - - let newEvents; - if(handlers instanceof Array) - newEvents = handlers.filter(f=>f) as Handler[]; - else - newEvents = [handlers /* single fn */]; - - for(const handler of newEvents) - Bangle.removeListener(event, handler); - - touchEvents[event] = newEvents; - } - }; - const resumeOthers = () => { - for(const event_ in touchEvents){ - const event = event_ as BangleEventKeys; - const handlers = touchEvents[event]; - touchEvents[event] = null; - - if(handlers) - for(const handler of handlers) - try{ - Bangle.on(event as any, handler); - }catch(e){ - console.log(`couldn't restore "${event}" handler:`, e); - } - } - }; })()