From c30327f73e5a539c6d5fedcb4f0ea0a23a761d05 Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Wed, 23 May 2018 18:07:32 +1000 Subject: [PATCH 01/12] Fix `:restart` command on Windows --- native/native_main.py | 157 ++++++++++++++++++++++++++++++++++++- native/test_native_main.py | 34 ++++++++ src/excmds.ts | 20 ++++- src/native_background.ts | 35 +++++++-- 4 files changed, 236 insertions(+), 10 deletions(-) create mode 100644 native/test_native_main.py diff --git a/native/native_main.py b/native/native_main.py index 81251ce9..e0dd354f 100755 --- a/native/native_main.py +++ b/native/native_main.py @@ -3,8 +3,10 @@ import sys import os -import json import re +import json +import time +import shutil import struct import subprocess import tempfile @@ -16,6 +18,14 @@ VERSION = "0.1.5" class NoConnectionError(Exception): """ Exception thrown when stdin cannot be read """ +def is_command_on_path(command): + """ Returns 'True' if the if the specified command is found on + user's $PATH. + """ + if shutil.which(command): + return True + else: + return False def eprint(*args, **kwargs): """ Print to stderr, which gets echoed in the browser console when run @@ -183,6 +193,151 @@ def handleMessage(message): elif cmd == 'env': reply['content'] = getenv(message["var"], "") + elif cmd == "win_restart_firefox": + profile_dir = message["profiledir"].strip() + + if profile_dir != "auto" and not os.path.isdir(profile_dir): + reply = { + "cmd": "error", + "error": "%s %s %s" % ( + "Invalid profile directory specified.", + "Profile directory path(s) can be found by", + "navigating to 'about:support'." + ) + } + + elif not is_command_on_path("firefox"): + reply = { + "cmd": "error", + "error": "firefox.exe is not found on PATH." + } + + else: + ## + ## Native messenger can't seem to create detached + ## process on Windows while Firefox is quitting, which + ## is essential to trigger restarting Firefox. So, + ## below we are resorting to create a scheduled task + ## with the task start-time set in the near future. + ## + + # + #subprocess.Popen( + # [ff_bin, "-profile", profile_dir], + # shell=False, + # creationflags=0x208 \ + # | subprocess.CREATE_NEW_PROCESS_GROUP) + # + + ## + ## 'schtasks.exe' is limited as in it doesn't + ## support task-time with granularity in seconds. So, + ## falling back to PowerShell as the last resort. + ## + + # out_str = "" + # task_time = time.strftime("%H:%M", + # time.localtime( + # time.time() + 60)) + # + # out_str = subprocess.check_output( + # ["schtasks.exe", + # "/Create", + # "/F", + # "/SC", + # "ONCE", + # "/TN", + # "tridactyl", + # "/TR", + # "calc", + # "/IT", + # "/ST", + # task_time], + # shell=True) + # + + ff_bin = "\"%s\"" % shutil.which("firefox") + + if profile_dir == "auto": + lock_file = ff_bin + ff_args = "\"-ProfileManager\"" + else: + lock_file = "\"%s\\parent.lock\"" % profile_dir + ff_args = "\"-profile\" \"%s\"" % profile_dir + + + try: + restart_ps1_content = '''$profileDir = "%s" +if ($profileDir -ne "auto") { + $lockFile = "$profileDir\parent.lock" + $locked = $true + $num_try = 10 +} else { + $locked = $false +} +while (($locked -eq $true) -and ($num_try -gt 0)) { + try { + [IO.File]::OpenWrite($lockFile).close() + $locked=$fals + } catch{ + $num_try-=1 + Write-Host "[+] Trial: $num_try [lock = true]" + Start-Sleep -Seconds 1 + } +} +if ($locked -eq $true) { + Add-Type -AssemblyName System.Windows.Forms + [System.Windows.MessageBox]::Show( + "Restarting Firefox failed. Please manually restart.", + "Tridactyl") +} else { + Write-Host "[+] Restarting Firefox ..." + & %s %s +} +''' % (profile_dir, ff_bin, ff_args) + + delay_sec= 1 + task_cmd = "powershell" + task_name = "firefox-restart" + + restart_ps1_path = os.path.expanduser("~") \ + + "\\.tridactyl\\win_firefox_restart.ps1" + + task_arg = "\"%s\"" % restart_ps1_path + + open(restart_ps1_path, + "w+").write(restart_ps1_content) + + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + subprocess.check_output( + ["powershell", + "-NonInteractive", + "-NoProfile", + "-WindowStyle", + "Minimized", + "-InputFormat", + "None", + "-ExecutionPolicy", + "Bypass", + "-Command", + "Register-ScheduledTask -TaskName '%s' -Force -Action (New-ScheduledTaskAction -Execute '%s' -Argument '%s') -Trigger (New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(%d).ToString('HH:mm:ss'))" % (task_name, task_cmd, task_arg, delay_sec)], + shell=False, + startupinfo=startupinfo, + ) + + reply = { + "code": 0, + "content": "Restarting in %d seconds..." % delay_sec + } + + except subprocess.CalledProcessError: + reply = { + "code": -1, + "cmd": "error", + "error": "Error creating restart task." + } + else: reply = {'cmd': 'error', 'error': 'Unhandled message'} eprint('Unhandled message: {}'.format(message)) diff --git a/native/test_native_main.py b/native/test_native_main.py new file mode 100644 index 00000000..64595bcd --- /dev/null +++ b/native/test_native_main.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import json +import struct + + +def usage(): + """Show usage and exit with non-zero status.""" + sys.stderr.write("[+] usage: %s cmd [command] | %s\n" % \ + (os.path.basename(__file__), + "native_main.py")) + exit(-1) + +if __name__ == "__main__": + msg = dict() + if len(sys.argv) > 1: + msg["cmd"] = sys.argv[1] + if len(sys.argv) > 2: + key = sys.argv[2].strip().split("##")[0] + val = sys.argv[2].strip().split("##")[1] + msg[key] = val + + if len(sys.argv) == 1: + usage() + + msg = json.dumps(msg) + msg = "\r\n" + msg + "\r\n" + msg = msg.encode("utf-8") + packed_len = struct.pack("@I", len(msg)) + + sys.stdout.buffer.write(packed_len + msg) diff --git a/src/excmds.ts b/src/excmds.ts index ad7da8ac..2548a1c2 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -400,11 +400,23 @@ export async function updatenative(interactive = true) { */ //#background export async function restart() { - const firefox = (await Native.ffargs()).join(" ") const profile = await Native.getProfileDir() - // Wait for the lock to disappear, then wait a bit more, then start firefox - Native.run(`while readlink ${profile}/lock ; do sleep 1 ; done ; sleep 1 ; ${firefox}`) - qall() + + if ((await browser.runtime.getPlatformInfo()).os === "win") { + let reply = await Native.winRestartFirefox(profile) + logger.info("[+] win_restart_firefox 'reply' = " + JSON.stringify(reply)) + if (Number(reply["code"]) === 0) { + fillcmdline("#" + reply["content"]) + qall() + } else { + fillcmdline("#" + reply["error"]) + } + } else { + const firefox = (await Native.ffargs()).join(" ") + // Wait for the lock to disappear, then wait a bit more, then start firefox + Native.run(`while readlink ${profile}/lock ; do sleep 1 ; done ; sleep 1 ; ${firefox}`) + qall() + } } // }}} diff --git a/src/native_background.ts b/src/native_background.ts index 6f7d36c6..2fdf755c 100644 --- a/src/native_background.ts +++ b/src/native_background.ts @@ -20,6 +20,7 @@ type MessageCommand = | "eval" | "getconfig" | "env" + | "win_restart_firefox" interface MessageResp { cmd: string version: number | null @@ -245,6 +246,10 @@ export async function temp(content: string, prefix: string) { return sendNativeMsg("temp", { content, prefix }) } +export async function winRestartFirefox(profiledir: string) { + return sendNativeMsg("win_restart_firefox", { profiledir }) +} + export async function run(command: string) { let msg = await sendNativeMsg("run", { command }) logger.info(msg) @@ -270,14 +275,34 @@ export async function getenv(variable: string) { You'll get both firefox binary (not necessarily an absolute path) and flags */ export async function ffargs(): Promise { // Using ' and + rather that ` because we don't want newlines - let output = await pyeval( - 'handleMessage({"cmd": "run", ' + - '"command": "ps -p " + str(os.getppid()) + " -oargs="})["content"]', - ) - return output.content.trim().split(" ") + if ((await browserBg.runtime.getPlatformInfo()).os === "win") { + return [] + } else { + let output = await pyeval( + 'handleMessage({"cmd": "run", ' + + '"command": "ps -p " + str(os.getppid()) + " -oargs="})["content"]', + ) + return output.content.trim().split(" ") + } } export async function getProfileDir() { + // Windows users must specify their Firefox profile directory + // via 'set profiledir [directory]'. Windows profile directory + // path must be properly escaped. For example, a correct way + // to set 'profiledir' on Windows is as shown below: + // + // : set profiledir C:\\Users\\\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\8s21wzbh.Default + if ((await browserBg.runtime.getPlatformInfo()).os === "win") { + let win_profiledir = config.get("profiledir") + win_profiledir = win_profiledir.trim() + if (win_profiledir.length > 0) { + return win_profiledir + } else { + throw new Error("Profile directory not set.") + } + } + // First, see if we can get the profile from the arguments that were given // to Firefox let args = await ffargs() From 66444af20a548dba07269ed30c39246f4d6acf08 Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Thu, 24 May 2018 12:56:06 +1000 Subject: [PATCH 02/12] Refactor and clean-up Python codes --- ...t_native_main.py => gen_native_message.py} | 17 +- native/native_main.py | 338 ++++++++++-------- 2 files changed, 204 insertions(+), 151 deletions(-) rename native/{test_native_main.py => gen_native_message.py} (55%) diff --git a/native/test_native_main.py b/native/gen_native_message.py similarity index 55% rename from native/test_native_main.py rename to native/gen_native_message.py index 64595bcd..2d72654b 100644 --- a/native/test_native_main.py +++ b/native/gen_native_message.py @@ -9,12 +9,22 @@ import struct def usage(): """Show usage and exit with non-zero status.""" - sys.stderr.write("[+] usage: %s cmd [command] | %s\n" % \ - (os.path.basename(__file__), - "native_main.py")) + sys.stderr.write("[+] Usage: %s cmd [command] | %s\n" % + (os.path.basename(__file__), + "native_main.py")) + + sys.stderr.write("\nNote: Use '##' as key-value separator") + sys.stderr.write("\nExample: %s %s %s | %s" % + (os.path.basename(__file__), + 'win_restart_firefox', + 'profiledir##auto', + 'native_main.py')) + exit(-1) + if __name__ == "__main__": + """Main functionalities are here for now.""" msg = dict() if len(sys.argv) > 1: msg["cmd"] = sys.argv[1] @@ -32,3 +42,4 @@ if __name__ == "__main__": packed_len = struct.pack("@I", len(msg)) sys.stdout.buffer.write(packed_len + msg) + sys.stdout.flush() diff --git a/native/native_main.py b/native/native_main.py index 8d88b70f..5b0b784d 100755 --- a/native/native_main.py +++ b/native/native_main.py @@ -7,16 +7,18 @@ import re import json import shutil import struct -import subprocess import tempfile +import pathlib +import subprocess import unicodedata -VERSION = "0.1.5" +VERSION = "0.1.6" class NoConnectionError(Exception): """ Exception thrown when stdin cannot be read """ + def is_command_on_path(command): """ Returns 'True' if the if the specified command is found on user's $PATH. @@ -26,9 +28,10 @@ def is_command_on_path(command): else: return False + def eprint(*args, **kwargs): - """ Print to stderr, which gets echoed in the browser console when run - by Firefox + """ Print to stderr, which gets echoed in the browser console + when run by Firefox """ print(*args, file=sys.stderr, flush=True, **kwargs) @@ -126,6 +129,187 @@ def sanitizeFilename(fn): fn = re.sub('[-/\s]+', '-', fn) return fn +def is_valid_firefox_profile(profile_dir): + is_valid = False + validity_indicator = "times.json" + + if pathlib.WindowsPath(profile_dir).is_dir(): + test_path = "%s\\%s" % (profile_dir, validity_indicator) + + if pathlib.WindowsPath(test_path).is_file(): + is_valid = True + + return is_valid + + +def win_firefox_restart(message): + """Handle 'win_firefox_restart' message.""" + reply = {} + profile_dir = message["profiledir"].strip() + + ff_bin_name = "firefox.exe" + ff_lock_name = "parent.lock" + + if profile_dir != "auto" \ + and not is_valid_firefox_profile(profile_dir): + reply = { + "cmd": "error", + "error": "%s %s %s" % ( + "Invalid profile directory specified.", + "Vaild profile directory path(s) can be found by", + "navigating to 'about:support'." + ) + } + + elif not is_command_on_path(ff_bin_name): + reply = { + "cmd": "error", + "error": "firefox.exe is not found on %PATH%." + } + + else: + # {{{ + # Native messenger can't seem to create detached process on + # Windows while Firefox is quitting, which is essential to + # trigger restarting Firefox. So, below we are resorting to + # create a scheduled task with the task start-time set in + # the near future. + # + + # + #subprocess.Popen( + # [ff_bin_path, "-profile", profile_dir], + # shell=False, + # creationflags=0x208 \ + # | subprocess.CREATE_NEW_PROCESS_GROUP) + # + + # + # 'schtasks.exe' is limited as in it doesn't support + # task-time with granularity in seconds. So, falling back + # to PowerShell as the last resort. + # + + # out_str = "" + # task_time = time.strftime("%H:%M", + # time.localtime( + # time.time() + 60)) + # + # out_str = subprocess.check_output( + # ["schtasks.exe", + # "/Create", + # "/F", + # "/SC", + # "ONCE", + # "/TN", + # "tridactyl", + # "/TR", + # "calc", + # "/IT", + # "/ST", + # task_time], + # shell=True) + # }}} + + ff_bin_path = "\"%s\"" % shutil.which(ff_bin_name) + + if profile_dir == "auto": + ff_args = "\"-ProfileManager\"" + ff_lock_path = ff_bin_path + else: + ff_args = "\"-profile\" \"%s\"" % profile_dir + ff_lock_path = "\"%s\\%s\"" % (profile_dir, + ff_lock_name) + + + try: + restart_ps1_content = '''$profileDir = "%s" +if ($profileDir -ne "auto") { + $lockFilePath = %s + $locked = $true + $num_try = 10 +} else { + $locked = $false +} +while (($locked -eq $true) -and ($num_try -gt 0)) { +try { + [IO.File]::OpenWrite($lockFilePath).close() + $locked=$fals +} catch{ + $num_try-=1 + Write-Host "[+] Trial: $num_try [lock == true]" + Start-Sleep -Seconds 1 +} +} +if ($locked -eq $true) { +Add-Type -AssemblyName System.Windows.Forms +[System.Windows.MessageBox]::Show( + "Restarting Firefox failed. Please manually restart.", + "Tridactyl") +} else { +Write-Host "[+] Restarting Firefox ..." +& %s %s +} +''' % (profile_dir, ff_lock_path, ff_bin_path, ff_args) + + delay_sec = 1 + task_cmd = "powershell" + task_name = "firefox-restart" + native_messenger_dirname = ".tridactyl" + + restart_ps1_path = "%s\\%s\\%s" % ( + os.path.expanduser('~'), + native_messenger_dirname, + "win_firefox_restart.ps1") + + task_arg = "\"%s\"" % restart_ps1_path + + open(restart_ps1_path, "w+").write(restart_ps1_content) + + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + + subprocess.check_output( + ["powershell", + "-NonInteractive", + "-NoProfile", + "-WindowStyle", + "Minimized", + "-InputFormat", + "None", + "-ExecutionPolicy", + "Bypass", + "-Command", + "Register-ScheduledTask \ + -TaskName '%s' \ + -Force \ + -Action (New-ScheduledTaskAction \ + -Execute '%s' \ + -Argument '%s') \ + -Trigger (New-ScheduledTaskTrigger \ + -Once \ + -At \ + (Get-Date).AddSeconds(%d).ToString(\ + 'HH:mm:ss'))" % (task_name, + task_cmd, + task_arg, + delay_sec) + ], shell=False, startupinfo=startupinfo) + + reply = { + "code": 0, + "content": "Restarting in %d seconds..." % delay_sec + } + + except subprocess.CalledProcessError: + reply = { + "code": -1, + "cmd": "error", + "error": "Error creating restart task." + } + + return reply + def handleMessage(message): """ Generate reply from incoming message. """ @@ -192,150 +376,8 @@ def handleMessage(message): elif cmd == 'env': reply['content'] = getenv(message["var"], "") - elif cmd == "win_restart_firefox": - profile_dir = message["profiledir"].strip() - - if profile_dir != "auto" and not os.path.isdir(profile_dir): - reply = { - "cmd": "error", - "error": "%s %s %s" % ( - "Invalid profile directory specified.", - "Profile directory path(s) can be found by", - "navigating to 'about:support'." - ) - } - - elif not is_command_on_path("firefox"): - reply = { - "cmd": "error", - "error": "firefox.exe is not found on PATH." - } - - else: - ## - ## Native messenger can't seem to create detached - ## process on Windows while Firefox is quitting, which - ## is essential to trigger restarting Firefox. So, - ## below we are resorting to create a scheduled task - ## with the task start-time set in the near future. - ## - - # - #subprocess.Popen( - # [ff_bin, "-profile", profile_dir], - # shell=False, - # creationflags=0x208 \ - # | subprocess.CREATE_NEW_PROCESS_GROUP) - # - - ## - ## 'schtasks.exe' is limited as in it doesn't - ## support task-time with granularity in seconds. So, - ## falling back to PowerShell as the last resort. - ## - - # out_str = "" - # task_time = time.strftime("%H:%M", - # time.localtime( - # time.time() + 60)) - # - # out_str = subprocess.check_output( - # ["schtasks.exe", - # "/Create", - # "/F", - # "/SC", - # "ONCE", - # "/TN", - # "tridactyl", - # "/TR", - # "calc", - # "/IT", - # "/ST", - # task_time], - # shell=True) - # - - ff_bin = "\"%s\"" % shutil.which("firefox") - - if profile_dir == "auto": - lock_file = ff_bin - ff_args = "\"-ProfileManager\"" - else: - lock_file = "\"%s\\parent.lock\"" % profile_dir - ff_args = "\"-profile\" \"%s\"" % profile_dir - - - try: - restart_ps1_content = '''$profileDir = "%s" -if ($profileDir -ne "auto") { - $lockFile = "$profileDir\parent.lock" - $locked = $true - $num_try = 10 -} else { - $locked = $false -} -while (($locked -eq $true) -and ($num_try -gt 0)) { - try { - [IO.File]::OpenWrite($lockFile).close() - $locked=$fals - } catch{ - $num_try-=1 - Write-Host "[+] Trial: $num_try [lock = true]" - Start-Sleep -Seconds 1 - } -} -if ($locked -eq $true) { - Add-Type -AssemblyName System.Windows.Forms - [System.Windows.MessageBox]::Show( - "Restarting Firefox failed. Please manually restart.", - "Tridactyl") -} else { - Write-Host "[+] Restarting Firefox ..." - & %s %s -} -''' % (profile_dir, ff_bin, ff_args) - - delay_sec = 1 - task_cmd = "powershell" - task_name = "firefox-restart" - - restart_ps1_path = os.path.expanduser("~") \ - + "\\.tridactyl\\win_firefox_restart.ps1" - - task_arg = "\"%s\"" % restart_ps1_path - - open(restart_ps1_path, - "w+").write(restart_ps1_content) - - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - subprocess.check_output( - ["powershell", - "-NonInteractive", - "-NoProfile", - "-WindowStyle", - "Minimized", - "-InputFormat", - "None", - "-ExecutionPolicy", - "Bypass", - "-Command", - "Register-ScheduledTask -TaskName '%s' -Force -Action (New-ScheduledTaskAction -Execute '%s' -Argument '%s') -Trigger (New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(%d).ToString('HH:mm:ss'))" % (task_name, task_cmd, task_arg, delay_sec)], - shell=False, - startupinfo=startupinfo, - ) - - reply = { - "code": 0, - "content": "Restarting in %d seconds..." % delay_sec - } - - except subprocess.CalledProcessError: - reply = { - "code": -1, - "cmd": "error", - "error": "Error creating restart task." - } + elif cmd == "win_firefox_restart": + reply = win_firefox_restart(message) else: reply = {'cmd': 'error', 'error': 'Unhandled message'} From ef788a393f09c17858e10e42f61eb00f6a7ed96e Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Thu, 24 May 2018 13:11:02 +1000 Subject: [PATCH 03/12] Handle @bovine3dom's comments from #598 --- src/excmds.ts | 4 ++-- src/native_background.ts | 24 +++++++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 380e72b9..9b9a6164 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -404,8 +404,8 @@ export async function restart() { const profile = await Native.getProfileDir() if ((await browser.runtime.getPlatformInfo()).os === "win") { - let reply = await Native.winRestartFirefox(profile) - logger.info("[+] win_restart_firefox 'reply' = " + JSON.stringify(reply)) + let reply = await Native.winFirefoxRestart(profile) + logger.info("[+] win_firefox_restart 'reply' = " + JSON.stringify(reply)) if (Number(reply["code"]) === 0) { fillcmdline("#" + reply["content"]) qall() diff --git a/src/native_background.ts b/src/native_background.ts index 2fdf755c..d2730235 100644 --- a/src/native_background.ts +++ b/src/native_background.ts @@ -20,7 +20,7 @@ type MessageCommand = | "eval" | "getconfig" | "env" - | "win_restart_firefox" + | "win_firefox_restart" interface MessageResp { cmd: string version: number | null @@ -246,8 +246,15 @@ export async function temp(content: string, prefix: string) { return sendNativeMsg("temp", { content, prefix }) } -export async function winRestartFirefox(profiledir: string) { - return sendNativeMsg("win_restart_firefox", { profiledir }) +export async function winFirefoxRestart(profiledir: string) { + let current_version = await getNativeMessengerVersion() + let required_version = "0.1.6" + + if (!await nativegate(required_version, false)) { + throw `Error: 'restart' on Windows needs native messenger version >= 0.1.6. Current: ${current_version}` + } + + return sendNativeMsg("win_firefox_restart", { profiledir }) } export async function run(command: string) { @@ -276,7 +283,7 @@ export async function getenv(variable: string) { export async function ffargs(): Promise { // Using ' and + rather that ` because we don't want newlines if ((await browserBg.runtime.getPlatformInfo()).os === "win") { - return [] + throw `Error: "ffargs() is currently broken on Windows and should be avoided."` } else { let output = await pyeval( 'handleMessage({"cmd": "run", ' + @@ -296,10 +303,17 @@ export async function getProfileDir() { if ((await browserBg.runtime.getPlatformInfo()).os === "win") { let win_profiledir = config.get("profiledir") win_profiledir = win_profiledir.trim() + logger.info("[+] profiledir original: " + win_profiledir) + + win_profiledir = win_profiledir.replace(/\\/g, "/") + logger.info("[+] profiledir escaped: " + win_profiledir) + if (win_profiledir.length > 0) { return win_profiledir } else { - throw new Error("Profile directory not set.") + throw new Error( + "Profile directory is not set. Profile directory path(s) can be found by navigating to 'about:support'.", + ) } } From 00baee326f884b5e54acdbd07e0597f3f7c466c5 Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Thu, 24 May 2018 14:30:53 +1000 Subject: [PATCH 04/12] Fix "parent.lock" path and Firefox restart params --- native/native_main.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/native/native_main.py b/native/native_main.py index 5b0b784d..67442863 100755 --- a/native/native_main.py +++ b/native/native_main.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import sys -import os -import re import json +import os +import pathlib +import re import shutil import struct -import tempfile -import pathlib import subprocess +import sys +import tempfile import unicodedata VERSION = "0.1.6" @@ -129,6 +129,7 @@ def sanitizeFilename(fn): fn = re.sub('[-/\s]+', '-', fn) return fn + def is_valid_firefox_profile(profile_dir): is_valid = False validity_indicator = "times.json" @@ -177,7 +178,7 @@ def win_firefox_restart(message): # # - #subprocess.Popen( + # subprocess.Popen( # [ff_bin_path, "-profile", profile_dir], # shell=False, # creationflags=0x208 \ @@ -214,13 +215,13 @@ def win_firefox_restart(message): ff_bin_path = "\"%s\"" % shutil.which(ff_bin_name) if profile_dir == "auto": - ff_args = "\"-ProfileManager\"" ff_lock_path = ff_bin_path + ff_args = "\"-foreground -ProfileManager\"" else: - ff_args = "\"-profile\" \"%s\"" % profile_dir - ff_lock_path = "\"%s\\%s\"" % (profile_dir, - ff_lock_name) - + ff_lock_path = "\"%s/%s\"" % (profile_dir, + ff_lock_name) + ff_args = "\"-foreground -profile\" \"%s\"" % \ + profile_dir try: restart_ps1_content = '''$profileDir = "%s" From ef23e8904fcd1ea93680216410fee2afb2fcde4a Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Fri, 25 May 2018 13:43:09 +1000 Subject: [PATCH 05/12] Remove redundant getNativeMessengerVersion() calls --- src/native_background.ts | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/native_background.ts b/src/native_background.ts index d2730235..0cf3e40a 100644 --- a/src/native_background.ts +++ b/src/native_background.ts @@ -247,11 +247,10 @@ export async function temp(content: string, prefix: string) { } export async function winFirefoxRestart(profiledir: string) { - let current_version = await getNativeMessengerVersion() let required_version = "0.1.6" if (!await nativegate(required_version, false)) { - throw `Error: 'restart' on Windows needs native messenger version >= 0.1.6. Current: ${current_version}` + throw `'restart' on Windows needs native messenger version >= ${required_version}.` } return sendNativeMsg("win_firefox_restart", { profiledir }) @@ -271,10 +270,12 @@ export async function pyeval(command: string): Promise { } export async function getenv(variable: string) { - let v = await getNativeMessengerVersion() - if (!await nativegate("0.1.2", false)) { - throw `Error: getenv needs native messenger v>=0.1.2. Current: ${v}` + let required_version = "0.1.2" + + if (!await nativegate(required_version, false)) { + throw `'getenv' needs native messenger version >= ${required_version}.` } + return (await sendNativeMsg("env", { var: variable })).content } @@ -294,12 +295,18 @@ export async function ffargs(): Promise { } export async function getProfileDir() { - // Windows users must specify their Firefox profile directory - // via 'set profiledir [directory]'. Windows profile directory - // path must be properly escaped. For example, a correct way - // to set 'profiledir' on Windows is as shown below: + // Windows users must explicitly specify their Firefox profile + // directory via 'set profiledir [directory]', or use the + // default 'profiledir' value as 'auto' (without quotes). + // + // Profile directory paths on Windows must _not_ need be + // excaped, and used exactly as shown in the 'about:support' + // page. + // + // Example: + // + // :set profiledir C:\Users\\AppData\Roaming\Mozilla\Firefox\Profiles\8s21wzbh.Default // - // : set profiledir C:\\Users\\\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\8s21wzbh.Default if ((await browserBg.runtime.getPlatformInfo()).os === "win") { let win_profiledir = config.get("profiledir") win_profiledir = win_profiledir.trim() From d81f5032593994eb9e5f45ad58f8670c7655c3f3 Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Fri, 25 May 2018 14:06:29 +1000 Subject: [PATCH 06/12] Use `-new-tab` to activate Tridactyl on restart --- native/native_main.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/native/native_main.py b/native/native_main.py index 67442863..f0693b4c 100755 --- a/native/native_main.py +++ b/native/native_main.py @@ -213,15 +213,24 @@ def win_firefox_restart(message): # }}} ff_bin_path = "\"%s\"" % shutil.which(ff_bin_name) + new_tab_url = "https://www.mozilla.org/en-US/firefox/" if profile_dir == "auto": ff_lock_path = ff_bin_path - ff_args = "\"-foreground -ProfileManager\"" + ff_args = "\"%s\" \"%s\" \"%s\" \"%s\"" % \ + ("-foreground", + "-ProfileManager", + "-new-tab", + new_tab_url) else: ff_lock_path = "\"%s/%s\"" % (profile_dir, ff_lock_name) - ff_args = "\"-foreground -profile\" \"%s\"" % \ - profile_dir + ff_args = "\"%s\" \"%s\" \"%s\" \"%s\" \"%s\"" % \ + ("-foreground", + "-profile", + profile_dir, + "-new-tab", + new_tab_url) try: restart_ps1_content = '''$profileDir = "%s" From ecc451ae323f3e10f5c96b7eab90c328276ccfe5 Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Fri, 25 May 2018 14:47:33 +1000 Subject: [PATCH 07/12] Fix minor typo --- src/native_background.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/native_background.ts b/src/native_background.ts index 0cf3e40a..c015e380 100644 --- a/src/native_background.ts +++ b/src/native_background.ts @@ -295,13 +295,12 @@ export async function ffargs(): Promise { } export async function getProfileDir() { - // Windows users must explicitly specify their Firefox profile + // Windows users must explicitly set their Firefox profile // directory via 'set profiledir [directory]', or use the // default 'profiledir' value as 'auto' (without quotes). // - // Profile directory paths on Windows must _not_ need be - // excaped, and used exactly as shown in the 'about:support' - // page. + // Profile directory paths on Windows must _not_ be escaped, and + // should be used exactly as shown in the 'about:support' page. // // Example: // From 93ca6c50a694bbaf6d093c37398856d10010ee9c Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Sat, 26 May 2018 10:17:54 +1000 Subject: [PATCH 08/12] Fix `:restart` under restricted PowerShell --- native/native_main.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/native/native_main.py b/native/native_main.py index f0693b4c..6f3f1a46 100755 --- a/native/native_main.py +++ b/native/native_main.py @@ -252,10 +252,12 @@ try { } } if ($locked -eq $true) { -Add-Type -AssemblyName System.Windows.Forms -[System.Windows.MessageBox]::Show( - "Restarting Firefox failed. Please manually restart.", - "Tridactyl") +$errorMsg = "Restarting Firefox failed. Please restart manually." +Write-Host "$errorMsg" +# Add-Type -AssemblyName System.Windows.Forms +# [System.Windows.MessageBox]::Show( +# $errorMsg, +# "Tridactyl") } else { Write-Host "[+] Restarting Firefox ..." & %s %s @@ -263,16 +265,24 @@ Write-Host "[+] Restarting Firefox ..." ''' % (profile_dir, ff_lock_path, ff_bin_path, ff_args) delay_sec = 1 - task_cmd = "powershell" task_name = "firefox-restart" native_messenger_dirname = ".tridactyl" - restart_ps1_path = "%s\\%s\\%s" % ( - os.path.expanduser('~'), - native_messenger_dirname, - "win_firefox_restart.ps1") + powershell_cmd = "powershell" + powershell_args = "%s %s" % \ + ("-NoProfile", + "-ExecutionPolicy Bypass") - task_arg = "\"%s\"" % restart_ps1_path + restart_ps1_path = "%s\\%s\\%s" % \ + (os.path.expanduser('~'), + native_messenger_dirname, + "win_firefox_restart.ps1") + + task_cmd = "cmd" + task_arg = "/c \"%s %s -File %s\"" % \ + (powershell_cmd, + powershell_args, + restart_ps1_path) open(restart_ps1_path, "w+").write(restart_ps1_content) From 9d5ce4050556914479d1b684567e549d4665f92f Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Sat, 26 May 2018 23:28:08 +1000 Subject: [PATCH 09/12] Remove `-ProfileManager` and `-new-tab` during Firefox restart --- native/native_main.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/native/native_main.py b/native/native_main.py index 6f3f1a46..d96257ff 100755 --- a/native/native_main.py +++ b/native/native_main.py @@ -213,24 +213,18 @@ def win_firefox_restart(message): # }}} ff_bin_path = "\"%s\"" % shutil.which(ff_bin_name) - new_tab_url = "https://www.mozilla.org/en-US/firefox/" if profile_dir == "auto": ff_lock_path = ff_bin_path - ff_args = "\"%s\" \"%s\" \"%s\" \"%s\"" % \ - ("-foreground", - "-ProfileManager", - "-new-tab", - new_tab_url) + ff_args = "\"%s\"" % \ + ("-foreground") else: ff_lock_path = "\"%s/%s\"" % (profile_dir, ff_lock_name) - ff_args = "\"%s\" \"%s\" \"%s\" \"%s\" \"%s\"" % \ + ff_args = "\"%s\" \"%s\" \"%s\"" % \ ("-foreground", "-profile", - profile_dir, - "-new-tab", - new_tab_url) + profile_dir) try: restart_ps1_content = '''$profileDir = "%s" From 47d247aff7ff71f935bac27ec88247fe89741c1d Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Mon, 28 May 2018 00:32:30 +1000 Subject: [PATCH 10/12] Fix restart on Windows without needing Firefox on %PATH% --- native/gen_native_message.py | 96 +++++++++-------- native/native_main.py | 202 +++++++++++++++++++++++------------ src/excmds.ts | 7 +- src/native_background.ts | 7 +- 4 files changed, 194 insertions(+), 118 deletions(-) diff --git a/native/gen_native_message.py b/native/gen_native_message.py index 2d72654b..19ffa83c 100644 --- a/native/gen_native_message.py +++ b/native/gen_native_message.py @@ -1,45 +1,51 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import os -import sys -import json -import struct - - -def usage(): - """Show usage and exit with non-zero status.""" - sys.stderr.write("[+] Usage: %s cmd [command] | %s\n" % - (os.path.basename(__file__), - "native_main.py")) - - sys.stderr.write("\nNote: Use '##' as key-value separator") - sys.stderr.write("\nExample: %s %s %s | %s" % - (os.path.basename(__file__), - 'win_restart_firefox', - 'profiledir##auto', - 'native_main.py')) - - exit(-1) - - -if __name__ == "__main__": - """Main functionalities are here for now.""" - msg = dict() - if len(sys.argv) > 1: - msg["cmd"] = sys.argv[1] - if len(sys.argv) > 2: - key = sys.argv[2].strip().split("##")[0] - val = sys.argv[2].strip().split("##")[1] - msg[key] = val - - if len(sys.argv) == 1: - usage() - - msg = json.dumps(msg) - msg = "\r\n" + msg + "\r\n" - msg = msg.encode("utf-8") - packed_len = struct.pack("@I", len(msg)) - - sys.stdout.buffer.write(packed_len + msg) - sys.stdout.flush() +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import json +import struct + + +def usage(): + """Show usage and exit with non-zero status.""" + sys.stderr.write( + "\n[+] Usage: %s cmd [command] | %s\n" + % (os.path.basename(__file__), "native_main.py") + ) + + sys.stderr.write("\n - Note: Use '..' as key-value separator") + sys.stderr.write( + "\n - Example: %s %s %s %s | %s\n" + % ( + os.path.basename(__file__), + "cmd..win_restart_firefox", + "profiledir..auto", + "browser..firefox", + "native_main.py", + ) + ) + + exit(-1) + + +if __name__ == "__main__": + """Main functionalities are here for now.""" + separator = ".." + msg = dict() + if len(sys.argv) > 1: + for i in range(1, len(sys.argv)): + key = sys.argv[i].strip().split(separator)[0] + val = sys.argv[i].strip().split(separator)[1] + msg[key] = val + + if len(sys.argv) == 1: + usage() + + msg = json.dumps(msg) + msg = "\r\n" + msg + "\r\n" + msg = msg.encode("utf-8") + packed_len = struct.pack("@I", len(msg)) + + sys.stdout.buffer.write(packed_len + msg) + sys.stdout.flush() diff --git a/native/native_main.py b/native/native_main.py index d96257ff..44596843 100755 --- a/native/native_main.py +++ b/native/native_main.py @@ -10,8 +10,10 @@ import struct import subprocess import sys import tempfile +import time import unicodedata +DEBUG = False VERSION = "0.1.6" @@ -146,26 +148,46 @@ def is_valid_firefox_profile(profile_dir): def win_firefox_restart(message): """Handle 'win_firefox_restart' message.""" reply = {} - profile_dir = message["profiledir"].strip() + profile_dir = None + browser_cmd = None - ff_bin_name = "firefox.exe" - ff_lock_name = "parent.lock" - - if profile_dir != "auto" \ - and not is_valid_firefox_profile(profile_dir): + try: + profile_dir = message["profiledir"].strip() + browser_cmd = message["browsercmd"].strip() + except KeyError: reply = { + "code": -1, "cmd": "error", - "error": "%s %s %s" % ( + "error": "Error parsing 'restart' message.", + } + return reply + + if ( + profile_dir + and profile_dir != "auto" + and not is_valid_firefox_profile(profile_dir) + ): + reply = { + "code": -1, + "cmd": "error", + "error": "%s %s %s" + % ( "Invalid profile directory specified.", "Vaild profile directory path(s) can be found by", - "navigating to 'about:support'." - ) + "navigating to 'about:support'.", + ), } - elif not is_command_on_path(ff_bin_name): + elif browser_cmd and not is_command_on_path(browser_cmd): reply = { + "code": -1, "cmd": "error", - "error": "firefox.exe is not found on %PATH%." + "error": "%s %s %s" + % ( + "'{0}' wasn't found on %PATH%.".format(browser_cmd), + "Please set valid browser by", + "'set browser [browser-command]'.", + ), } else: @@ -212,71 +234,93 @@ def win_firefox_restart(message): # shell=True) # }}} - ff_bin_path = "\"%s\"" % shutil.which(ff_bin_name) + ff_lock_name = "parent.lock" + + ff_bin_name = browser_cmd + ff_bin_path = '"%s"' % shutil.which(ff_bin_name) + + ff_bin_dir = '"%s"' % str( + pathlib.WindowsPath(shutil.which(ff_bin_name)).parent + ) if profile_dir == "auto": ff_lock_path = ff_bin_path - ff_args = "\"%s\"" % \ - ("-foreground") + ff_args = '"%s"' % ("-foreground") else: - ff_lock_path = "\"%s/%s\"" % (profile_dir, - ff_lock_name) - ff_args = "\"%s\" \"%s\" \"%s\"" % \ - ("-foreground", - "-profile", - profile_dir) + ff_lock_path = '"%s/%s"' % (profile_dir, ff_lock_name) + ff_args = '"%s","%s","%s"' % ( + "-foreground", + "-profile", + profile_dir, + ) try: - restart_ps1_content = '''$profileDir = "%s" -if ($profileDir -ne "auto") { - $lockFilePath = %s + restart_ps1_content = """ +$env:PATH=$env:PATH;{ff_bin_dir} +Set-Location -Path {ff_bin_dir} +$profileDir = "{profile_dir}" +if ($profileDir -ne "auto") {{ + $lockFilePath = {ff_lock_path} $locked = $true $num_try = 10 -} else { +}} else {{ $locked = $false -} -while (($locked -eq $true) -and ($num_try -gt 0)) { -try { +}} +while (($locked -eq $true) -and ($num_try -gt 0)) {{ +try {{ [IO.File]::OpenWrite($lockFilePath).close() - $locked=$fals -} catch{ + $locked=$false +}} catch {{ $num_try-=1 Write-Host "[+] Trial: $num_try [lock == true]" Start-Sleep -Seconds 1 -} -} -if ($locked -eq $true) { +}} +}} +if ($locked -eq $true) {{ $errorMsg = "Restarting Firefox failed. Please restart manually." Write-Host "$errorMsg" # Add-Type -AssemblyName System.Windows.Forms # [System.Windows.MessageBox]::Show( # $errorMsg, # "Tridactyl") -} else { +}} else {{ Write-Host "[+] Restarting Firefox ..." -& %s %s -} -''' % (profile_dir, ff_lock_path, ff_bin_path, ff_args) +Start-Process ` + -WorkingDirectory {ff_bin_dir} ` + -FilePath {ff_bin_path} ` + -ArgumentList {ff_args} ` + -WindowStyle Normal +}} +""".format( + ff_bin_dir=ff_bin_dir, + profile_dir=profile_dir, + ff_lock_path=ff_lock_path, + ff_bin_path=ff_bin_path, + ff_args=ff_args, + ) - delay_sec = 1 + delay_sec = 1.5 task_name = "firefox-restart" native_messenger_dirname = ".tridactyl" powershell_cmd = "powershell" - powershell_args = "%s %s" % \ - ("-NoProfile", - "-ExecutionPolicy Bypass") + powershell_args = "%s %s" % ( + "-NoProfile", + "-ExecutionPolicy Bypass", + ) - restart_ps1_path = "%s\\%s\\%s" % \ - (os.path.expanduser('~'), - native_messenger_dirname, - "win_firefox_restart.ps1") + restart_ps1_path = "%s\\%s\\%s" % ( + os.path.expanduser("~"), + native_messenger_dirname, + "win_firefox_restart.ps1", + ) task_cmd = "cmd" - task_arg = "/c \"%s %s -File %s\"" % \ - (powershell_cmd, - powershell_args, - restart_ps1_path) + task_arg = '/c "%s %s -File %s"' % ( + powershell_cmd, + powershell_args, + restart_ps1_path, + ) open(restart_ps1_path, "w+").write(restart_ps1_content) @@ -284,17 +328,18 @@ Write-Host "[+] Restarting Firefox ..." startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW subprocess.check_output( - ["powershell", - "-NonInteractive", - "-NoProfile", - "-WindowStyle", - "Minimized", - "-InputFormat", - "None", - "-ExecutionPolicy", - "Bypass", - "-Command", - "Register-ScheduledTask \ + [ + "powershell", + "-NonInteractive", + "-NoProfile", + "-WindowStyle", + "Minimized", + "-InputFormat", + "None", + "-ExecutionPolicy", + "Bypass", + "-Command", + "Register-ScheduledTask \ -TaskName '%s' \ -Force \ -Action (New-ScheduledTaskAction \ @@ -303,32 +348,53 @@ Write-Host "[+] Restarting Firefox ..." -Trigger (New-ScheduledTaskTrigger \ -Once \ -At \ - (Get-Date).AddSeconds(%d).ToString(\ - 'HH:mm:ss'))" % (task_name, - task_cmd, - task_arg, - delay_sec) - ], shell=False, startupinfo=startupinfo) + (Get-Date).AddSeconds(%d).ToString('HH:mm:ss'))" + % (task_name, task_cmd, task_arg, delay_sec), + ], + shell=False, + startupinfo=startupinfo, + ) reply = { "code": 0, - "content": "Restarting in %d seconds..." % delay_sec + "content": "Restarting in %d seconds..." + % delay_sec, } except subprocess.CalledProcessError: reply = { "code": -1, "cmd": "error", - "error": "Error creating restart task." + "error": "error creating restart task.", } return reply +def write_log(msg): + debug_log_dirname = ".tridactyl" + debug_log_filename = "native_main.log" + + debug_log_path = "%s\\%s\\%s" % ( + os.path.expanduser("~"), + debug_log_dirname, + debug_log_filename, + ) + + open(debug_log_path, "a+").write(msg) + + def handleMessage(message): """ Generate reply from incoming message. """ cmd = message["cmd"] - reply = {'cmd': cmd} + reply = {"cmd": cmd} + + if DEBUG: + msg = "%s %s\n" % ( + time.strftime("%H:%M:%S %p", time.localtime()), + str(message), + ) + write_log(msg) if cmd == 'version': reply = {'version': VERSION} diff --git a/src/excmds.ts b/src/excmds.ts index 9b9a6164..ec001b65 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -401,10 +401,11 @@ export async function updatenative(interactive = true) { */ //#background export async function restart() { - const profile = await Native.getProfileDir() + const profiledir = await Native.getProfileDir() + const browsercmd = await config.get("browser") if ((await browser.runtime.getPlatformInfo()).os === "win") { - let reply = await Native.winFirefoxRestart(profile) + let reply = await Native.winFirefoxRestart(profiledir, browsercmd) logger.info("[+] win_firefox_restart 'reply' = " + JSON.stringify(reply)) if (Number(reply["code"]) === 0) { fillcmdline("#" + reply["content"]) @@ -415,7 +416,7 @@ export async function restart() { } else { const firefox = (await Native.ffargs()).join(" ") // Wait for the lock to disappear, then wait a bit more, then start firefox - Native.run(`while readlink ${profile}/lock ; do sleep 1 ; done ; sleep 1 ; ${firefox}`) + Native.run(`while readlink ${profiledir}/lock ; do sleep 1 ; done ; sleep 1 ; ${firefox}`) qall() } } diff --git a/src/native_background.ts b/src/native_background.ts index c015e380..d5968bae 100644 --- a/src/native_background.ts +++ b/src/native_background.ts @@ -246,14 +246,17 @@ export async function temp(content: string, prefix: string) { return sendNativeMsg("temp", { content, prefix }) } -export async function winFirefoxRestart(profiledir: string) { +export async function winFirefoxRestart( + profiledir: string, + browsercmd: string, +) { let required_version = "0.1.6" if (!await nativegate(required_version, false)) { throw `'restart' on Windows needs native messenger version >= ${required_version}.` } - return sendNativeMsg("win_firefox_restart", { profiledir }) + return sendNativeMsg("win_firefox_restart", { profiledir, browsercmd }) } export async function run(command: string) { From 75f21da3846dff7b6e059733119f0976c0c0817b Mon Sep 17 00:00:00 2001 From: Babil Golam Sarwar Date: Mon, 28 May 2018 09:04:01 +1000 Subject: [PATCH 11/12] Add consistent Python formatting --- native/gen_native_message.py | 102 +++++++++++++++++------------------ native/native_main.py | 101 ++++++++++++++++++---------------- 2 files changed, 107 insertions(+), 96 deletions(-) diff --git a/native/gen_native_message.py b/native/gen_native_message.py index 19ffa83c..0071eeb6 100644 --- a/native/gen_native_message.py +++ b/native/gen_native_message.py @@ -1,51 +1,51 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import os -import sys -import json -import struct - - -def usage(): - """Show usage and exit with non-zero status.""" - sys.stderr.write( - "\n[+] Usage: %s cmd [command] | %s\n" - % (os.path.basename(__file__), "native_main.py") - ) - - sys.stderr.write("\n - Note: Use '..' as key-value separator") - sys.stderr.write( - "\n - Example: %s %s %s %s | %s\n" - % ( - os.path.basename(__file__), - "cmd..win_restart_firefox", - "profiledir..auto", - "browser..firefox", - "native_main.py", - ) - ) - - exit(-1) - - -if __name__ == "__main__": - """Main functionalities are here for now.""" - separator = ".." - msg = dict() - if len(sys.argv) > 1: - for i in range(1, len(sys.argv)): - key = sys.argv[i].strip().split(separator)[0] - val = sys.argv[i].strip().split(separator)[1] - msg[key] = val - - if len(sys.argv) == 1: - usage() - - msg = json.dumps(msg) - msg = "\r\n" + msg + "\r\n" - msg = msg.encode("utf-8") - packed_len = struct.pack("@I", len(msg)) - - sys.stdout.buffer.write(packed_len + msg) - sys.stdout.flush() +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import json +import struct + + +def usage(): + """Show usage and exit with non-zero status.""" + sys.stderr.write( + "\n[+] Usage: %s cmd [command] | %s\n" + % (os.path.basename(__file__), "native_main.py") + ) + + sys.stderr.write("\n - Note: Use '..' as key-value separator") + sys.stderr.write( + "\n - Example: %s %s %s %s | %s\n" + % ( + os.path.basename(__file__), + "cmd..win_restart_firefox", + "profiledir..auto", + "browser..firefox", + "native_main.py", + ) + ) + + exit(-1) + + +if __name__ == "__main__": + """Main functionalities are here for now.""" + separator = ".." + msg = dict() + if len(sys.argv) > 1: + for i in range(1, len(sys.argv)): + key = sys.argv[i].strip().split(separator)[0] + val = sys.argv[i].strip().split(separator)[1] + msg[key] = val + + if len(sys.argv) == 1: + usage() + + msg = json.dumps(msg) + msg = "\r\n" + msg + "\r\n" + msg = msg.encode("utf-8") + packed_len = struct.pack("@I", len(msg)) + + sys.stdout.buffer.write(packed_len + msg) + sys.stdout.flush() diff --git a/native/native_main.py b/native/native_main.py index 44596843..941d9c86 100755 --- a/native/native_main.py +++ b/native/native_main.py @@ -55,8 +55,8 @@ def getMessage(): rawLength = sys.stdin.buffer.read(4) if len(rawLength) == 0: sys.exit(0) - messageLength = struct.unpack('@I', rawLength)[0] - message = sys.stdin.buffer.read(messageLength).decode('utf-8') + messageLength = struct.unpack("@I", rawLength)[0] + message = sys.stdin.buffer.read(messageLength).decode("utf-8") return json.loads(message) @@ -64,18 +64,18 @@ def getMessage(): # given its content. def encodeMessage(messageContent): """ Encode a message for transmission, given its content.""" - encodedContent = json.dumps(messageContent).encode('utf-8') - encodedLength = struct.pack('@I', len(encodedContent)) - return {'length': encodedLength, 'content': encodedContent} + encodedContent = json.dumps(messageContent).encode("utf-8") + encodedLength = struct.pack("@I", len(encodedContent)) + return {"length": encodedLength, "content": encodedContent} # Send an encoded message to stdout def sendMessage(encodedMessage): """ Send an encoded message to stdout.""" - sys.stdout.buffer.write(encodedMessage['length']) - sys.stdout.buffer.write(encodedMessage['content']) + sys.stdout.buffer.write(encodedMessage["length"]) + sys.stdout.buffer.write(encodedMessage["content"]) try: - sys.stdout.buffer.write(encodedMessage['code']) + sys.stdout.buffer.write(encodedMessage["code"]) except KeyError: pass @@ -86,13 +86,15 @@ def findUserConfigFile(): """ Find a user config file, if it exists. Return the file path, or None if not found """ - home = os.path.expanduser('~') - config_dir = getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) + home = os.path.expanduser("~") + config_dir = getenv( + "XDG_CONFIG_HOME", os.path.expanduser("~/.config") + ) # Will search for files in this order candidate_files = [ os.path.join(config_dir, "tridactyl", "tridactylrc"), - os.path.join(home, '.tridactylrc') + os.path.join(home, ".tridactylrc"), ] config_path = None @@ -116,7 +118,7 @@ def getUserConfig(): # for now, this is a simple file read, but if the files can # include other files, that will need more work - return open(cfg_file, 'r').read() + return open(cfg_file, "r").read() def sanitizeFilename(fn): @@ -124,11 +126,14 @@ def sanitizeFilename(fn): From https://stackoverflow.com/a/295466/147356""" - fn = unicodedata.normalize('NFKD', fn).encode( - 'ascii', 'ignore').decode('ascii') - fn = re.sub('[^\w\s/.-]', '', fn).strip().lower() - fn = re.sub('\.\.+', '', fn) - fn = re.sub('[-/\s]+', '-', fn) + fn = ( + unicodedata.normalize("NFKD", fn) + .encode("ascii", "ignore") + .decode("ascii") + ) + fn = re.sub("[^\w\s/.-]", "", fn).strip().lower() + fn = re.sub("\.\.+", "", fn) + fn = re.sub("[-/\s]+", "-", fn) return fn @@ -396,17 +401,17 @@ def handleMessage(message): ) write_log(msg) - if cmd == 'version': - reply = {'version': VERSION} + if cmd == "version": + reply = {"version": VERSION} - elif cmd == 'getconfig': + elif cmd == "getconfig": file_content = getUserConfig() if file_content: - reply['content'] = file_content + reply["content"] = file_content else: - reply['code'] = 'File not found' + reply["code"] = "File not found" - elif cmd == 'run': + elif cmd == "run": commands = message["command"] try: @@ -418,50 +423,56 @@ def handleMessage(message): reply["code"] = process.returncode reply["content"] = process.output.decode("utf-8") - elif cmd == 'eval': + elif cmd == "eval": output = eval(message["command"]) - reply['content'] = output + reply["content"] = output - elif cmd == 'read': + elif cmd == "read": try: - with open(os.path.expandvars(os.path.expanduser(message["file"])), "r") as file: - reply['content'] = file.read() - reply['code'] = 0 + with open( + os.path.expandvars( + os.path.expanduser(message["file"]) + ), + "r", + ) as file: + reply["content"] = file.read() + reply["code"] = 0 except FileNotFoundError: - reply['content'] = "" - reply['code'] = 2 + reply["content"] = "" + reply["code"] = 2 - elif cmd == 'mkdir': + elif cmd == "mkdir": os.makedirs( - os.path.relpath(message["dir"]), exist_ok=message["exist_ok"] + os.path.relpath(message["dir"]), + exist_ok=message["exist_ok"], ) - reply['content'] = "" - reply['code'] = 0 + reply["content"] = "" + reply["code"] = 0 - elif cmd == 'write': + elif cmd == "write": with open(message["file"], "w") as file: file.write(message["content"]) - elif cmd == 'temp': - prefix = message.get('prefix') + elif cmd == "temp": + prefix = message.get("prefix") if prefix is None: - prefix = '' - prefix = 'tmp_{}_'.format(sanitizeFilename(prefix)) + prefix = "" + prefix = "tmp_{}_".format(sanitizeFilename(prefix)) (handle, filepath) = tempfile.mkstemp(prefix=prefix) with os.fdopen(handle, "w") as file: file.write(message["content"]) - reply['content'] = filepath + reply["content"] = filepath - elif cmd == 'env': - reply['content'] = getenv(message["var"], "") + elif cmd == "env": + reply["content"] = getenv(message["var"], "") elif cmd == "win_firefox_restart": reply = win_firefox_restart(message) else: - reply = {'cmd': 'error', 'error': 'Unhandled message'} - eprint('Unhandled message: {}'.format(message)) + reply = {"cmd": "error", "error": "Unhandled message"} + eprint("Unhandled message: {}".format(message)) return reply From 173e4fe896553a61452e2a4c861123c1494a1e66 Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 30 May 2018 11:25:05 +0100 Subject: [PATCH 12/12] Make gen_native_message executable --- native/gen_native_message.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 native/gen_native_message.py diff --git a/native/gen_native_message.py b/native/gen_native_message.py old mode 100644 new mode 100755