mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
feat: add QR Generator launcher (#2941)
* feat: add QR Generator modal with auto-generate on input debounce - QRGeneratorModal with dual-buffer sequential fade animation - Auto-generate QR code on typing stop (200ms debounce) - Clear text button on input field - Escape key closes modal - Register modal in PopoutService and AppSearchService - Go backend for text QR code generation * fix: add QR Generator toggle to Launcher DMS settings * qr: also add qrg built in launcher plugin --------- Co-authored-by: bbedward <bbedward@gmail.com>
This commit is contained in:
@@ -43,6 +43,8 @@ func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) {
|
|||||||
handleGetNetworkQRCode(conn, req, manager)
|
handleGetNetworkQRCode(conn, req, manager)
|
||||||
case "network.qrcode-content":
|
case "network.qrcode-content":
|
||||||
handleGetNetworkQRCodeContent(conn, req, manager)
|
handleGetNetworkQRCodeContent(conn, req, manager)
|
||||||
|
case "network.generate-qrcode":
|
||||||
|
handleGenerateQRCode(conn, req)
|
||||||
case "network.delete-qrcode":
|
case "network.delete-qrcode":
|
||||||
handleDeleteQRCode(conn, req, manager)
|
handleDeleteQRCode(conn, req, manager)
|
||||||
case "network.ethernet.info":
|
case "network.ethernet.info":
|
||||||
@@ -365,6 +367,22 @@ func handleGetNetworkQRCodeContent(conn *models.Conn, req models.Request, manage
|
|||||||
models.Respond(conn, req.ID, content)
|
models.Respond(conn, req.ID, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleGenerateQRCode(conn *models.Conn, req models.Request) {
|
||||||
|
text, err := params.String(req.Params, "text")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paths, err := generateTextQRCode(text)
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
models.Respond(conn, req.ID, paths)
|
||||||
|
}
|
||||||
|
|
||||||
func handleDeleteQRCode(conn *models.Conn, req models.Request, _ *Manager) {
|
func handleDeleteQRCode(conn *models.Conn, req models.Request, _ *Manager) {
|
||||||
path, err := params.String(req.Params, "path")
|
path, err := params.String(req.Params, "path")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/yeqown/go-qrcode/v2"
|
||||||
|
"github.com/yeqown/go-qrcode/writer/standard"
|
||||||
|
)
|
||||||
|
|
||||||
|
const textQRCodeTmpPrefix = "/tmp/dank-text-qrcode-"
|
||||||
|
|
||||||
|
func generateTextQRCode(text string) ([2]string, error) {
|
||||||
|
qrc, err := qrcode.New(text)
|
||||||
|
if err != nil {
|
||||||
|
return [2]string{}, fmt.Errorf("failed to create QR code for text: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pathThemed, pathNormal := textQRCodePaths(text)
|
||||||
|
|
||||||
|
if err := saveQRCodePNG(qrc, pathThemed, standard.WithBgTransparent(), standard.WithFgColorRGBHex("#ffffff")); err != nil {
|
||||||
|
return [2]string{}, err
|
||||||
|
}
|
||||||
|
if err := saveQRCodePNG(qrc, pathNormal); err != nil {
|
||||||
|
return [2]string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return [2]string{pathThemed, pathNormal}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to a temp file and rename into place so the shell's Image never
|
||||||
|
// observes a partially written PNG.
|
||||||
|
func saveQRCodePNG(qrc *qrcode.QRCode, path string, opts ...standard.ImageOption) error {
|
||||||
|
tmpPath := path + ".tmp"
|
||||||
|
opts = append(opts, standard.WithBuiltinImageEncoder(standard.PNG_FORMAT))
|
||||||
|
|
||||||
|
w, err := standard.New(tmpPath, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create QR code writer: %w", err)
|
||||||
|
}
|
||||||
|
if err := qrc.Save(w); err != nil {
|
||||||
|
os.Remove(tmpPath)
|
||||||
|
return fmt.Errorf("failed to save QR code: %w", err)
|
||||||
|
}
|
||||||
|
if err := os.Rename(tmpPath, path); err != nil {
|
||||||
|
os.Remove(tmpPath)
|
||||||
|
return fmt.Errorf("failed to move QR code into place: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paths are unique per generation, not per text: the library's mask selection
|
||||||
|
// is non-deterministic, so regenerating the same text produces different
|
||||||
|
// bytes, and reusing a path lets the shell's URL-keyed pixmap cache serve a
|
||||||
|
// stale pattern over the new file.
|
||||||
|
func textQRCodePaths(text string) (themed, normal string) {
|
||||||
|
hash := fmt.Sprintf("%x", sha256.Sum256([]byte(text)))[:8]
|
||||||
|
nonce := time.Now().UnixNano()
|
||||||
|
themed = fmt.Sprintf("%s%s-%d-themed.png", textQRCodeTmpPrefix, hash, nonce)
|
||||||
|
normal = fmt.Sprintf("%s%s-%d-normal.png", textQRCodeTmpPrefix, hash, nonce)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ func qrCodePaths(ssid string) (themed, normal string) {
|
|||||||
|
|
||||||
func isValidQRCodePath(path string) bool {
|
func isValidQRCodePath(path string) bool {
|
||||||
clean := filepath.Clean(path)
|
clean := filepath.Clean(path)
|
||||||
return strings.HasPrefix(clean, qrCodeTmpPrefix) && strings.HasSuffix(clean, ".png")
|
return (strings.HasPrefix(clean, qrCodeTmpPrefix) || strings.HasPrefix(clean, textQRCodeTmpPrefix)) && strings.HasSuffix(clean, ".png")
|
||||||
}
|
}
|
||||||
|
|
||||||
var safePathChar = regexp.MustCompile(`[^a-zA-Z0-9_-]`)
|
var safePathChar = regexp.MustCompile(`[^a-zA-Z0-9_-]`)
|
||||||
|
|||||||
@@ -445,6 +445,23 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LazyLoader {
|
||||||
|
id: qrGeneratorModalLoader
|
||||||
|
active: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.qrGeneratorModalLoader = qrGeneratorModalLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRGeneratorModal {
|
||||||
|
id: qrGeneratorModalItem
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.qrGeneratorModal = qrGeneratorModalItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LazyLoader {
|
LazyLoader {
|
||||||
id: polkitAuthModalLoader
|
id: polkitAuthModalLoader
|
||||||
active: false
|
active: false
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Effects
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Modals.Common
|
||||||
|
import qs.Modals.FileBrowser
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
DankModal {
|
||||||
|
id: root
|
||||||
|
visible: false
|
||||||
|
layerNamespace: "dms:qr-generator"
|
||||||
|
|
||||||
|
property bool disablePopupTransparency: true
|
||||||
|
property bool generating: false
|
||||||
|
property string themedQrCodePath: ""
|
||||||
|
property string normalQrCodePath: ""
|
||||||
|
property string initialText: ""
|
||||||
|
property string _pendingPayload: ""
|
||||||
|
property string _generatingPayload: ""
|
||||||
|
property string _displayedPayload: ""
|
||||||
|
modalWidth: 420
|
||||||
|
modalHeight: 440
|
||||||
|
onBackgroundClicked: hide()
|
||||||
|
onOpened: {
|
||||||
|
Qt.callLater(() => {
|
||||||
|
modalFocusScope.forceActiveFocus();
|
||||||
|
const item = contentLoader.item;
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
item.saveBrowserLoader = saveBrowserLoader;
|
||||||
|
if (item.textInput) {
|
||||||
|
item.textInput.text = initialText;
|
||||||
|
item.textInput.forceActiveFocus();
|
||||||
|
}
|
||||||
|
if (initialText.length > 0) {
|
||||||
|
_pendingPayload = initialText;
|
||||||
|
generateQR(initialText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function show(text) {
|
||||||
|
generating = false;
|
||||||
|
initialText = text || "";
|
||||||
|
_pendingPayload = "";
|
||||||
|
_generatingPayload = "";
|
||||||
|
_displayedPayload = "";
|
||||||
|
themedQrCodePath = "";
|
||||||
|
normalQrCodePath = "";
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
deleteQrCodeFiles(themedQrCodePath, normalQrCodePath);
|
||||||
|
themedQrCodePath = "";
|
||||||
|
normalQrCodePath = "";
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteQrCodeFiles(themed, normal) {
|
||||||
|
if (themed.length > 0)
|
||||||
|
DMSService.sendRequest("network.delete-qrcode", {
|
||||||
|
path: themed
|
||||||
|
});
|
||||||
|
if (normal.length > 0)
|
||||||
|
DMSService.sendRequest("network.delete-qrcode", {
|
||||||
|
path: normal
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: genTimer
|
||||||
|
interval: 200
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.generateQR(root._pendingPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateQR(text) {
|
||||||
|
const trimmed = (text || "").trim();
|
||||||
|
if (trimmed.length === 0 || trimmed === _displayedPayload || generating)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_generatingPayload = trimmed;
|
||||||
|
generating = true;
|
||||||
|
|
||||||
|
DMSService.sendRequest("network.generate-qrcode", {
|
||||||
|
text: trimmed
|
||||||
|
}, response => {
|
||||||
|
root.generating = false;
|
||||||
|
if (response.error) {
|
||||||
|
ToastService.showError(I18n.tr("Failed to generate QR code: %1").arg(JSON.stringify(response.error)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!response.result)
|
||||||
|
return;
|
||||||
|
if (root._generatingPayload !== root._pendingPayload.trim()) {
|
||||||
|
root.deleteQrCodeFiles(response.result[0], response.result[1]);
|
||||||
|
genTimer.restart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldThemed = root.themedQrCodePath;
|
||||||
|
const oldNormal = root.normalQrCodePath;
|
||||||
|
root._displayedPayload = root._generatingPayload;
|
||||||
|
root.themedQrCodePath = response.result[0];
|
||||||
|
root.normalQrCodePath = response.result[1];
|
||||||
|
root.deleteQrCodeFiles(oldThemed, oldNormal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTextChanged(text) {
|
||||||
|
_pendingPayload = text;
|
||||||
|
const trimmed = text.trim();
|
||||||
|
if (trimmed.length === 0 || trimmed === _displayedPayload) {
|
||||||
|
genTimer.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
genTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyLoader {
|
||||||
|
id: saveBrowserLoader
|
||||||
|
active: false
|
||||||
|
|
||||||
|
FileBrowserSurfaceModal {
|
||||||
|
id: saveBrowser
|
||||||
|
|
||||||
|
browserTitle: I18n.tr("Save QR Code")
|
||||||
|
browserIcon: "qr_code"
|
||||||
|
browserType: "default"
|
||||||
|
fileExtensions: ["*.png"]
|
||||||
|
allowStacking: true
|
||||||
|
saveMode: true
|
||||||
|
defaultFileName: "qrcode.png"
|
||||||
|
onFileSelected: path => {
|
||||||
|
const cleanPath = decodeURI(path.toString().replace(/^file:\/\//, ''));
|
||||||
|
copyQrCodeProcess.exec(["cp", "-f", root.normalQrCodePath, cleanPath]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: copyQrCodeProcess
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
saveBrowser.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content: Component {
|
||||||
|
Item {
|
||||||
|
id: contentItem
|
||||||
|
|
||||||
|
property alias textInput: textInput
|
||||||
|
property var saveBrowserLoader: null
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: modalTitle
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("QR Generator")
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Bold
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
onClicked: root.hide()
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: textInput
|
||||||
|
width: parent.width
|
||||||
|
placeholderText: I18n.tr("Enter text to encode")
|
||||||
|
showClearButton: true
|
||||||
|
focus: true
|
||||||
|
onTextEdited: root.onTextChanged(text)
|
||||||
|
Keys.onEscapePressed: event => {
|
||||||
|
event.accepted = true;
|
||||||
|
root.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: qrContainer
|
||||||
|
height: Math.min(parent.height - parent.spacing - modalTitle.height - textInput.height - parent.spacing * 4, 260)
|
||||||
|
width: height
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 80
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: qrCodeImg
|
||||||
|
anchors.fill: parent
|
||||||
|
source: root.themedQrCodePath
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
asynchronous: true
|
||||||
|
cache: false
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
onSourceChanged: qrContainer.opacity = 0
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Ready)
|
||||||
|
qrContainer.opacity = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiEffect {
|
||||||
|
source: qrCodeImg
|
||||||
|
anchors.fill: qrCodeImg
|
||||||
|
colorization: 1.0
|
||||||
|
colorizationColor: Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
width: parent.width
|
||||||
|
visible: root.themedQrCodePath.length > 0
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Save")
|
||||||
|
iconName: "save"
|
||||||
|
backgroundColor: Theme.surfaceContainer
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
onClicked: {
|
||||||
|
contentItem.saveBrowserLoader.active = true;
|
||||||
|
if (contentItem.saveBrowserLoader.item) {
|
||||||
|
contentItem.saveBrowserLoader.item.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Copy")
|
||||||
|
iconName: "content_copy"
|
||||||
|
backgroundColor: Theme.primary
|
||||||
|
textColor: Theme.onPrimary
|
||||||
|
onClicked: {
|
||||||
|
if (root.normalQrCodePath.length > 0)
|
||||||
|
DMSService.sendRequest("clipboard.copyFile", {
|
||||||
|
filePath: root.normalQrCodePath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -822,7 +822,7 @@ Item {
|
|||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ["dms_settings", "dms_notepad", "dms_sysmon", "dms_settings_search", "dms_clipboard_search", "dms_colorpicker"]
|
model: ["dms_settings", "dms_notepad", "dms_sysmon", "dms_settings_search", "dms_clipboard_search", "dms_colorpicker", "dms_qr_generator"]
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
id: pluginDelegate
|
id: pluginDelegate
|
||||||
|
|||||||
@@ -210,6 +210,19 @@ Singleton {
|
|||||||
defaultTrigger: "",
|
defaultTrigger: "",
|
||||||
isLauncher: false
|
isLauncher: false
|
||||||
},
|
},
|
||||||
|
"dms_qr_generator": {
|
||||||
|
id: "dms_qr_generator",
|
||||||
|
name: I18n.tr("QR Generator"),
|
||||||
|
icon: "svg+corner:" + dmsLogoPath + "|qr_code",
|
||||||
|
cornerIcon: "qr_code",
|
||||||
|
comment: "DMS",
|
||||||
|
action: "ipc:qr-generator",
|
||||||
|
categories: ["Utility"],
|
||||||
|
defaultTrigger: "qrg",
|
||||||
|
isLauncher: true,
|
||||||
|
viewMode: "list",
|
||||||
|
viewModeEnforced: true
|
||||||
|
},
|
||||||
"dms_settings_search": {
|
"dms_settings_search": {
|
||||||
id: "dms_settings_search",
|
id: "dms_settings_search",
|
||||||
name: I18n.tr("Settings Search"),
|
name: I18n.tr("Settings Search"),
|
||||||
@@ -244,7 +257,7 @@ Singleton {
|
|||||||
if (!SettingsData.getBuiltInPluginSetting(pluginId, "enabled", true))
|
if (!SettingsData.getBuiltInPluginSetting(pluginId, "enabled", true))
|
||||||
continue;
|
continue;
|
||||||
const plugin = builtInPlugins[pluginId];
|
const plugin = builtInPlugins[pluginId];
|
||||||
if (plugin.isLauncher)
|
if (plugin.isLauncher && !plugin.action)
|
||||||
continue;
|
continue;
|
||||||
apps.push({
|
apps.push({
|
||||||
name: plugin.name,
|
name: plugin.name,
|
||||||
@@ -309,6 +322,20 @@ Singleton {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pluginId === "dms_qr_generator") {
|
||||||
|
const text = (query || "").toString().trim();
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: text.length > 0 ? text : I18n.tr("Enter text to encode"),
|
||||||
|
icon: "material:qr_code",
|
||||||
|
comment: I18n.tr("QR Generator"),
|
||||||
|
action: "qr_generate:" + text,
|
||||||
|
isBuiltInLauncher: true,
|
||||||
|
builtInPluginId: pluginId
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
if (pluginId !== "dms_settings_search")
|
if (pluginId !== "dms_settings_search")
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
@@ -340,15 +367,21 @@ Singleton {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const parts = item.action.split(":");
|
const parts = item.action.split(":");
|
||||||
if (parts[0] !== "settings_nav")
|
switch (parts[0]) {
|
||||||
return false;
|
case "settings_nav":
|
||||||
|
{
|
||||||
const tabIndex = parseInt(parts[1]);
|
const tabIndex = parseInt(parts[1]);
|
||||||
const section = parts.slice(2).join(":");
|
const section = parts.slice(2).join(":");
|
||||||
SettingsSearchService.navigateToSection(section);
|
SettingsSearchService.navigateToSection(section);
|
||||||
PopoutService.openSettingsWithTabIndex(tabIndex);
|
PopoutService.openSettingsWithTabIndex(tabIndex);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case "qr_generate":
|
||||||
|
PopoutService.showQRGeneratorModal(parts.slice(1).join(":"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function getCoreApps(query) {
|
function getCoreApps(query) {
|
||||||
if (!query || query.length === 0)
|
if (!query || query.length === 0)
|
||||||
@@ -378,6 +411,9 @@ Singleton {
|
|||||||
case "color-picker":
|
case "color-picker":
|
||||||
PopoutService.showColorPicker();
|
PopoutService.showColorPicker();
|
||||||
return true;
|
return true;
|
||||||
|
case "qr-generator":
|
||||||
|
PopoutService.showQRGeneratorModal();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ Singleton {
|
|||||||
property var wifiPasswordModalLoader: null
|
property var wifiPasswordModalLoader: null
|
||||||
property var wifiQRCodeModal: null
|
property var wifiQRCodeModal: null
|
||||||
property var wifiQRCodeModalLoader: null
|
property var wifiQRCodeModalLoader: null
|
||||||
|
property var qrGeneratorModal: null
|
||||||
|
property var qrGeneratorModalLoader: null
|
||||||
property var polkitAuthModal: null
|
property var polkitAuthModal: null
|
||||||
property var polkitAuthModalLoader: null
|
property var polkitAuthModalLoader: null
|
||||||
property var bluetoothPairingModal: null
|
property var bluetoothPairingModal: null
|
||||||
@@ -891,6 +893,13 @@ Singleton {
|
|||||||
wifiQRCodeModal.show(ssid);
|
wifiQRCodeModal.show(ssid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showQRGeneratorModal(initialText) {
|
||||||
|
if (qrGeneratorModalLoader)
|
||||||
|
qrGeneratorModalLoader.active = true;
|
||||||
|
if (qrGeneratorModal)
|
||||||
|
qrGeneratorModal.show(initialText || "");
|
||||||
|
}
|
||||||
|
|
||||||
function showHiddenNetworkModal() {
|
function showHiddenNetworkModal() {
|
||||||
if (wifiPasswordModalLoader)
|
if (wifiPasswordModalLoader)
|
||||||
wifiPasswordModalLoader.active = true;
|
wifiPasswordModalLoader.active = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user