Compare commits

...

22 Commits

Author SHA1 Message Date
Daniel Barlow c3b796ef79 add maps app (doesn't do much yet) 2024-07-22 22:25:25 +01:00
Daniel Barlow 6fd41b7e1c fix "could not acquire 'org.freedesktop.ModemManager1'" error
I don't know why this works, but modemmanager-small needs to be
in environment.systemPackages
2024-07-22 16:16:01 +01:00
Daniel Barlow d12cb03a4e clean up README (a bit) 2024-07-22 15:44:55 +01:00
Daniel Barlow f0de997680 build ModemManager with introspection
as the files it creates are required by Satellite

I thought originally that this was stopping it from cross-compiling,
but it must have been something else
2024-07-22 14:49:31 +01:00
Daniel Barlow d83cce069c remove awful pkg-config kludge, use depwBuildBuld 2024-07-22 14:47:28 +01:00
Daniel Barlow f847830a06 put modem-manager-small in overlay 2024-07-22 14:45:47 +01:00
Daniel Barlow 7ad42c43b2 add lk2nd makefile target 2024-07-21 21:02:17 +01:00
Daniel Barlow c5a546f10f add wlr-randr, set output scale to 2 2024-07-21 21:01:39 +01:00
Daniel Barlow 1c61ff87b2 start cage on boot, run saturn 2024-07-21 21:00:36 +01:00
Daniel Barlow 298822d493 ModemManager needs dbus 2024-07-21 20:59:07 +01:00
Daniel Barlow 25ab42dad0 add to saturn 2024-07-21 20:58:29 +01:00
Daniel Barlow 6d7bf21483 move modem-manager into pkgs/ 2024-07-21 20:57:55 +01:00
Daniel Barlow a0e7e97a5c add "saturn", a launcher package 2024-07-21 20:56:12 +01:00
Daniel Barlow 0bedce6144 disable dyndbg kernel param 2024-07-19 23:48:04 +01:00
Daniel Barlow 4d968948cd remoe commented-out dead code 2024-07-19 23:47:24 +01:00
Daniel Barlow 09a57c4269 disable the gps engine lock 2024-07-19 23:44:36 +01:00
Daniel Barlow 82635955fe add modemmanager service and udev rules to tag the modem parts
a surprisngly large number of the udev rules do turn out to
be actually needed. Ignore the pasted comments  about rpmsgexport,
even those ones are important
2024-07-19 23:44:19 +01:00
Daniel Barlow ff895276c8 add minimal ModemManager for use with GPS
can't use the regular nixos modem-manager because it doesn't
cross-compile
2024-07-19 22:47:22 +01:00
Daniel Barlow 3174caa0ed delete commented-out Potter relics 2024-02-19 20:04:57 +00:00
Daniel Barlow 4e78ad4d91 enable rmtfs: file server for remote processors
(I'm guessing it's actually more of a "block storage" service as it
expects to serve straight from mmc partitions with no filesystem in
sight)

This is step 1 towards getting the modem working, and also seems to
have initialised the audio

[  682.111619] remoteproc remoteproc1: remote processor 4080000.remoteproc is now up
[...]
[  682.580706] input: msm8916 Headset Jack as /devices/platform/soc@0/7702000.sound/sound/card0/input5
[  687.912897] wwan wwan0: port wwan0at0 attached
[  687.914081] wwan wwan0: port wwan0at1 attached
[  687.944139] wwan wwan0: port wwan0qmi0 attached
2024-02-19 20:00:38 +00:00
Daniel Barlow 1ed8201d86 use networkd, it's the future
I turned this on while tryig to make wifi work and although it
didn't have that effect I also haven't subsequently tried turning it off
2024-02-19 19:58:36 +00:00
Daniel Barlow 2932649a51 don't growsf on boot, it seems to cause corruption 2024-02-19 19:57:03 +00:00
14 changed files with 920 additions and 21 deletions

View File

@ -9,6 +9,10 @@ default toplevel:
android-bootimg android-recovery:
nix-build $(NFLAGS) -A outputs.android.$@ -o $@
lk2nd:
nix-build $(NFLAGS) ../mobile-nixos --argstr device motorola-harpia -A pkgs.pkgsBuildBuild.lk2nd.msm8916 -o lk2nd
update: toplevel
nix-copy-closure --to $(DEVICE) -v --include-outputs ./toplevel
ssh $(DEVICE) "nix-env --profile /nix/var/nix/profiles/system --set `readlink toplevel` && /nix/var/nix/profiles/system/bin/switch-to-configuration switch"

40
README
View File

@ -1,5 +1,5 @@
Config for my moto potter to turn it into a bike computer. We will
Config for my moto harpia to turn it into a bike computer. We will
need
- working gps
@ -19,19 +19,43 @@ write the app in fennel. I want it to
- show where I am on a map
- record trail of where I've been (note: indoor counts too)
we can use l"ove2d instead of dragging in that gtk stuff
SDL2
To run an SDL2 application on Wayland, set
SDL_VIDEODRIVER=wayland.
can we do bluetooth not through dbus? dbus seems to make it flakey
can we somehow do non-flakey bluetooth (is it dbus?)
cool extras
===========
adjust screen brightness (go dark when not moving)
adjust screen brightness
* turn off backlight when stopped after a minute
* dim the screen when following a route and the next
navigation instruction is still some way off
* tap to wake screen
note that IPS LCD requires as much battery for dark pixels
as light ones, so we get no power saving by colouring only part
of the screen
------
random notes follow ...
1) Satellite requires these commands to be run before
it will work.
mmcli -m 0 --enable
mmcli -m 0 --location-enable-gps-raw
mmcli -m 0 --location-enable-gps-nmea
mmcli -m 0 --location-status
2) we should make saturn give visual feedback while it's loading an
app. How does it know when the app has finished loading? Maybe it
could get told when its window is obscured

View File

@ -12,6 +12,8 @@ let
makeFlags = ["prefix=${placeholder "out"}"];
};
saturn = pkgs.callPackage ./pkgs/saturn {};
drm-framebuffer = pkgs.stdenv.mkDerivation {
name = "drm-framebuffer";
src = pkgs.fetchFromGitHub {
@ -38,12 +40,21 @@ in {
];
config = {
nixpkgs.overlays = [
(final: prev:
let mm = pkgs.callPackage ./pkgs/modem-manager {};
in {
wlr-randr = prev.wlr-randr.overrideAttrs(o: {
depsBuildBuild = [ final.pkgsBuildBuild.pkg-config ];
});
modemmanager-small = mm;
satellite = prev.satellite.override { modemmanager = mm; };
maps = final.callPackage ./pkgs/maps {};
})
];
mobile = {
adbd.enable = true;
# boot.stage-1.gui.enable = false;
# beautification.splash = false;
# quirks.fb-refresher.enable = lib.mkForce false;
# quirks.qualcomm.msm8953-modem.enable = true;
boot.stage-1.firmware = [
modemFirmware
pkgs.wireless-regdb
@ -56,28 +67,142 @@ in {
system.stateVersion = "23.11";
boot.kernelParams = [
"dyndbg=\"file drivers/base/firmware_loader/main.c +fmp\""
# "dyndbg=\"file drivers/base/firmware_loader/main.c +fmp\""
# "dyndbg=\"file drivers/gpu/drm/panel/msm8953-generated/panel-boe-bs052fhm-a00-6c01.c +fmp\""
# "dyndbg=\"file drivers/video/backlight/qcom-wled.c +fmp\""
];
# according to fdisk, /dev/mmcblk0p41 is 30469887-7471104=22998783
# blocks, and if we let it get resized by whatever magic this is
# (I'm assuming systemd-growfs), on the subsequent boot we'll get
# an error
# EXT4-fs (mmcblk0p41): bad geometry: block count 2883067 exceeds size of device (2874848 blocks)
# in which neither number matches the reported size (they're both
# about 10% of it). cool.
boot.growPartition = lib.mkForce false;
fileSystems."/" = lib.mkDefault {
autoResize = lib.mkForce false;
};
services.udev.extraRules = ''
ACTION!="add|change|move|bind", GOTO="mm_qcom_soc_end"
# Process only known wwan, net and rpmsg ports
SUBSYSTEM=="net", DRIVERS=="bam-dmux", GOTO="mm_qcom_soc_process"
# SUBSYSTEM=="platform", DRIVERS=="bam-dmux", GOTO="mm_qcom_soc_process"
SUBSYSTEM=="net", DRIVERS=="ipa", GOTO="mm_qcom_soc_process"
SUBSYSTEM=="wwan", DRIVERS=="qcom-q6v5-mss", GOTO="mm_qcom_soc_process"
SUBSYSTEM=="rpmsg", DRIVERS=="qcom-q6v5-mss", GOTO="mm_qcom_soc_process"
GOTO="mm_qcom_soc_end"
LABEL="mm_qcom_soc_process"
# Flag the port as being part of the SoC
ENV{ID_MM_QCOM_SOC}="1"
# #
# # Add a common physdev UID to all ports in the Qualcomm SoC, so that they
# # are all bound together to the same modem object.
# #
# # The MSM8916, MSM8974, .... Qualcomm SoCs use the combination of RPMSG/WWAN
# # based control ports plus BAM-DMUX based network ports.
# #
ENV{ID_MM_PHYSDEV_UID}="qcom-soc"
# port type hints for the rpmsgexport-ed ports
SUBSYSTEM=="rpmsg", ATTR{name}=="DATA*", ATTR{name}=="*_CNTL", ENV{ID_MM_PORT_TYPE_QMI}="1"
SUBSYSTEM=="rpmsg", ATTR{name}=="DATA*", ATTR{name}!="*_CNTL", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
# ignore every other port without explicit hints
SUBSYSTEM=="rpmsg", ENV{ID_MM_PORT_TYPE_QMI}!="1", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}!="1", ENV{ID_MM_PORT_IGNORE}="1"
# explicitly ignore ports intended for USB tethering (DATA40, DATA40_CNTL)
SUBSYSTEM=="rpmsg", ATTR{name}=="DATA40*", ENV{ID_MM_PORT_IGNORE}="1"
KERNEL=="rmnet_usb*", ENV{ID_MM_PORT_IGNORE}="1"
# flag all rpmsg ports under this plugin as candidate
# KERNEL=="rpmsg*", SUBSYSTEM=="rpmsg", ENV{ID_MM_CANDIDATE}="1"
KERNEL=="rpmsg*", SUBSYSTEM=="rpmsg", ENV{ID_MM_CANDIDATE}="1"
LABEL="mm_qcom_soc_end"
ACTION!="add|change|move|bind", GOTO="mm_candidate_end"
# # Opening bound but disconnected Bluetooth RFCOMM ttys would initiate the
# # connection. Don't do that.
KERNEL=="rfcomm*", DEVPATH=="*/virtual/*", GOTO="mm_candidate_end" # */
SUBSYSTEM=="net", ENV{ID_MM_CANDIDATE}="1"
# WWAN subsystem port handling
# - All USB devices ignored for now, only PCI devices expected
# - Only "wwan_port" device types processed (single ports); we fully ignore
# the "wwan_dev" device type (full device, not just one port)
SUBSYSTEMS=="usb", GOTO="mm_candidate_end"
SUBSYSTEM=="wwan", ENV{DEVTYPE}=="wwan_dev", GOTO="mm_candidate_end"
SUBSYSTEM=="wwan", ENV{ID_MM_CANDIDATE}="1"
LABEL="mm_candidate_end"
# unlock the gps engine
SUBSYSTEM=="wwan", ENV{DEVNAME}=="/dev/wwan0qmi0", ENV{DEVTYPE}=="wwan_port", \
RUN+="${pkgs.libqmi}/bin/qmicli -pd $env{DEVNAME} --loc-set-engine-lock=none"
'';
systemd.services.ModemManager = {
wantedBy = [ "multi-user.target" ];
serviceConfig =
let script = pkgs.writeScript "start-modem-manager" ''
#!${pkgs.bash}/bin/bash
source ${config.system.build.setEnvironment}
${pkgs.modemmanager-small}/bin/ModemManager
'';
in {
StandardInput = "journal";
StandardError = "journal";
StandardOutput = "journal";
SyslogIdentifier = "ModemManager";
ExecStart = script;
Restart = "always";
BindsTo = ["dbus.service"];
};
};
networking = {
useDHCP = true;
hostName = "biscuit";
networkmanager = { enable = false; };
useNetworkd = true;
inherit (secrets) wireless;
};
services.openssh.enable = true;
# environment.systemPackages = [ qrtr tqftpserv ];
services.udev.extraRules = ''
# copied from https://github.com/andersson/rpmsgexport
ACTION=="add", SUBSYSTEM=="rpmsg", KERNEL=="rpmsg_ctrl[0-9]*", ATTRS{rpmsg_name}=="pronto", RUN+="${rpmsgexport}/bin/rpmsgexport /dev/$name APPS_RIVA_CTRL"
SUBSYSTEM=="rpmsg", KERNEL=="rpmsg_ctrl[0-9]*", ATTRS{rpmsg_name}=="?*", SYMLINK+="rpmsg/$attr{rpmsg_name}/ctrl"
SUBSYSTEM=="rpmsg", KERNEL=="rpmsg[0-9]*", ATTR{name}=="?*", ATTRS{rpmsg_name}=="?*", SYMLINK+="rpmsg/$attr{rpmsg_name}/$attr{name}"
'';
environment.systemPackages = with pkgs; [ drm-framebuffer];
services.cage =
let wlinit = pkgs.writeScript "wlinit" ''
${pkgs.wlr-randr}/bin/wlr-randr --output DSI-1 --scale 2
exec ${saturn}/bin/saturn
'';
in {
enable = true;
program = wlinit;
user = "dan";
};
environment.systemPackages = with pkgs; [
drm-framebuffer
saturn
satellite
wlr-randr
modemmanager-small
maps
];
users.users.dan = {
isNormalUser = true;
@ -95,5 +220,30 @@ in {
hardware.opengl = {
enable = true; driSupport = true;
};
systemd.services = {
rmtfs =
# the four files in here are partition images:
# partlabel => filename
# modemst1 => modem_fs1
# modemst2 => modem_fs2
# fsc => modem_fsc
# fsg => modem_fsg
let files = pkgs.runCommand "rmtfs" {} ''
mkdir -p $out/lib/firmware
cp ${./local/uncompressed-firmware/rmtfs}/* $out/lib/firmware
'';
in {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
# https://github.com/andersson/rmtfs/blob/7a5ae7e0a57be3e09e0256b51b9075ee6b860322/rmtfs.c#L507-L541
ExecStart = "${pkgs.rmtfs}/bin/rmtfs -s -r -o ${files}/lib/firmware";
Restart = "always";
RestartSec = "10";
};
};
};
};
}

16
pkgs/maps/Makefile Normal file
View File

@ -0,0 +1,16 @@
FENNEL?=fennel
PREFIX?=/usr/local
NAME?=maps
MODULES=main.fnl
%.lua : %.fnl
$(FENNEL) --compile $< > $@
$(NAME): $(patsubst %.fnl,%.lua,$(MODULES)) Makefile
(echo -e "#!/usr/bin/env lua\n" ; cat main.lua ) > $@
chmod +x $@
install:
mkdir -p $(PREFIX)/bin $(PREFIX)/
cp $(NAME) $(PREFIX)/bin

85
pkgs/maps/default.nix Normal file
View File

@ -0,0 +1,85 @@
{ stdenv
, pkg-config
, buildPackages
, callPackage
, clutter
, fetchFromGitHub
, fetchurl
, gobject-introspection
, gtk3
, lib
, lua53Packages
, lua5_3
, makeDesktopItem
, makeWrapper
, wrapGAppsHook3
, writeText
, osm-gps-map
, glib-networking
, copyDesktopItems
}:
let
luaPackages = lua53Packages;
fennel = luaPackages.fennel;
lgi = luaPackages.buildLuaPackage {
pname = "lgi";
version = "0.9.2-2";
buildInputs = [ gobject-introspection ];
nativeBuildInputs = [ pkg-config ];
src = fetchFromGitHub {
owner = "lgi-devs";
repo = "lgi";
rev = "e06ad94c8a1c84e3cdb80cee293450a280dfcbc7";
hash = "sha256-VYr/DV1FAyzPe6p6Quc1nmsHup23IAMfz532rL167Q4=";
};
};
lua = lua5_3.withPackages (ps: with ps; [
lgi
luafilesystem
luaposix
readline
]);
pname = "maps";
in stdenv.mkDerivation {
inherit pname;
version = "0.1";
src =./.;
buildInputs = [
lua
gtk3.dev
gobject-introspection # .dev
osm-gps-map
glib-networking
# gdk-pixbuf
# glib
# libchamplain
];
nativeBuildInputs = [
buildPackages.lua
gobject-introspection
makeWrapper
fennel
wrapGAppsHook3
copyDesktopItems
];
GIO_EXTRA_MODULES = [ "${glib-networking.out}/lib/gio/modules" ];
makeFlags = [ "PREFIX=${placeholder "out"}" "NAME=${pname}" ];
postInstall = ''
mkdir -p $out/share/icons/
cp icon.svg $out/share/icons/${pname}.svg
'';
desktopItems = [
(makeDesktopItem {
name = pname;
desktopName = "Maps";
exec = pname;
type = "Application";
icon = "nix-snowflake"; # "${placeholder "out"}/share/icons/${pname}.svg";
})
];
}

71
pkgs/maps/main.fnl Normal file
View File

@ -0,0 +1,71 @@
; (local { : view } (require :fennel))
(local {
: Gtk
: OsmGpsMap
: Gdk
}
(require :lgi))
(local CSS "
label.readout {
font: 48px \"Noto Sans\";
margin: 10px;
padding: 5px;
background-color: rgba(0, 0, 0, 0.2);
}
")
(fn styles []
(let [style_provider (Gtk.CssProvider)]
(Gtk.StyleContext.add_provider_for_screen
(Gdk.Screen.get_default)
style_provider
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
(style_provider:load_from_data CSS)))
(local window (Gtk.Window {
:title "Map"
:name "toplevel"
:default_width 720
:default_height 800
:on_destroy Gtk.main_quit
}))
(fn osm-widget []
(doto (OsmGpsMap.Map {})
(tset :map-source OsmGpsMap.MapSource_t.OPENSTREETMAP)
(: :set_center_and_zoom 52.595 -0.1 14)
(: :layer_add (OsmGpsMap.MapOsd {
:show_copyright true
; :show_coordinates true
:show_scale true
}))
))
(fn readout [text]
(doto (Gtk.Label {:label text})
(-> (: :get_style_context) (: :add_class :readout))))
(fn readouts []
(doto (Gtk.Box
{
:orientation Gtk.Orientation.VERTICAL
:halign Gtk.Align.END
})
(-> (: :get_style_context) (: :add_class :readouts))
(: :add (readout "21:05:00"))
(: :add (readout "00:00"))
(: :add (readout "25 km/h"))))
(window:add
(doto (Gtk.Overlay {})
(: :add (osm-widget))
(: :add_overlay (readouts))
))
(window:show_all)
(styles)
(Gtk:main)

9
pkgs/maps/shell.nix Normal file
View File

@ -0,0 +1,9 @@
with import <nixpkgs> {};
let package = pkgs.callPackage ./. {};
in
package.overrideAttrs(o: {
shellHook = ''
export LUA_CPATH=$(lua -e "print(package.cpath)")
export LUA_PATH=$(lua -e "print(package.path)")
'';
})

View File

@ -0,0 +1,84 @@
{
stdenv,
fetchFromGitLab,
libxslt,
bash, # shebangs in share/ModemManager/fcc-unlock.available.d/,
bash-completion,
dbus,
gettext,
glib,
libgudev,
libqmi,
meson,
ninja,
cmake,
pkg-config,
python3,
systemd,
gobject-introspection,
pkgsBuildBuild,
buildPackages
}: stdenv.mkDerivation rec {
pname = "modemmanager";
version = "1.22.0";
src = fetchFromGitLab {
domain = "gitlab.freedesktop.org";
owner = "mobile-broadband";
repo = "ModemManager";
rev = version;
hash = "sha256-/D9b2rCCUhpDCUfSNAWR65+3EyUywzFdH1R17eSKRDo=";
};
patches = [
/home/dan/src/nixpkgs/pkgs/tools/networking/modemmanager/no-dummy-dirs-in-sysconfdir.patch
];
nativeBuildInputs = [
meson
ninja
gobject-introspection
gettext
pkg-config
libxslt
python3
cmake
glib # for gdbus-codegen
];
buildInputs = [
glib
libgudev
# polkit
pkg-config
# ppp
# libmbim
libqmi
systemd
bash-completion
dbus
# cmake
bash # shebangs in share/ModemManager/fcc-unlock.available.d/
];
depsBuildBuild = [
pkg-config
];
mesonFlags = [
"-Dudevdir=${placeholder "out"}/lib/udev"
"-Ddbus_policy_dir=${placeholder "out"}/share/dbus-1/system.d"
"--sysconfdir=/etc"
"--localstatedir=/var"
"-Dvapi=false"
"-Dmbim=false"
"-Dqmi=true"
"-Dqrtr=false"
"-Dpolkit=no"
"-Dplugin_foxconn=disabled"
"-Dplugin_quectel=disabled"
"-Dman=false"
"-Dexamples=false"
"-Dtests=false"
# "-Dintrospection=false"
];
}

15
pkgs/saturn/Makefile Normal file
View File

@ -0,0 +1,15 @@
FENNEL?=fennel
PREFIX?=/usr/local
MODULES=main.fnl
%.lua : %.fnl
$(FENNEL) --compile $< > $@
saturn: $(patsubst %.fnl,%.lua,$(MODULES)) Makefile
(echo -e "#!/usr/bin/env lua\n" ; cat main.lua ) > $@
chmod +x $@
install:
mkdir -p $(PREFIX)/bin
cp saturn $(PREFIX)/bin

9
pkgs/saturn/README.md Normal file
View File

@ -0,0 +1,9 @@
# Saturn
> Saturn 5, you really were the greatest sight
A very simple launcher app for the Pinephone, written using Fennel and
the LGI bindings to gobject-introspection.
I may someday separate this from the rest of Slab but for the moment
it's more convenient to keep it all together

99
pkgs/saturn/default.nix Normal file
View File

@ -0,0 +1,99 @@
{ stdenv
, pkg-config
, buildPackages
, callPackage
, fennel
, fetchFromGitHub
, fetchurl
, gdk-pixbuf
, glib
, gobject-introspection
, gtk3
, harfbuzz
, lib
, librsvg
, lua53Packages
, lua5_3
, makeWrapper
, pango
, wrapGAppsHook3
, writeText
}:
let
luaPackages = lua53Packages;
lgi = luaPackages.buildLuaPackage {
pname = "lgi";
version = "0.9.2-2";
buildInputs = [ gobject-introspection ];
nativeBuildInputs = [ pkg-config ];
src = fetchFromGitHub {
owner = "lgi-devs";
repo = "lgi";
rev = "e06ad94c8a1c84e3cdb80cee293450a280dfcbc7";
hash = "sha256-VYr/DV1FAyzPe6p6Quc1nmsHup23IAMfz532rL167Q4=";
};
};
luaDbusProxy = callPackage ./lua-dbus-proxy.nix {
inherit (luaPackages) buildLuaPackage;
inherit lgi;
lua = lua5_3;
};
inifile = luaPackages.buildLuaPackage rec {
pname = "inifile";
name = "${pname}-${version}";
version = "1.0.2";
src = fetchFromGitHub {
owner = "bartbes";
repo = "inifile";
rev = "f0b41a8a927f3413310510121c5767021957a4e0";
sha256 = "1ry0q238vbp8wxwy4qp1aychh687lvbckcf647pmc03rwkakxm4r";
};
buildPhase = ":";
installPhase = ''
mkdir -p "$out/share/lua/${lua.luaversion}"
cp inifile.lua "$out/share/lua/${lua.luaversion}/"
'';
};
lua = lua5_3.withPackages (ps: with ps; [
luaDbusProxy
inifile
inspect
lgi
luafilesystem
luaposix
penlight
readline
]);
in stdenv.mkDerivation {
pname = "saturn";
version = "0.4.9"; # nearly Saturn 0.5
src =./.;
buildInputs = [
lua
gtk3.dev
gobject-introspection # .dev
gdk-pixbuf
glib
];
nativeBuildInputs = [
buildPackages.lua
gobject-introspection
makeWrapper
fennel
wrapGAppsHook3
];
makeFlags = [ "PREFIX=${placeholder "out"}" ];
postInstall = ''
mkdir -p $out/share/dbus-1/services
cat <<SERVICE > $out/share/dbus-1/services/net.telent.saturn.service
[D-BUS Service]
Name=net.telent.saturn
Exec=$out/bin/saturn
SERVICE
'';
}

View File

@ -0,0 +1,25 @@
{ lua, lgi, pkg-config, buildLuaPackage, fetchFromGitHub }:
let
simpleName = "dbus_proxy";
in buildLuaPackage rec {
version = "0.10.2";
pname = simpleName;
nativeBuildInputs = [ pkg-config ];
src = fetchFromGitHub {
owner = "stefano-m";
repo = "lua-${simpleName}";
rev = "v${version}";
sha256 = "0kl8ff1g1kpmslzzf53cbzfl1bmb5cb91w431hbz0z0vdrramh6l";
};
propagatedBuildInputs = [ lgi ];
buildPhase = ":";
installPhase = ''
mkdir -p "$out/share/lua/${lua.luaversion}"
cp -r src/${pname} "$out/share/lua/${lua.luaversion}/"
'';
}

301
pkgs/saturn/main.fnl Normal file
View File

@ -0,0 +1,301 @@
(local {: Gio
: GLib
: GObject
: Gtk
: GdkPixbuf
: Gdk
: Pango}
(require :lgi))
(local {: List
: stringx
: tablex
}
((require :pl.import_into)))
(local dbus (require :dbus_proxy))
(local inspect (require :inspect))
(local lfs (require :lfs))
(local inifile (require :inifile))
(local posix (require :posix))
(local ICON_SIZE 64)
(local CSS "
* {
color: rgb(255, 255, 255);
text-shadow:
0px 1px rgba(0, 0, 0, 255)
, 1px 0px rgba(0, 0, 0, 255)
, 0px -1px rgba(0, 0, 0, 255)
, -1px 0px rgba(0, 0, 0, 255)
, 1px 1px rgba(0, 0, 0, 255)
, 1px -1px rgba(0, 0, 0, 255)
, -1px 1px rgba(0, 0, 0, 255)
, -1px -1px rgba(0, 0, 0, 255)
;
}
button.appbutton {
padding: 0px;
}
#toplevel {
background-color: rgba(0.2, 0.2, 0.4, 1.0);
}
")
(local dbus-service-attrs
{
:bus dbus.Bus.SESSION
:name "net.telent.saturn"
:interface "net.telent.saturn"
:path "/net/telent/saturn"
})
(local bus (dbus.Proxy:new
{
:bus dbus.Bus.SESSION
:name "org.freedesktop.DBus"
:interface "org.freedesktop.DBus"
:path "/org/freedesktop/DBus"
}))
(local interface-info
(let [xml
"<node>
<interface name='net.telent.saturn'>
<method name='SetVisible'>
<arg type='b' name='visible' direction='in'/>
<doc:doc><doc:description>
Switch visibility of launcher window
</doc:description></doc:doc>
</method>
<method name='ToggleVisible'>
<doc:doc><doc:description>
Toggle launcher window visible/invisible
</doc:description></doc:doc>
</method>
<property name='Visible' type='b' access='read'>
</property>
</interface>
</node>"
node-info (Gio.DBusNodeInfo.new_for_xml xml)]
(. node-info.interfaces 1)))
;; these values don't seem to be available through introspection
(local DBUS_NAME_FLAG_DO_NOT_QUEUE 4)
(local DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1)
(local DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2)
(local DBUS_REQUEST_NAME_REPLY_EXISTS 3)
(local DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4)
(let [ret (bus:RequestName dbus-service-attrs.name
DBUS_NAME_FLAG_DO_NOT_QUEUE)]
(match ret
DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
true
DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER
true
DBUS_REQUEST_NAME_REPLY_IN_QUEUE
(error "unexpected DBUS_REQUEST_NAME_REPLY_IN_QUEUE")
DBUS_REQUEST_NAME_REPLY_EXISTS
;; Show the currently running instance
(let [saturn (dbus.Proxy:new dbus-service-attrs)]
(saturn:SetVisible true)
(os.exit 0))))
(local path {
:absolute? (fn [str] (= (str:sub 1 1) "/"))
:concat (fn [...] (table.concat [...] "/"))
})
(local search-path {
:concat (fn [...] (table.concat [...] ":"))
})
(local icon-theme (Gtk.IconTheme.get_default))
;; Use the declared CSS for this app
(let [style_provider (Gtk.CssProvider)]
(style_provider:load_from_data CSS)
(Gtk.StyleContext.add_provider_for_screen
(Gdk.Screen.get_default)
style_provider
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
))
(local window (Gtk.Window {
:title "Saturn"
:name "toplevel"
:default_width 720
:default_height 800
:on_destroy Gtk.main_quit
}))
;; Using RGBA visual for semi-transparent backgrounds
;; Requires compositing (e.g. a compositor on X11)
(let [screen (window:get_screen)
visual (screen:get_rgba_visual)]
(window:set_visual visual))
(fn find-icon [name]
(var found false)
(if (path.absolute? name)
;; From a direct path
(set found (GdkPixbuf.Pixbuf.new_from_file_at_size name ICON_SIZE ICON_SIZE))
;; From icon theme
(let [sizes (icon-theme:get_icon_sizes name)]
;; Uses a list of "safe fallback" values
;; Try the desired size first
(each [_ res (pairs [ICON_SIZE 128 64 48]) :until found]
(set found
(-?> (icon-theme:load_icon
name res
(+ Gtk.IconLookupFlags.FORCE_SVG Gtk.IconLookupFlags.USE_BUILTIN))
(: :scale_simple ICON_SIZE ICON_SIZE GdkPixbuf.InterpType.BILINEAR))))
))
(Gtk.Image.new_from_pixbuf found))
(fn read-desktop-file [f]
(let [parsed (inifile.parse f)
vals (. parsed "Desktop Entry")]
(tset vals "IconImage"
(find-icon (or vals.Icon "application-x-executable")))
(tset vals "ID" (f:sub 0 -9))
vals))
(fn current-user-home []
"Returns current user's home directory."
(-> (posix.unistd.getuid)
(posix.pwd.getpwuid)
(. :pw_dir)))
(fn xdg-data-home []
"Provides XDG_DATA_HOME or its default fallback value"
(or (os.getenv "XDG_DATA_HOME")
(path.concat (current-user-home) ".local/share/")))
(fn xdg-data-dirs []
"Provides all data-dirs as a List. Most important first."
;; Expected to be used with gmatch as a generator.
(let [dirs (List)]
(dirs:append (xdg-data-home))
(dirs:extend (stringx.split (os.getenv "XDG_DATA_DIRS") ":"))
dirs
))
(fn all-apps []
;; Each desktop entry representing an application is identified
;; by its desktop file ID, which is based on its filename.
;; — https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id
"Provides apps in a List, sorted by name"
(var apps-table {})
;; Reversing the data dirs gives priority to the first elements.
;; This means conflicting `.desktop` files (or: desktop file ID) are given
;; priority to the first elements by "simply" reading it last.
(each [path (List.iter (List.reverse (xdg-data-dirs)))]
(let [apps-dir (.. path "/applications/")]
(when (lfs.attributes apps-dir)
(each [f (lfs.dir apps-dir)]
(when (= (f:sub -8) ".desktop")
(let [attrs (read-desktop-file (.. apps-dir f))]
(when (not attrs.NoDisplay)
(tset apps-table attrs.ID attrs))))))))
;; We have a table indexed by IDs, we don't care about the indexing.
;; Make a List and sort it by name.
(List.sort (List (tablex.values apps-table))
(fn [a b] (< (string.upper a.Name) (string.upper b.Name)))))
;; Exec entries in desktop files may contain %u %f and other characters
;; in which the launcher is supposed to interpolate filenames/urls etc.
;; We don't afford the user any way to pick filenames, but we do need
;; to remove the placeholders.
(fn parse-percents [str]
(str:gsub "%%(.)" (fn [c] (if (= c "%") "%" ""))))
(fn spawn-async [vec]
(let [pid (posix.unistd.fork)]
(if (> pid 0) true
(< pid 0) (assert (= "can't fork" nil))
(do
(for [f 3 255] (posix.unistd.close f))
(posix.execp "/usr/bin/env" vec)))))
(fn launch [app]
;; FIXME check app.DBusActivatable and do DBus launch if true
(let [cmd (parse-percents app.Exec)]
(if app.Terminal
(spawn-async ["kitty" cmd])
(spawn-async ["sh" "-c" cmd]))
; (window:hide)
))
(fn button-for [app]
(doto (Gtk.Button
{
:image-position Gtk.PositionType.TOP
:relief Gtk.ReliefStyle.NONE
:on_clicked #(launch app)
})
(-> (: :get_style_context) (: :add_class "appbutton"))
(: :add
(doto (Gtk.Box {:orientation Gtk.Orientation.VERTICAL})
(: :pack_start app.IconImage false false 0)
(: :pack_start
(doto
(Gtk.Label {
;; https://stackoverflow.com/questions/27462926/how-to-set-max-width-of-gtklabel-properly
:label app.Name
:justify Gtk.Justification.CENTER
:ellipsize Pango.EllipsizeMode.END
:hexpand true
})
(: :set_max_width_chars 1))
true true 0)
))))
(fn handle-dbus-method-call [conn sender path interface method params invocation]
(when (and (= path dbus-service-attrs.path)
(= interface dbus-service-attrs.interface))
(match method
"SetVisible"
(let [[value] (dbus.variant.strip params)]
(if value (window:show_all) (window:hide))
(invocation:return_value nil))
"ToggleVisible"
(let [v window.visible]
(if v (window:hide) (window:show_all))
(invocation:return_value nil)))))
(fn handle-dbus-get [conn sender path interface name]
(when (and (= path dbus-service-attrs.path)
(= interface dbus-service-attrs.interface)
(= name "Visible"))
(GLib.Variant "b" window.visible)))
(Gio.DBusConnection.register_object
bus.connection
dbus-service-attrs.path
interface-info
(GObject.Closure handle-dbus-method-call)
(GObject.Closure handle-dbus-get)
(GObject.Closure (fn [a] (print "set"))))
(let [grid (Gtk.FlowBox {
:orientation Gtk.Orientation.HORIZONTAL
:valign Gtk.Align.START
:column_spacing 2
:row_spacing 5
:homogeneous true
})
scrolled-window (Gtk.ScrolledWindow {})]
(each [app (List.iter (all-apps))]
(grid:insert (button-for app) -1))
(scrolled-window:add grid)
(window:add scrolled-window))
(window:show_all)
(Gtk:main)

7
pkgs/saturn/shell.nix Normal file
View File

@ -0,0 +1,7 @@
with import <nixpkgs> { overlays = [ (import ../../overlay.nix) ]; } ;
(callPackage ./. {
}).overrideAttrs(o: {
GDK_PIXBUF_MODULE_FILE = "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache";
nativeBuildInputs = o.nativeBuildInputs ++
[ ] ;
})