CommandOutput, DailyForecast, CondensedWeather Ported To Plasma 6

Also, @dhruv8sh has forked the Plasma 5 AlphaBlack Control widget and ported it to Plasma 6. It can be found at https://store.kde.org/p/2136860.

Porting to Plasma 6

After finally setting up a Virt-Manager VM with Plasma 6, I’ve been steadily porting my widget build scripts to Plasma 6.

Most of my old build scripts used kreadconfig5 to read data from the .ini like .desktop files to get the widget’s namespace (eg: com.github.zren.commandoutput) and ServiceType (eg: Plasma/Applet).

packageNamespace=`kreadconfig5 --file="$PWD/package/metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Name"`
packageServiceType=`kreadconfig5 --file="$PWD/package/metadata.desktop" --group="Desktop Entry" --key="X-KDE-ServiceTypes"`

The ServiceType can be assumed if I only use this build script for widgets, but I still need to read the former in my ./install script since I need to check if the namespace is already installed to switch between kpackagetool6 --install and kpackagetool6 --upgrade since --install will fail if the widget already exists in ~/.local/share/plasma/plasmoids.

isAlreadyInstalled=false
kpackagetool5 --type="Plasma/Applet" --show="com.github.zren.commandoutput" &> /dev/null
if [ $? == 0 ]; then
	isAlreadyInstalled=true
fi
if $isAlreadyInstalled; then
	kpackagetool5 --type "Plasma/Applet" --upgrade ./package/
	restartPlasmashell=true
else
	kpackagetool5 --type "Plasma/Applet" --install ./package/
fi

Since Plasma 6 now forces the use of metadata.json, I now need a command that’ll read a JSON object key, using only the software installed by default on most distros. I need to check if jq is installed by default on every distro since that would simplify things. That said, every distro will have python3 installed so we can just use it’s json module.

packageNamespace=`python3 -c 'import sys, json; print(json.load(sys.stdin).get("KPlugin", {}).get("Id", ""))' < "$PWD/package/metadata.json"`

Where things become annoying is with parsing the ServiceType. By default, desktoptojson will convert X-KDE-ServiceTypes=Plasma/Applet in metadata.desktop to this in metadata.json.

{ "KPlugin": { "ServiceTypes": [ "Plasma/Applet" ] } }

However in Plasma 6, you manually need to convert it to:

{ "KPackageStructure": "Plasma/Applet" }

So we need to parse both cases in the build scripts.

packageServiceType=`python3 -c 'import sys, json; print(json.load(sys.stdin).get("KPackageStructure",""))' < "$PWD/package/metadata.json"`
if [ -z "$packageServiceType" ]; then # desktoptojson will set KPlugin.ServiceTypes[0] instead of KPackageStructure
	packageServiceType=`python3 -c 'import sys, json; print((json.load(sys.stdin).get("KPlugin", {}).get("ServiceTypes", [])+[""])[0])' < "$PWD/package/metadata.json"`
	echo "[warning] metadata.json needs KPackageStructure set in Plasma6"
fi

[Plasma 5] Parsing I18n in metadata.desktop

Next up was the problem I’d been dreading since metadata.desktop was deprecated in Plasma 5. Parsing/extracting i18n translations from the new metadata.json then modifying the JSON with the new translations.

With the old metadata.desktop, the xgettext and msgfmt tools had a built in ability to edit .desktop files.

# Note: xgettext v0.20.1 (Kubuntu 20.04) and below will attempt to translate Icon,
# so we need to specify Name, GenericName, Comment, and Keywords.
# https://github.com/Zren/plasma-applet-lib/issues/1
# https://savannah.gnu.org/support/?108887
find "${packageRoot}" -name '*.desktop' | sort > "${DIR}/infiles.list"
xgettext --files-from="${DIR}/infiles.list" --language=Desktop \
	-k -kName -kGenericName -kComment -kKeywords \
	-D "${packageRoot}"	-D "${DIR}"
	-o "template.pot.new" \
	|| \
	{ echoRed "[translate/merge] error while calling xgettext. aborting."; exit 1; }
touch "$DIR/LINGUAS" # List all available translation languages
for cat in `find . -name '*.po' | sort`; do
	catLocale=`basename ${cat%.*}` # "fr.po" => "fr"
	echo "${catLocale}" >> "$DIR/LINGUAS"
done

cp -f "$DIR/../metadata.desktop" "$DIR/template.desktop"
sed -i '/^Name\[/ d; /^GenericName\[/ d; /^Comment\[/ d; /^Keywords\[/ d' "$DIR/template.desktop"

msgfmt --desktop --template="$DIR/template.desktop" \
	-d "$DIR/" \
	-o "$DIR/new.desktop"

[Plasma 6] Parsing I18n in metadata.json

KDE might be using itstool I think (they use it in org.kde.plasmashell.metainfo.po) but plasma-desktop._json_.po which contains the metadata.json translations doesn’t reference it at all.

#. (itstool) path: component/name
#: org.kde.plasmashell.metainfo.xml:9
msgid "KDE Plasma Desktop"
msgstr "Bureau Plasma de KDE"
#: applets/activitypager/metadata.json
msgctxt "Name"
msgid "Activity Pager"
msgstr "Gestionnaire d'activités"

When I realized there wasn’t a simple tool to extract update metadata.json with translations, I realized I’d probably have to write something myself.

Extracting the Name and Description from metadata.json was fairly simple. Just read the keys from JSON and create a simple template.pot. We can just ignore filling out the .pot header as it gets overwritten in the xgettext --join-existing later on.

with open(self.jsonMetaFilepath, 'r') as fin:
	metadata = json.load(fin)
# This template header is overwritten later so the only changes needed is
# 'charset=CHARSET' => 'charset=UTF-8' so that xgettext doesn't complain.
# POT_DEFAULT_HEADER already has the charset=UTF-8 replaced.
newTemplateText = POT_DEFAULT_HEADER
kp = metadata.get('KPlugin', {})
trKeywords = [
	'Name',
	'Description',
]
relativeMetadataPath = os.path.relpath(self.jsonMetaFilepath, self.translateDir)
for keyword in trKeywords:
	keywordMessage = kp.get(keyword, '')
	keywordMessage = keywordMessage.replace('\"', r'\"')
	if keyword != "":
		# keywordText = f"#: {relativeMetadataPath}\nmsgctxt \"{keyword}\"\nmsgid \"{keywordMessage}\"\nmsgstr \"\"\n\n"
		keywordText = f"#: {relativeMetadataPath}\nmsgid \"{keywordMessage}\"\nmsgstr \"\"\n\n"
		newTemplateText += keywordText
with open(newTemplatePath, 'w') as fout:
	fout.write(newTemplateText)

Now the tricky part, parsing the translated language.po files, then editing the metadata.json.

Checkout the regex I used here: https://regex101.com/r/kEJCVL/5

github.com/Zren/plasma-applet-lib/…/kpac#L97

PoMessage = namedtuple('PoMessage', ['ctxt', 'id', 'str'])
class PoFile:
	def __init__(self, filepath):
		self.filepath = filepath
		self.text = None
		self.messages = []
		with open(self.filepath, 'r') as fin:
			self.text = fin.read()
		self.parse()
	@property
	def msgPattern(self):
		# Edit/Test: https://regex101.com/r/kEJCVL
		patt = r'(msgctxt[ \t]+(".*")[ \t]*\n)?'
		patt += r'((".*"[ \t]*\n)*)'
		patt += r'(msgid[ \t]+(".*")[ \t]*\n)'
		patt += r'((".*"[ \t]*\n)*)'
		patt += r'(msgstr[ \t]+(".*")[ \t]*\n)'
		patt += r'((".*"[ \t]*\n)*)'
		return patt
	def _joinMsgStr(self, line1, line234):
		if line1 is None:
			return None
		elif line234 is None:
			return line1.strip().strip('\"')
		else:
			lines = [line1] + line234.split('\n')
			msgstr = ""
			for line in lines:
				msgstr += line.strip().strip('\"')
			return msgstr
	def parse(self):
		for m in re.finditer(self.msgPattern, self.text):
			msgCtx = self._joinMsgStr(m.group(2), m.group(3))
			msgId = self._joinMsgStr(m.group(6), m.group(7))
			msgStr = self._joinMsgStr(m.group(10), m.group(11))
			msg = PoMessage(msgCtx, msgId, msgStr)
			self.messages.append(msg)
	def getMsgStr(self, msgid, msgctxt=None):
		for msg in self.messages:
			if msg.ctxt == msgctxt and msg.id == msgid:
				return msg.str
		return None

github.com/Zren/plasma-applet-lib/…/kpac#L881

with open(self.jsonMetaFilepath, 'r') as fin:
	metadata = json.load(fin)
trKeywordsMap = {
	'Name': '',
	'Description': '',
}
kp = metadata.get('KPlugin', {})
for keyword in trKeywordsMap.keys():
	trKeywordsMap[keyword] = kp.get(keyword, '')
