mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-04 12:52:06 -04:00
feat(cups): add manual printer addition by IP/hostname (#1868)
Add a new "Add by Address" flow in the printer settings that allows users to manually add printers by IP address or hostname, enabling printing to devices not visible via mDNS/Avahi discovery (e.g., printers behind Tailscale subnet routers, VPNs, or across network boundaries). Go backend: - New cups.testConnection IPC method that probes remote printers via IPP Get-Printer-Attributes with /ipp/print then / fallback - Input validation with host sanitization and protocol allowlist - Auth-aware probing (HTTP 401/403 reported as reachable) - lpadmin CLI fallback for CreatePrinter/DeletePrinter when cups-pk-helper polkit authorization fails QML frontend: - "Add by Address" toggle alongside existing device discovery - Manual entry form with host, port, protocol fields - Test Connection button with loading state and result display - Smart PPD auto-selection by probed makeModel with driverless fallback - All strings use I18n.tr() with translator context Includes 20+ unit tests covering validation, probe delegation, TLS flag propagation, auth error detection, and handler routing.
This commit is contained in:
@@ -14,6 +14,12 @@ Item {
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property bool showAddPrinter: false
|
||||
property bool manualEntryMode: false
|
||||
property string manualHost: ""
|
||||
property string manualPort: "631"
|
||||
property string manualProtocol: "ipp"
|
||||
property bool testingConnection: false
|
||||
property var testConnectionResult: null
|
||||
property string newPrinterName: ""
|
||||
property string selectedDeviceUri: ""
|
||||
property var selectedDevice: null
|
||||
@@ -23,6 +29,12 @@ Item {
|
||||
property var suggestedPPDs: []
|
||||
|
||||
function resetAddPrinterForm() {
|
||||
manualEntryMode = false;
|
||||
manualHost = "";
|
||||
manualPort = "631";
|
||||
manualProtocol = "ipp";
|
||||
testingConnection = false;
|
||||
testConnectionResult = null;
|
||||
newPrinterName = "";
|
||||
selectedDeviceUri = "";
|
||||
selectedDevice = null;
|
||||
@@ -32,6 +44,45 @@ Item {
|
||||
suggestedPPDs = [];
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CupsService
|
||||
function onPpdsChanged() {
|
||||
if (printerTab.manualEntryMode && printerTab.testConnectionResult?.success)
|
||||
printerTab.selectDriverlessPPD();
|
||||
}
|
||||
}
|
||||
|
||||
function selectDriverlessPPD() {
|
||||
if (printerTab.selectedPpd || CupsService.ppds.length === 0)
|
||||
return;
|
||||
|
||||
const probeModel = printerTab.testConnectionResult?.data?.makeModel || "";
|
||||
let suggested = [];
|
||||
|
||||
// Try to find a model-specific PPD match
|
||||
if (probeModel) {
|
||||
const normalizedModel = probeModel.toLowerCase().replace(/[^a-z0-9]/g, "");
|
||||
const modelMatches = CupsService.ppds.filter(p => {
|
||||
const normalizedPPD = (p.makeModel || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
||||
return normalizedPPD.includes(normalizedModel) || normalizedModel.includes(normalizedPPD);
|
||||
});
|
||||
if (modelMatches.length > 0)
|
||||
suggested = suggested.concat(modelMatches);
|
||||
}
|
||||
|
||||
// Always include driverless as an option
|
||||
const driverless = CupsService.ppds.filter(p => p.name === "driverless" || p.name === "everywhere");
|
||||
for (const d of driverless) {
|
||||
if (!suggested.find(s => s.name === d.name))
|
||||
suggested.push(d);
|
||||
}
|
||||
|
||||
if (suggested.length > 0) {
|
||||
printerTab.selectedPpd = suggested[0].name;
|
||||
printerTab.suggestedPPDs = suggested;
|
||||
}
|
||||
}
|
||||
|
||||
function selectDevice(device) {
|
||||
if (!device)
|
||||
return;
|
||||
@@ -276,9 +327,93 @@ Item {
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
width: discoverRow.width + Theme.spacingM * 2
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: !printerTab.manualEntryMode ? Theme.primary : (discoverArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight)
|
||||
|
||||
Row {
|
||||
id: discoverRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "search"
|
||||
size: 16
|
||||
color: !printerTab.manualEntryMode ? Theme.onPrimary : Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Discover Devices", "Toggle button to scan for printers via mDNS/Avahi")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: !printerTab.manualEntryMode ? Theme.onPrimary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: discoverArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
printerTab.manualEntryMode = false;
|
||||
printerTab.testConnectionResult = null;
|
||||
printerTab.testingConnection = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: manualRow.width + Theme.spacingM * 2
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: printerTab.manualEntryMode ? Theme.primary : (manualArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight)
|
||||
|
||||
Row {
|
||||
id: manualRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "edit"
|
||||
size: 16
|
||||
color: printerTab.manualEntryMode ? Theme.onPrimary : Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Add by Address", "Toggle button to manually add a printer by IP or hostname")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: printerTab.manualEntryMode ? Theme.onPrimary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: manualArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
printerTab.manualEntryMode = true;
|
||||
printerTab.selectedDevice = null;
|
||||
printerTab.selectedDeviceUri = "";
|
||||
if (CupsService.ppds.length === 0)
|
||||
CupsService.getPPDs();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: !printerTab.manualEntryMode
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
@@ -351,6 +486,202 @@ Item {
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: printerTab.manualEntryMode
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Host", "Label for printer IP address or hostname input field")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: 80
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: parent.width - 80 - Theme.spacingS
|
||||
placeholderText: I18n.tr("IP address or hostname", "Placeholder text for manual printer address input")
|
||||
text: printerTab.manualHost
|
||||
onTextEdited: {
|
||||
printerTab.manualHost = text;
|
||||
printerTab.testConnectionResult = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Port", "Label for printer port number input field")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: 80
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: 80
|
||||
placeholderText: "631"
|
||||
text: printerTab.manualPort
|
||||
onTextEdited: {
|
||||
printerTab.manualPort = text;
|
||||
printerTab.testConnectionResult = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Protocol", "Label for printer protocol selector, e.g. ipp, ipps, lpd, socket")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: 80
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
id: protocolDropdown
|
||||
dropdownWidth: 120
|
||||
popupWidth: 120
|
||||
currentValue: printerTab.manualProtocol
|
||||
options: ["ipp", "ipps", "lpd", "socket"]
|
||||
onValueChanged: value => {
|
||||
printerTab.manualProtocol = value;
|
||||
printerTab.testConnectionResult = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: 80
|
||||
height: 1
|
||||
}
|
||||
|
||||
DankButton {
|
||||
text: printerTab.testingConnection ? I18n.tr("Testing...", "Button state while testing printer connection") : I18n.tr("Test Connection", "Button to test connection to a printer by IP address")
|
||||
iconName: printerTab.testingConnection ? "sync" : "lan"
|
||||
buttonHeight: 36
|
||||
enabled: printerTab.manualHost.length > 0 && !printerTab.testingConnection
|
||||
onClicked: {
|
||||
printerTab.testingConnection = true;
|
||||
printerTab.testConnectionResult = null;
|
||||
const port = parseInt(printerTab.manualPort) || 631;
|
||||
CupsService.testConnection(printerTab.manualHost, port, printerTab.manualProtocol, response => {
|
||||
printerTab.testingConnection = false;
|
||||
if (response.error) {
|
||||
printerTab.testConnectionResult = {
|
||||
"success": false,
|
||||
"error": response.error
|
||||
};
|
||||
} else if (response.result) {
|
||||
printerTab.testConnectionResult = {
|
||||
"success": response.result.reachable === true,
|
||||
"data": response.result
|
||||
};
|
||||
if (response.result.reachable) {
|
||||
if (response.result.uri)
|
||||
printerTab.selectedDeviceUri = response.result.uri;
|
||||
if (response.result.name && !printerTab.newPrinterName)
|
||||
printerTab.newPrinterName = response.result.name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 32) || "Printer";
|
||||
// Load PPDs if not loaded yet, then select driverless
|
||||
if (CupsService.ppds.length === 0) {
|
||||
CupsService.getPPDs();
|
||||
}
|
||||
selectDriverlessPPD();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
visible: printerTab.testConnectionResult !== null
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: 80
|
||||
height: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 8
|
||||
height: 8
|
||||
radius: 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: printerTab.testConnectionResult?.success ? Theme.success : Theme.error
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: printerTab.testConnectionResult?.success ? I18n.tr("Printer reachable", "Status message when test connection to printer succeeds") : I18n.tr("Connection failed", "Status message when test connection to printer fails")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: printerTab.testConnectionResult?.success ? Theme.success : Theme.error
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
visible: printerTab.testConnectionResult?.success && (printerTab.testConnectionResult?.data?.makeModel || printerTab.testConnectionResult?.data?.info)
|
||||
|
||||
Item {
|
||||
width: 80
|
||||
height: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: printerTab.testConnectionResult?.data?.makeModel || printerTab.testConnectionResult?.data?.info || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
visible: !printerTab.testConnectionResult?.success && printerTab.testConnectionResult?.data?.error
|
||||
|
||||
Item {
|
||||
width: 80
|
||||
height: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: printerTab.testConnectionResult?.data?.error || printerTab.testConnectionResult?.error || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.parent.width - 80 - Theme.spacingS
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
@@ -479,6 +479,21 @@ Singleton {
|
||||
});
|
||||
}
|
||||
|
||||
function testConnection(host, port, protocol, callback) {
|
||||
if (!cupsAvailable)
|
||||
return;
|
||||
const params = {
|
||||
"host": host,
|
||||
"port": port,
|
||||
"protocol": protocol
|
||||
};
|
||||
|
||||
DMSService.sendRequest("cups.testConnection", params, response => {
|
||||
if (callback)
|
||||
callback(response);
|
||||
});
|
||||
}
|
||||
|
||||
function createPrinter(name, deviceURI, ppd, options) {
|
||||
if (!cupsAvailable)
|
||||
return;
|
||||
|
||||
@@ -659,6 +659,12 @@
|
||||
"reference": "Modules/Settings/DesktopWidgetsTab.qml:84",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Add by Address",
|
||||
"context": "Toggle button to manually add a printer by IP or hostname",
|
||||
"reference": "Modules/Settings/PrinterTab.qml:351",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Adjust the number of columns in grid view mode.",
|
||||
"context": "Adjust the number of columns in grid view mode.",
|
||||
@@ -2429,6 +2435,12 @@
|
||||
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:297",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Connection failed",
|
||||
"context": "Status message when test connection to printer fails",
|
||||
"reference": "Modules/Settings/PrinterTab.qml:603",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Contains",
|
||||
"context": "notification rule match type option",
|
||||
@@ -3293,6 +3305,12 @@
|
||||
"reference": "Services/DMSNetworkService.qml:480",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Discover Devices",
|
||||
"context": "Toggle button to scan for printers via mDNS/Avahi",
|
||||
"reference": "Modules/Settings/PrinterTab.qml:313",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Disk",
|
||||
"context": "Disk",
|
||||
@@ -5267,6 +5285,12 @@
|
||||
"reference": "Modals/FileBrowser/FileBrowserContent.qml:241",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Host",
|
||||
"context": "Label for printer IP address or hostname input field",
|
||||
"reference": "Modules/Settings/PrinterTab.qml:462",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Hostname",
|
||||
"context": "system info label",
|
||||
@@ -5345,6 +5369,12 @@
|
||||
"reference": "Modules/Settings/NetworkTab.qml:943",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "IP address or hostname",
|
||||
"context": "Placeholder text for manual printer address input",
|
||||
"reference": "Modules/Settings/PrinterTab.qml:472",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "ISO Date",
|
||||
"context": "date format option",
|
||||
@@ -8261,6 +8291,12 @@
|
||||
"reference": "Modals/Greeter/GreeterCompletePage.qml:398",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Port",
|
||||
"context": "Label for printer port number input field",
|
||||
"reference": "Modules/Settings/PrinterTab.qml:486",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Portal",
|
||||
"context": "wallpaper transition option",
|
||||
@@ -8483,6 +8519,12 @@
|
||||
"reference": "Modules/Settings/PrinterTab.qml:433",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Printer reachable",
|
||||
"context": "Status message when test connection to printer succeeds",
|
||||
"reference": "Modules/Settings/PrinterTab.qml:603",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Printers",
|
||||
"context": "Printers",
|
||||
@@ -10775,6 +10817,12 @@
|
||||
"reference": "Modules/Settings/ThemeColorsTab.qml:1944",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Test Connection",
|
||||
"context": "Button to test connection to a printer by IP address",
|
||||
"reference": "Modules/Settings/PrinterTab.qml:541",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Test Page",
|
||||
"context": "Test Page",
|
||||
@@ -10787,6 +10835,12 @@
|
||||
"reference": "Services/CupsService.qml:627",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Testing...",
|
||||
"context": "Button state while testing printer connection",
|
||||
"reference": "Modules/Settings/PrinterTab.qml:541",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Text",
|
||||
"context": "shadow color option | text color",
|
||||
|
||||
@@ -769,6 +769,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Add by Address",
|
||||
"translation": "",
|
||||
"context": "Toggle button to manually add a printer by IP or hostname",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Adjust the number of columns in grid view mode.",
|
||||
"translation": "",
|
||||
@@ -1994,6 +2001,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Caps Lock is on",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Center Section",
|
||||
"translation": "",
|
||||
@@ -2834,6 +2848,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Connection failed",
|
||||
"translation": "",
|
||||
"context": "Status message when test connection to printer fails",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Contains",
|
||||
"translation": "",
|
||||
@@ -3842,6 +3863,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Discover Devices",
|
||||
"translation": "",
|
||||
"context": "Toggle button to scan for printers via mDNS/Avahi",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Disk",
|
||||
"translation": "",
|
||||
@@ -5711,6 +5739,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Got It",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Goth Corner Radius",
|
||||
"translation": "",
|
||||
@@ -6145,6 +6180,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Host",
|
||||
"translation": "",
|
||||
"context": "Label for printer IP address or hostname input field",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Hostname",
|
||||
"translation": "",
|
||||
@@ -6236,6 +6278,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "IP address or hostname",
|
||||
"translation": "",
|
||||
"context": "Placeholder text for manual printer address input",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "ISO Date",
|
||||
"translation": "",
|
||||
@@ -9638,6 +9687,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Port",
|
||||
"translation": "",
|
||||
"context": "Label for printer port number input field",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Portal",
|
||||
"translation": "",
|
||||
@@ -9897,6 +9953,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Printer reachable",
|
||||
"translation": "",
|
||||
"context": "Status message when test connection to printer succeeds",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Printers",
|
||||
"translation": "",
|
||||
@@ -10128,6 +10191,20 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Read Full Release Notes",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Read Full Release Notes",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Read:",
|
||||
"translation": "",
|
||||
@@ -12571,6 +12648,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Test Connection",
|
||||
"translation": "",
|
||||
"context": "Button to test connection to a printer by IP address",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Test Page",
|
||||
"translation": "",
|
||||
@@ -12585,6 +12669,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Testing...",
|
||||
"translation": "",
|
||||
"context": "Button state while testing printer connection",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Text",
|
||||
"translation": "",
|
||||
@@ -13922,6 +14013,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "What's New",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.",
|
||||
"translation": "",
|
||||
@@ -14727,53 +14825,18 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "↑/↓: Navigate • Enter: Paste • Del: Delete • F10: Help",
|
||||
"translation": "",
|
||||
"context": "Keyboard hints when enter-to-paste is enabled",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "What's New",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Read Full Release Notes",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Read Full Release Notes",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Got It",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Caps Lock is on",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "↑/↓: Nav • Space: Expand • Enter: Action/Expand • E: Text",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "↑/↓: Navigate • Enter: Paste • Del: Delete • F10: Help",
|
||||
"translation": "",
|
||||
"context": "Keyboard hints when enter-to-paste is enabled",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user