Laucher: Fix wayland warning about focus surface stealing
This commit is contained in:
parent
1599ee5682
commit
7548ffc191
2 changed files with 89 additions and 83 deletions
|
|
@ -174,29 +174,39 @@ NPanel {
|
||||||
anchors.margins: Style.marginL * scaling
|
anchors.margins: Style.marginL * scaling
|
||||||
spacing: Style.marginM * scaling
|
spacing: Style.marginM * scaling
|
||||||
|
|
||||||
// Wrapper ensures the input stretches to full width under RowLayout
|
FocusScope {
|
||||||
Item {
|
|
||||||
id: searchInputWrap
|
id: searchInputWrap
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: Math.round(Style.barHeight * scaling)
|
Layout.preferredHeight: Math.round(Style.barHeight * scaling)
|
||||||
|
|
||||||
// Search input
|
// This FocusScope should get focus when panel opens
|
||||||
|
focus: true
|
||||||
|
|
||||||
NTextInput {
|
NTextInput {
|
||||||
id: searchInput
|
id: searchInput
|
||||||
anchors.fill: parent // The NTextInput fills the wrapper
|
anchors.fill: parent
|
||||||
Layout.preferredHeight: Style.barHeight * scaling
|
|
||||||
|
// The input should have focus within the scope
|
||||||
|
focus: true
|
||||||
|
|
||||||
placeholderText: "Search entries... or use > for commands"
|
placeholderText: "Search entries... or use > for commands"
|
||||||
text: searchText
|
text: searchText
|
||||||
inputMaxWidth: Number.MAX_SAFE_INTEGER
|
inputMaxWidth: Number.MAX_SAFE_INTEGER
|
||||||
|
|
||||||
function forceActiveFocus() {
|
function forceActiveFocus() {
|
||||||
inputItem.forceActiveFocus()
|
// First ensure the scope has focus
|
||||||
|
searchInputWrap.forceActiveFocus()
|
||||||
|
// Then focus the actual input
|
||||||
|
if (inputItem && inputItem.visible) {
|
||||||
|
inputItem.forceActiveFocus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
inputItem.font.pointSize = Style.fontSizeL * scaling
|
if (inputItem) {
|
||||||
inputItem.verticalAlignment = TextInput.AlignVCenter
|
inputItem.font.pointSize = Style.fontSizeL * scaling
|
||||||
|
inputItem.verticalAlignment = TextInput.AlignVCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTextChanged: searchText = text
|
onTextChanged: searchText = text
|
||||||
|
|
|
||||||
|
|
@ -5,91 +5,88 @@ import qs.Services
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Plugin metadata
|
// Plugin metadata
|
||||||
property string name: "Clipboard History"
|
property string name: "Clipboard History"
|
||||||
property var launcher: null
|
property var launcher: null
|
||||||
|
|
||||||
// Plugin capabilities
|
// Plugin capabilities
|
||||||
property bool handleSearch: false // Don't handle regular search
|
property bool handleSearch: false // Don't handle regular search
|
||||||
|
|
||||||
// Initialize plugin
|
// Initialize plugin
|
||||||
function init() {
|
function init() {
|
||||||
Logger.log("ClipboardPlugin", "Initialized")
|
Logger.log("ClipboardPlugin", "Initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when launcher opens
|
// Called when launcher opens
|
||||||
function onOpened() {
|
function onOpened() {
|
||||||
// Refresh clipboard history when launcher opens
|
// Refresh clipboard history when launcher opens
|
||||||
CliphistService.list(100)
|
CliphistService.list(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this plugin handles the command
|
// Check if this plugin handles the command
|
||||||
function handleCommand(searchText) {
|
function handleCommand(searchText) {
|
||||||
return searchText.startsWith(">clip")
|
return searchText.startsWith(">clip")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return available commands when user types ">"
|
// Return available commands when user types ">"
|
||||||
function commands() {
|
function commands() {
|
||||||
return [
|
return [{
|
||||||
{
|
"name": ">clip",
|
||||||
name: ">clip",
|
"description": "Search clipboard history",
|
||||||
description: "Search clipboard history",
|
"icon": "content_paste",
|
||||||
icon: "content_paste",
|
"onActivate": function () {
|
||||||
onActivate: function() {
|
launcher.setSearchText(">clip ")
|
||||||
launcher.setSearchText(">clip ")
|
}
|
||||||
}
|
}, {
|
||||||
},
|
"name": ">clip clear",
|
||||||
{
|
"description": "Clear all clipboard history",
|
||||||
name: ">clip clear",
|
"icon": "delete_sweep",
|
||||||
description: "Clear all clipboard history",
|
"onActivate": function () {
|
||||||
icon: "delete_sweep",
|
CliphistService.wipeAll()
|
||||||
onActivate: function() {
|
launcher.close()
|
||||||
CliphistService.wipeAll()
|
}
|
||||||
launcher.close()
|
}]
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get search results
|
// Get search results
|
||||||
function getResults(searchText) {
|
function getResults(searchText) {
|
||||||
if (!searchText.startsWith(">clip")) {
|
if (!searchText.startsWith(">clip")) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = []
|
const results = []
|
||||||
const query = searchText.slice(5).trim()
|
const query = searchText.slice(5).trim()
|
||||||
|
|
||||||
// Special command: clear
|
// Special command: clear
|
||||||
if (query === "clear") {
|
if (query === "clear") {
|
||||||
return [{
|
return [{
|
||||||
name: "Clear Clipboard History",
|
"name": "Clear Clipboard History",
|
||||||
description: "Remove all items from clipboard history",
|
"description": "Remove all items from clipboard history",
|
||||||
icon: "delete_sweep",
|
"icon": "delete_sweep",
|
||||||
onActivate: function() {
|
"onActivate": function () {
|
||||||
CliphistService.wipeAll()
|
CliphistService.wipeAll()
|
||||||
launcher.close()
|
launcher.close()
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search clipboard items
|
// Search clipboard items
|
||||||
const searchTerm = query.toLowerCase()
|
const searchTerm = query.toLowerCase()
|
||||||
|
|
||||||
// Force dependency update
|
// Force dependency update
|
||||||
const _rev = CliphistService.revision
|
const _rev = CliphistService.revision
|
||||||
const items = CliphistService.items || []
|
const items = CliphistService.items || []
|
||||||
|
|
||||||
// Filter and format results
|
// Filter and format results
|
||||||
items.forEach(function(item) {
|
items.forEach(function (item) {
|
||||||
const preview = (item.preview || "").toLowerCase()
|
const preview = (item.preview || "").toLowerCase()
|
||||||
|
|
||||||
// Skip if search term doesn't match
|
// Skip if search term doesn't match
|
||||||
if (searchTerm && preview.indexOf(searchTerm) === -1) {
|
if (searchTerm && preview.indexOf(searchTerm) === -1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format the result based on type
|
// Format the result based on type
|
||||||
let entry
|
let entry
|
||||||
if (item.isImage) {
|
if (item.isImage) {
|
||||||
|
|
@ -97,53 +94,52 @@ QtObject {
|
||||||
} else {
|
} else {
|
||||||
entry = formatTextEntry(item)
|
entry = formatTextEntry(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add activation handler
|
// Add activation handler
|
||||||
entry.onActivate = function() {
|
entry.onActivate = function () {
|
||||||
CliphistService.copyToClipboard(item.id)
|
CliphistService.copyToClipboard(item.id)
|
||||||
launcher.close()
|
launcher.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
results.push(entry)
|
results.push(entry)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Show empty state if no results
|
// Show empty state if no results
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
results.push({
|
results.push({
|
||||||
name: searchTerm ? "No matching clipboard items" : "Clipboard is empty",
|
"name": searchTerm ? "No matching clipboard items" : "Clipboard is empty",
|
||||||
description: searchTerm ? `No items containing "${query}"` : "Copy something to see it here",
|
"description": searchTerm ? `No items containing "${query}"` : "Copy something to see it here",
|
||||||
icon: "content_paste_off",
|
"icon": "content_paste_off",
|
||||||
onActivate: function() {
|
"onActivate": function () {// Do nothing
|
||||||
// Do nothing
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Format image clipboard entry
|
// Helper: Format image clipboard entry
|
||||||
function formatImageEntry(item) {
|
function formatImageEntry(item) {
|
||||||
const meta = parseImageMeta(item.preview)
|
const meta = parseImageMeta(item.preview)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: meta ? `Image ${meta.w}×${meta.h}` : "Image",
|
"name": meta ? `Image ${meta.w}×${meta.h}` : "Image",
|
||||||
description: meta ? `${meta.fmt} • ${meta.size}` : item.mime || "Image data",
|
"description": meta ? `${meta.fmt} • ${meta.size}` : item.mime || "Image data",
|
||||||
icon: "image"
|
"icon": "image"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Format text clipboard entry
|
// Helper: Format text clipboard entry
|
||||||
function formatTextEntry(item) {
|
function formatTextEntry(item) {
|
||||||
const preview = (item.preview || "").trim()
|
const preview = (item.preview || "").trim()
|
||||||
const lines = preview.split('\n').filter(l => l.trim())
|
const lines = preview.split('\n').filter(l => l.trim())
|
||||||
|
|
||||||
// Use first line as title, limit length
|
// Use first line as title, limit length
|
||||||
let title = lines[0] || "Empty text"
|
let title = lines[0] || "Empty text"
|
||||||
if (title.length > 60) {
|
if (title.length > 60) {
|
||||||
title = title.substring(0, 57) + "..."
|
title = title.substring(0, 57) + "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use second line or character count as description
|
// Use second line or character count as description
|
||||||
let description = ""
|
let description = ""
|
||||||
if (lines.length > 1) {
|
if (lines.length > 1) {
|
||||||
|
|
@ -156,28 +152,28 @@ QtObject {
|
||||||
const words = preview.split(/\s+/).length
|
const words = preview.split(/\s+/).length
|
||||||
description = `${chars} characters, ${words} word${words !== 1 ? 's' : ''}`
|
description = `${chars} characters, ${words} word${words !== 1 ? 's' : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: title,
|
"name": title,
|
||||||
description: description,
|
"description": description,
|
||||||
icon: "description"
|
"icon": "description"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Parse image metadata from preview string
|
// Helper: Parse image metadata from preview string
|
||||||
function parseImageMeta(preview) {
|
function parseImageMeta(preview) {
|
||||||
const re = /\[\[\s*binary data\s+([\d\.]+\s*(?:KiB|MiB|GiB|B))\s+(\w+)\s+(\d+)x(\d+)\s*\]\]/i
|
const re = /\[\[\s*binary data\s+([\d\.]+\s*(?:KiB|MiB|GiB|B))\s+(\w+)\s+(\d+)x(\d+)\s*\]\]/i
|
||||||
const match = (preview || "").match(re)
|
const match = (preview || "").match(re)
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
size: match[1],
|
"size": match[1],
|
||||||
fmt: (match[2] || "").toUpperCase(),
|
"fmt": (match[2] || "").toUpperCase(),
|
||||||
w: Number(match[3]),
|
"w": Number(match[3]),
|
||||||
h: Number(match[4])
|
"h": Number(match[4])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue