commit ee78eef527e3dbb5867de91fcadd6fbae411a81d Author: Squibid Date: Wed Sep 17 23:05:25 2025 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c73b058 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +misctils.xpi diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bef4f6f --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +MOZ_NATIVE=$(HOME)/.mozilla/native-messaging-hosts + +release: xpi + +xpi: + zip -r misctils.xpi *.js manifest.json icons/ startpage/ + +startpage: + ./genfs.sh + +install: + mkdir -p $(MOZ_NATIVE) + cp nativemsg/bg.py $(MOZ_NATIVE) + + # make the home directory work for all users + sed "s@HOME@$(HOME)@" nativemsg/bg_app.json > $(MOZ_NATIVE)/bg_app.json + chmod +x $(MOZ_NATIVE)/bg.py + +uninstall: + rm $(MOZ_NATIVE)/bg_app.json $(MOZ_NATIVE)/bg.py + +clean: + rm misctils.xpi + +.PHONY: startpage diff --git a/background.js b/background.js new file mode 100644 index 0000000..bc352fe --- /dev/null +++ b/background.js @@ -0,0 +1,96 @@ +/* add a context menu for setting the background */ +browser.menus.create({ + id: "set-background", + title: "Set Background Image", + contexts: [ "image" ], + icons: { + "16": "icons/background.png", + "32": "icons/background.png", + "64": "icons/background.png", + "128": "icons/background.png", + "256": "icons/background.png" + } +}) + +/* add a context menu for accessing repo on github */ +browser.menus.create({ + id: "search-github", + title: "Open in Github", + contexts: [ "selection" ] +}) + +browser.menus.onClicked.addListener(info => { + if (info.menuItemId == "set-background") { + browser.runtime.sendNativeMessage("bg_app", info.srcUrl); + } else if (info.menuItemId == "search-github" && info.selectionText) { + const trimmedText = info.selectionText.trim(); + const githubUrl = `https://github.com/${trimmedText}`; + chrome.tabs.create({ url: githubUrl }) + } +}); + +/* add functionality to commands defined in manifest.json */ +chrome.commands.onCommand.addListener(function(action) { + action.preventDefault(); + switch (action) { + case "toggle-pin-tab": /* toggle current tab pinning */ + chrome.tabs.query({ currentWindow: true, active: true }, function(found_tab) { + const currentTab = found_tab[0] + const toggledValue = !currentTab.pinned; + + chrome.tabs.update(currentTab.id, { pinned: toggledValue }); + }); + break; + + /* switch between tabs */ + case "tab-up": + chrome.tabs.query({ currentWindow: true, active: true }, function(found_tab) { + switch_tab(found_tab[0].index - 1); + }); + break; + case "tab-down": + chrome.tabs.query({ currentWindow: true, active: true }, function(found_tab) { + switch_tab(found_tab[0].index + 1); + }); + break; + + /* close tab */ + case "tab-close": + chrome.tabs.query({ currentWindow: true, active: true }, function(found_tab) { + browser.tabs.remove(found_tab[0].id); + }); + break; + case "scroll-half-down": + window.scrollBy(0, window.innerHeight / 2, "smooth"); + console.log(window) + console.log("hello world") + break; + case "scroll-half-up": + window.scrollBy(0, -(window.innerHeight / 2), "smooth"); + console.log(window) + console.log("hello world") + break; + } +}); + +function switch_tab(tab_number) { + /* I don't want it to cycle around */ + if (tab_number < 0) { + return; + } + + chrome.tabs.query({ currentWindow: true, active: true }, function(found_tabs) { + const currentTab = found_tabs[0]; + if (currentTab.index == tab_number) { + /* we're already on the current tab */ + return; + } + + /* change the selected tab to active */ + browser.tabs.query({ currentWindow: true }, function(window_tabs) { + if (typeof window_tabs.at(tab_number) != "undefined") { + chrome.tabs.update(window_tabs.at(tab_number).id, { active: true }); + } + }); + }); +} diff --git a/genfs.sh b/genfs.sh new file mode 100755 index 0000000..eba1faa --- /dev/null +++ b/genfs.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# generate fs files because we can't do directory traversal from js within a +# browser. + +for d in ./startpage/root/*; do + if [ ! -d "$d" ]; then + continue + fi + + s="" + for f in "$d"/*; do + s=${s:+$s\\n}$(echo "$f" | cut -d '/' -f 5-) + done + echo "$s" > ./startpage/root/."$(echo "$d" | cut -d '/' -f 4-)" +done diff --git a/icons/background.png b/icons/background.png new file mode 100644 index 0000000..2bdfeae Binary files /dev/null and b/icons/background.png differ diff --git a/icons/misc.png b/icons/misc.png new file mode 100644 index 0000000..335529d Binary files /dev/null and b/icons/misc.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..0a21fd0 --- /dev/null +++ b/manifest.json @@ -0,0 +1,59 @@ +{ + "name": "misctils", + "version": "1.0", + "description": "Miscelanious Utils for Firefox", + "icons": { + "16": "icons/misc.png", + "32": "icons/misc.png", + "64": "icons/misc.png", + "128": "icons/misc.png", + "256": "icons/misc.png" + }, + "manifest_version": 2, + "permissions": [ "menus", "activeTab", "tabs", "nativeMessaging", "storage" ], + + "browser_specific_settings": { + "gecko": { + "id": "misc@tils", + "strict_min_version": "58.0" + } + }, + + "background": { + "scripts": [ "background.js" ], + "persistent": false + }, + "chrome_settings_overrides" : { + "homepage": "startpage/index.html" + }, + "chrome_url_overrides" : { + "newtab": "startpage/index.html" + }, + + "commands": { + "toggle-pin-tab": { + "suggested_key": { "default": "Alt+P" }, + "description": "Pin the currently active tab." + }, + "tab-up": { + "suggested_key": { "default": "Alt+K" }, + "description": "Switch tab focus to one up." + }, + "tab-down": { + "suggested_key": { "default": "Alt+J" }, + "description": "Switch tab focus to one down." + }, + "tab-close": { + "suggested_key": { "default": "Alt+C" }, + "description": "Close the current tab." + }, + "scroll-half-down": { + "suggested_key": { "default": "Alt+D" }, + "description": "Scroll down half a page." + }, + "scroll-half-up": { + "suggested_key": { "default": "Alt+U" }, + "description": "Scroll up half a page." + } + } +} diff --git a/nativemsg/bg.py b/nativemsg/bg.py new file mode 100644 index 0000000..48242ec --- /dev/null +++ b/nativemsg/bg.py @@ -0,0 +1,18 @@ +#!/usr/bin/env -S python3 +import sys +import struct +import subprocess + +# Read a message from stdin and decode it. +rawLength = sys.stdin.buffer.read(4) +if len(rawLength) == 0: + sys.exit(0) + +# Read the length of the message +messageLength: int = struct.unpack("@I", rawLength)[0] + +# Decode the message +message = sys.stdin.buffer.read(messageLength).decode("utf-8") + +# Run the background setting with a link to the image +_ = subprocess.run(["setbg", "-tw", message.replace('"', "")]) diff --git a/nativemsg/bg_app.json b/nativemsg/bg_app.json new file mode 100644 index 0000000..7fe4e79 --- /dev/null +++ b/nativemsg/bg_app.json @@ -0,0 +1,7 @@ +{ + "name": "bg_app", + "description": "set background via native script", + "path": "HOME/.mozilla/native-messaging-hosts/bg.py", + "type": "stdio", + "allowed_extensions": [ "misc@tils" ] +} diff --git a/startpage/index.html b/startpage/index.html new file mode 100644 index 0000000..e3a7635 --- /dev/null +++ b/startpage/index.html @@ -0,0 +1,28 @@ + + + + + + + + Startpage + + +
+

+

+
+

+

+

+ + + diff --git a/startpage/index.js b/startpage/index.js new file mode 100644 index 0000000..c414ef1 --- /dev/null +++ b/startpage/index.js @@ -0,0 +1,134 @@ +let clock = document.getElementById("clock"); +let date = document.getElementById("date"); +let input = document.getElementById("search-bar"); +let result = document.getElementById("search-results"); + +/* Allow toggling between military and 12 hr time */ +let military_time = false +clock.onclick = function() { + military_time = !military_time; + time(); +} + +function time(timeout) { + let d = new Date(); + let m = d.getMinutes(); + let h = d.getHours(); + + /* if it hasn't been set then set it here */ + if (military_time) { + clock.textContent = `${("0" + h).substr(-2)}:${("0" + m).substr(-2)}`; + } else { + let hours = h % 12; + let minutes = ("0" + m).substr(-2); + hours = hours ? hours : 12; + + clock.textContent = `${hours}:${minutes} ${(h >= 12 ? "PM" : "AM")}`; + } + + /* set the time every second */ + timeout && setTimeout(() => time(true), (60 - d.getSeconds()) * 1000); +} + +let last_date; +/* Sun 31st, May */ +function cal(force) { + const days = [ "Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat" ]; + const months = [ "January", "Febuary", "March", "April", "May", "June", + "July", "August", "September", "November", "December", ]; + + let d = new Date(); + let day = days[d.getDay()]; + let day_of_month = d.getDate(); + let month = months[d.getMonth()]; + let day_suffix + + /* get a suffix for the day of the month */ + if (day_of_month > 3 && day_of_month < 21) { + day_suffix = "th"; + } else if ((day_of_month / 1) % 10 == 1) { + day_suffix = "st"; + } else if ((day_of_month / 1) % 10 == 2) { + day_suffix = "nd"; + } else if ((day_of_month / 1) % 10 == 3) { + day_suffix = "rd"; + } else { + day_suffix = "th"; + } + + /* check if this date has already been set before */ + if (!force && last_date == `${d.getDay()}${d.getMonth()}`) { + return; + } + + date.textContent = `${day} ${day_of_month}${day_suffix}, ${month}`; + + /* store the last date that was set */ + last_date = `${d.getDay()}${d.getMonth()}`; +} + +/* show the time on the page */ +time(true); + +/* just incase something dumb happens the time should update when the user + * focus's the page */ +window.addEventListener('focus', time); + +cal(); +setInterval(cal, 1000); + +function shell(event) { + var env = { + path: "/startpage/root/bin/", + }; + + /* convert a directory such as /startpage/root/bin/ to /startpage/root/.bin + * to read static fs data. + */ + function convertPath(path) { + if (path.endsWith('/')) { + path = path.slice(0, -1); + } + const parts = path.split('/'); + const hidden = '.' + parts.pop(); + return parts.concat(hidden).join('/'); + } + + async function run(path) { + try { + const response = await fetch(convertPath(env.path)); + if (!response.ok) { + throw new Error(`Failed to load file: ${response.status}`); + } + + const text = await response.text(); + const lines = text.split(/\r?\n/); + const matchedLine = lines.find(line => line.split(".")[0] == path); + if (!matchedLine) { + throw new Error(`${path} cannot be found in path.`); + } + + const file = env.path + matchedLine; + const { cmd } = await import(file); + return await cmd(input.value, event.key); + } catch (err) { + throw err; + } + } + + if (input.value == "") { + input.style.color = "white"; + } else { + run(input.value.substring(0, input.value.indexOf(" ")) || input.value) + .then(res => { + result.innerHTML = res; + input.style.color = "var(--blue)"; + }) + .catch(err => { + input.style.color = "white"; + }); + } +} + +input.addEventListener('keyup', shell); +shell({ key: "" }); diff --git a/startpage/root/.bin b/startpage/root/.bin new file mode 100644 index 0000000..6524928 --- /dev/null +++ b/startpage/root/.bin @@ -0,0 +1,4 @@ +clear.js +fetch.js +gpt.js +man.js diff --git a/startpage/root/bin/clear.js b/startpage/root/bin/clear.js new file mode 100644 index 0000000..ba1a2d6 --- /dev/null +++ b/startpage/root/bin/clear.js @@ -0,0 +1,12 @@ +export async function cmd() { + return ``; +} + +export let man = ` +
+CLEAR(1)
+
+NAME
+\tclear - clear the screen
+
+` diff --git a/startpage/root/bin/fetch.js b/startpage/root/bin/fetch.js new file mode 100644 index 0000000..723adbb --- /dev/null +++ b/startpage/root/bin/fetch.js @@ -0,0 +1,29 @@ +export async function cmd(_) { +return ` +
+         _____\t\t\tOS: ${navigator.platform}
+     .-""     ""-.\t\tLang: ${navigator.language}
+  /(/   _      }\`;\`.\t\tTimezone: ${Intl.DateTimeFormat().resolvedOptions().timeZone}
+  |\`---'/       \` \`;\\\t\tAgent: ${navigator.userAgent}
+ .'    (\`._      ;  \`:
+:       \--'     '|\`;,;
+|    .---'        ; | |
+:    ' \`.__.-.   /  ; ;
+ :      .----' .'  / :
+  \`.    \`----'  .' /
+   \`.\`--.  __ .-  .'
+     \`-._     _.-' fsc
+         \`""""
+
+`; +} + +export let man = ` +
+FETCH(1)
+
+NAME
+\tfetch – Get information about the browser. This is specific to firefox because
+\tthis addon is specific to firefox.
+
+` diff --git a/startpage/root/bin/gpt.js b/startpage/root/bin/gpt.js new file mode 100644 index 0000000..d1882cf --- /dev/null +++ b/startpage/root/bin/gpt.js @@ -0,0 +1,26 @@ +export async function cmd(inp, k) { + var search = "" + if (inp.indexOf(" ") > 0) { + search = inp.substring(inp.indexOf(" ") + 1) + } + + if (k == 'Enter') { + window.open(`https://chatgpt.com/?q=${search}`, '_self'); + } + + return ` +
+press Enter to search chat.openai.com for: '${search}'
+
+`; +} + +export let man = ` +
+GPT(1)
+
+NAME
+\tgpt - search chat.openai.com for something. You'll probably only get slop
+\tbut it may have a chance of helping.
+
+` diff --git a/startpage/root/bin/man.js b/startpage/root/bin/man.js new file mode 100644 index 0000000..f3f8f44 --- /dev/null +++ b/startpage/root/bin/man.js @@ -0,0 +1,53 @@ +var getmanval; +async function getman(path) { + try { + const module = await import("/startpage/" + path); + return module.man; + } catch (error) { + return null; + } +} + +/* TODO: update to use the path in the env this can be done once we first + * make the env available to the programs running in the shell + */ +export async function cmd(inp) { + var search = "" + if (inp.indexOf(" ") > 0) { + search = inp.substring(inp.indexOf(" ") + 1) + } + switch (search) { + case "fetch": return getman('/root/bin/fetch.js'); + case "gpt": return getman('/root/bin/gpt.js'); + case "clear": return getman('/root/bin/clear.js'); + case "shell": return manshell; + case "man": default: return man; + } +} + +export let man = ` +
+MAN(1)
+
+NAME
+\tman – display manual pages
+
+SEE ALSO
+\tfetch(1), gpt(1), shell(1), clear(1)
+
+`; + +export let manshell = ` +
+SHELL(1)
+
+NAME
+\tshell – command interpreter
+
+DESCRIPTION
+\tshell is a shell for my startpage. It's not POSIX nor will it ever be. It's
+\tsole job it to interpret text and run a command if a valid one is found.
+\tWhile this is quite unintuitive in a standard UNIX shell in my opinion this
+\tmakes sense here because of such a limited number of commands.
+
+`; diff --git a/startpage/style.css b/startpage/style.css new file mode 100644 index 0000000..d803252 --- /dev/null +++ b/startpage/style.css @@ -0,0 +1,103 @@ +:root { + --bg: #161617; + --fg: #c9c7cd; + --bg-dark: #131314; + + /* Normal */ + --black: #27272a; + --red: #f5a191; + --green: #90b99f; + --yellow: #e6b99d; + --blue: #aca1cf; + --magenta: #e29eca; + --cyan: #ea83a5; + --white: #c1c0d4; + /* Bright */ + --bright-black: #353539; + --bright-red: #ffae9f; + --bright-green: #9dc6ac; + --bright-yellow: #f0c5a9; + --bright-blue: #b9aeda; + --bright-magenta: #ecaad6; + --bright-cyan: #f591b2; + --bright-white: #cac9dd; + /* Grays */ + --gray01: #1b1b1d; + --gray02: #2a2a2d; + --gray03: #3e3e43; + --gray04: #57575f; + --gray05: #757581; + --gray06: #9998a8; + --gray07: var(--white); +} + +html, body { + background-color: var(--bg-dark); + max-width: 80ch; + max-height: 100%; + margin: auto; +} + +@media (orientation: portrait) { + html, body { + max-width: 100% !important; + padding: 5px !important; + } +} + +#font, p, ul, ol, h1, h2, h3, h4, h5, table { + font-family: sans-serif; + color: white; +} +pre { + color: white; +} +h1 { + font-size: 3em; +} +a { + font-family: sans-serif; + text-decoration: none; + color: var(--cyan); +} +a:hover, a:active { + text-decoration: underline; +} + +#search { + display: flex; + align-items: center; + + #search-bar, #search-btn { + height: 2.25em; + background-color: var(--bg); + color: white; + padding: 0 15px; + border: none; + } + #search-bar { + width: 100%; + } + #search-bar:focus { + outline: none; + background-color: var(--gray01); + } + #search-btn { + background-color: var(--yellow); + color: var(--bg); + font-weight: bold; + cursor: pointer; + } + #search-btn:focus { + outline: none; + background-color: var(--bright-yellow); + } +} +#search-results { + height: 15lh; + margin-top: 0px; + margin-bottom: 0px; + padding: 15px; + overflow: scroll; + background-color: #111112; +}