Questa guida elenca tutte le patch applicate al codice sorgente per la compatibilità con Linux/Debian, come verificarle e come applicarle manualmente se necessario.
File: src/songpressplusplus/SongpressFrame.py
Problema: Su Linux con tema scuro, il testo della finestra About è bianco
su sfondo bianco e risulta invisibile. wxPython non eredita il colore del testo
da SetBackgroundColour(wx.WHITE).
Fix: Aggiunge SetForegroundColour(wx.BLACK) su ogni wx.StaticText
della finestra About.
python3 - <<'PY'
with open("src/songpressplusplus/SongpressFrame.py", "r") as f:
content = f.read()
checks = [
' title_lbl.SetForegroundColour(wx.BLACK)',
' lbl.SetForegroundColour(wx.BLACK)',
]
for c in checks:
print(f"{'✅' if c in content else '❌'} {c.strip()!r}")
PYOutput atteso:
✅ 'title_lbl.SetForegroundColour(wx.BLACK)'
✅ 'lbl.SetForegroundColour(wx.BLACK)'
python3 - <<'PY'
with open("src/songpressplusplus/SongpressFrame.py", "r") as f:
content = f.read()
fixes = [
(
' title_lbl.SetFont(font_title)\n hbox_title.Add(title_lbl, 0, wx.ALIGN_CENTER_VERTICAL)',
' title_lbl.SetFont(font_title)\n title_lbl.SetForegroundColour(wx.BLACK)\n hbox_title.Add(title_lbl, 0, wx.ALIGN_CENTER_VERTICAL)'
),
(
' except Exception:\n title_lbl = wx.StaticText(panel, label=u"Songpress++ - The Song Editor {}".format(glb.VERSION))\n vbox.Add(title_lbl, 0, wx.ALIGN_CENTER | wx.ALL, 10)',
' except Exception:\n title_lbl = wx.StaticText(panel, label=u"Songpress++ - The Song Editor {}".format(glb.VERSION))\n title_lbl.SetForegroundColour(wx.BLACK)\n vbox.Add(title_lbl, 0, wx.ALIGN_CENTER | wx.ALL, 10)'
),
(
' def add_text(text):\n lbl = wx.StaticText(panel, label=text)\n vbox.Add(lbl, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, 15)',
' def add_text(text):\n lbl = wx.StaticText(panel, label=text)\n lbl.SetForegroundColour(wx.BLACK)\n vbox.Add(lbl, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, 15)'
),
]
count = sum(1 for old, new in fixes if old in content)
for old, new in fixes:
content = content.replace(old, new)
with open("src/songpressplusplus/SongpressFrame.py", "w") as f:
f.write(content)
print(f"Fix applicati: {count}/3")
PYsudo python3 - <<'PY'
path = "/usr/local/lib/python3.13/dist-packages/songpressplusplus/SongpressFrame.py"
with open(path, "r") as f:
content = f.read()
fixes = [
(
' title_lbl.SetFont(font_title)\n hbox_title.Add(title_lbl, 0, wx.ALIGN_CENTER_VERTICAL)',
' title_lbl.SetFont(font_title)\n title_lbl.SetForegroundColour(wx.BLACK)\n hbox_title.Add(title_lbl, 0, wx.ALIGN_CENTER_VERTICAL)'
),
(
' except Exception:\n title_lbl = wx.StaticText(panel, label=u"Songpress++ - The Song Editor {}".format(glb.VERSION))\n vbox.Add(title_lbl, 0, wx.ALIGN_CENTER | wx.ALL, 10)',
' except Exception:\n title_lbl = wx.StaticText(panel, label=u"Songpress++ - The Song Editor {}".format(glb.VERSION))\n title_lbl.SetForegroundColour(wx.BLACK)\n vbox.Add(title_lbl, 0, wx.ALIGN_CENTER | wx.ALL, 10)'
),
(
' def add_text(text):\n lbl = wx.StaticText(panel, label=text)\n vbox.Add(lbl, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, 15)',
' def add_text(text):\n lbl = wx.StaticText(panel, label=text)\n lbl.SetForegroundColour(wx.BLACK)\n vbox.Add(lbl, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, 15)'
),
]
count = sum(1 for old, new in fixes if old in content)
for old, new in fixes:
content = content.replace(old, new)
with open(path, "w") as f:
f.write(content)
print(f"Fix applicati: {count}/3")
PYFile: src/songpressplusplus/MyPreferencesDialog.py
Problema: Su Linux GetCustomColour(i) può restituire un colore non
valido (slot vuoto). Chiamare Red() su un colore non valido causa un crash
wx._core.wxAssertionError.
Fix: Aggiunge un controllo IsOk() prima di leggere i componenti RGB.
python3 - <<'PY'
with open("src/songpressplusplus/MyPreferencesDialog.py", "r") as f:
content = f.read()
check = " if not colour.IsOk():"
print(f"{'✅' if check in content else '❌'} Controllo IsOk() presente")
PYpython3 - <<'PY'
with open("src/songpressplusplus/MyPreferencesDialog.py", "r") as f:
content = f.read()
old = """ def _colour_to_hex(self, colour):
return '#{:02X}{:02X}{:02X}'.format(colour.Red(), colour.Green(), colour.Blue())"""
new = """ def _colour_to_hex(self, colour):
if not colour.IsOk():
return '#FFFFFF'
return '#{:02X}{:02X}{:02X}'.format(colour.Red(), colour.Green(), colour.Blue())"""
if old in content:
content = content.replace(old, new)
with open("src/songpressplusplus/MyPreferencesDialog.py", "w") as f:
f.write(content)
print("OK")
else:
print("ERRORE - testo non trovato")
PYsudo python3 - <<'PY'
path = "/usr/local/lib/python3.13/dist-packages/songpressplusplus/MyPreferencesDialog.py"
with open(path, "r") as f:
content = f.read()
old = """ def _colour_to_hex(self, colour):
return '#{:02X}{:02X}{:02X}'.format(colour.Red(), colour.Green(), colour.Blue())"""
new = """ def _colour_to_hex(self, colour):
if not colour.IsOk():
return '#FFFFFF'
return '#{:02X}{:02X}{:02X}'.format(colour.Red(), colour.Green(), colour.Blue())"""
if old in content:
content = content.replace(old, new)
with open(path, "w") as f:
f.write(content)
print("OK")
else:
print("ERRORE - testo non trovato")
PYFile: src/songpressplusplus/PreferencesDialog.py
Problema: Su Linux i pulsanti "Associa tutto", "Disassocia tutto" e
"Applica ora" creano file locali in ~/.local/share/ che entrano in conflitto
con le associazioni di sistema installate dal pacchetto .deb. Anche i
checkbox delle singole estensioni devono essere disabilitati per evitare
modifiche accidentali.
Fix: Disabilita i tre pulsanti e tutti i checkbox delle estensioni automaticamente quando il sistema è Linux.
python3 - <<'PY'
with open("src/songpressplusplus/PreferencesDialog.py", "r") as f:
content = f.read()
checks = [
("btnAssocAll.Disable()", "Disabilita btnAssocAll"),
("btnUnassocAll.Disable()", "Disabilita btnUnassocAll"),
("btnApply.Disable()", "Disabilita btnApply"),
("cb.Disable()", "Disabilita checkbox estensioni"),
]
for code, desc in checks:
print(f"{'✅' if code in content else '❌'} {desc}")
PYOutput atteso:
✅ Disabilita btnAssocAll
✅ Disabilita btnUnassocAll
✅ Disabilita btnApply
✅ Disabilita checkbox estensioni
python3 - <<'PY'
with open("src/songpressplusplus/PreferencesDialog.py", "r") as f:
content = f.read()
fixes = [
(
" self._btnAssocAll = btnAssocAll\n self._btnUnassocAll = btnUnassocAll",
" self._btnAssocAll = btnAssocAll\n self._btnUnassocAll = btnUnassocAll\n import platform as _pl\n if _pl.system() == 'Linux':\n btnAssocAll.Disable()\n btnUnassocAll.Disable()"
),
(
" self._btnApplyFileAssoc = btnApply",
" self._btnApplyFileAssoc = btnApply\n import platform as _pl2\n if _pl2.system() == 'Linux':\n btnApply.Disable()"
),
]
count = sum(1 for old, new in fixes if old in content)
for old, new in fixes:
content = content.replace(old, new)
with open("src/songpressplusplus/PreferencesDialog.py", "w") as f:
f.write(content)
print(f"Fix applicati: {count}/3")
PYpython3 - <<'PY'
with open("src/songpressplusplus/PreferencesDialog.py", "r") as f:
content = f.read()
old = """ for ext in self._fileAssocExts:
cb = wx.CheckBox(self.fileAssocPanel, wx.ID_ANY, u"." + ext)
cb.SetToolTip(_(u"Associate the .%s file extension with Songpress++.") % ext)
self._fileAssocCBs[ext] = cb
bSizerFA.Add(cb, 0, wx.ALL, 4)"""
new = """ import platform as _plcb
for ext in self._fileAssocExts:
cb = wx.CheckBox(self.fileAssocPanel, wx.ID_ANY, u"." + ext)
cb.SetToolTip(_(u"Associate the .%s file extension with Songpress++.") % ext)
self._fileAssocCBs[ext] = cb
if _plcb.system() == 'Linux':
cb.Disable()
bSizerFA.Add(cb, 0, wx.ALL, 4)"""
if old in content:
content = content.replace(old, new)
with open("src/songpressplusplus/PreferencesDialog.py", "w") as f:
f.write(content)
print("OK")
else:
print("ERRORE - testo non trovato")
PYsudo python3 - <<'PY'
path = "/usr/local/lib/python3.13/dist-packages/songpressplusplus/PreferencesDialog.py"
with open(path, "r") as f:
content = f.read()
fixes = [
(
" self._btnAssocAll = btnAssocAll\n self._btnUnassocAll = btnUnassocAll",
" self._btnAssocAll = btnAssocAll\n self._btnUnassocAll = btnUnassocAll\n import platform as _pl\n if _pl.system() == 'Linux':\n btnAssocAll.Disable()\n btnUnassocAll.Disable()"
),
(
" self._btnApplyFileAssoc = btnApply",
" self._btnApplyFileAssoc = btnApply\n import platform as _pl2\n if _pl2.system() == 'Linux':\n btnApply.Disable()"
),
]
count = sum(1 for old, new in fixes if old in content)
for old, new in fixes:
content = content.replace(old, new)
with open(path, "w") as f:
f.write(content)
print(f"Fix applicati: {count}/2")
PYFile: src/songpressplusplus/SongpressFrame.py
Problema: Su Linux con tema scuro:
- Lo sfondo del dialog è bianco hardcoded (
wx.Colour(250,250,252)), ignorando il tema di sistema. - Il testo delle righe chiave/valore (Struttura, Testo, Accordi, Metadati) non ha colore esplicito e risulta invisibile.
- Le stelle e il verdetto ("Ottimo per principianti" ecc.) erano in un
wx.Panelcon sfondo bianco hardcoded. - Il testo del verdetto non aveva colore esplicito e risultava invisibile su tema scuro.
Fix:
BG→wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)(segue il tema).wx.WHITEewx.Colour(240,245,255)→SYS_COLOUR_WINDOW(finestra About, eval_panel, tastiera).eval_panelrimosso: stelle e verdetto aggiunti direttamente al sizer senza pannello con sfondo fisso.wx.BLACK→SYS_COLOUR_WINDOWTEXTsu tutti iSetForegroundColourhardcoded.SetForegroundColour(SYS_COLOUR_WINDOWTEXT)esplicito suk_lbl,v_lble_lbl_verdict.
Usa la sezione Verifica rapida in fondo al documento (controlla Patch 4a–4e).
SPPY=/usr/local/lib/python3.13/dist-packages/songpressplusplus/SongpressFrame.py
sudo sed -i \
-e 's/SetForegroundColour(wx\.BLACK)/SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))/g' \
-e 's/SetBackgroundColour(wx\.WHITE)/SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))/g' \
-e 's/wx\.Colour(240, 245, 255)/wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)/g' \
"$SPPY"Nota: questa patch è applicata automaticamente da
build_deb.sh(sezione1e).
File: src/songpressplusplus/SongpressFrame.py e
src/songpressplusplus/SongpressToolbars.py
Problema: Cambiando lingua (es. da Italiano a Inglese) l'app fa un restart:
chiude la finestra e rilancia il processo. Mentre la finestra si chiude GTK
emette un EVT_SIZE che programma un aggiornamento differito delle toolbar
(wx.CallAfter(_deferred_tb_update)). Quando la callback viene eseguita l'AUI
manager è già stato smontato (UnInit) e l'attributo _mgr non esiste più sul
frame, quindi:
AttributeError: 'SongpressFrame' object has no attribute '_mgr'
File ".../SongpressToolbars.py", line 202, in _FinalizeToolbarLayout
pane = self._mgr.GetPane(tb)
La guardia if self.frame: non basta: self.frame resta "truthy" durante la
chiusura mentre _mgr è già sparito. È una race di teardown, non un bug di i18n.
Fix:
_deferred_tb_updatenon tocca il layout se_mgrè assente o se il frame è in chiusura (_closing).OnCloseimpostaself._closing = Truecome prima istruzione._FinalizeToolbarLayoutesce subito se_mgrèNoneo se le toolbar non esistono più (protegge anche gli altri chiamanti).
python3 - <<'PY'
checks = [
("SongpressFrame.py", "getattr(self, '_mgr', None) is not None"),
("SongpressFrame.py", "self._closing = True"),
("SongpressToolbars.py", "if getattr(self, '_mgr', None) is None:"),
]
for fn, needle in checks:
with open("src/songpressplusplus/" + fn) as f:
ok = needle in f.read()
print(f"{'✅' if ok else '❌'} {fn}: {needle!r}")
PYpython3 - <<'PY'
FR = "src/songpressplusplus/SongpressFrame.py"
TB = "src/songpressplusplus/SongpressToolbars.py"
with open(FR) as f: fr = f.read()
with open(TB) as f: tb = f.read()
# --- Frame: guardia in _deferred_tb_update ---
old_a = ''' def _deferred_tb_update():
self._tb_layout_pending = False
if self.frame:
self._FinalizeToolbarLayout()'''
new_a = ''' def _deferred_tb_update():
self._tb_layout_pending = False
# Durante la chiusura/riavvio l'AUI manager puo' essere
# gia' stato smontato (UnInit): _mgr non esiste piu'.
# self.frame resta "truthy" ma il layout non va toccato.
if (self.frame
and getattr(self, '_mgr', None) is not None
and not getattr(self, '_closing', False)):
self._FinalizeToolbarLayout()'''
# --- Frame: _closing in OnClose ---
old_b = ''' def OnClose(self, evt):
if hasattr(self, '_lockKeysTimer') and self._lockKeysTimer.IsRunning():'''
new_b = ''' def OnClose(self, evt):
self._closing = True
if hasattr(self, '_lockKeysTimer') and self._lockKeysTimer.IsRunning():'''
# --- Toolbars: guardia in _FinalizeToolbarLayout ---
old_c = ''' self._tb_finalizing = True
try:
for tb in (self.mainToolBar, self.formatToolBar,
self.insertToolBar, self.viewToolBar):
tb.SetGripperVisible(False)'''
new_c = ''' # Se l'AUI manager e' gia' stato smontato (chiusura/riavvio) o le
# toolbar non esistono piu', non c'e' layout da ricalcolare.
if getattr(self, '_mgr', None) is None:
return
if not all(getattr(self, name, None) is not None for name in
('mainToolBar', 'formatToolBar',
'insertToolBar', 'viewToolBar')):
return
self._tb_finalizing = True
try:
for tb in (self.mainToolBar, self.formatToolBar,
self.insertToolBar, self.viewToolBar):
tb.SetGripperVisible(False)'''
count = 0
for old, new, tgt in ((old_a, new_a, 'fr'), (old_b, new_b, 'fr'), (old_c, new_c, 'tb')):
src = fr if tgt == 'fr' else tb
if old in src:
if tgt == 'fr': fr = fr.replace(old, new)
else: tb = tb.replace(old, new)
count += 1
with open(FR, "w") as f: f.write(fr)
with open(TB, "w") as f: f.write(tb)
print(f"Fix applicati: {count}/3")
PYsudo python3 - <<'PY'
BASE = "/usr/local/lib/python3.13/dist-packages/songpressplusplus/"
FR = BASE + "SongpressFrame.py"
TB = BASE + "SongpressToolbars.py"
with open(FR) as f: fr = f.read()
with open(TB) as f: tb = f.read()
old_a = ''' def _deferred_tb_update():
self._tb_layout_pending = False
if self.frame:
self._FinalizeToolbarLayout()'''
new_a = ''' def _deferred_tb_update():
self._tb_layout_pending = False
# Durante la chiusura/riavvio l'AUI manager puo' essere
# gia' stato smontato (UnInit): _mgr non esiste piu'.
# self.frame resta "truthy" ma il layout non va toccato.
if (self.frame
and getattr(self, '_mgr', None) is not None
and not getattr(self, '_closing', False)):
self._FinalizeToolbarLayout()'''
old_b = ''' def OnClose(self, evt):
if hasattr(self, '_lockKeysTimer') and self._lockKeysTimer.IsRunning():'''
new_b = ''' def OnClose(self, evt):
self._closing = True
if hasattr(self, '_lockKeysTimer') and self._lockKeysTimer.IsRunning():'''
old_c = ''' self._tb_finalizing = True
try:
for tb in (self.mainToolBar, self.formatToolBar,
self.insertToolBar, self.viewToolBar):
tb.SetGripperVisible(False)'''
new_c = ''' # Se l'AUI manager e' gia' stato smontato (chiusura/riavvio) o le
# toolbar non esistono piu', non c'e' layout da ricalcolare.
if getattr(self, '_mgr', None) is None:
return
if not all(getattr(self, name, None) is not None for name in
('mainToolBar', 'formatToolBar',
'insertToolBar', 'viewToolBar')):
return
self._tb_finalizing = True
try:
for tb in (self.mainToolBar, self.formatToolBar,
self.insertToolBar, self.viewToolBar):
tb.SetGripperVisible(False)'''
count = 0
for old, new, tgt in ((old_a, new_a, 'fr'), (old_b, new_b, 'fr'), (old_c, new_c, 'tb')):
src = fr if tgt == 'fr' else tb
if old in src:
if tgt == 'fr': fr = fr.replace(old, new)
else: tb = tb.replace(old, new)
count += 1
with open(FR, "w") as f: f.write(fr)
with open(TB, "w") as f: f.write(tb)
print(f"Fix applicati: {count}/3")
PYFile: src/songpressplusplus/i18n.py
Problema: Cambiando lingua in inglese su un sistema dove il locale C
corrispondente (es. en_US.UTF-8) non è stato generato con locale-gen,
wx.Locale(langid) non riesce a chiamare setlocale() e wxWidgets emette un
wxLogWarning che, col log target GUI di default, appare come finestra
"Cannot set locale to language 'English'.". Inoltre, se
FindLanguageInfo(l) restituisce None per un codice sconosciuto, l'accesso
a .Language provoca un AttributeError.
Fix:
- Guardia su
FindLanguageInfo: fallback awx.LANGUAGE_DEFAULTseNone. - Costruzione di
wx.Localeavvolta inwx.LogNull(): il warning non genera più la finestra. I cataloghi.mosi caricano comunque (la traduzione dipende dalla lingua impostata inwx.Locale, non dal locale C) e l'oggettomylocaleresta valido perwx.GetLocale()usato altrove.
python3 - <<'PY'
with open("src/songpressplusplus/i18n.py") as f:
c = f.read()
checks = [
"info = wx.Locale.FindLanguageInfo(l)",
"info.Language if info is not None else wx.LANGUAGE_DEFAULT",
"_nolog = wx.LogNull()",
]
for n in checks:
print(f"{'✅' if n in c else '❌'} {n!r}")
PYpython3 - <<'PY'
path = "src/songpressplusplus/i18n.py"
with open(path) as f:
content = f.read()
old = '''def setLang(l):
global current_language, mylocale, _
current_language = l
langid = wx.Locale.FindLanguageInfo(l).Language
mylocale = wx.Locale(langid)
localedir = os.path.join(glb.path, "locale")'''
new = '''def setLang(l):
global current_language, mylocale, _
current_language = l
info = wx.Locale.FindLanguageInfo(l)
langid = info.Language if info is not None else wx.LANGUAGE_DEFAULT
# Su sistemi dove il locale C della lingua richiesta (es. en_US.UTF-8)
# non e' stato generato con locale-gen, wx.Locale non riesce a chiamare
# setlocale() ed emette un wxLogWarning che, col log target GUI di
# default, compare come finestra "Cannot set locale to language ...".
# La traduzione (cataloghi .mo) funziona comunque, perche' dipende dalla
# lingua impostata in wx.Locale e non dal locale C: silenziamo quindi
# solo quel warning durante la costruzione, senza perdere le traduzioni
# ne' l'oggetto mylocale (usato da wx.GetLocale() altrove).
_nolog = wx.LogNull()
try:
mylocale = wx.Locale(langid)
finally:
del _nolog
localedir = os.path.join(glb.path, "locale")'''
if old in content:
content = content.replace(old, new)
with open(path, "w") as f:
f.write(content)
print("OK")
else:
print("Patch già presente o testo non trovato")
PYsudo python3 - <<'PY'
path = "/usr/local/lib/python3.13/dist-packages/songpressplusplus/i18n.py"
with open(path) as f:
content = f.read()
old = '''def setLang(l):
global current_language, mylocale, _
current_language = l
langid = wx.Locale.FindLanguageInfo(l).Language
mylocale = wx.Locale(langid)
localedir = os.path.join(glb.path, "locale")'''
new = '''def setLang(l):
global current_language, mylocale, _
current_language = l
info = wx.Locale.FindLanguageInfo(l)
langid = info.Language if info is not None else wx.LANGUAGE_DEFAULT
# Su sistemi dove il locale C della lingua richiesta (es. en_US.UTF-8)
# non e' stato generato con locale-gen, wx.Locale non riesce a chiamare
# setlocale() ed emette un wxLogWarning che, col log target GUI di
# default, compare come finestra "Cannot set locale to language ...".
# La traduzione (cataloghi .mo) funziona comunque, perche' dipende dalla
# lingua impostata in wx.Locale e non dal locale C: silenziamo quindi
# solo quel warning durante la costruzione, senza perdere le traduzioni
# ne' l'oggetto mylocale (usato da wx.GetLocale() altrove).
_nolog = wx.LogNull()
try:
mylocale = wx.Locale(langid)
finally:
del _nolog
localedir = os.path.join(glb.path, "locale")'''
if old in content:
content = content.replace(old, new)
with open(path, "w") as f:
f.write(content)
print("OK")
else:
print("Patch già presente o testo non trovato")
PYNota — locale di sistema (facoltativo): la patch fa sì che l'inglese compaia senza avvisi anche se il locale C non è generato. Per avere anche la formattazione di numeri/date corretta si può generare il locale a livello di sistema (operazione manuale dell'utente, non inclusa nel
.deb):sudo sed -i 's/^# *\(en_US.UTF-8 UTF-8\)/\1/' /etc/locale.gen sudo locale-gen
File: src/songpressplusplus/PreviewCanvas.py, src/songpressplusplus/SongpressFrame.py
Problema (tre parti):
- Icona. L'icona del pulsante "copia" nella toolbar dell'anteprima era
disegnata a mano; si voleva usare il PNG del progetto
img/copy_2.png. - Il pulsante non copiava. Il pulsante rilanciava un
wx.adv.HyperlinkEventsintetico confidando nella propagazione dell'evento fino amain_panel: su wxGTK questa propagazione dalBitmapButtonnon è affidabile, quindiOnCopyAsImagenon veniva mai chiamato. - La copia immagine non funziona su Wayland. Anche chiamando direttamente
CopyAsImage, la clipboard immagine di wxGTK su Wayland non registra i formati immagine (wl-paste --list-typesmostra solo testo), pur riportando successo. È un limite noto di wxGTK/Wayland.
Fix:
PreviewCanvas._load_copy_bitmap()caricaimg/copy_2.png(conglb.AddPath), scalata a 16px, e ripiega silenziosamente sull'icona disegnata se il file manca (controlloos.path.isfile+wx.LogNull()per non far comparire il popup d'errore di wxLog).PreviewCanvasesponeSetCopyCallback()e_OnCopyButtonchiama la callback diretta (niente più eventi wx).SongpressFramela registra conself.previewCanvas.SetCopyCallback(self.CopyAsImage).CopyAsImagesu Wayland genera il PNG e lo mette negli appunti conwl-copy --type image/png(pacchettowl-clipboard). Su X11/macOS resta la clipboard di wx (SVG + PNG); su Windows invariato (metafile). Sewl-copymanca, avvisa di installarewl-clipboard.
Packaging:
wl-clipboardè aggiunto tra iRecommendsdel.deb(vedibuild_deb.sh), così su Wayland viene installato di default da apt. Nota: il.debavvia comunque l'app conGDK_BACKEND=x11(XWayland); il compositor (KDE) fa da ponte tra la clipboard Wayland diwl-copye le app X11, quindi la copia funziona in entrambi i casi.
python3 - <<'PY'
import os
BASE = "src/songpressplusplus"
checks = [
("PreviewCanvas.py", "def _load_copy_bitmap", "Patch 7a — icona copy_2.png"),
("PreviewCanvas.py", "def SetCopyCallback", "Patch 7b — callback copia esposta"),
("PreviewCanvas.py", "self._on_copy_callback", "Patch 7c — _OnCopyButton usa la callback"),
("SongpressFrame.py", "SetCopyCallback(self.CopyAsImage)", "Patch 7d — callback registrata"),
("SongpressFrame.py", "def _copy_bytes_wayland", "Patch 7e — copia via wl-copy su Wayland"),
("SongpressFrame.py", "wl-copy", "Patch 7f — uso di wl-copy"),
]
for filename, needle, desc in checks:
path = os.path.join(BASE, filename)
try:
with open(path) as f:
found = needle in f.read()
print(f"{'✅' if found else '❌'} {desc}")
except FileNotFoundError:
print(f"⚠️ File non trovato: {path}")
PYQuesta patch riscrive interi metodi e ne aggiunge di nuovi in due file: non è
pratica come blocco content.replace(). I file sorgente
(PreviewCanvas.py, SongpressFrame.py) contengono già il fix e sono la fonte
di verità. Per applicarla al file installato senza ricostruire il .deb,
copiare i due file:
SRC=~/Songpress_DEFINitiVO3/SongpressPlusPlus/src/songpressplusplus
DST=/usr/local/lib/python3.13/dist-packages/songpressplusplus
sudo cp "$SRC/PreviewCanvas.py" "$DST/PreviewCanvas.py"
sudo cp "$SRC/SongpressFrame.py" "$DST/SongpressFrame.py"Serve inoltre il PNG dell'icona e il pacchetto wl-clipboard:
# icona (se non già presente nell'installato)
sudo cp "$SRC/img/copy_2.png" "$DST/img/copy_2.png"
# strumento clipboard Wayland
sudo apt install wl-clipboardPer prima cosa, verifica se la sessione è X11 o Wayland (il percorso wl-copy
si attiva solo su Wayland):
# metodo rapido (variabile d'ambiente della sessione grafica)
echo "$XDG_SESSION_TYPE" # stampa wayland oppure x11
# metodo autorevole (systemd-logind, consigliato sulle distro recenti)
loginctl show-session "$(loginctl --no-legend list-sessions | awk -v u="$USER" '$3==u {print $1; exit}')" -p Type --valuePoi prova la copia:
# dopo aver premuto "copia" in Songpress, con l'app ancora aperta:
wl-paste --list-types # deve comparire image/png
wl-paste --type image/png > /tmp/t.png && xdg-open /tmp/t.pngpython3 - <<'PY'
import os
BASE = "src/songpressplusplus"
FG = "wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)"
checks = [
("SongpressFrame.py", f' title_lbl.SetForegroundColour({FG})', "Patch 1a — titolo About leggibile"),
("SongpressFrame.py", f' lbl.SetForegroundColour({FG})', "Patch 1b — testo About leggibile"),
("MyPreferencesDialog.py", " if not colour.IsOk():", "Patch 2 — crash colore"),
("PreferencesDialog.py", "btnAssocAll.Disable()", "Patch 3a — disabilita AssocAll"),
("PreferencesDialog.py", "btnApply.Disable()", "Patch 3b — disabilita Apply"),
("PreferencesDialog.py", "cb.Disable()", "Patch 3c — disabilita checkbox ext"),
("SongpressFrame.py", "BG = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)","Patch 4a — BG statistiche tema sistema"),
("SongpressFrame.py", f" _lbl_verdict.SetForegroundColour({FG})","Patch 4b — verdetto multi-tab leggibile"),
("SongpressFrame.py", f" _lbl_verdict.SetForegroundColour({FG})", "Patch 4c — verdetto singolo leggibile"),
("SongpressFrame.py", f" k_lbl.SetForegroundColour({FG})", "Patch 4d — testo _row multi-tab leggibile"),
("SongpressFrame.py", f" k_lbl.SetForegroundColour({FG})", "Patch 4e — testo _row singolo leggibile"),
("SongpressFrame.py", "getattr(self, '_mgr', None) is not None", "Patch 5a — guardia _mgr in _deferred_tb_update"),
("SongpressFrame.py", "self._closing = True", "Patch 5b — _closing in OnClose"),
("SongpressToolbars.py", "if getattr(self, '_mgr', None) is None:", "Patch 5c — guardia _mgr in _FinalizeToolbarLayout"),
("i18n.py", "_nolog = wx.LogNull()", "Patch 6 — warning locale silenziato"),
("PreviewCanvas.py", "def _load_copy_bitmap", "Patch 7a — icona copy_2.png"),
("PreviewCanvas.py", "def SetCopyCallback", "Patch 7b — callback copia esposta"),
("PreviewCanvas.py", "self._on_copy_callback", "Patch 7c — _OnCopyButton usa la callback"),
("SongpressFrame.py", "SetCopyCallback(self.CopyAsImage)", "Patch 7d — callback registrata"),
("SongpressFrame.py", "def _copy_bytes_wayland", "Patch 7e — copia via wl-copy su Wayland"),
]
for filename, needle, desc in checks:
path = os.path.join(BASE, filename)
try:
with open(path) as f:
found = needle in f.read()
print(f"{'✅' if found else '❌'} {desc}")
except FileNotFoundError:
print(f"⚠️ File non trovato: {path}")
PYIl pacchetto .deb registra automaticamente tutte le estensioni ChordPro
(.crd, .cho, .chordpro, .chopro, .pro, .sng) tramite il file
/usr/share/mime/packages/songpressplusplus.xml e il campo MimeType=
nel file .desktop. Non è necessario usare la scheda "Associazioni file"
dentro Songpress++ — i pulsanti sono disabilitati automaticamente su Linux.
chmod +x ~/Songpress_DEFINitiVO3/SongpressPlusPlus/build_deb.sh
cd ~/Songpress_DEFINitiVO3/SongpressPlusPlus
./build_deb.sh
sudo dpkg -i build_deb/songpressplusplus_*.deb- Tutte le patch sono idempotenti: se già presenti nel sorgente, lo script le rileva e non le applica due volte.
- Il
build_deb.shapplica automaticamente tutte le patch prima di costruire la wheel (ordine: patch → wheel → install). Non è necessario applicare nessuna patch manualmente. - Importante: le patch devono essere applicate prima di
pip wheel, non dopo. Se la wheel viene costruita prima delle patch, le modifiche al sorgente non entrano nel pacchetto installato. - Dopo aver applicato una patch al file installato (senza ricostruire
il
.deb), il fix è attivo immediatamente al prossimo avvio di Songpress++, senza reinstallare nulla.