Compare commits
No commits in common. "4f9daa2d75cb6760a06f1c09d34620e3177ade94" and "db60fdd6358df8a603079ff758f35470c7f81e4d" have entirely different histories.
4f9daa2d75
...
db60fdd635
@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"Bash(git -C /Users/oliver/Development/Projekte/kydriv log --oneline)",
|
|
||||||
"Bash(pip3 install *)",
|
|
||||||
"Bash(git add *)",
|
|
||||||
"Bash(git commit *)",
|
|
||||||
"Bash(python3 *)",
|
|
||||||
"Bash(git *)",
|
|
||||||
"Bash(cupstestppd *)",
|
|
||||||
"Bash(echo \"Exit code: $?\")",
|
|
||||||
"Bash(Read the *)",
|
|
||||||
"Bash(awk -F'.' '{print $1\".\"$2}')",
|
|
||||||
"Bash(chmod 644 /Users/oliver/Development/Projekte/kydriv/ppd/TA3505ci_AS.ppd)",
|
|
||||||
"Bash(python *)",
|
|
||||||
"Bash(chmod +x *)",
|
|
||||||
"Bash(pytest *)",
|
|
||||||
"Bash(echo \"exit: $?\")",
|
|
||||||
"Bash(bash *)",
|
|
||||||
"Bash(pkgutil *)",
|
|
||||||
"Read(//tmp/kydriv_expanded/kydriv-driver-1.0.0.pkg/Scripts/**)",
|
|
||||||
"Read(//tmp/kydriv_expanded/**)",
|
|
||||||
"Bash(rm -rf /tmp/kydriv_expanded)",
|
|
||||||
"Read(//private/tmp/kydriv_expanded/**)",
|
|
||||||
"Bash(xxd)",
|
|
||||||
"Read(//usr/bin/**)",
|
|
||||||
"Read(//usr/libexec/cups/**)",
|
|
||||||
"Bash(lsbom /tmp/kydriv_pkg_inspect/Bom)",
|
|
||||||
"Bash(csrutil status *)"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""CUPS filter for TA 3505ci - injects department code into PostScript stream."""
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def parse_options(options_str):
|
|
||||||
options = {}
|
|
||||||
for token in options_str.split():
|
|
||||||
if '=' in token:
|
|
||||||
k, v = token.split('=', 1)
|
|
||||||
options[k] = v
|
|
||||||
else:
|
|
||||||
options[token] = 'true'
|
|
||||||
return options
|
|
||||||
|
|
||||||
|
|
||||||
def get_account_code(options):
|
|
||||||
km = options.get('KmManagment', 'Default')
|
|
||||||
if km == 'Default' or not km.startswith('MG'):
|
|
||||||
return None
|
|
||||||
code = km[2:]
|
|
||||||
if not code.isdigit():
|
|
||||||
return None
|
|
||||||
return code # raw digits, no zero-padding — matches kyofilter_E behaviour
|
|
||||||
|
|
||||||
|
|
||||||
def process_stream(lines, account_code):
|
|
||||||
if not account_code:
|
|
||||||
yield from lines
|
|
||||||
return
|
|
||||||
|
|
||||||
inject = f"({account_code}) statusdict /setmanagementnumber get exec\n".encode()
|
|
||||||
injected = False
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
stripped = line.rstrip(b'\r\n')
|
|
||||||
if not injected and (stripped.rstrip() == b'%%EndSetup' or stripped.startswith(b'%%Page:')):
|
|
||||||
yield inject
|
|
||||||
injected = True
|
|
||||||
yield line
|
|
||||||
|
|
||||||
if not injected:
|
|
||||||
sys.stderr.write("kyofilter: WARNING: no injection point found in PostScript stream\n")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) < 6:
|
|
||||||
sys.stderr.write("Usage: kyofilter job-id user title copies options [file]\n")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
options = parse_options(sys.argv[5])
|
|
||||||
account_code = get_account_code(options)
|
|
||||||
|
|
||||||
infile = open(sys.argv[6], 'rb') if len(sys.argv) > 6 else sys.stdin.buffer
|
|
||||||
try:
|
|
||||||
for chunk in process_stream(infile, account_code):
|
|
||||||
sys.stdout.buffer.write(chunk)
|
|
||||||
finally:
|
|
||||||
if len(sys.argv) > 6:
|
|
||||||
infile.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
18
install.sh
18
install.sh
@ -1,18 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
|
|
||||||
echo "kydriv - Kyocera 3505ci Treiber installieren"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
sudo cp "$SCRIPT_DIR/ppd/TA3505ci_AS.ppd" \
|
|
||||||
/Library/Printers/PPDs/Contents/Resources/TA3505ci_AS.ppd
|
|
||||||
sudo cp "$SCRIPT_DIR/filter/kyofilter" \
|
|
||||||
/usr/libexec/cups/filter/kyofilter
|
|
||||||
sudo chmod 755 /usr/libexec/cups/filter/kyofilter
|
|
||||||
sudo chown root:wheel /usr/libexec/cups/filter/kyofilter
|
|
||||||
sudo launchctl kickstart -k system/org.cups.cupsd
|
|
||||||
|
|
||||||
echo "Fertig. Jetzt in Systemeinstellungen → Drucker & Scanner:"
|
|
||||||
echo " + → IP → Adresse eingeben → Verwenden: '3505ci (kydriv)'"
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
VERSION="1.0.0"
|
|
||||||
IDENTIFIER="de.kydriv.driver"
|
|
||||||
PKG_NAME="kydriv-driver-${VERSION}.pkg"
|
|
||||||
|
|
||||||
# Build installer/root from source files
|
|
||||||
mkdir -p installer/root/Library/Printers/PPDs/Contents/Resources
|
|
||||||
mkdir -p installer/root/usr/libexec/cups/filter
|
|
||||||
|
|
||||||
cp ppd/TA3505ci_AS.ppd \
|
|
||||||
installer/root/Library/Printers/PPDs/Contents/Resources/
|
|
||||||
cp filter/kyofilter \
|
|
||||||
installer/root/usr/libexec/cups/filter/
|
|
||||||
chmod 755 installer/root/usr/libexec/cups/filter/kyofilter
|
|
||||||
|
|
||||||
# Validate PPD (fail if it cannot be parsed at all)
|
|
||||||
echo "Validating PPD..."
|
|
||||||
cupstestppd installer/root/Library/Printers/PPDs/Contents/Resources/TA3505ci_AS.ppd 2>&1 \
|
|
||||||
| grep -v "WARN\|8-Bit\|Übersetzung\|sollte\|Präfix\|übliches\|Seite\|Abschnitt" \
|
|
||||||
| grep -v "FeedingEdgeConstraint\|kyofilter" \
|
|
||||||
| grep "FEHLER" && { echo "ERROR: PPD has unexpected errors" >&2; exit 1; } || true
|
|
||||||
echo "PPD OK"
|
|
||||||
|
|
||||||
# Build (unsigned if SIGN_IDENTITY not set)
|
|
||||||
SIGN_ARGS=()
|
|
||||||
if [ -n "${SIGN_IDENTITY}" ]; then
|
|
||||||
SIGN_ARGS=(--sign "${SIGN_IDENTITY}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
pkgbuild \
|
|
||||||
--root installer/root \
|
|
||||||
--scripts installer/scripts \
|
|
||||||
--identifier "${IDENTIFIER}" \
|
|
||||||
--version "${VERSION}" \
|
|
||||||
"${SIGN_ARGS[@]}" \
|
|
||||||
"${PKG_NAME}"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Built: ${PKG_NAME}"
|
|
||||||
if [ -z "${SIGN_IDENTITY}" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "To sign: SIGN_IDENTITY='Developer ID Installer: NAME (TEAMID)' bash installer/build.sh"
|
|
||||||
echo ""
|
|
||||||
echo "To notarize after signing:"
|
|
||||||
echo " xcrun notarytool submit ${PKG_NAME} --apple-id YOUR@EMAIL --team-id TEAMID --password APP-PW --wait"
|
|
||||||
echo " xcrun stapler staple ${PKG_NAME}"
|
|
||||||
fi
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
chmod 755 /usr/libexec/cups/filter/kyofilter
|
|
||||||
chown root:wheel /usr/libexec/cups/filter/kyofilter
|
|
||||||
launchctl kickstart -k system/org.cups.cupsd
|
|
||||||
exit 0
|
|
||||||
@ -24,8 +24,8 @@
|
|||||||
*PSVersion: "(3011.103) 1"
|
*PSVersion: "(3011.103) 1"
|
||||||
*Manufacturer: "UTAX/TA"
|
*Manufacturer: "UTAX/TA"
|
||||||
*ModelName: "3505ci KPDL"
|
*ModelName: "3505ci KPDL"
|
||||||
*ShortNickName: "3505ci (kydriv)"
|
*ShortNickName: "3505ci (KPDL)"
|
||||||
*NickName: "3505ci (kydriv)"
|
*NickName: "3505ci (KPDL)"
|
||||||
*PCFileName: "TA3505ci.PPD"
|
*PCFileName: "TA3505ci.PPD"
|
||||||
|
|
||||||
*1284DeviceID: "MDL:3505ci;MFG:UTAX"
|
*1284DeviceID: "MDL:3505ci;MFG:UTAX"
|
||||||
@ -3416,14 +3416,16 @@ userdict /180rotdetail true put
|
|||||||
*OpenGroup: AccountingOptions/Job Accounting
|
*OpenGroup: AccountingOptions/Job Accounting
|
||||||
|
|
||||||
*% ---------------------------------------------------------
|
*% ---------------------------------------------------------
|
||||||
*% Kostenstelle eintragen. Zeile kopieren und Zahl anpassen:
|
*% Kostenstelle (5-stellig) eintragen - ohne fuehrende Nullen.
|
||||||
|
*% Zeile kopieren und Zahl anpassen:
|
||||||
*% *KmManagment MG12345/Code 12345: ""
|
*% *KmManagment MG12345/Code 12345: ""
|
||||||
|
*% Zero-Padding auf 8 Stellen uebernimmt der Filter.
|
||||||
*% ---------------------------------------------------------
|
*% ---------------------------------------------------------
|
||||||
*OpenUI *KmManagment/Job Accounting: PickOne
|
*OpenUI *KmManagment/Job Accounting: PickOne
|
||||||
*OrderDependency: 60 AnySetup *KmManagment
|
*OrderDependency: 60 AnySetup *KmManagment
|
||||||
*DefaultKmManagment: Default
|
*DefaultKmManagment: Default
|
||||||
*KmManagment Default/Off: ""
|
\*KmManagment Default/Off: ""
|
||||||
*KmManagment MG39321/Code 39321: ""
|
*KmManagment MG12345/Code 12345: ""
|
||||||
*?KmManagment: ""
|
*?KmManagment: ""
|
||||||
*End
|
*End
|
||||||
*CloseUI: *KmManagment
|
*CloseUI: *KmManagment
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
import importlib.util, pathlib, sys
|
|
||||||
from importlib.machinery import SourceFileLoader
|
|
||||||
|
|
||||||
_path = pathlib.Path(__file__).parent.parent / "filter" / "kyofilter"
|
|
||||||
_spec = importlib.util.spec_from_loader("kyofilter", SourceFileLoader("kyofilter", str(_path)))
|
|
||||||
_mod = importlib.util.module_from_spec(_spec)
|
|
||||||
_spec.loader.exec_module(_mod)
|
|
||||||
sys.modules["kyofilter"] = _mod
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
import kyofilter
|
|
||||||
|
|
||||||
|
|
||||||
class TestParseOptions:
|
|
||||||
def test_single_key_value(self):
|
|
||||||
assert kyofilter.parse_options("KmManagment=MG12345") == {"KmManagment": "MG12345"}
|
|
||||||
|
|
||||||
def test_multiple_options(self):
|
|
||||||
result = kyofilter.parse_options("KmManagment=MG12345 Duplex=DuplexNoTumble")
|
|
||||||
assert result == {"KmManagment": "MG12345", "Duplex": "DuplexNoTumble"}
|
|
||||||
|
|
||||||
def test_empty_string(self):
|
|
||||||
assert kyofilter.parse_options("") == {}
|
|
||||||
|
|
||||||
def test_flag_without_value(self):
|
|
||||||
assert kyofilter.parse_options("SomeFlag") == {"SomeFlag": "true"}
|
|
||||||
|
|
||||||
|
|
||||||
class TestGetAccountCode:
|
|
||||||
def test_five_digit_code_returned_as_is(self):
|
|
||||||
assert kyofilter.get_account_code({"KmManagment": "MG39321"}) == "39321"
|
|
||||||
|
|
||||||
def test_one_digit_code_returned_as_is(self):
|
|
||||||
assert kyofilter.get_account_code({"KmManagment": "MG1"}) == "1"
|
|
||||||
|
|
||||||
def test_default_returns_none(self):
|
|
||||||
assert kyofilter.get_account_code({"KmManagment": "Default"}) is None
|
|
||||||
|
|
||||||
def test_missing_key_returns_none(self):
|
|
||||||
assert kyofilter.get_account_code({}) is None
|
|
||||||
|
|
||||||
def test_no_mg_prefix_returns_none(self):
|
|
||||||
assert kyofilter.get_account_code({"KmManagment": "12345"}) is None
|
|
||||||
|
|
||||||
def test_non_numeric_code_returns_none(self):
|
|
||||||
assert kyofilter.get_account_code({"KmManagment": "MGabc45"}) is None
|
|
||||||
|
|
||||||
|
|
||||||
class TestProcessStream:
|
|
||||||
def _stream(self, text):
|
|
||||||
return iter(line.encode() for line in text.splitlines(keepends=True))
|
|
||||||
|
|
||||||
def _collect(self, text, code):
|
|
||||||
return b"".join(kyofilter.process_stream(self._stream(text), code))
|
|
||||||
|
|
||||||
def test_passthrough_when_no_code(self):
|
|
||||||
ps = "%!PS\n%%BeginSetup\n%%EndSetup\n%%Page: 1 1\n"
|
|
||||||
assert self._collect(ps, None) == ps.encode()
|
|
||||||
|
|
||||||
def test_injects_raw_code_before_end_setup(self):
|
|
||||||
ps = "%!PS\n%%BeginSetup\n%%EndSetup\n%%Page: 1 1\n"
|
|
||||||
result = self._collect(ps, "39321")
|
|
||||||
assert b"(39321) statusdict /setmanagementnumber get exec\n%%EndSetup\n" in result
|
|
||||||
|
|
||||||
def test_end_setup_preserved_after_injection(self):
|
|
||||||
ps = "%!PS\n%%BeginSetup\n%%EndSetup\n"
|
|
||||||
result = self._collect(ps, "39321")
|
|
||||||
assert b"%%EndSetup\n" in result
|
|
||||||
|
|
||||||
def test_fallback_injects_before_first_page_when_no_end_setup(self):
|
|
||||||
ps = "%!PS\n%%Page: 1 1\nshowpage\n"
|
|
||||||
result = self._collect(ps, "39321")
|
|
||||||
assert b"(39321) statusdict /setmanagementnumber get exec\n%%Page: 1 1\n" in result
|
|
||||||
|
|
||||||
def test_injects_only_once(self):
|
|
||||||
ps = "%!PS\n%%BeginSetup\n%%EndSetup\n%%Page: 1 1\n%%Page: 2 1\n"
|
|
||||||
result = self._collect(ps, "39321")
|
|
||||||
assert result.count(b"setmanagementnumber") == 1
|
|
||||||
|
|
||||||
def test_content_preserved(self):
|
|
||||||
ps = "%!PS\n%%BeginSetup\n/mydict 10 dict def\n%%EndSetup\nshowpage\n"
|
|
||||||
result = self._collect(ps, "39321")
|
|
||||||
assert b"/mydict 10 dict def\n" in result
|
|
||||||
assert b"showpage\n" in result
|
|
||||||
|
|
||||||
def test_warns_when_no_injection_point(self, capsys):
|
|
||||||
ps = "%!PS\nshowpage\n"
|
|
||||||
self._collect(ps, "39321")
|
|
||||||
assert "WARNING" in capsys.readouterr().err
|
|
||||||
Loading…
x
Reference in New Issue
Block a user