for catFilepath in glob.glob(os.path.join(self.translateDir, '*.po')):
	catFilename = os.path.basename(catFilepath)
	catLocale = os.path.splitext(catFilename)[0]
	catFile = PoFile(catFilepath)
	for keyword, msgid in trKeywordsMap.items():
		catMsgStr = catFile.getMsgStr(msgid)
		catKeyword = f"{keyword}[{catLocale}]"
		if kp.get(catKeyword) != catMsgStr and catMsgStr != "":
			kp[catKeyword] = catMsgStr
with open(filepath, 'w') as fout:
	json.dump(data, fout, ensure_ascii=False, indent='\t', sort_keys=True)
	fout.write('\n') # Trailing newline at EOF

An important part was to use ensure_ascii=False in json.dump() to keep the unicode text.

Drawing the rest of the Owl in Plasma 6

Okay now I needed to do everything else listed on:
https://develop.kde.org/docs/plasma/widget/porting_kf6/

Well since I have like a dozen widgets, I needed to automate some of this.

Manipulating metadata.json with python3 is easy enough as we demonstrated above.

github.com/Zren/plasma-applet-lib/…/kpac#L1055

For the rest, the easiest way would be with some simple sed text replacements. To make life easier I just kept using python though.

github.com/Zren/plasma-applet-lib/…/kpac#L1216

Then I grepped for the new KSvg. namespaces, and added an import org.kde.ksvg as KSvg at the top of the file if it was missing. I may or may not have forgotten to write the rest of the file contents and deleted a few files when testing. Glory to git checkout path/to/file.txt for saving my bacon here.

