Compare commits

..

No commits in common. "c3b796ef793eed845da99a9518ec019df8a0a470" and "63359b5d064a119cf4354a05bd2263d110dec6dc" have entirely different histories.

14 changed files with 21 additions and 920 deletions

View File

@ -9,10 +9,6 @@ default toplevel:
android-bootimg android-recovery: android-bootimg android-recovery:
nix-build $(NFLAGS) -A outputs.android.$@ -o $@ 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 update: toplevel
nix-copy-closure --to $(DEVICE) -v --include-outputs ./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" 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 harpia to turn it into a bike computer. We will Config for my moto potter to turn it into a bike computer. We will
need need
- working gps - working gps
@ -19,43 +19,19 @@ write the app in fennel. I want it to
- show where I am on a map - show where I am on a map
- record trail of where I've been (note: indoor counts too) - record trail of where I've been (note: indoor counts too)
we can use l"ove2d instead of dragging in that gtk stuff
SDL2
can we somehow do non-flakey bluetooth (is it dbus?) To run an SDL2 application on Wayland, set
SDL_VIDEODRIVER=wayland.
can we do bluetooth not through dbus? dbus seems to make it flakey
cool extras cool extras
=========== ===========
adjust screen brightness adjust screen brightness (go dark when not moving)
* 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,8 +12,6 @@ let
makeFlags = ["prefix=${placeholder "out"}"]; makeFlags = ["prefix=${placeholder "out"}"];
}; };
saturn = pkgs.callPackage ./pkgs/saturn {};
drm-framebuffer = pkgs.stdenv.mkDerivation { drm-framebuffer = pkgs.stdenv.mkDerivation {
name = "drm-framebuffer"; name = "drm-framebuffer";
src = pkgs.fetchFromGitHub { src = pkgs.fetchFromGitHub {
@ -40,21 +38,12 @@ in {
]; ];
config = { 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 = { mobile = {
adbd.enable = true; 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 = [ boot.stage-1.firmware = [
modemFirmware modemFirmware
pkgs.wireless-regdb pkgs.wireless-regdb
@ -67,142 +56,28 @@ in {
system.stateVersion = "23.11"; system.stateVersion = "23.11";
boot.kernelParams = [ 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/gpu/drm/panel/msm8953-generated/panel-boe-bs052fhm-a00-6c01.c +fmp\""
# "dyndbg=\"file drivers/video/backlight/qcom-wled.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 = { networking = {
useDHCP = true; useDHCP = true;
hostName = "biscuit"; hostName = "biscuit";
networkmanager = { enable = false; }; networkmanager = { enable = false; };
useNetworkd = true;
inherit (secrets) wireless; inherit (secrets) wireless;
}; };
services.openssh.enable = true; services.openssh.enable = true;
services.cage = # environment.systemPackages = [ qrtr tqftpserv ];
let wlinit = pkgs.writeScript "wlinit" '' services.udev.extraRules = ''
${pkgs.wlr-randr}/bin/wlr-randr --output DSI-1 --scale 2 # copied from https://github.com/andersson/rpmsgexport
exec ${saturn}/bin/saturn 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"
in { SUBSYSTEM=="rpmsg", KERNEL=="rpmsg[0-9]*", ATTR{name}=="?*", ATTRS{rpmsg_name}=="?*", SYMLINK+="rpmsg/$attr{rpmsg_name}/$attr{name}"
enable = true; '';
program = wlinit; environment.systemPackages = with pkgs; [ drm-framebuffer];
user = "dan";
};
environment.systemPackages = with pkgs; [
drm-framebuffer
saturn
satellite
wlr-randr
modemmanager-small
maps
];
users.users.dan = { users.users.dan = {
isNormalUser = true; isNormalUser = true;
@ -220,30 +95,5 @@ SUBSYSTEM=="wwan", ENV{DEVNAME}=="/dev/wwan0qmi0", ENV{DEVTYPE}=="wwan_port", \
hardware.opengl = { hardware.opengl = {
enable = true; driSupport = true; 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";
};
};
};
}; };
} }

View File

@ -1,16 +0,0 @@
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

View File

@ -1,85 +0,0 @@
{ 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";
})
];
}

View File

@ -1,71 +0,0 @@
; (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)

View File

@ -1,9 +0,0 @@
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

@ -1,84 +0,0 @@
{
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"
];
}

View File

@ -1,15 +0,0 @@
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

View File

@ -1,9 +0,0 @@
# 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

View File

@ -1,99 +0,0 @@
{ 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

@ -1,25 +0,0 @@
{ 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}/"
'';
}

View File

@ -1,301 +0,0 @@
(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)

View File

@ -1,7 +0,0 @@
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 ++
[ ] ;
})