├── .gitignore ├── res ├── history.gif ├── last-tab.gif ├── open-tab.gif └── same-page.gif ├── host ├── io.github.tcode2k16.rofi.chrome.firefox.json ├── io.github.tcode2k16.rofi.chrome.chromium-browser.json ├── io.github.tcode2k16.rofi.chrome.google-chrome.json └── main.py ├── extension ├── manifest.json └── bg.js ├── scripts ├── uninstall.sh └── install.sh └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | host/.log 2 | host/log-file 3 | *.mp4 4 | *.pem 5 | *.crx -------------------------------------------------------------------------------- /res/history.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcode2k16/rofi-chrome/HEAD/res/history.gif -------------------------------------------------------------------------------- /res/last-tab.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcode2k16/rofi-chrome/HEAD/res/last-tab.gif -------------------------------------------------------------------------------- /res/open-tab.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcode2k16/rofi-chrome/HEAD/res/open-tab.gif -------------------------------------------------------------------------------- /res/same-page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcode2k16/rofi-chrome/HEAD/res/same-page.gif -------------------------------------------------------------------------------- /host/io.github.tcode2k16.rofi.chrome.firefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "io.github.tcode2k16.rofi.chrome", 3 | "description": "rofi chrome", 4 | "path": "HOST_PATH", 5 | "type": "stdio", 6 | "allowed_extensions": [ "rofi_chrome@tcode2k16.github.io" ] 7 | } 8 | -------------------------------------------------------------------------------- /host/io.github.tcode2k16.rofi.chrome.chromium-browser.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "io.github.tcode2k16.rofi.chrome", 3 | "description": "rofi chrome", 4 | "path": "HOST_PATH", 5 | "type": "stdio", 6 | "allowed_origins": [ 7 | "chrome-extension://aocepclkpgckjeikiphffdlileoaceec/" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /host/io.github.tcode2k16.rofi.chrome.google-chrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "io.github.tcode2k16.rofi.chrome", 3 | "description": "rofi chrome", 4 | "path": "HOST_PATH", 5 | "type": "stdio", 6 | "allowed_origins": [ 7 | "chrome-extension://aocepclkpgckjeikiphffdlileoaceec/" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | // id: aocepclkpgckjeikiphffdlileoaceec 3 | "name": "rofi-chrome", 4 | "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuHwUuBKUt9Ac7Sge8cHpFEkiX3wn/yTiVY9lPzRuK6D/9BwqMSydTXsaHTHjl/3FV7m1fmL338itD2ilR3e5m/8XnYowL6QoBVJAeHIWylmEE3/XKVjn1ETd0QnXj/3euG2Z+oWsVcWhhxfflOPErm+tO+LrFINwkRWire8Wdz4sCugYlPUL95dCRlRYwAcyc4i+9y1AB7meCDHDYeMc1arm9Aad0ePqGCulJlFH2ocq5LRkrXacmrGVohAhslKo3TBtvXaWZRlHDce6VhWL2C/G81EHNDOLlNnhPTQPjf1p5pBVxl10RVAJWvOCxKY74mLIM5ZG5Qb4b0C9lpVzyQIDAQAB", 5 | "version": "1.0", 6 | "description": "rofi with chrome", 7 | "manifest_version": 2, 8 | "permissions": [ 9 | "activeTab", "nativeMessaging", "tabs", "history", "bookmarks", "storage", "sessions", "downloads", "topSites", "downloads.shelf", "clipboardRead", "clipboardWrite", "webNavigation" 10 | ], 11 | "background": { 12 | "persistant": false, 13 | "scripts": [ "bg.js" ] 14 | }, 15 | "browser_specific_settings": { 16 | "gecko": { 17 | "id": "rofi_chrome@tcode2k16.github.io" 18 | } 19 | }, 20 | "commands": { 21 | "switchTab": { 22 | "description": "switch to another tab" 23 | }, 24 | "openHistory": { 25 | "description": "open tab from history" 26 | }, 27 | "pageFunc": { 28 | "description": "page-dependent search" 29 | }, 30 | "goLastTab": { 31 | "description": "go to last tab" 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /host/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | 3 | import struct 4 | import sys 5 | import json 6 | import subprocess 7 | 8 | def send_message(message): 9 | sys.stdout.write(struct.pack('I', len(message))) 10 | sys.stdout.write(message) 11 | sys.stdout.flush() 12 | 13 | def log(msg): 14 | pass 15 | 16 | def call_rofi(param): 17 | options = param['opts'] 18 | rofi_opts = ['rofi', '-dmenu'] 19 | if 'rofi-opts' in param: 20 | rofi_opts.extend(param['rofi-opts']) 21 | 22 | sh = subprocess.Popen(rofi_opts, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 23 | sh.stdin.write('\n'.join(map(lambda x: x.encode('utf8'), options))) 24 | sh.stdin.flush() 25 | sh.stdin.close() 26 | 27 | return sh.stdout.read().strip() 28 | 29 | def main(): 30 | log('launched') 31 | while True: 32 | data_length_bytes = sys.stdin.read(4) 33 | 34 | if len(data_length_bytes) == 0: 35 | break 36 | 37 | data_length = struct.unpack('i', data_length_bytes)[0] 38 | data = sys.stdin.read(data_length).decode('utf-8') 39 | data = json.loads(data) 40 | log(data) 41 | 42 | cmd = data['cmd'] 43 | param = data['param'] 44 | info = data['info'] 45 | if cmd == 'dmenu': 46 | output = { 47 | 'cmd': 'dmenu', 48 | 'result': call_rofi(param), 49 | 'info': info 50 | } 51 | else: 52 | output = { 53 | 'result': 'unknow command: {}'.foramt(cmd) 54 | } 55 | send_message(json.dumps(output)) 56 | 57 | 58 | sys.exit(0) 59 | 60 | 61 | if __name__ == '__main__': 62 | main() 63 | -------------------------------------------------------------------------------- /scripts/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | OS="$(uname -s)" 5 | NAME="io.github.tcode2k16.rofi.chrome" 6 | SOURCE_DIR="$(cd "$(dirname "$0")" && pwd)" 7 | SOURCE_DIR="$SOURCE_DIR/../host" 8 | 9 | # colors 10 | none='\033[0m' 11 | bold='\033[1m' 12 | red='\033[31m' 13 | green='\033[32m' 14 | yellow='\033[33m' 15 | blue='\033[34m' 16 | magenta='\033[35m' 17 | cyan='\033[36m' 18 | 19 | main() { 20 | 21 | print_horizontal_line 22 | 23 | # OS 24 | case "$OS" in 25 | Linux) 26 | if command -v "google-chrome" >/dev/null 2>&1; then 27 | on_key 'Uninstall for google-chrome? (y/n)' 28 | if test "$key" = 'y'; then 29 | browser_uninstall "$HOME/.config/google-chrome/NativeMessagingHosts" "google-chrome" 30 | fi 31 | fi 32 | 33 | if command -v "chromium-browser" >/dev/null 2>&1; then 34 | on_key 'Uninstall for chromium-browser? (y/n)' 35 | if test "$key" = 'y'; then 36 | browser_uninstall "$HOME/.config/chromium/NativeMessagingHosts" "chromium-browser" 37 | fi 38 | fi 39 | 40 | if command -v "firefox" >/dev/null 2>&1; then 41 | on_key 'Uninstall for firefox? (y/n)' 42 | if test "$key" = 'y'; then 43 | browser_uninstall "$HOME/.mozilla/native-messaging-hosts" "firefox" 44 | fi 45 | fi 46 | ;; 47 | *) 48 | printf "${red}Error${none} %s is not currently supported" "$OS" 49 | ;; 50 | esac 51 | 52 | } 53 | 54 | browser_uninstall() { 55 | TARGET_DIR=$1 56 | browser_name=$2 57 | config_file="$TARGET_DIR/$NAME.json" 58 | if test -f "$config_file"; then 59 | rm "$config_file" 60 | fi 61 | 62 | printf "❯ ${green}Successfully uninstalled for %s ${none}\n" "$browser_name" 63 | } 64 | 65 | # Helpers 66 | 67 | on_key() { 68 | prompt=$1 69 | printf "${blue}❯${none} $prompt\n" 70 | read -n 1 key /dev/null 2>&1; then 38 | on_key 'Install for google-chrome? (y/n)' 39 | if test "$key" = 'y'; then 40 | browser_install "$HOME/.config/google-chrome/NativeMessagingHosts" "google-chrome" 41 | fi 42 | fi 43 | 44 | if command -v "chromium-browser" >/dev/null 2>&1; then 45 | on_key 'Install for chromium-browser? (y/n)' 46 | if test "$key" = 'y'; then 47 | browser_install "$HOME/.config/chromium/NativeMessagingHosts" "chromium-browser" 48 | fi 49 | fi 50 | 51 | if command -v "firefox" >/dev/null 2>&1; then 52 | on_key 'Install for firefox? (y/n)' 53 | if test "$key" = 'y'; then 54 | browser_install "$HOME/.mozilla/native-messaging-hosts" "firefox" 55 | fi 56 | fi 57 | ;; 58 | *) 59 | printf "${red}Error${none} %s is not currently supported" "$OS" 60 | ;; 61 | esac 62 | 63 | } 64 | 65 | browser_install() { 66 | TARGET_DIR=$1 67 | browser_name=$2 68 | 69 | mkdir -p "$TARGET_DIR" 70 | 71 | cp "$SOURCE_DIR/$NAME.$browser_name.json" "$TARGET_DIR/$NAME.json" 72 | 73 | HOST_PATH="$SOURCE_DIR/main.py" 74 | ESCAPED_HOST_PATH=${HOST_PATH////\\/} 75 | sed -i -e "s/HOST_PATH/$ESCAPED_HOST_PATH/" "$TARGET_DIR/$NAME.json" 76 | 77 | 78 | chmod o+r "$TARGET_DIR/$NAME.json" 79 | printf "❯ ${green}Successfully installed for %s ${none}\n" "$browser_name" 80 | 81 | } 82 | 83 | # Helpers 84 | 85 | on_key() { 86 | prompt=$1 87 | printf "${blue}❯${none} $prompt\n" 88 | read -n 1 key /dev/null 2>&1; then 109 | printf "❯ ${green}%s${none}\n" "$name" 110 | else 111 | printf "❯ ${red}%s${none}\n" "$name" >/dev/stderr 112 | printf 'Please install %s\n' "$name" >/dev/stderr 113 | printf '%s\n' "$url" >/dev/stderr 114 | if test "$optional" != yes; then 115 | exit 1 116 | fi 117 | fi 118 | } 119 | 120 | main "$@" 121 | -------------------------------------------------------------------------------- /extension/bg.js: -------------------------------------------------------------------------------- 1 | /*** data ***/ 2 | 3 | const HOST_NAME = 'io.github.tcode2k16.rofi.chrome'; 4 | 5 | let state = { 6 | port: null, 7 | lastTabId: [0, 0], 8 | }; 9 | 10 | /*** utils ***/ 11 | 12 | function goToTab(id) { 13 | chrome.tabs.get(id, function (tabInfo) { 14 | chrome.windows.update(tabInfo.windowId, { focused: true }, function () { 15 | chrome.tabs.update(id, { active: true, highlighted: true }); 16 | }); 17 | }); 18 | } 19 | 20 | function openUrlInNewTab(url) { 21 | chrome.tabs.create({ url }); 22 | } 23 | 24 | function refreshHistory(callback) { 25 | chrome.history.search({ 26 | text: '', 27 | startTime: 0, 28 | maxResults: 2147483647, 29 | }, function (results) { 30 | callback(results); 31 | }); 32 | } 33 | 34 | /*** commands ***/ 35 | 36 | const CMDS = { 37 | switchTab() { 38 | chrome.tabs.query({}, function (tabs) { 39 | state.port.postMessage({ 40 | 'cmd': 'dmenu', 41 | 'info': 'switchTab', 42 | 'param': { 43 | 'rofi-opts': ['-i', '-p', 'tab'], 44 | 'opts': tabs.map(e => (e.id) + ': ' + e.title + ' ::: ' + e.url), 45 | } 46 | }); 47 | }); 48 | }, 49 | 50 | openHistory() { 51 | refreshHistory(function (results) { 52 | state.port.postMessage({ 53 | 'cmd': 'dmenu', 54 | 'info': 'openHistory', 55 | 'param': { 56 | 'rofi-opts': ['-matching', 'normal', '-i', '-p', 'history'], 57 | 'opts': results.map(e => e.title + ' ::: ' + e.url), 58 | } 59 | }); 60 | }); 61 | }, 62 | 63 | goLastTab() { 64 | goToTab(state.lastTabId[1]); 65 | }, 66 | 67 | pageFunc() { 68 | chrome.tabs.query({ active: true, currentWindow: true }, async function (tabInfo) { 69 | if (tabInfo.length < 1) return; 70 | const pageOrigin = (new URL(tabInfo[0].url)).origin; 71 | 72 | refreshHistory(function (results) { 73 | state.port.postMessage({ 74 | 'cmd': 'dmenu', 75 | 'info': 'changeToPage', 76 | 'param': { 77 | 'rofi-opts': ['-matching', 'normal', '-i', '-p', 'page'], 78 | 'opts': results.filter(e => e.url.indexOf(pageOrigin) === 0).map(e => e.title + ' ::: ' + e.url), 79 | } 80 | }); 81 | }); 82 | }); 83 | }, 84 | }; 85 | 86 | /*** listeners ***/ 87 | 88 | function onNativeMessage(message) { 89 | if (message.info === 'switchTab' && message.result !== '') { 90 | goToTab(parseInt(message.result.split(': ')[0])); 91 | } else if (message.info === 'openHistory' && message.result !== '') { 92 | let parts = message.result.split(' ::: '); 93 | 94 | openUrlInNewTab(parts[parts.length - 1]); 95 | } else if (message.info === 'changeToPage' && message.result !== '') { 96 | let parts = message.result.split(' ::: '); 97 | chrome.tabs.query({ active: true, currentWindow: true }, function (tabInfo) { 98 | chrome.tabs.update(tabInfo[0].id, { 99 | url: parts[parts.length - 1], 100 | }); 101 | }); 102 | } else if (message.result === '') { 103 | // do nothing 104 | } else { 105 | alert(JSON.stringify(message)); 106 | } 107 | 108 | // console.log("Received message: " + JSON.stringify(message)); 109 | } 110 | 111 | function onDisconnected() { 112 | alert("Failed to connect: " + chrome.runtime.lastError.message); 113 | state.port = null; 114 | } 115 | 116 | function addChromeListeners() { 117 | const listeners = { 118 | commands: { 119 | onCommand: function (command) { 120 | if (command in CMDS) { 121 | CMDS[command](); 122 | } else { 123 | alert('unknown command: ' + command) 124 | } 125 | } 126 | }, 127 | tabs: { 128 | onActivated: function (activeInfo) { 129 | state.lastTabId[1] = state.lastTabId[0]; 130 | state.lastTabId[0] = activeInfo.tabId; 131 | } 132 | } 133 | }; 134 | 135 | for (let api in listeners) { 136 | for (let method in listeners[api]) { 137 | chrome[api][method].addListener(listeners[api][method]); 138 | } 139 | } 140 | } 141 | 142 | /*** main ***/ 143 | 144 | function main() { 145 | state.port = chrome.runtime.connectNative(HOST_NAME); 146 | state.port.onMessage.addListener(onNativeMessage); 147 | state.port.onDisconnect.addListener(onDisconnected); 148 | 149 | addChromeListeners(); 150 | }; 151 | 152 | main(); --------------------------------------------------------------------------------