{"action":"created","comment":{"url":"https://api.github.com/repos/ultrabug/py3status/pulls/comments/144699031","pull_request_review_id":69396927,"id":144699031,"diff_hunk":"@@ -0,0 +1,118 @@\n+# -*- coding: utf-8 -*-\n+\"\"\"\n+Show an indicator when YubiKey is waiting for a touch.\n+\n+Configuration parameters:\n+    cache_timeout: How often to detect a pending touch request.\n+        (default 1)\n+    format: Display format for the module.\n+        (default '\\?if=is_waiting&color=bad Y')\n+    u2f_keys_path: Full path to u2f_keys if you want to monitor sudo access.\n+        (default '~/.config/Yubico/u2f_keys')\n+\n+SAMPLE OUTPUT\n+{'color': '#FF0000', 'full_text': 'Y'}\n+\n+Dependencies:\n+    gpg: to check for pending gpg access request\n+    inotify-simple: to check for pending sudo request\n+    subprocess32: if after all these years you are still using python2\n+\n+@author Maxim Baz (https://github.com/maximbaz)\n+@license BSD\n+\"\"\"\n+\n+import time\n+import os\n+import signal\n+import sys\n+import threading\n+import inotify_simple\n+\n+if os.name == 'posix' and sys.version_info[0] < 3:\n+    import subprocess32 as subprocess\n+else:\n+    import subprocess\n+\n+\n+class Py3status:\n+    \"\"\"\n+    \"\"\"\n+    # available configuration parameters\n+    cache_timeout = 1\n+    format = '\\?if=is_waiting&color=bad Y'\n+    u2f_keys_path = '~/.config/Yubico/u2f_keys'\n+\n+    def post_config_hook(self):\n+        self.u2f_keys_path = os.path.expanduser(self.u2f_keys_path)\n+\n+        self.killed = threading.Event()\n+\n+        self.pending_gpg = False\n+        self.pending_sudo = False\n+        self.sudo_reset_timer = None\n+\n+        class GpgThread(threading.Thread):\n+            def run(this):\n+                while not self.killed.is_set():\n+                    with subprocess.Popen(\n+                            'gpg --no-tty --card-status',\n+                            shell=True,\n+                            stdout=subprocess.DEVNULL,\n+                            stderr=subprocess.DEVNULL,\n+                            preexec_fn=os.setsid) as process:\n+                        try:\n+                            process.communicate(timeout=0.1)\n+                            self.pending_gpg = False\n+                        except subprocess.TimeoutExpired:\n+                            self.pending_gpg = True\n+                            os.killpg(process.pid, signal.SIGINT)\n+                            process.communicate()\n+                    time.sleep(self.cache_timeout)\n+\n+        class SudoThread(threading.Thread):\n+            def run(this):\n+                inotify = inotify_simple.INotify()\n+                watch = inotify.add_watch(self.u2f_keys_path,\n+                                          inotify_simple.flags.ACCESS)\n+                while not self.killed.is_set():\n+                    for event in inotify.read():\n+                        self._restart_sudo_reset_timer()\n+                        self.pending_sudo = True\n+                inotify.rm_watch(watch)\n+\n+        GpgThread().start()\n+\n+        if os.path.isfile(self.u2f_keys_path):\n+            SudoThread().start()\n+\n+    def _restart_sudo_reset_timer(self):\n+        def reset_pending_sudo():\n+            self.pending_sudo = False\n+\n+        if self.sudo_reset_timer is not None:\n+            self.sudo_reset_timer.cancel()\n+\n+        self.sudo_reset_timer = threading.Timer(5, reset_pending_sudo)\n+        self.sudo_reset_timer.start()\n+\n+    def yubikey(self):\n+        is_waiting = self.pending_gpg or self.pending_sudo\n+        response = {\n+            'cached_until':\n+            self.py3.time_in(self.cache_timeout),\n+            'full_text':\n+            self.py3.safe_format(self.format, {'is_waiting': is_waiting})","path":"py3status/modules/yubikey.py","position":105,"original_position":105,"commit_id":"b2ccc012361eeef0b1215df1a2cc6194de2cc4ac","original_commit_id":"b2ccc012361eeef0b1215df1a2cc6194de2cc4ac","user":{"login":"lasers","id":852504,"avatar_url":"https://avatars2.githubusercontent.com/u/852504?v=4","gravatar_id":"","url":"https://api.github.com/users/lasers","html_url":"https://github.com/lasers","followers_url":"https://api.github.com/users/lasers/followers","following_url":"https://api.github.com/users/lasers/following{/other_user}","gists_url":"https://api.github.com/users/lasers/gists{/gist_id}","starred_url":"https://api.github.com/users/lasers/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lasers/subscriptions","organizations_url":"https://api.github.com/users/lasers/orgs","repos_url":"https://api.github.com/users/lasers/repos","events_url":"https://api.github.com/users/lasers/events{/privacy}","received_events_url":"https://api.github.com/users/lasers/received_events","type":"User","site_admin":false},"body":"(Nit)\r\n```\r\n    return {\r\n            'cached_until': self.py3.time_in(self.cache_timeout),\r\n            'full_text': self.py3.safe_format(\r\n                self.format, {'is_waiting': is_waiting})\r\n        }\r\n```","created_at":"2017-10-14T17:22:30Z","updated_at":"2017-10-14T17:24:59Z","html_url":"https://github.com/ultrabug/py3status/pull/1110#discussion_r144699031","pull_request_url":"https://api.github.com/repos/ultrabug/py3status/pulls/1110","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/ultrabug/py3status/pulls/comments/144699031"},"html":{"href":"https://github.com/ultrabug/py3status/pull/1110#discussion_r144699031"},"pull_request":{"href":"https://api.github.com/repos/ultrabug/py3status/pulls/1110"}}},"pull_request":{"url":"https://api.github.com/repos/ultrabug/py3status/pulls/1110","id":146603367,"html_url":"https://github.com/ultrabug/py3status/pull/1110","diff_url":"https://github.com/ultrabug/py3status/pull/1110.diff","patch_url":"https://github.com/ultrabug/py3status/pull/1110.patch","issue_url":"https://api.github.com/repos/ultrabug/py3status/issues/1110","number":1110,"state":"open","locked":false,"title":"Add YubiKey module (show a visible indication that YubiKey is waiting for a touch)","user":{"login":"maximbaz","id":1177900,"avatar_url":"https://avatars2.githubusercontent.com/u/1177900?v=4","gravatar_id":"","url":"https://api.github.com/users/maximbaz","html_url":"https://github.com/maximbaz","followers_url":"https://api.github.com/users/maximbaz/followers","following_url":"https://api.github.com/users/maximbaz/following{/other_user}","gists_url":"https://api.github.com/users/maximbaz/gists{/gist_id}","starred_url":"https://api.github.com/users/maximbaz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maximbaz/subscriptions","organizations_url":"https://api.github.com/users/maximbaz/orgs","repos_url":"https://api.github.com/users/maximbaz/repos","events_url":"https://api.github.com/users/maximbaz/events{/privacy}","received_events_url":"https://api.github.com/users/maximbaz/received_events","type":"User","site_admin":false},"body":"This module is designed to show a visible indication that YubiKey is waiting for a touch.\r\n\r\nBasically it uses a bunch of hacks to solve [this issue](https://forum.yubico.com/viewtopic.php?f=35&t=2397). \r\nOwners of YubiKey, especially YubiKey 4 Nano would be extremely glad for this module 🙂\r\n\r\nUsed hacks:\r\n- To detect if gpg access is being requested, a separate thread is calling `gpg --card-status`. I noticed that this command hands indefinitely when card is waiting for a touch.\r\n- To detect if sudo access is being requested, a separate thread is monitoring access to `u2f_keys` file - every access usually means that a sudo is requested somewhere on the system.\r\n    - I haven't figured out yet [how to detect if access was granted](https://github.com/Yubico/pam-u2f/issues/77), thus for now I clear the status after hardcoded 5 seconds.\r\n\r\n\r\n**DEMO**:\r\n\r\n`gpg`\r\n\r\n![yubikey_gpg](https://user-images.githubusercontent.com/1177900/31576216-98a01e72-b0f6-11e7-8bda-ee22502ff7c9.gif)\r\n\r\n`sudo`\r\n\r\n![yubikey_sudo](https://user-images.githubusercontent.com/1177900/31576218-9dda5d08-b0f6-11e7-9bce-55fd74033c1c.gif)\r\n","created_at":"2017-10-14T13:46:04Z","updated_at":"2017-10-14T17:24:59Z","closed_at":null,"merged_at":null,"merge_commit_sha":"81919929ea4909035e93065e0be9fa30f001cd9b","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/ultrabug/py3status/pulls/1110/commits","review_comments_url":"https://api.github.com/repos/ultrabug/py3status/pulls/1110/comments","review_comment_url":"https://api.github.com/repos/ultrabug/py3status/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ultrabug/py3status/issues/1110/comments","statuses_url":"https://api.github.com/repos/ultrabug/py3status/statuses/b2ccc012361eeef0b1215df1a2cc6194de2cc4ac","head":{"label":"maximbaz:add_yubikey_module","ref":"add_yubikey_module","sha":"b2ccc012361eeef0b1215df1a2cc6194de2cc4ac","user":{"login":"maximbaz","id":1177900,"avatar_url":"https://avatars2.githubusercontent.com/u/1177900?v=4","gravatar_id":"","url":"https://api.github.com/users/maximbaz","html_url":"https://github.com/maximbaz","followers_url":"https://api.github.com/users/maximbaz/followers","following_url":"https://api.github.com/users/maximbaz/following{/other_user}","gists_url":"https://api.github.com/users/maximbaz/gists{/gist_id}","starred_url":"https://api.github.com/users/maximbaz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maximbaz/subscriptions","organizations_url":"https://api.github.com/users/maximbaz/orgs","repos_url":"https://api.github.com/users/maximbaz/repos","events_url":"https://api.github.com/users/maximbaz/events{/privacy}","received_events_url":"https://api.github.com/users/maximbaz/received_events","type":"User","site_admin":false},"repo":{"id":44889392,"name":"py3status","full_name":"maximbaz/py3status","owner":{"login":"maximbaz","id":1177900,"avatar_url":"https://avatars2.githubusercontent.com/u/1177900?v=4","gravatar_id":"","url":"https://api.github.com/users/maximbaz","html_url":"https://github.com/maximbaz","followers_url":"https://api.github.com/users/maximbaz/followers","following_url":"https://api.github.com/users/maximbaz/following{/other_user}","gists_url":"https://api.github.com/users/maximbaz/gists{/gist_id}","starred_url":"https://api.github.com/users/maximbaz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maximbaz/subscriptions","organizations_url":"https://api.github.com/users/maximbaz/orgs","repos_url":"https://api.github.com/users/maximbaz/repos","events_url":"https://api.github.com/users/maximbaz/events{/privacy}","received_events_url":"https://api.github.com/users/maximbaz/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/maximbaz/py3status","description":"py3status is an extensible i3status wrapper written in python","fork":true,"url":"https://api.github.com/repos/maximbaz/py3status","forks_url":"https://api.github.com/repos/maximbaz/py3status/forks","keys_url":"https://api.github.com/repos/maximbaz/py3status/keys{/key_id}","collaborators_url":"https://api.github.com/repos/maximbaz/py3status/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/maximbaz/py3status/teams","hooks_url":"https://api.github.com/repos/maximbaz/py3status/hooks","issue_events_url":"https://api.github.com/repos/maximbaz/py3status/issues/events{/number}","events_url":"https://api.github.com/repos/maximbaz/py3status/events","assignees_url":"https://api.github.com/repos/maximbaz/py3status/assignees{/user}","branches_url":"https://api.github.com/repos/maximbaz/py3status/branches{/branch}","tags_url":"https://api.github.com/repos/maximbaz/py3status/tags","blobs_url":"https://api.github.com/repos/maximbaz/py3status/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/maximbaz/py3status/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/maximbaz/py3status/git/refs{/sha}","trees_url":"https://api.github.com/repos/maximbaz/py3status/git/trees{/sha}","statuses_url":"https://api.github.com/repos/maximbaz/py3status/statuses/{sha}","languages_url":"https://api.github.com/repos/maximbaz/py3status/languages","stargazers_url":"https://api.github.com/repos/maximbaz/py3status/stargazers","contributors_url":"https://api.github.com/repos/maximbaz/py3status/contributors","subscribers_url":"https://api.github.com/repos/maximbaz/py3status/subscribers","subscription_url":"https://api.github.com/repos/maximbaz/py3status/subscription","commits_url":"https://api.github.com/repos/maximbaz/py3status/commits{/sha}","git_commits_url":"https://api.github.com/repos/maximbaz/py3status/git/commits{/sha}","comments_url":"https://api.github.com/repos/maximbaz/py3status/comments{/number}","issue_comment_url":"https://api.github.com/repos/maximbaz/py3status/issues/comments{/number}","contents_url":"https://api.github.com/repos/maximbaz/py3status/contents/{+path}","compare_url":"https://api.github.com/repos/maximbaz/py3status/compare/{base}...{head}","merges_url":"https://api.github.com/repos/maximbaz/py3status/merges","archive_url":"https://api.github.com/repos/maximbaz/py3status/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/maximbaz/py3status/downloads","issues_url":"https://api.github.com/repos/maximbaz/py3status/issues{/number}","pulls_url":"https://api.github.com/repos/maximbaz/py3status/pulls{/number}","milestones_url":"https://api.github.com/repos/maximbaz/py3status/milestones{/number}","notifications_url":"https://api.github.com/repos/maximbaz/py3status/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/maximbaz/py3status/labels{/name}","releases_url":"https://api.github.com/repos/maximbaz/py3status/releases{/id}","deployments_url":"https://api.github.com/repos/maximbaz/py3status/deployments","created_at":"2015-10-24T23:43:23Z","updated_at":"2015-10-24T23:43:25Z","pushed_at":"2017-10-14T16:47:29Z","git_url":"git://github.com/maximbaz/py3status.git","ssh_url":"git@github.com:maximbaz/py3status.git","clone_url":"https://github.com/maximbaz/py3status.git","svn_url":"https://github.com/maximbaz/py3status","homepage":"","size":3144,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"ultrabug:master","ref":"master","sha":"2873692c36e4dffe2dbf22adba624570735d04f2","user":{"login":"ultrabug","id":474389,"avatar_url":"https://avatars3.githubusercontent.com/u/474389?v=4","gravatar_id":"","url":"https://api.github.com/users/ultrabug","html_url":"https://github.com/ultrabug","followers_url":"https://api.github.com/users/ultrabug/followers","following_url":"https://api.github.com/users/ultrabug/following{/other_user}","gists_url":"https://api.github.com/users/ultrabug/gists{/gist_id}","starred_url":"https://api.github.com/users/ultrabug/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ultrabug/subscriptions","organizations_url":"https://api.github.com/users/ultrabug/orgs","repos_url":"https://api.github.com/users/ultrabug/repos","events_url":"https://api.github.com/users/ultrabug/events{/privacy}","received_events_url":"https://api.github.com/users/ultrabug/received_events","type":"User","site_admin":false},"repo":{"id":8292338,"name":"py3status","full_name":"ultrabug/py3status","owner":{"login":"ultrabug","id":474389,"avatar_url":"https://avatars3.githubusercontent.com/u/474389?v=4","gravatar_id":"","url":"https://api.github.com/users/ultrabug","html_url":"https://github.com/ultrabug","followers_url":"https://api.github.com/users/ultrabug/followers","following_url":"https://api.github.com/users/ultrabug/following{/other_user}","gists_url":"https://api.github.com/users/ultrabug/gists{/gist_id}","starred_url":"https://api.github.com/users/ultrabug/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ultrabug/subscriptions","organizations_url":"https://api.github.com/users/ultrabug/orgs","repos_url":"https://api.github.com/users/ultrabug/repos","events_url":"https://api.github.com/users/ultrabug/events{/privacy}","received_events_url":"https://api.github.com/users/ultrabug/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/ultrabug/py3status","description":"py3status is an extensible i3status wrapper written in python","fork":false,"url":"https://api.github.com/repos/ultrabug/py3status","forks_url":"https://api.github.com/repos/ultrabug/py3status/forks","keys_url":"https://api.github.com/repos/ultrabug/py3status/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ultrabug/py3status/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ultrabug/py3status/teams","hooks_url":"https://api.github.com/repos/ultrabug/py3status/hooks","issue_events_url":"https://api.github.com/repos/ultrabug/py3status/issues/events{/number}","events_url":"https://api.github.com/repos/ultrabug/py3status/events","assignees_url":"https://api.github.com/repos/ultrabug/py3status/assignees{/user}","branches_url":"https://api.github.com/repos/ultrabug/py3status/branches{/branch}","tags_url":"https://api.github.com/repos/ultrabug/py3status/tags","blobs_url":"https://api.github.com/repos/ultrabug/py3status/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ultrabug/py3status/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ultrabug/py3status/git/refs{/sha}","trees_url":"https://api.github.com/repos/ultrabug/py3status/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ultrabug/py3status/statuses/{sha}","languages_url":"https://api.github.com/repos/ultrabug/py3status/languages","stargazers_url":"https://api.github.com/repos/ultrabug/py3status/stargazers","contributors_url":"https://api.github.com/repos/ultrabug/py3status/contributors","subscribers_url":"https://api.github.com/repos/ultrabug/py3status/subscribers","subscription_url":"https://api.github.com/repos/ultrabug/py3status/subscription","commits_url":"https://api.github.com/repos/ultrabug/py3status/commits{/sha}","git_commits_url":"https://api.github.com/repos/ultrabug/py3status/git/commits{/sha}","comments_url":"https://api.github.com/repos/ultrabug/py3status/comments{/number}","issue_comment_url":"https://api.github.com/repos/ultrabug/py3status/issues/comments{/number}","contents_url":"https://api.github.com/repos/ultrabug/py3status/contents/{+path}","compare_url":"https://api.github.com/repos/ultrabug/py3status/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ultrabug/py3status/merges","archive_url":"https://api.github.com/repos/ultrabug/py3status/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ultrabug/py3status/downloads","issues_url":"https://api.github.com/repos/ultrabug/py3status/issues{/number}","pulls_url":"https://api.github.com/repos/ultrabug/py3status/pulls{/number}","milestones_url":"https://api.github.com/repos/ultrabug/py3status/milestones{/number}","notifications_url":"https://api.github.com/repos/ultrabug/py3status/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ultrabug/py3status/labels{/name}","releases_url":"https://api.github.com/repos/ultrabug/py3status/releases{/id}","deployments_url":"https://api.github.com/repos/ultrabug/py3status/deployments","created_at":"2013-02-19T14:59:13Z","updated_at":"2017-10-14T00:53:25Z","pushed_at":"2017-10-14T16:47:30Z","git_url":"git://github.com/ultrabug/py3status.git","ssh_url":"git@github.com:ultrabug/py3status.git","clone_url":"https://github.com/ultrabug/py3status.git","svn_url":"https://github.com/ultrabug/py3status","homepage":"","size":3172,"stargazers_count":447,"watchers_count":447,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":165,"mirror_url":null,"open_issues_count":113,"forks":165,"open_issues":113,"watchers":447,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ultrabug/py3status/pulls/1110"},"html":{"href":"https://github.com/ultrabug/py3status/pull/1110"},"issue":{"href":"https://api.github.com/repos/ultrabug/py3status/issues/1110"},"comments":{"href":"https://api.github.com/repos/ultrabug/py3status/issues/1110/comments"},"review_comments":{"href":"https://api.github.com/repos/ultrabug/py3status/pulls/1110/comments"},"review_comment":{"href":"https://api.github.com/repos/ultrabug/py3status/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ultrabug/py3status/pulls/1110/commits"},"statuses":{"href":"https://api.github.com/repos/ultrabug/py3status/statuses/b2ccc012361eeef0b1215df1a2cc6194de2cc4ac"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"lasers"}}