Add .config/DankMaterialShell/firefox.css

Add .config/DankMaterialShell/plugin_settings.json
Add .config/DankMaterialShell/plugins/calculator/CalculatorLauncher.qml
Add .config/DankMaterialShell/plugins/calculator/CalculatorSettings.qml
Add .config/DankMaterialShell/plugins/calculator/README.md
Add .config/DankMaterialShell/plugins/calculator/calculator.js
Add .config/DankMaterialShell/plugins/calculator/.git/HEAD
Add .config/DankMaterialShell/plugins/calculator/.git/config
Add .config/DankMaterialShell/plugins/calculator/.git/index
Add .config/DankMaterialShell/plugins/calculator/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/calculator/.git/objects/pack/pack-67f644835e660794f65c9273e46788b0c3da57cf.idx
Add .config/DankMaterialShell/plugins/calculator/.git/objects/pack/pack-67f644835e660794f65c9273e46788b0c3da57cf.rev
Add .config/DankMaterialShell/plugins/calculator/.git/objects/pack/pack-67f644835e660794f65c9273e46788b0c3da57cf.pack
Add .config/DankMaterialShell/plugins/calculator/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/calculator/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/calculator/.git/refs/remotes/origin/qalc
Add .config/DankMaterialShell/plugins/calculator/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/calculator/plugin.json
Add .config/DankMaterialShell/plugins/calculator/screenshot.png
Add .config/DankMaterialShell/plugins/calculator/test_precision.js
Add .config/DankMaterialShell/plugins/calculator/test_single.js
Add .config/DankMaterialShell/plugins/commandRunner/CommandRunner.qml
Add .config/DankMaterialShell/plugins/commandRunner/CommandRunnerSettings.qml
Add .config/DankMaterialShell/plugins/commandRunner/LICENSE
Add .config/DankMaterialShell/plugins/commandRunner/README.md
Add .config/DankMaterialShell/plugins/commandRunner/.git/HEAD
Add .config/DankMaterialShell/plugins/commandRunner/.git/config
Add .config/DankMaterialShell/plugins/commandRunner/.git/index
Add .config/DankMaterialShell/plugins/commandRunner/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/commandRunner/.git/objects/pack/pack-5a720f795fd2994ef506cd867d86ca7df8a25a31.idx
Add .config/DankMaterialShell/plugins/commandRunner/.git/objects/pack/pack-5a720f795fd2994ef506cd867d86ca7df8a25a31.rev
Add .config/DankMaterialShell/plugins/commandRunner/.git/objects/pack/pack-5a720f795fd2994ef506cd867d86ca7df8a25a31.pack
Add .config/DankMaterialShell/plugins/commandRunner/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/commandRunner/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/commandRunner/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/commandRunner/plugin.json
Add .config/DankMaterialShell/plugins/commandRunner/screenshot.png
Add .config/DankMaterialShell/plugins/dankActions.meta
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/DankActionsSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/DankActionsWidget.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/DankBatteryAlerts.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/DankBatteryAlertsSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/DankHooks.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/DankHooksSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/README.md
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/DankPomodoroSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/DankPomodoroWidget.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/LICENSE
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/README.md
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/HEAD
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/config
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/index
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/heads/master
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/remotes/origin/master
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.gitignore
Add .config/DankMaterialShell/plugins/emojiLauncher/EmojiLauncher.qml
Add .config/DankMaterialShell/plugins/emojiLauncher/EmojiLauncherSettings.qml
Add .config/DankMaterialShell/plugins/emojiLauncher/LICENSE
Add .config/DankMaterialShell/plugins/emojiLauncher/README.md
Add .config/DankMaterialShell/plugins/emojiLauncher/catalog.js
Add .config/DankMaterialShell/plugins/emojiLauncher/data/emojis.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/data/math.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/data/nerdfont.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/HEAD
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/config
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/index
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.idx
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.rev
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.pack
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/emojiLauncher/plugin.json
Add .config/DankMaterialShell/plugins/emojiLauncher/screenshot.png
Add .config/DankMaterialShell/plugins/emojiLauncher/scripts/generate_catalog.py
Add .config/DankMaterialShell/plugins/dankActions
Add .config/DankMaterialShell/plugins/webSearch/LICENSE
Add .config/DankMaterialShell/plugins/webSearch/README.md
Add .config/DankMaterialShell/plugins/webSearch/WebSearch.qml
Add .config/DankMaterialShell/plugins/webSearch/WebSearchSettings.qml
Add .config/DankMaterialShell/plugins/webSearch/.git/HEAD
Add .config/DankMaterialShell/plugins/webSearch/.git/config
Add .config/DankMaterialShell/plugins/webSearch/.git/index
Add .config/DankMaterialShell/plugins/webSearch/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/webSearch/.git/objects/pack/pack-6a60c736f418e5b4b1f0505f66c1e2a371d46fed.idx
Add .config/DankMaterialShell/plugins/webSearch/.git/objects/pack/pack-6a60c736f418e5b4b1f0505f66c1e2a371d46fed.rev
Add .config/DankMaterialShell/plugins/webSearch/.git/objects/pack/pack-6a60c736f418e5b4b1f0505f66c1e2a371d46fed.pack
Add .config/DankMaterialShell/plugins/webSearch/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/webSearch/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/webSearch/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/webSearch/plugin.json
Add .config/DankMaterialShell/plugins/webSearch/screenshot.png
Add .config/DankMaterialShell/settings.json
This commit is contained in:
Lewis Wynne 2025-12-12 05:12:42 +00:00
parent b22161e0ca
commit 809b12203c
103 changed files with 25020 additions and 0 deletions

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 DankMaterialShell Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,237 @@
# Web Search
A DankMaterialShell launcher plugin for searching the web with 23+ built-in search engines and support for custom search engines.
![Web Search Screenshot](screenshot.png)
## Features
- **23+ Built-in Search Engines** - Google, DuckDuckGo, GitHub, Stack Overflow, and more
- **Custom Search Engines** - Add your own search engines with URL templates
- **Keyword-Based Selection** - Type keywords to use specific engines (e.g., `github rust`)
- **Configurable Default Engine** - Set your preferred search engine
- **One-Click Search** - Select and press Enter to open browser
- **Toast Notifications** - Visual feedback for every search
- **Configurable Trigger** - Default `?` or set your own trigger
## Installation
### From Plugin Registry (Recommended)
```bash
# Coming soon - will be available via DMS plugin manager
```
### Manual Installation
```bash
# Copy plugin to DMS plugins directory
cp -r WebSearch ~/.config/DankMaterialShell/plugins/
# Enable in DMS
# 1. Open Settings (Ctrl+,)
# 2. Go to Plugins tab
# 3. Click "Scan for Plugins"
# 4. Toggle "Web Search" to enable
```
## Usage
### Basic Search
1. Open launcher (Ctrl+Space)
2. Type `?` followed by search query
3. Examples:
- `? rust tutorials` - Search with default engine
- `? linux kernel` - General search
4. Select engine and press Enter to open browser
### Engine-Specific Search
Use keywords to search specific engines directly:
- `? github awesome-linux` - Search GitHub
- `? youtube music video` - Search YouTube
- `? wiki quantum physics` - Search Wikipedia
- `? stackoverflow async rust` - Search Stack Overflow
## Built-in Search Engines
### General Search
- **Google** - Keywords: `google`, `search`
- **DuckDuckGo** - Keywords: `ddg`, `duckduckgo`, `privacy`
- **Brave Search** - Keywords: `brave`, `privacy`
- **Bing** - Keywords: `bing`, `microsoft`
### Development
- **GitHub** - Keywords: `github`, `code`, `git`
- **Stack Overflow** - Keywords: `stackoverflow`, `stack`, `coding`
- **npm** - Keywords: `npm`, `node`, `javascript`
- **PyPI** - Keywords: `pypi`, `python`, `pip`
- **crates.io** - Keywords: `crates`, `rust`, `cargo`
- **MDN Web Docs** - Keywords: `mdn`, `mozilla`, `web`, `docs`
### Linux & Packages
- **Arch Linux Wiki** - Keywords: `arch`, `linux`, `packages`
- **AUR** - Keywords: `aur`, `arch`, `packages`
### Social & Media
- **YouTube** - Keywords: `youtube`, `video`, `yt`
- **Reddit** - Keywords: `reddit`
- **Twitter/X** - Keywords: `twitter`, `x`, `social`
- **LinkedIn** - Keywords: `linkedin`, `job`, `professional`
### Reference
- **Wikipedia** - Keywords: `wikipedia`, `wiki`
- **Google Translate** - Keywords: `translate`, `translation`
- **IMDb** - Keywords: `imdb`, `movies`, `tv`
### Shopping
- **Amazon** - Keywords: `amazon`, `shop`, `shopping`
- **eBay** - Keywords: `ebay`, `shop`, `auction`
### Utilities
- **Google Maps** - Keywords: `maps`, `location`, `directions`
- **Google Images** - Keywords: `images`, `pictures`, `photos`
## Custom Search Engines
Add your own search engines via Settings:
1. Open Settings → Plugins → Web Search
2. Scroll to "Custom Search Engines"
3. Fill in the form:
- **ID**: Unique identifier (e.g., `myengine`)
- **Name**: Display name (e.g., `My Search Engine`)
- **Icon**: Material icon name (e.g., `search`), prefix with `material:` to pull a Material Symbol (e.g., `material:travel_explore`), or prefix with `unicode:` to use emoji/Nerd Font glyphs (e.g., `unicode:`)
- **URL**: Search URL with `%s` placeholder
- **Keywords**: Comma-separated keywords for quick access
### Example Custom Engines
**Rust Documentation:**
```
ID: rustdoc
Name: Rust Docs
Icon: unicode:🦀
URL: https://doc.rust-lang.org/std/?search=%s
Keywords: rust,docs,documentation
```
**Arch Wiki:**
```
ID: archwiki
Name: Arch Wiki
Icon: material:menu_book
URL: https://wiki.archlinux.org/index.php?search=%s
Keywords: arch,wiki,documentation
```
**GitLab:**
```
ID: gitlab
Name: GitLab
Icon: material:code
URL: https://gitlab.com/search?search=%s
Keywords: gitlab,code
```
## Configuration
Access settings via DMS Settings → Plugins → Web Search:
- **Trigger**: Set custom trigger (`?`, `/`, `/search`, etc.) or disable for always-on mode
- **Default Search Engine**: Choose your preferred engine (Google, DuckDuckGo, Brave, Bing)
- **Custom Search Engines**: Add/manage your own search engines
## Search Examples
### General Queries
```
? rust programming # Search with default engine
? how to install arch # General search
```
### Development
```
? github awesome-linux # Search GitHub
? stackoverflow async rust # Search Stack Overflow
? npm react hooks # Search npm packages
? pypi requests # Search Python packages
```
### Linux
```
? arch firefox # Search Arch packages
? aur brave-bin # Search AUR
? wiki systemd # Search Wikipedia
```
### Media
```
? youtube rust tutorial # Search YouTube
? reddit linux gaming # Search Reddit
? imdb inception # Search IMDb
```
## Requirements
- DankMaterialShell >= 0.1.0
- Web browser (default system browser via `xdg-open`)
- Wayland compositor
## Compatibility
- **Compositors**: Niri and Hyprland
- **Distros**: Universal - works on any Linux distribution
- **Browsers**: Works with any default browser
## Technical Details
- **Type**: Launcher plugin
- **Trigger**: `?` (configurable)
- **Language**: QML (Qt Modeling Language)
- **Browser Launch**: Uses `xdg-open` for system default browser
## Tips & Tricks
1. **Keyword Shortcuts**: Type engine keywords first for direct search
- `github rust` instead of `? github rust`
2. **Combine Keywords**: Use multiple keywords to narrow results
- `stackoverflow rust async await`
3. **Custom Engines**: Add frequently-used sites for quick access
- Add your company's internal documentation
- Add specialized search engines for your workflow
## Contributing
Want to add more built-in search engines? Open an issue or submit a pull request!
## License
MIT License - See LICENSE file for details
## Author
Created for the DankMaterialShell community
## Links
- [DankMaterialShell](https://github.com/AvengeMedia/DankMaterialShell)
- [Plugin Registry](https://github.com/AvengeMedia/dms-plugin-registry)

View file

@ -0,0 +1,352 @@
import QtQuick
import Quickshell
import qs.Services
Item {
id: root
property var pluginService: null
property string trigger: "?"
property var searchEngines: []
property string defaultEngine: "google"
signal itemsChanged()
property var builtInEngines: [
{
id: "google",
name: "Google",
icon: "material:travel_explore",
url: "https://www.google.com/search?q=%s",
keywords: ["google", "search"]
},
{
id: "duckduckgo",
name: "DuckDuckGo",
icon: "material:shield",
url: "https://duckduckgo.com/?q=%s",
keywords: ["ddg", "duckduckgo", "privacy"]
},
{
id: "brave",
name: "Brave Search",
icon: "material:security",
url: "https://search.brave.com/search?q=%s",
keywords: ["brave", "privacy"]
},
{
id: "bing",
name: "Bing",
icon: "material:language",
url: "https://www.bing.com/search?q=%s",
keywords: ["bing", "microsoft"]
},
{
id: "youtube",
name: "YouTube",
icon: "material:youtube_activity",
url: "https://www.youtube.com/results?search_query=%s",
keywords: ["youtube", "video", "yt"]
},
{
id: "github",
name: "GitHub",
icon: "unicode:",
url: "https://github.com/search?q=%s",
keywords: ["github", "code", "git"]
},
{
id: "stackoverflow",
name: "Stack Overflow",
icon: "unicode:",
url: "https://stackoverflow.com/search?q=%s",
keywords: ["stackoverflow", "stack", "coding"]
},
{
id: "reddit",
name: "Reddit",
icon: "unicode:",
url: "https://www.reddit.com/search?q=%s",
keywords: ["reddit"]
},
{
id: "wikipedia",
name: "Wikipedia",
icon: "material:menu_book",
url: "https://en.wikipedia.org/wiki/Special:Search?search=%s",
keywords: ["wikipedia", "wiki"]
},
{
id: "amazon",
name: "Amazon",
icon: "material:shopping_cart",
url: "https://www.amazon.com/s?k=%s",
keywords: ["amazon", "shop", "shopping"]
},
{
id: "ebay",
name: "eBay",
icon: "material:local_mall",
url: "https://www.ebay.com/sch/i.html?_nkw=%s",
keywords: ["ebay", "shop", "auction"]
},
{
id: "maps",
name: "Google Maps",
icon: "material:map",
url: "https://www.google.com/maps/search/%s",
keywords: ["maps", "location", "directions"]
},
{
id: "images",
name: "Google Images",
icon: "material:photo_library",
url: "https://www.google.com/search?tbm=isch&q=%s",
keywords: ["images", "pictures", "photos"]
},
{
id: "twitter",
name: "Twitter/X",
icon: "unicode:",
url: "https://twitter.com/search?q=%s",
keywords: ["twitter", "x", "social"]
},
{
id: "linkedin",
name: "LinkedIn",
icon: "unicode:",
url: "https://www.linkedin.com/search/results/all/?keywords=%s",
keywords: ["linkedin", "job", "professional"]
},
{
id: "imdb",
name: "IMDb",
icon: "unicode:",
url: "https://www.imdb.com/find?q=%s",
keywords: ["imdb", "movies", "tv"]
},
{
id: "translate",
name: "Google Translate",
icon: "material:g_translate",
url: "https://translate.google.com/?text=%s",
keywords: ["translate", "translation"]
},
{
id: "archlinux",
name: "Arch Linux Wiki",
icon: "material:terminal",
url: "https://wiki.archlinux.org/index.php?search=%s",
keywords: ["arch", "linux", "packages"]
},
{
id: "aur",
name: "AUR",
icon: "unicode:",
url: "https://aur.archlinux.org/packages?K=%s",
keywords: ["aur", "arch", "packages"]
},
{
id: "npmjs",
name: "npm",
icon: "unicode:",
url: "https://www.npmjs.com/search?q=%s",
keywords: ["npm", "node", "javascript"]
},
{
id: "pypi",
name: "PyPI",
icon: "unicode:",
url: "https://pypi.org/search/?q=%s",
keywords: ["pypi", "python", "pip"]
},
{
id: "crates",
name: "crates.io",
icon: "material:inventory_2",
url: "https://crates.io/search?q=%s",
keywords: ["crates", "rust", "cargo"]
},
{
id: "mdn",
name: "MDN Web Docs",
icon: "material:code_blocks",
url: "https://developer.mozilla.org/en-US/search?q=%s",
keywords: ["mdn", "mozilla", "web", "docs"]
}
]
Component.onCompleted: {
loadSettings()
}
onPluginServiceChanged: {
if (pluginService) {
loadSettings()
}
}
function loadSettings() {
if (pluginService) {
trigger = pluginService.loadPluginData("webSearch", "trigger", "?")
defaultEngine = pluginService.loadPluginData("webSearch", "defaultEngine", "google")
searchEngines = pluginService.loadPluginData("webSearch", "searchEngines", [])
}
}
function getItems(query) {
const items = []
const allEngines = builtInEngines.concat(searchEngines)
if (!query || query.trim().length === 0) {
items.push({
name: "Type a search query",
icon: "unicode:🔍",
comment: "Search the web with your default engine (" + getEngineName(defaultEngine) + ")",
action: "noop",
categories: ["Web Search"]
})
for (let i = 0; i < allEngines.length; i++) {
const engine = allEngines[i]
items.push({
name: engine.name,
icon: engine.icon || "unicode:🔍",
comment: engine.keywords ? engine.keywords.join(", ") : "Search engine",
action: "noop",
categories: ["Web Search"]
})
}
return items
}
let matchedEngineId = null
let searchQuery = query.trim()
let fallbackQuery = query.trim()
for (let i = 0; i < allEngines.length; i++) {
const engine = allEngines[i]
if (engine.keywords) {
for (let k = 0; k < engine.keywords.length; k++) {
const keyword = engine.keywords[k]
if (searchQuery.toLowerCase().startsWith(keyword + " ")) {
matchedEngineId = engine.id
searchQuery = searchQuery.substring(keyword.length + 1).trim()
break
}
}
if (matchedEngineId) break
}
}
const primaryEngineId = matchedEngineId || defaultEngine
const primaryEngineObj = allEngines.find(e => e.id === primaryEngineId)
if (primaryEngineObj) {
items.push({
name: "Search with " + primaryEngineObj.name + ": " + searchQuery,
icon: primaryEngineObj.icon || "unicode:🔍",
comment: "Open in browser",
action: "search:" + primaryEngineId + ":" + searchQuery,
categories: ["Web Search"]
})
}
for (let i = 0; i < allEngines.length; i++) {
const engine = allEngines[i]
if (engine.id !== primaryEngineId) {
items.push({
name: "Search with " + engine.name + ": " + (matchedEngineId ? fallbackQuery : searchQuery),
icon: engine.icon || "material:search",
comment: "Open in browser",
action: "search:" + engine.id + ":" + (matchedEngineId ? fallbackQuery : searchQuery),
categories: ["Web Search"]
})
}
}
return items
}
function executeItem(item) {
if (!item || !item.action) {
console.warn("WebSearch: Invalid item or action")
return
}
console.log("WebSearch: Executing item:", item.name, "with action:", item.action)
const actionParts = item.action.split(":")
const actionType = actionParts[0]
switch (actionType) {
case "noop":
return
case "search":
performSearch(actionParts)
break
default:
console.warn("WebSearch: Unknown action type:", actionType)
showToast("Unknown action: " + actionType)
}
}
function performSearch(actionParts) {
const engineId = actionParts[1]
const query = actionParts.slice(2).join(":")
const allEngines = builtInEngines.concat(searchEngines)
const engine = allEngines.find(e => e.id === engineId)
if (engine) {
const encodedQuery = encodeQuery(query)
const url = engine.url.replace("%s", encodedQuery)
Quickshell.execDetached(["xdg-open", url])
showToast("Searching " + engine.name + " for: " + query)
} else {
console.warn("WebSearch: Engine not found:", engineId)
showToast("Search engine not found: " + engineId)
}
}
function showToast(message) {
if (typeof ToastService !== "undefined") {
ToastService.showInfo("Web Search", message)
} else {
console.log("WebSearch Toast:", message)
}
}
function getEngineName(engineId) {
const allEngines = builtInEngines.concat(searchEngines)
const engine = allEngines.find(e => e.id === engineId)
return engine ? engine.name : "Unknown"
}
function encodeQuery(str) {
return str.replace(/ /g, "+")
}
onTriggerChanged: {
if (pluginService) {
pluginService.savePluginData("webSearch", "trigger", trigger)
}
itemsChanged()
}
onDefaultEngineChanged: {
if (pluginService) {
pluginService.savePluginData("webSearch", "defaultEngine", defaultEngine)
}
itemsChanged()
}
onSearchEnginesChanged: {
if (pluginService) {
pluginService.savePluginData("webSearch", "searchEngines", searchEngines)
}
itemsChanged()
}
}

View file

@ -0,0 +1,727 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Widgets
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "webSearch"
property int editingIndex: -1
StyledText {
width: parent.width
text: "Web Search Settings"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
width: parent.width
text: "Search the web with built-in and custom search engines directly from the launcher."
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
StyledRect {
width: parent.width
height: 1
color: Theme.outlineVariant
}
Column {
spacing: 12
width: parent.width
StyledText {
text: "Trigger Configuration"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: noTriggerToggle.checked ? "Items will always show in the launcher (no trigger needed)." : "Set the trigger text to activate web search. Type the trigger in the launcher followed by your search query."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
Row {
spacing: 12
CheckBox {
id: noTriggerToggle
text: "No trigger (always show)"
checked: root.loadValue("noTrigger", false)
contentItem: StyledText {
text: noTriggerToggle.text
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
leftPadding: noTriggerToggle.indicator.width + 8
verticalAlignment: Text.AlignVCenter
}
indicator: StyledRect {
implicitWidth: 20
implicitHeight: 20
radius: Theme.cornerRadiusSmall
border.color: noTriggerToggle.checked ? Theme.primary : Theme.outline
border.width: 2
color: noTriggerToggle.checked ? Theme.primary : "transparent"
StyledRect {
width: 12
height: 12
anchors.centerIn: parent
radius: 2
color: Theme.onPrimary
visible: noTriggerToggle.checked
}
}
onCheckedChanged: {
root.saveValue("noTrigger", checked)
if (checked) {
root.saveValue("trigger", "")
} else {
root.saveValue("trigger", triggerField.text || "?")
}
}
}
}
Row {
spacing: 12
width: parent.width
visible: !noTriggerToggle.checked
StyledText {
text: "Trigger:"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
id: triggerField
width: 100
height: 40
text: root.loadValue("trigger", "?")
placeholderText: "?"
backgroundColor: Theme.surfaceContainer
textColor: Theme.surfaceText
onTextEdited: {
const newTrigger = text.trim()
root.saveValue("trigger", newTrigger || "?")
root.saveValue("noTrigger", newTrigger === "")
}
}
StyledText {
text: "Examples: ?, /, /search, etc."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
}
}
StyledRect {
width: parent.width
height: 1
color: Theme.outlineVariant
}
SelectionSetting {
settingKey: "defaultEngine"
label: "Default Search Engine"
description: "The search engine used when no keyword is specified"
options: [
{label: "Google", value: "google"},
{label: "DuckDuckGo", value: "duckduckgo"},
{label: "Brave Search", value: "brave"},
{label: "Bing", value: "bing"}
]
defaultValue: "google"
}
StyledRect {
width: parent.width
height: 1
color: Theme.outlineVariant
}
StyledRect {
width: parent.width
height: addEngineColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: addEngineColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
StyledText {
text: root.editingIndex === -1 ? "Create Custom Search Engine" : "Edit Custom Search Engine"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
Row {
width: parent.width
spacing: Theme.spacingM
Column {
width: (parent.width - Theme.spacingM) / 2
spacing: Theme.spacingXS
StyledText {
text: "Engine ID *"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: idField
width: parent.width
placeholderText: "e.g., myengine"
keyNavigationTab: nameField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(idField)
}
}
}
Column {
width: (parent.width - Theme.spacingM) / 2
spacing: Theme.spacingXS
StyledText {
text: "Display Name *"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: nameField
width: parent.width
placeholderText: "e.g., My Engine"
keyNavigationBacktab: idField
keyNavigationTab: iconField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(nameField)
}
}
}
}
Row {
width: parent.width
spacing: Theme.spacingM
Column {
width: (parent.width - Theme.spacingM) / 2
spacing: Theme.spacingXS
StyledText {
text: "Icon"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: iconField
width: parent.width
placeholderText: "e.g., unicode:🔍"
keyNavigationBacktab: nameField
keyNavigationTab: urlField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(iconField)
}
}
StyledText {
text: "Prefix with 'material:' for Material Symbols or 'unicode:' for emoji/Nerd Font glyphs."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
Column {
width: (parent.width - Theme.spacingM) / 2
spacing: Theme.spacingXS
StyledText {
text: "Search URL *"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: urlField
width: parent.width
placeholderText: "e.g., https://example.com/search?q=%s"
keyNavigationBacktab: iconField
keyNavigationTab: keywordsField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(urlField)
}
}
StyledText {
text: "Use %s as placeholder for search query"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: "Keywords (comma separated)"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: keywordsField
width: parent.width
placeholderText: "e.g., my,engine,search"
keyNavigationBacktab: urlField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(keywordsField)
}
}
StyledText {
text: "Use these keywords to trigger this engine (e.g., '? keyword query')"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
Row {
spacing: Theme.spacingM
DankButton {
id: addButton
text: root.editingIndex === -1 ? "Create Engine" : "Update Engine"
iconName: root.editingIndex === -1 ? "add" : "save"
onClicked: {
const id = idField.text.trim()
const name = nameField.text.trim()
const url = urlField.text.trim()
if (!id || !name || !url) {
if (typeof ToastService !== "undefined") {
ToastService.showError("Please fill in required fields (ID, Name, URL)")
}
return
}
const keywordsText = keywordsField.text.trim()
const keywords = keywordsText ? keywordsText.split(",").map(k => k.trim()).filter(k => k.length > 0) : []
const engine = {
id: id,
name: name,
icon: iconField.text.trim() || "unicode:🔍",
url: url,
keywords: keywords
}
const currentEngines = root.loadValue("searchEngines", [])
if (root.editingIndex === -1) {
const updatedEngines = currentEngines.concat([engine])
root.saveValue("searchEngines", updatedEngines)
} else {
currentEngines[root.editingIndex] = engine
root.saveValue("searchEngines", currentEngines)
root.editingIndex = -1
}
idField.text = ""
nameField.text = ""
iconField.text = ""
urlField.text = ""
keywordsField.text = ""
idField.forceActiveFocus()
}
}
DankButton {
text: "Cancel"
iconName: "close"
visible: root.editingIndex !== -1
onClicked: {
root.editingIndex = -1
idField.text = ""
nameField.text = ""
iconField.text = ""
urlField.text = ""
keywordsField.text = ""
}
}
}
}
}
StyledRect {
width: parent.width
height: Math.max(200, enginesColumn.implicitHeight + Theme.spacingL * 2)
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: enginesColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
StyledText {
text: "Existing Custom Engines"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
ListView {
width: parent.width
height: Math.max(100, contentHeight)
clip: true
spacing: Theme.spacingXS
model: root.variantsModel.count > 0 ? root.variantsModel : root.loadValue("searchEngines", [])
delegate: StyledRect {
required property var model
required property int index
width: ListView.view.width
height: engineColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: engineMouseArea.containsMouse ? Theme.surfaceContainerHighest : Theme.surfaceContainer
Column {
id: engineColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
Row {
width: parent.width
spacing: Theme.spacingM
Item {
width: Theme.iconSize
height: Theme.iconSize
anchors.verticalCenter: parent.verticalCenter
visible: false // Workaround for DankMaterialShell bug with unicode/material icons
DankIcon {
anchors.centerIn: parent
name: model.icon || "unicode:🔍"
size: Theme.iconSize
color: Theme.surfaceText
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
width: parent.width - editButton.width - deleteButton.width - Theme.spacingM * 3
StyledText {
text: model.name || "Unnamed"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
}
StyledText {
text: "ID: " + (model.id || "") + " | URL: " + (model.url || "")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
elide: Text.ElideRight
}
StyledText {
text: {
const kw = model.keywords
if (kw && kw.length > 0) {
let result = []
for (let i = 0; i < kw.length; i++) {
result.push(kw[i])
}
return "Keywords: " + result.join(", ")
}
return "No keywords"
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
elide: Text.ElideRight
}
}
Rectangle {
id: editButton
anchors.verticalCenter: parent.verticalCenter
width: 32
height: 32
radius: 16
color: editArea.containsMouse ? Theme.primary : "transparent"
DankIcon {
anchors.centerIn: parent
name: "edit"
size: 16
color: editArea.containsMouse ? Theme.onPrimary : Theme.surfaceVariantText
}
MouseArea {
id: editArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.editingIndex = index
const engine = root.loadValue("searchEngines", [])[index]
idField.text = engine.id
nameField.text = engine.name
iconField.text = engine.icon
urlField.text = engine.url
keywordsField.text = Array.isArray(engine.keywords) ? engine.keywords.join(", ") : ""
root.ensureItemVisible(idField)
}
}
}
Rectangle {
id: deleteButton
anchors.verticalCenter: parent.verticalCenter
width: 32
height: 32
radius: 16
color: deleteArea.containsMouse ? Theme.error : "transparent"
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 16
color: deleteArea.containsMouse ? Theme.onError : Theme.surfaceVariantText
}
MouseArea {
id: deleteArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const currentEngines = root.loadValue("searchEngines", [])
const updatedEngines = currentEngines.filter((_, i) => i !== index)
root.saveValue("searchEngines", updatedEngines)
}
}
}
}
}
MouseArea {
id: engineMouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
}
}
StyledText {
anchors.centerIn: parent
text: "No custom engines created yet"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
visible: parent.count === 0
}
}
}
}
StyledRect {
width: parent.width
height: 1
color: Theme.outlineVariant
}
Column {
spacing: 8
width: parent.width
StyledText {
text: "Built-in Search Engines:"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
Column {
spacing: 4
leftPadding: 16
StyledText {
text: "• Google, DuckDuckGo, Brave Search, Bing"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "• YouTube, GitHub, Stack Overflow, Reddit, Wikipedia"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "• Amazon, eBay, Google Maps, Google Images"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "• Twitter/X, LinkedIn, IMDb, Google Translate"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "• Arch Linux, AUR, npm, PyPI, crates.io, MDN"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
StyledRect {
width: parent.width
height: 1
color: Theme.outlineVariant
}
Column {
spacing: 8
width: parent.width
StyledText {
text: "Usage:"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
Column {
spacing: 4
leftPadding: 16
bottomPadding: 24
StyledText {
text: "1. Open Launcher (Ctrl+Space or click launcher button)"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: noTriggerToggle.checked ? "2. Type your search query directly" : "2. Type your trigger (default: ?) followed by search query"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: noTriggerToggle.checked ? "3. Example: 'linux kernel' or 'github rust'" : "3. Example: '? linux kernel' or '? github rust'"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "4. Use keywords for specific engines: 'youtube music', 'github project', 'wiki topic'"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "5. Select search engine and press Enter to open in browser"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
Column {
spacing: 8
width: parent.width
StyledText {
text: "Adding Custom Search Engines:"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
Column {
spacing: 4
leftPadding: 16
bottomPadding: 24
StyledText {
text: "1. Find the search URL for your desired website"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "2. Replace the search query with %s in the URL"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "3. Example: https://mysite.com/search?q=%s"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "4. Add it using the Custom Search Engines section above"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "5. Set keywords for quick access (e.g., 'mysite' or 'ms')"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
}

View file

@ -0,0 +1 @@
ref: refs/heads/main

View file

@ -0,0 +1,9 @@
[core]
bare = false
filemode = true
[remote "origin"]
url = https://github.com/devnullvoid/dms-web-search
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main

View file

@ -0,0 +1 @@
81ccd9fd8249b3c9ef40dde42549f807e36ae3e3

View file

@ -0,0 +1 @@
81ccd9fd8249b3c9ef40dde42549f807e36ae3e3

View file

@ -0,0 +1,20 @@
{
"id": "webSearch",
"name": "Web Search",
"description": "Search the web with custom search engines",
"version": "1.2.0",
"author": "devnullvoid",
"icon": "search",
"type": "launcher",
"capabilities": [
"web-search",
"custom-engines"
],
"component": "./WebSearch.qml",
"settings": "./WebSearchSettings.qml",
"trigger": "?",
"permissions": [
"settings_read",
"settings_write"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB