From 509dbf9a94bd3c8810fef3f641d184f1147e63b8 Mon Sep 17 00:00:00 2001 From: David Tejada Date: Wed, 4 Sep 2024 17:07:17 +0200 Subject: [PATCH 1/5] Add command to focus the last tab that emitted sound --- src/background/actions/focusTabBySound.ts | 19 +++++++++++++++++++ src/background/commands/dispatchCommand.ts | 1 + .../commands/runBackgroundCommand.ts | 5 +++++ src/background/setup/initBackgroundScript.ts | 8 ++++++++ src/typings/RangoAction.ts | 1 + 5 files changed, 34 insertions(+) create mode 100644 src/background/actions/focusTabBySound.ts diff --git a/src/background/actions/focusTabBySound.ts b/src/background/actions/focusTabBySound.ts new file mode 100644 index 00000000..ddcac1c8 --- /dev/null +++ b/src/background/actions/focusTabBySound.ts @@ -0,0 +1,19 @@ +import browser from "webextension-polyfill"; +import { notify } from "../utils/notify"; + +let tabLastSounded: number | undefined; + +export function setTabLastSounded(tabId: number) { + tabLastSounded = tabId; +} + +export async function focusTabLastSounded() { + if (!tabLastSounded) + return notify("No tab has emitted sound since startup.", { + type: "warning", + }); + + const tab = await browser.tabs.get(tabLastSounded); + await browser.windows.update(tab.windowId!, { focused: true }); + await browser.tabs.update(tabLastSounded, { active: true }); +} diff --git a/src/background/commands/dispatchCommand.ts b/src/background/commands/dispatchCommand.ts index 53e9a0b1..01039898 100644 --- a/src/background/commands/dispatchCommand.ts +++ b/src/background/commands/dispatchCommand.ts @@ -43,6 +43,7 @@ const backgroundCommands = new Set([ "focusOrCreateTabByUrl", "focusTabByText", "cycleTabsByText", + "focusTabLastSounded", ]); export async function dispatchCommand( diff --git a/src/background/commands/runBackgroundCommand.ts b/src/background/commands/runBackgroundCommand.ts index 498a8a8b..2e5429a7 100644 --- a/src/background/commands/runBackgroundCommand.ts +++ b/src/background/commands/runBackgroundCommand.ts @@ -21,6 +21,7 @@ import { refreshTabMarkers } from "../misc/tabMarkers"; import { getCurrentTab } from "../utils/getCurrentTab"; import { notifySettingRemoved } from "../utils/notify"; import { closeTab } from "../actions/closeTab"; +import { focusTabLastSounded } from "../actions/focusTabBySound"; export async function runBackgroundCommand( command: RangoAction @@ -147,6 +148,10 @@ export async function runBackgroundCommand( await focusPreviousTab(); break; + case "focusTabLastSounded": + await focusTabLastSounded(); + break; + case "copyLocationProperty": if (currentTab) { return copyLocationProperty(currentTab, command.arg); diff --git a/src/background/setup/initBackgroundScript.ts b/src/background/setup/initBackgroundScript.ts index 956e0370..3f212d1c 100644 --- a/src/background/setup/initBackgroundScript.ts +++ b/src/background/setup/initBackgroundScript.ts @@ -1,6 +1,7 @@ import browser from "webextension-polyfill"; import { retrieve, store } from "../../common/storage"; import { urls } from "../../common/urls"; +import { setTabLastSounded } from "../actions/focusTabBySound"; import { watchNavigation } from "../hints/watchNavigation"; import { sendRequestToContent } from "../messaging/sendRequestToContent"; import { createContextMenus } from "../misc/createContextMenus"; @@ -117,3 +118,10 @@ browser.bookmarks?.onCreated.addListener(resetBookmarkTitle); // the title of the bookmark will be changed again to the value of the input // field of the popup window. browser.bookmarks?.onChanged.addListener(resetBookmarkTitle); + +browser.tabs.onUpdated.addListener(async (tabId, { audible }) => { + if (audible === true) { + const tab = await browser.tabs.get(tabId); + if (!tab.mutedInfo?.muted) setTabLastSounded(tabId); + } +}); diff --git a/src/typings/RangoAction.ts b/src/typings/RangoAction.ts index 84df7620..d92453bd 100644 --- a/src/typings/RangoAction.ts +++ b/src/typings/RangoAction.ts @@ -12,6 +12,7 @@ interface RangoActionWithoutTargetWithoutArg { | "moveCurrentTabToNewWindow" | "focusPreviousTab" | "focusFirstInput" + | "focusTabLastSounded" | "unhoverAll" | "copyCurrentTabMarkdownUrl" | "getBareTitle" From aeb236427e103fff818817bf64eaea7c8febe06f Mon Sep 17 00:00:00 2001 From: David Tejada Date: Fri, 6 Sep 2024 13:23:44 +0200 Subject: [PATCH 2/5] Add command to focus the next tab that is reproducing sound --- src/background/actions/focusTabBySound.ts | 22 +++++++++++++++++++ src/background/commands/dispatchCommand.ts | 1 + .../commands/runBackgroundCommand.ts | 9 +++++++- src/typings/RangoAction.ts | 1 + 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/background/actions/focusTabBySound.ts b/src/background/actions/focusTabBySound.ts index ddcac1c8..09b5a952 100644 --- a/src/background/actions/focusTabBySound.ts +++ b/src/background/actions/focusTabBySound.ts @@ -1,5 +1,6 @@ import browser from "webextension-polyfill"; import { notify } from "../utils/notify"; +import { getCurrentTab } from "../utils/getCurrentTab"; let tabLastSounded: number | undefined; @@ -17,3 +18,24 @@ export async function focusTabLastSounded() { await browser.windows.update(tab.windowId!, { focused: true }); await browser.tabs.update(tabLastSounded, { active: true }); } + +export async function focusNextTabWithSound() { + const currentTab = await getCurrentTab(); + const tabsWithSound = await browser.tabs.query({ + audible: true, + muted: false, + }); + + const nextTabWithSound = + tabsWithSound.find( + (tab) => + (tab.windowId === currentTab.windowId && + tab.index > currentTab.index) || + tab.windowId !== currentTab.windowId + ) ?? tabsWithSound[0]; + + if (!nextTabWithSound) return; + + await browser.windows.update(nextTabWithSound.windowId!, { focused: true }); + await browser.tabs.update(nextTabWithSound.id, { active: true }); +} diff --git a/src/background/commands/dispatchCommand.ts b/src/background/commands/dispatchCommand.ts index 01039898..f639763d 100644 --- a/src/background/commands/dispatchCommand.ts +++ b/src/background/commands/dispatchCommand.ts @@ -43,6 +43,7 @@ const backgroundCommands = new Set([ "focusOrCreateTabByUrl", "focusTabByText", "cycleTabsByText", + "focusNextTabWithSound", "focusTabLastSounded", ]); diff --git a/src/background/commands/runBackgroundCommand.ts b/src/background/commands/runBackgroundCommand.ts index 2e5429a7..82527779 100644 --- a/src/background/commands/runBackgroundCommand.ts +++ b/src/background/commands/runBackgroundCommand.ts @@ -21,7 +21,10 @@ import { refreshTabMarkers } from "../misc/tabMarkers"; import { getCurrentTab } from "../utils/getCurrentTab"; import { notifySettingRemoved } from "../utils/notify"; import { closeTab } from "../actions/closeTab"; -import { focusTabLastSounded } from "../actions/focusTabBySound"; +import { + focusNextTabWithSound, + focusTabLastSounded, +} from "../actions/focusTabBySound"; export async function runBackgroundCommand( command: RangoAction @@ -148,6 +151,10 @@ export async function runBackgroundCommand( await focusPreviousTab(); break; + case "focusNextTabWithSound": + await focusNextTabWithSound(); + break; + case "focusTabLastSounded": await focusTabLastSounded(); break; diff --git a/src/typings/RangoAction.ts b/src/typings/RangoAction.ts index d92453bd..ed6dca8c 100644 --- a/src/typings/RangoAction.ts +++ b/src/typings/RangoAction.ts @@ -12,6 +12,7 @@ interface RangoActionWithoutTargetWithoutArg { | "moveCurrentTabToNewWindow" | "focusPreviousTab" | "focusFirstInput" + | "focusNextTabWithSound" | "focusTabLastSounded" | "unhoverAll" | "copyCurrentTabMarkdownUrl" From fe4884f9fda9402c57b04151b1cf73addba4f1a2 Mon Sep 17 00:00:00 2001 From: David Tejada Date: Fri, 6 Sep 2024 13:38:09 +0200 Subject: [PATCH 3/5] Add command to focus the next muted tab --- src/background/actions/focusTabBySound.ts | 32 +++++++++++++------ src/background/commands/dispatchCommand.ts | 1 + .../commands/runBackgroundCommand.ts | 5 +++ src/typings/RangoAction.ts | 1 + 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/background/actions/focusTabBySound.ts b/src/background/actions/focusTabBySound.ts index 09b5a952..a4dd31f1 100644 --- a/src/background/actions/focusTabBySound.ts +++ b/src/background/actions/focusTabBySound.ts @@ -20,22 +20,36 @@ export async function focusTabLastSounded() { } export async function focusNextTabWithSound() { - const currentTab = await getCurrentTab(); const tabsWithSound = await browser.tabs.query({ audible: true, muted: false, }); - const nextTabWithSound = - tabsWithSound.find( - (tab) => - (tab.windowId === currentTab.windowId && - tab.index > currentTab.index) || - tab.windowId !== currentTab.windowId - ) ?? tabsWithSound[0]; - + const nextTabWithSound = await getNextTabByIndex(tabsWithSound); if (!nextTabWithSound) return; await browser.windows.update(nextTabWithSound.windowId!, { focused: true }); await browser.tabs.update(nextTabWithSound.id, { active: true }); } + +export async function focusNextMutedTab() { + const mutedTabs = await browser.tabs.query({ muted: true }); + const nextMutedTab = await getNextTabByIndex(mutedTabs); + if (!nextMutedTab) return; + + await browser.windows.update(nextMutedTab.windowId!, { focused: true }); + await browser.tabs.update(nextMutedTab.id, { active: true }); +} + +async function getNextTabByIndex(tabs: browser.Tabs.Tab[]) { + const currentTab = await getCurrentTab(); + + return ( + tabs.find( + (tab) => + (tab.windowId === currentTab.windowId && + tab.index > currentTab.index) || + tab.windowId !== currentTab.windowId + ) ?? tabs[0] + ); +} diff --git a/src/background/commands/dispatchCommand.ts b/src/background/commands/dispatchCommand.ts index f639763d..b9d703c1 100644 --- a/src/background/commands/dispatchCommand.ts +++ b/src/background/commands/dispatchCommand.ts @@ -44,6 +44,7 @@ const backgroundCommands = new Set([ "focusTabByText", "cycleTabsByText", "focusNextTabWithSound", + "focusNextMutedTab", "focusTabLastSounded", ]); diff --git a/src/background/commands/runBackgroundCommand.ts b/src/background/commands/runBackgroundCommand.ts index 82527779..8bc1ca51 100644 --- a/src/background/commands/runBackgroundCommand.ts +++ b/src/background/commands/runBackgroundCommand.ts @@ -22,6 +22,7 @@ import { getCurrentTab } from "../utils/getCurrentTab"; import { notifySettingRemoved } from "../utils/notify"; import { closeTab } from "../actions/closeTab"; import { + focusNextMutedTab, focusNextTabWithSound, focusTabLastSounded, } from "../actions/focusTabBySound"; @@ -155,6 +156,10 @@ export async function runBackgroundCommand( await focusNextTabWithSound(); break; + case "focusNextMutedTab": + await focusNextMutedTab(); + break; + case "focusTabLastSounded": await focusTabLastSounded(); break; diff --git a/src/typings/RangoAction.ts b/src/typings/RangoAction.ts index ed6dca8c..a1d57876 100644 --- a/src/typings/RangoAction.ts +++ b/src/typings/RangoAction.ts @@ -13,6 +13,7 @@ interface RangoActionWithoutTargetWithoutArg { | "focusPreviousTab" | "focusFirstInput" | "focusNextTabWithSound" + | "focusNextMutedTab" | "focusTabLastSounded" | "unhoverAll" | "copyCurrentTabMarkdownUrl" From 099bf6fda34e549bad540f6a09990c561728bdd8 Mon Sep 17 00:00:00 2001 From: David Tejada Date: Wed, 11 Sep 2024 10:49:16 +0200 Subject: [PATCH 4/5] Add command to focus next tab that is playing audio --- src/background/actions/focusTabBySound.ts | 9 +++++++++ src/background/commands/dispatchCommand.ts | 1 + src/background/commands/runBackgroundCommand.ts | 5 +++++ src/typings/RangoAction.ts | 1 + 4 files changed, 16 insertions(+) diff --git a/src/background/actions/focusTabBySound.ts b/src/background/actions/focusTabBySound.ts index a4dd31f1..58dc3291 100644 --- a/src/background/actions/focusTabBySound.ts +++ b/src/background/actions/focusTabBySound.ts @@ -41,6 +41,15 @@ export async function focusNextMutedTab() { await browser.tabs.update(nextMutedTab.id, { active: true }); } +export async function focusNextAudibleTab() { + const audibleTabs = await browser.tabs.query({ audible: true }); + const nextAudibleTab = await getNextTabByIndex(audibleTabs); + if (!nextAudibleTab) return; + + await browser.windows.update(nextAudibleTab.windowId!, { focused: true }); + await browser.tabs.update(nextAudibleTab.id, { active: true }); +} + async function getNextTabByIndex(tabs: browser.Tabs.Tab[]) { const currentTab = await getCurrentTab(); diff --git a/src/background/commands/dispatchCommand.ts b/src/background/commands/dispatchCommand.ts index b9d703c1..ac985bd9 100644 --- a/src/background/commands/dispatchCommand.ts +++ b/src/background/commands/dispatchCommand.ts @@ -45,6 +45,7 @@ const backgroundCommands = new Set([ "cycleTabsByText", "focusNextTabWithSound", "focusNextMutedTab", + "focusNextAudibleTab", "focusTabLastSounded", ]); diff --git a/src/background/commands/runBackgroundCommand.ts b/src/background/commands/runBackgroundCommand.ts index 8bc1ca51..0e5e8e4b 100644 --- a/src/background/commands/runBackgroundCommand.ts +++ b/src/background/commands/runBackgroundCommand.ts @@ -22,6 +22,7 @@ import { getCurrentTab } from "../utils/getCurrentTab"; import { notifySettingRemoved } from "../utils/notify"; import { closeTab } from "../actions/closeTab"; import { + focusNextAudibleTab, focusNextMutedTab, focusNextTabWithSound, focusTabLastSounded, @@ -160,6 +161,10 @@ export async function runBackgroundCommand( await focusNextMutedTab(); break; + case "focusNextAudibleTab": + await focusNextAudibleTab(); + break; + case "focusTabLastSounded": await focusTabLastSounded(); break; diff --git a/src/typings/RangoAction.ts b/src/typings/RangoAction.ts index a1d57876..aed8a6b6 100644 --- a/src/typings/RangoAction.ts +++ b/src/typings/RangoAction.ts @@ -14,6 +14,7 @@ interface RangoActionWithoutTargetWithoutArg { | "focusFirstInput" | "focusNextTabWithSound" | "focusNextMutedTab" + | "focusNextAudibleTab" | "focusTabLastSounded" | "unhoverAll" | "copyCurrentTabMarkdownUrl" From 6bc9c06081d836dea92c6050bb55904d1f5f2c18 Mon Sep 17 00:00:00 2001 From: David Tejada Date: Wed, 11 Sep 2024 12:46:40 +0200 Subject: [PATCH 5/5] Add several commands for muting or unmuting tabs --- src/background/actions/focusTabBySound.ts | 15 +---- src/background/actions/muteTabs.ts | 67 +++++++++++++++++++ src/background/commands/dispatchCommand.ts | 8 +++ .../commands/runBackgroundCommand.ts | 39 +++++++++++ src/background/utils/tabUtils.ts | 21 ++++++ src/typings/RangoAction.ts | 8 +++ 6 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 src/background/actions/muteTabs.ts create mode 100644 src/background/utils/tabUtils.ts diff --git a/src/background/actions/focusTabBySound.ts b/src/background/actions/focusTabBySound.ts index 58dc3291..186cdc64 100644 --- a/src/background/actions/focusTabBySound.ts +++ b/src/background/actions/focusTabBySound.ts @@ -1,6 +1,6 @@ import browser from "webextension-polyfill"; import { notify } from "../utils/notify"; -import { getCurrentTab } from "../utils/getCurrentTab"; +import { getNextTabByIndex } from "../utils/tabUtils"; let tabLastSounded: number | undefined; @@ -49,16 +49,3 @@ export async function focusNextAudibleTab() { await browser.windows.update(nextAudibleTab.windowId!, { focused: true }); await browser.tabs.update(nextAudibleTab.id, { active: true }); } - -async function getNextTabByIndex(tabs: browser.Tabs.Tab[]) { - const currentTab = await getCurrentTab(); - - return ( - tabs.find( - (tab) => - (tab.windowId === currentTab.windowId && - tab.index > currentTab.index) || - tab.windowId !== currentTab.windowId - ) ?? tabs[0] - ); -} diff --git a/src/background/actions/muteTabs.ts b/src/background/actions/muteTabs.ts new file mode 100644 index 00000000..0bd3d8fc --- /dev/null +++ b/src/background/actions/muteTabs.ts @@ -0,0 +1,67 @@ +import browser from "webextension-polyfill"; +import { getCurrentTabId } from "../utils/getCurrentTab"; +import { getTabIdForMarker } from "../misc/tabMarkers"; +import { getNextTabByIndex } from "../utils/tabUtils"; +import { notify } from "../utils/notify"; + +export async function muteTab(tabMarkers?: string[], mute = true) { + if (tabMarkers) { + const tabsToMute = await Promise.all(tabMarkers.map(getTabIdForMarker)); + + return Promise.all( + tabsToMute.map(async (tabId) => + browser.tabs.update(tabId, { muted: mute }) + ) + ); + } + + const tabToMute = await getCurrentTabId(); + return browser.tabs.update(tabToMute, { muted: mute }); +} + +export async function muteNextTabWithSound() { + const tabsWithSound = await browser.tabs.query({ + audible: true, + muted: false, + }); + + const nextTabWithSound = await getNextTabByIndex(tabsWithSound); + if (!nextTabWithSound) + return notify("There are currently no tabs with sound", { + type: "warning", + }); + + await browser.tabs.update(nextTabWithSound.id, { muted: true }); +} + +export async function unmuteNextMutedTab() { + const mutedTabs = await browser.tabs.query({ muted: true }); + const nextMutedTab = await getNextTabByIndex(mutedTabs); + if (!nextMutedTab) + return notify("There are currently no muted tabs", { + type: "warning", + }); + + await browser.tabs.update(nextMutedTab.id, { muted: false }); +} + +export async function muteAllTabsWithSound() { + const tabsWithSound = await browser.tabs.query({ + audible: true, + muted: false, + }); + + await Promise.all( + tabsWithSound.map(async (tab) => + browser.tabs.update(tab.id, { muted: true }) + ) + ); +} + +export async function unmuteAllMutedTabs() { + const mutedTabs = await browser.tabs.query({ muted: true }); + + await Promise.all( + mutedTabs.map(async (tab) => browser.tabs.update(tab.id, { muted: false })) + ); +} diff --git a/src/background/commands/dispatchCommand.ts b/src/background/commands/dispatchCommand.ts index ac985bd9..d61a5c9d 100644 --- a/src/background/commands/dispatchCommand.ts +++ b/src/background/commands/dispatchCommand.ts @@ -47,6 +47,14 @@ const backgroundCommands = new Set([ "focusNextMutedTab", "focusNextAudibleTab", "focusTabLastSounded", + "muteCurrentTab", + "unmuteCurrentTab", + "muteTab", + "unmuteTab", + "muteNextTabWithSound", + "unmuteNextMutedTab", + "muteAllTabsWithSound", + "unmuteAllMutedTabs", ]); export async function dispatchCommand( diff --git a/src/background/commands/runBackgroundCommand.ts b/src/background/commands/runBackgroundCommand.ts index 0e5e8e4b..3dd28ef0 100644 --- a/src/background/commands/runBackgroundCommand.ts +++ b/src/background/commands/runBackgroundCommand.ts @@ -27,6 +27,13 @@ import { focusNextTabWithSound, focusTabLastSounded, } from "../actions/focusTabBySound"; +import { + muteAllTabsWithSound, + muteNextTabWithSound, + muteTab, + unmuteAllMutedTabs, + unmuteNextMutedTab, +} from "../actions/muteTabs"; export async function runBackgroundCommand( command: RangoAction @@ -169,6 +176,38 @@ export async function runBackgroundCommand( await focusTabLastSounded(); break; + case "muteCurrentTab": + await muteTab(); + break; + + case "unmuteCurrentTab": + await muteTab(undefined, false); + break; + + case "muteTab": + await muteTab(command.target); + break; + + case "unmuteTab": + await muteTab(command.target, false); + break; + + case "muteNextTabWithSound": + await muteNextTabWithSound(); + break; + + case "unmuteNextMutedTab": + await unmuteNextMutedTab(); + break; + + case "muteAllTabsWithSound": + await muteAllTabsWithSound(); + break; + + case "unmuteAllMutedTabs": + await unmuteAllMutedTabs(); + break; + case "copyLocationProperty": if (currentTab) { return copyLocationProperty(currentTab, command.arg); diff --git a/src/background/utils/tabUtils.ts b/src/background/utils/tabUtils.ts new file mode 100644 index 00000000..a2a124b5 --- /dev/null +++ b/src/background/utils/tabUtils.ts @@ -0,0 +1,21 @@ +import browser from "webextension-polyfill"; +import { getCurrentTab } from "./getCurrentTab"; + +/** + * Given an array of tabs as a parameter, return the first tab in the array that + * has a greater index than the current tab. If no such tab exists in the + * current window cycle through all existing windows returning to the start of + * the current window if necessary. + */ +export async function getNextTabByIndex(tabs: browser.Tabs.Tab[]) { + const currentTab = await getCurrentTab(); + + return ( + tabs.find( + (tab) => + (tab.windowId === currentTab.windowId && + tab.index > currentTab.index) || + tab.windowId !== currentTab.windowId + ) ?? tabs[0] + ); +} diff --git a/src/typings/RangoAction.ts b/src/typings/RangoAction.ts index aed8a6b6..a7280079 100644 --- a/src/typings/RangoAction.ts +++ b/src/typings/RangoAction.ts @@ -16,6 +16,12 @@ interface RangoActionWithoutTargetWithoutArg { | "focusNextMutedTab" | "focusNextAudibleTab" | "focusTabLastSounded" + | "muteCurrentTab" + | "unmuteCurrentTab" + | "muteNextTabWithSound" + | "unmuteNextMutedTab" + | "muteAllTabsWithSound" + | "unmuteAllMutedTabs" | "unhoverAll" | "copyCurrentTabMarkdownUrl" | "getBareTitle" @@ -107,6 +113,8 @@ interface RangoActionWithoutTargetWithOptionalNumberArg { export interface RangoActionWithTargets { type: | "activateTab" + | "muteTab" + | "unmuteTab" | "closeTab" | "openInBackgroundTab" | "clickElement"