diff --git a/apps/presentor/ChangeLog b/apps/presentor/ChangeLog index 807f41e79..a42c6594d 100644 --- a/apps/presentor/ChangeLog +++ b/apps/presentor/ChangeLog @@ -6,3 +6,9 @@ 0.06: Initial internal git(hub) release. Added icon and such. 0.07: Begin work on presentation parts. 0.08: Presentation parts! +0.09: Code cleanup and windows 11 support. +0.10: Bugfixes. +0.11: Fix mouse move getting stuck. +0.12: Added support for mouse dragging action (click then drag). +0.13: Removed mouse dragging (too buggy/unuseful). +0.14: Bugfix and add pointer mode. Also added default screen when no parts are there. \ No newline at end of file diff --git a/apps/presentor/README.md b/apps/presentor/README.md index 8b22eb228..37d3e0009 100644 --- a/apps/presentor/README.md +++ b/apps/presentor/README.md @@ -1,2 +1,80 @@ # Presentor Use your Bangle to present! +This app basically turns your BangleJS watch into a presentor tool. +Very useful for presentations or when you are teaching a lesson. + +### Features: ++ Control your (powerpoint) slides by swiping left and right. ++ See pre-programmed presentation notes by swiping up and down. ++ See if you are on time in your presentation. ++ Get notified if you go over-time for a certain subject. ++ Control cursor or spotlight in mouse mode (pressing the button). + +### Usage: +Here is a step by step guide (thanks mu1) +1. If bluetooth connected to PC/laptop (check in bluetooth settings): +Select Bangle.js > Remove Device + +2. On your smartphone, in "Gadgetbridge", close connection by long-pressing "Bangle.js" + +3. Open banglejs.com/apps and select "Connect" (right upper corner) + +4. Select Bangle.js in blue bar in pop-up window and select "Connect" + +5. Search "Presentor" app and select diskette icon + +6. Select "Clear" to start making new notes. + +7. Fill the subjects, times in minutes (seconds optional) and Notes (optional) +The subject/times are on your watch displayed in a tall font, the notes in a very small font +Note: If you don't fill in any notes you can still save and the presentor will be for input only and not give any timing details. + +8. Select "Save" and exit the pop-up window + +9. Select "Disconnect" (right upper corner) + +10. Open the Presentor app. This will start the correct HID service as well. + +11. Open "Bluetooth & other devices" screen on PC/laptop and select "+" to add a device + +12. Select "Bluetooth" and select Bangle.js +If "Try connecting your device again." is shown, switch PC/laptop bluetooth off and on again + +13. Start your presentation. + - Swipe up to start timer for first subject + - Swipe down to pause + - Push button to switch between mouse mode and timer/presentation mode + +14. Timer function + - Next subject: Swipe up + - Previous subject: Swipe down + - Pause timer: Swipe down to begin + +15. Presentation function + - Next presentation slide: Swipe right or just touch + - Previous presentation slide: Swipe left + +16. Mouse mode + - Swipe to move pointer over the presentation screen + - Tap to left click. + - Hold longer to right click. + +17. Holding mode +In timer/presentation mode, hold one finger on your Bangle screen, point your arm foreward + - Mouse up/down on screen: Tilt your hand up/down + - Mouse left/right on screen: Rotate your hand counterclockwise/clockwise + +18. Spotlight mode + - Dubble press button to go into presentor mode. + - Allows to move mouse but no clicking. + - Presses SHIFT+F10 on start (you can use app such as powertoys to set spotlight to this combo) + - Presses F10 at the end (will dis-engage spotlight for instance in powertoys) + +19. End of presentation? +Switch bluetooth PC/laptop off or see step 1 + +20. Re-activate smartphone bluetooth (incl. Gadgetbridge) + +### Creator +[7kasper](https://github.com/7kasper) +Issues or feature requests are welcome on my [Github](https://github.com/7kasper/BangleApps) or on the [Espruino Forums](https://forum.espruino.com/conversations/371443/)! \ No newline at end of file diff --git a/apps/presentor/app.js b/apps/presentor/app.js index 6b7450a0c..a501b3376 100644 --- a/apps/presentor/app.js +++ b/apps/presentor/app.js @@ -1,80 +1,12 @@ // Presentor by 7kasper (Kasper Müller) -// Version 3.0 - -const SpecialReport = new Uint8Array([ - 0x05, 0x01, // USAGE_PAGE (Generic Desktop) - 0x09, 0x02, // USAGE (Mouse) - 0xa1, 0x01, // COLLECTION (Application) - 0x85, 0x01, // REPORT_ID (1) - 0x09, 0x01, // USAGE (Pointer) - 0xa1, 0x00, // COLLECTION (Physical) - 0x05, 0x09, // USAGE_PAGE (Button) - 0x19, 0x01, // USAGE_MINIMUM (Button 1) - 0x29, 0x05, // USAGE_MAXIMUM (Button 5) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x25, 0x01, // LOGICAL_MAXIMUM (1) - 0x95, 0x05, // REPORT_COUNT (5) - 0x75, 0x01, // REPORT_SIZE (1) - 0x81, 0x02, // INPUT (Data,Var,Abs) - 0x95, 0x01, // REPORT_COUNT (1) - 0x75, 0x03, // REPORT_SIZE (3) - 0x81, 0x03, // INPUT (Cnst,Var,Abs) - 0x05, 0x01, // USAGE_PAGE (Generic Desktop) - 0x09, 0x30, // USAGE (X) - 0x09, 0x31, // USAGE (Y) - 0x09, 0x38, // USAGE (Wheel) - 0x15, 0x81, // LOGICAL_MINIMUM (-127) - 0x25, 0x7f, // LOGICAL_MAXIMUM (127) - 0x75, 0x08, // REPORT_SIZE (8) - 0x95, 0x03, // REPORT_COUNT (3) - 0x81, 0x06, // INPUT (Data,Var,Rel) - 0x05, 0x0c, // USAGE_PAGE (Consumer Devices) - 0x0a, 0x38, 0x02, // USAGE (AC Pan) - 0x15, 0x81, // LOGICAL_MINIMUM (-127) - 0x25, 0x7f, // LOGICAL_MAXIMUM (127) - 0x75, 0x08, // REPORT_SIZE (8) - 0x95, 0x01, // REPORT_COUNT (1) - 0x81, 0x06, // INPUT (Data,Var,Rel) - 0xc0, // END_COLLECTION - 0xc0, // END_COLLECTION - 0x05, 0x01, // USAGE_PAGE (Generic Desktop) - 0x09, 0x06, // USAGE (Keyboard) - 0xa1, 0x01, // COLLECTION (Application) - 0x85, 0x02, // REPORT_ID (2) - 0x05, 0x07, // USAGE_PAGE (Keyboard) - 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl) - 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x25, 0x01, // LOGICAL_MAXIMUM (1) - 0x75, 0x01, // REPORT_SIZE (1) - 0x95, 0x08, // REPORT_COUNT (8) - 0x81, 0x02, // INPUT (Data,Var,Abs) - 0x75, 0x08, // REPORT_SIZE (8) - 0x95, 0x01, // REPORT_COUNT (1) - 0x81, 0x01, // INPUT (Cnst,Ary,Abs) - 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) - 0x29, 0x73, // USAGE_MAXIMUM (Keyboard F24) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x25, 0x73, // LOGICAL_MAXIMUM (115) - 0x95, 0x05, // REPORT_COUNT (5) - 0x75, 0x08, // REPORT_SIZE (8) - 0x81, 0x00, // INPUT (Data,Ary,Abs) - 0xc0 // END_COLLECTION -]); - -const MouseButton = { - NONE : 0, - LEFT : 1, - RIGHT : 2, - MIDDLE : 4, - BACK : 8, - FORWARD: 16 -}; - -const kb = require("ble_hid_keyboard"); +// Version 0.14 +// Imports +const bt = require("ble_hid_combo"); const Layout = require("Layout"); const Locale = require("locale"); + +// App Layout let mainLayout = new Layout({ 'type': 'v', filly: 1, @@ -153,18 +85,20 @@ let lasty = 0; // Mouse states let holding = false; let trackPadMode = false; +let focusMode = false; // Timeout IDs. let timeoutId = -1; let timeoutHolding = -1; let timeoutDraw = -1; - +let timeoutSendMouse = -1; let homeRoll = 0; let homePitch = 0; let mCal = 0; let mttl = 0; let cttl = 0; +let bttl = 0; // BT helper. let clearToSend = true; @@ -174,7 +108,7 @@ let ptimers = []; function delay(t, v) { return new Promise((resolve) => { - setTimeout(resolve, t) + setTimeout(resolve, t); }); } @@ -266,6 +200,11 @@ function drawMain() { function doPPart(r) { pparti += r; + if (settings.pparts.length == 0) { + mainLayout.Subject.label = 'PRESENTOR'; + mainLayout.Notes.label = ''; + return; + } if (pparti < 0) { pparti = -1; mainLayout.Subject.label = 'PAUSED'; @@ -289,56 +228,34 @@ function doPPart(r) { drawMainFrame(); } -NRF.setServices(undefined, { hid : SpecialReport }); -// TODO: figure out how to detect HID. +// Turn on Bluetooth as presentor. +NRF.setServices(undefined, { hid : bt.report }); NRF.on('HID', function() { - HIDenabled = true; + if (!HIDenabled) { + Bangle.buzz(200); + HIDenabled = true; + } }); - -function moveMouse(x,y,b,wheel,hwheel,callback) { - if (!HIDenabled) return; - if (!b) b = 0; - if (!wheel) wheel = 0; - if (!hwheel) hwheel = 0; - NRF.sendHIDReport([1,b,x,y,wheel,hwheel,0,0], function() { - if (callback) callback(); - }); -} +// +NRF.setAdvertising([ + {}, // include original Advertising packet + [ // second packet containing 'appearance' + 2, 1, 6, // standard Bluetooth flags + 3,3,0x12,0x18, // HID Service + 3,0x19,0xCA,0x03 // Appearance: Presentation Remote + ] +]); // function getSign(x) { // return ((x > 0) - (x < 0)) || +x; // } -function scroll(wheel,hwheel,callback) { - moveMouse(0,0,0,wheel,hwheel,callback); -} - -// Single click a certain button (immidiatly release). -function clickMouse(b, callback) { - if (!HIDenabled) return; - NRF.sendHIDReport([1,b,0,0,0,0,0,0], function() { - NRF.sendHIDReport([1,0,0,0,0,0,0,0], function() { - if (callback) callback(); - }); - }); -} - -function pressKey(keyCode, modifiers, callback) { - if (!HIDenabled) return; - if (!modifiers) modifiers = 0; - NRF.sendHIDReport([2, modifiers,0,keyCode,0,0,0,0], function() { - NRF.sendHIDReport([2,0,0,0,0,0,0,0], function() { - if (callback) callback(); - }); - }); -} - function handleAcc(acc) { let rRoll = acc.y * -50; let rPitch = acc.x * -100; if (mCal > 10) { //console.log("x: " + (rRoll - homeRoll) + " y:" + (rPitch - homePitch)); - moveMouse(acc.y * -50 - homeRoll, acc.x * -100 - homePitch); + bt.moveMouse(acc.y * -50 - homeRoll, acc.x * -100 - homePitch); } else { //console.log("homeroll: " +homeRoll +"homepitch: " + homePitch); homeRoll = rRoll * 0.7 + homeRoll * 0.3; @@ -347,34 +264,37 @@ function handleAcc(acc) { } } Bangle.on('lock', function(on) { - if (on && holding) { + if (on && (holding || trackPadMode)) { Bangle.setLocked(false); Bangle.setLCDPower(1); } }); function startHolding() { - pressKey(kb.KEY.F10); + bt.tapKey(bt.KEY.F10, bt.MODIFY.SHIFT); holding = true; + focusMode = true; Bangle.buzz(); E.showMessage('Holding'); Bangle.on('accel', handleAcc); Bangle.setLCDPower(1); } function stopHolding() { - clearTimeout(timeoutId); if (holding) { - pressKey(kb.KEY.F10); + bt.tapKey(bt.KEY.F10); + // bt.tapKey(bt.KEY.F10); homePitch = 0; homeRoll = 0; holding = false; + focusMode = false; mCal = 0; Bangle.removeListener('accel', handleAcc); Bangle.buzz(); drawMain(); - } else { - timeoutId = setTimeout(drawMain, 1000); - } + } + // else { + // timeoutId = setTimeout(drawMain, 1000); + // } clearTimeout(timeoutHolding); timeoutHolding = -1; } @@ -395,24 +315,28 @@ Bangle.on('drag', function(e) { //let qX = getSign(difX) * Math.pow(Math.abs(difX), 1.2); //let qY = getSign(difY) * Math.pow(Math.abs(difY), 1.2); let qX = difX + 0.02 * vX, qY = difY + 0.02 * vY; - moveMouse(qX, qY, 0, 0, 0, function() { - setTimeout(function() {clearToSend = true;}, 50); + bt.moveMouse(qX, qY, 0, 0, 0, function() { + timeoutSendMouse = setTimeout(function() {clearToSend = true; timeoutSendMouse = -1;}, 50); }); lastx = e.x; lasty = e.y; mttl = getTime(); console.log("Dx: " + (qX) + " Dy: " + (qY)); + } else if (timeoutSendMouse == -1) { // Can happen perhaps on single bluetooth failure. + timeoutSendMouse = setTimeout(function() {clearToSend = true; timeoutSendMouse = -1;}, 50); } if (!e.b) { - // short press - if (getTime() - cttl < 0.2) { - clickMouse(MouseButton.LEFT); - console.log("click left"); - } - // longer press in center - else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) { - clickMouse(MouseButton.RIGHT); - console.log("click right"); + if (!focusMode) { + // short press + if (getTime() - cttl < 0.2) { + bt.clickButton(bt.BUTTON.LEFT); + console.log("click left"); + } + // longer press in center + else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) { + bt.clickButton(bt.BUTTON.RIGHT); + console.log("click right"); + } } cttl = 0; lastx = 0; @@ -430,15 +354,17 @@ Bangle.on('drag', function(e) { } else if(lastx > 40){ // E.showMessage('right'); //kb.tap(kb.KEY.RIGHT, 0); - scroll(-1); + bt.scroll(-1); } else if(lastx < -40){ // E.showMessage('left'); //kb.tap(kb.KEY.LEFT, 0); - scroll(1); - } else if(lastx==0 && lasty==0 && holding == false){ - // E.showMessage('press'); - clickMouse(MouseButton.LEFT); - } + bt.scroll(1); + } + // Todo re-implement? Seems bit buggy or unnecessary for now. + // else if(lastx==0 && lasty==0 && holding == false){ + // // E.showMessage('press'); + // bt.clickButton(bt.BUTTON.LEFT); + // } stopHolding(); lastx = 0; lasty = 0; @@ -452,20 +378,37 @@ Bangle.on('drag', function(e) { } }); - function onBtn() { if (trackPadMode) { - trackPadMode = false; - stopHolding(); - drawMain(); + if ((getTime() - bttl < 0.4 && !focusMode)) { + E.showMessage('Pointer'); + focusMode = true; + bt.tapKey(bt.KEY.F10, bt.MODIFY.SHIFT); + } else { + trackPadMode = false; + stopHolding(); + drawMain(); + if (focusMode) { + bt.tapKey(bt.KEY.F10); + focusMode = false; + } + } } else { + stopHolding(); clearToSend = true; trackPadMode = true; E.showMessage('Mouse'); + // Also skip drawing thingy for now. + if (timeoutDraw != -1) { + clearTimeout(timeoutDraw); + timeoutDraw = -1; + } + bttl = getTime(); } Bangle.buzz(); } setWatch(onBtn, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat: true}); +// Start App loadSettings(); drawMain(); \ No newline at end of file diff --git a/apps/presentor/metadata.json b/apps/presentor/metadata.json index 2d0a22102..e44684fda 100644 --- a/apps/presentor/metadata.json +++ b/apps/presentor/metadata.json @@ -1,7 +1,7 @@ { "id": "presentor", "name": "Presentor", - "version": "0.08", + "version": "0.14", "description": "Use your Bangle to present!", "icon": "app.png", "type": "app",