{"id":136,"date":"2016-08-18T12:19:42","date_gmt":"2016-08-18T11:19:42","guid":{"rendered":"https:\/\/gavincarpenter.co.uk\/adops\/?p=136"},"modified":"2016-08-18T12:19:42","modified_gmt":"2016-08-18T11:19:42","slug":"side-project-screenwatcher-alerts-for-anything","status":"publish","type":"post","link":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/2016\/08\/18\/side-project-screenwatcher-alerts-for-anything\/","title":{"rendered":"Side Project: ScreenWatcher \u2013 Alerts for Anything"},"content":{"rendered":"<p>Bill Gates once said:<\/p>\n<blockquote><p>\u201cI choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it.\u201d<\/p><\/blockquote>\n<p>Ok, now that I\u2019ve fulfilled the vaguely related quote requirement for a blog post we can get down to business.<\/p>\n<p>We\u2019ve all been there, you start a report running, a file downloading or a program installing and you know it\u2019s going to take a while to finish. Some systems have the functionality to send you an email once finished but the vast majority of them don\u2019t plus it\u2019s quite limited.<\/p>\n<p>So what you end up doing is keep looking\/walking back over to the screen to check how far it\u2019s got\u00a0and getting disappointed how far it\u2019s not moved, like the proverbial watched kettle.<\/p>\n<p>To alleviate this first world problem, I\u2019ve built a quick widget to watch the screen for me and to alert me if it changes.<!--more--><\/p>\n<p>It works like this (Google&#8217;s built in countdown timer used as an example of a progress bar):<\/p>\n<ul>\n<li>Open the widget &#8211; a screenshot is taken (multiple screens work too)<\/li>\n<\/ul>\n<p><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" class=\"center\" src=\"https:\/\/i0.wp.com\/media.licdn.com\/mpr\/mpr\/shrinknp_800_800\/AAEAAQAAAAAAAAdjAAAAJGU1M2M2MGFhLTRjY2YtNDNlNi1hM2VjLWRkZTAxYTg5OGFjZQ.png?resize=640%2C419&#038;ssl=1\" width=\"640\" height=\"419\" \/><\/p>\n<ul>\n<li>The widget shows you the screenshot in a scrolling window &#8211; select the area of interest by dragging a rectangle around it, this is the area that doesn&#8217;t change until the action you want to monitor is done.<\/li>\n<\/ul>\n<p><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" class=\"center\" src=\"https:\/\/i0.wp.com\/media.licdn.com\/mpr\/mpr\/shrinknp_800_800\/AAEAAQAAAAAAAAl3AAAAJDcyMzU4NGNlLTA4NDEtNGI1My04N2EyLTVjNjU4MTRjMTcxOA.png?resize=640%2C419&#038;ssl=1\" width=\"640\" height=\"419\" \/><\/p>\n<ul>\n<li>After clicking OK the widget disappears but it&#8217;s actually still running and constantly monitoring the area of interest comparing the colour of the pixels to the original version.<\/li>\n<li>Once they change the widget announces the event\u00a0to the\u00a0very cool automation system <a href=\"https:\/\/ifttt.com\/recipes\" target=\"_blank\" rel=\"nofollow\">IFTTT <\/a>(If This Then That) along with the optional details entered earlier. From here you can do almost anything, have your phone get a notification, send an SMS, turn on\/off a light\/plug, play a song, etc.<\/li>\n<\/ul>\n<p><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" class=\"center\" src=\"https:\/\/i0.wp.com\/media.licdn.com\/mpr\/mpr\/shrinknp_800_800\/AAEAAQAAAAAAAAjHAAAAJGE5YWE4MDg3LTAyOGQtNDRlMC04OWNmLTk2MTdmMmY4Yjk3Mw.png?resize=458%2C205&#038;ssl=1\" width=\"458\" height=\"205\" \/><\/p>\n<p>Here it is in action:<br \/>\n<iframe loading=\"lazy\" src=\"https:\/\/www.youtube.com\/embed\/ZW8jjr_5lIc\" width=\"560\" height=\"315\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><br \/>\n&nbsp;<\/p>\n<h3>Challenges (this bits a bit technical if you want to skip it)<\/h3>\n<ul>\n<li>Handling multi monitors is a real pain. The Java library used to capture screenshots doesn&#8217;t easily deal with above and below layouts very well. I think I&#8217;ve solved this by storing the lowest possible X and Y value for the top left corner of any monitor (it goes negative if it&#8217;s above and\/or left of the primary display) and then requesting a screenshot of a giant rectangle from that top corner to the total of all the widths and heights combined.<\/li>\n<li>Checking the RGB value of a pixel on the screen is computationally expensive. Each check takes around 20ms it seems. So if you have a 20&#215;20 grid of pixels to check that&#8217;s 20x20x20=8000ms; a whole 8 seconds! I&#8217;d actually had similar problems before back at uni doing computer vision work. What I&#8217;ve done is give the system a max number of pixels to search (50) and have it skip over pixels in the grid in a predictable way so that only that number is ever checked. Luckily in all the tests I&#8217;ve done at least one of these pixels in the search area has changed allowing for the system to pick it up.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>Now this didn&#8217;t take very long to code up and it&#8217;s mainly based on bolted together StackOverflow answers but it does the job very well indeed and acts as a nice proof of concept for other ideas and tools.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Bill Gates once said: \u201cI choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it.\u201d Ok, now that I\u2019ve fulfilled the vaguely related quote requirement for a blog post we can get down to business. We\u2019ve all been there, you start a report running, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[33],"tags":[34,36,35],"class_list":["post-136","post","type-post","status-publish","format-standard","hentry","category-side-projects","tag-coding","tag-first-world-problem","tag-time-saving"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p79kpO-2c","_links":{"self":[{"href":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/wp-json\/wp\/v2\/posts\/136","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/wp-json\/wp\/v2\/comments?post=136"}],"version-history":[{"count":2,"href":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/wp-json\/wp\/v2\/posts\/136\/revisions"}],"predecessor-version":[{"id":138,"href":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/wp-json\/wp\/v2\/posts\/136\/revisions\/138"}],"wp:attachment":[{"href":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/wp-json\/wp\/v2\/media?parent=136"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/wp-json\/wp\/v2\/categories?post=136"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gavincarpenter.co.uk\/adops\/index.php\/wp-json\/wp\/v2\/tags?post=136"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}