Lastly I detected if the file was main.qml, and replaced any line starting with ^Item\s*\{ with PlasmoidItem { since I usually keep my code properly indented.

As always, creating a list of all the various Plasmoid.___ properties to replace was a pain.

[Plasma5] Global plasmoid property (which is also attached to the widget root item as Plasmoid)

[Plasma6] PlasmoidItem root item

[Plasma6] Attached Plasmoid similar to Layout which dynamically grabs the value from the Applet class

Since Plasma6 made things a little confusing as to what is in PlasmoidItem {} and what is accessed with Plasmoid.___ I settled on the following for now:

github.com/Zren/plasma-applet-lib/…/kpac#L1185

plasmoidPropsPorted = [
	# AppletQuickItem
	'switchWidth', 'switchHeight',
	'compactRepresentation', 'fullRepresentation',
	'preloadFullRepresentation', 'preferredRepresentation',
	'expanded', 'activationTogglesExpanded',
	'hideOnWindowDeactivate',
	# PlasmoidItem
	'toolTipMainText', 'toolTipSubText', 'toolTipTextFormat', 'toolTipItem',
	'hideOnWindowDeactivate',
]
for prop in plasmoidPropsPorted:
	line = line.replace(f"Plasmoid.{prop}:", f"{prop}:")

The last things I had to do manually was convert the plasmoid.setAction('refresh, ...) calls to the new Plasmoid.contextualActions: [ PlasmaCore.Action {...} ] which would be a tad too complicated to automate.

I was able to quickly sed replace my plasmoid.action('configure').trigger() trick to open the config dialog automatically when testing though which I have in most of my widgets.

PlasmoidItem {
	Component.onCompleted: Plasmoid.internalAction("configure").trigger()
}

In Closing

Overall, with my new script it should be as easy to do most of the dull text replacement work.

The hard part will be converting any deprecated QQC1.Buttons and other GUI elements that might have custom styling to their QQC2.Button counterparts. TiledMenu and EventCalendar will probably be more annoying to port than just running the following:

github.com/Zren/plasma-applet-lib/…/kpac

python3 ./kpac updatelib ../plasma-applet-lib
python3 ./kpac plasma6
# Scan changes 
python3 ./kpac i18n # Update metadata.json and convert .po => .mo
git commit . -m 'Update'
python3 ./kpac build # Create a zipped .plasmoid

I got too tired last night, so firsts things up is porting SimpleWeather today since it’s basically the same code as the other weather widgets.

This “widgets have been updated” blog post rambled on more than I expected, sorry bout that.

OpenSUSE Krypton Plasma 6 Setup

The last time I tried installing Plasma 6 in a VM with OpenSUSE Krypton, I failed to actually install Plasma 6 for development use. Recently I noticed a tip on the KDE Wiki and managed to get it working.

QML Profiling Plasmashell

Since my last article on the subject, I’ve found out that plasmashell has an environment variable that enables QML Profiling which makes debugging Plasma far simpler.

KDE Neon Update Bug (Feb 16) (Fixed)

TL;DR: I went to TTY2 and ran the update again in the morning (12 hours later), then went back to TTY1 and could login. Seems libkf5coreaddons5 was missing in the update last night.

Custom Weather Widgets in Plasma 5.24

For those unaware, KDE ships it’s own Weather Widget. The weather service parsers are a Plasma DataEngine shipped in plasma-workspace while the actual visual widget is in the kdeplasma-addons git repo. Kubuntu breaks up that repo up into several packages however, so you’ll need to install plasma-widgets-addons.

Firefox/Chrome Profile App Launchers

If you share a computer and want separate bookmarks/extensions without multiple OS users, using multiple web browser profiles is the solution. In Windows, creating a new browser profile will create an app launcher for you, but in Linux you have to do it manually.

Developing KRunner

KRunner’s source code is split amongst many repos, so I figured I’d jot down their locations here.

Testing Wayland in a Neon LiveCD

I’d like to easily test window decoration in Wayland, which requires restarting kwin_wayland. Unfortunately we can’t do that as kwin_wayland is the login session.

NightColor.py for Setting Temp From Terminal

I recently looked into the nightcolor widget hoping to add a mousewheel control to temporarily force a specific color like the Redshift Control widget does. Unfortunately I realized the existing API in the NightColor QML plugin doesn’t have the function to do so. So I dived into the NightColor DBus API using qdbusviewer, as I was hoping to run a qdbus command. The Night Color effect is done by KWin, and is codenamed “ColorCorrect” in it’s code.

Show KOrganizer Events in the Panel's Calendar

This is a video demonstration on how to install and setup kdepim-addons which binds the events shown in the KOrganizer app with the calendar in the KDE Plasma panel.

In order to install the entire KDE PIM suite, run:

sudo apt install kdepim

If you only want to connect KOrganizer with the panel, you probably only need:

sudo apt install kdepim-addons

Building Plasma's System Monitor (Ctrl+Esc) By Itself

The “System Monitor” that ships with the plasma-workspace package is fairly useful, and is similar to KSysGuard. The System Monitor has a default shortcut (Ctrl+Esc) so you can quickly access it on any KDE Plasma installation. I usally have System Monitor sorted by “Relative Sort Time” so that I can quickly see the newest processes and if they’re the cause of what’s slowing down my PC.

Building EnvCan Weather Ion By Itself

We first open up the plasma-workspace repo, and navigate to dataengines/weather/ions/envcan. This will be the folder we’ll try to compile by itself.

Picture In Picture With Firefox in KDE

I used to be able to get Picture In Picture with Chrome by maximizing the video, Alt+F3+M+F to exit fullscreen but stay in Chrome’s “fullscreen mode” with the addressbar and window frame hidden. I would then hit Alt+F3+M+A to make the window stay on top. Finally I would use Alt+RightClick to resize the window to be smaller, and Alt+LeftClick to move the window into the bottom right of the screen. This worked for the most part, but was cumbersome to setup.

Creating a New Plasma Look and Feel

A user on reddit recently asked how to modify the Breeze Alt+Tab theme. Unfortunately it seems to be tied to the Look and Feel as of Plasma 5.14.

Simpler kdesrc-build Script

Using kdesrc-build to compile kde repositories is quite verbose, and by default will build all dependencies which can take ages. So I wrote a script to quickly build a single repo and install it.

KWin TabBox Keyboard Events

In case you didn’t know, TabBox is the codename for the Alt+Tab Task Switcher. There are 2 ways to create a Task Switcher skin. A simple QML skin, or a C++ “Desktop Effect”. Somehwere along the way, the QML skins were no longer able to use keyboard shortcuts.

Compiling Dolphin

First install the build dependencies with apt-get build-dep.

AlphaBlack Control Widget

For a long while now, I’ve had a widget that simplifies changing the accent color of my Breeze AlphaBlack desktop theme. It had a few major usuability flaws so I never got around to cleaning it up for general release.

Patching Breeze Window Decorations

KDE ships with the Breeze window decorations. Which are drawn via a C++ KDecoration2 “plugin”. You can also download other decorations from the KDE Store (via Get Hot New Stuff) that are SVG based themes for the Aurorae KDecoration2 “plugin” (which is also preinstalled in KDE).

XBox One Controller in KDE Neon

As of Dec 9th 2016, KDE Neon (which is based on Ubuntu 16.04 LTS) is only using the Linux 4.4.0-53 kernel. Which means the XBox One controller isn’t fully supported without some running some commands manually.