mirror of
https://github.com/NixOS/mobile-nixos.git
synced 2024-12-14 09:16:39 +03:00
examples/hello: introduce a minimal useful example system
The examples/hello system can be used by users that want to boot a minimal, and cross-compilable system. This is better than a "raw" build of the root of the Mobile NixOS repo since it provides a stage-2 application stating the system booted successfully.
This commit is contained in:
parent
b38caeec1a
commit
3d0552223d
@ -60,6 +60,8 @@ let
|
||||
;
|
||||
in
|
||||
(
|
||||
# Don't break if `device` is not set.
|
||||
if device == null then (id: id) else
|
||||
if device ? special
|
||||
then header "Evaluating: ${device.name}"
|
||||
else header "Evaluating device: ${device}"
|
||||
@ -94,6 +96,13 @@ in
|
||||
un-configured image. That image can be configured using `local.nix`.
|
||||
**Note that an unconfigured image may appear to hang at boot.**
|
||||
|
||||
An alternative is to use one of the `examples` system. They differ in their
|
||||
configuration. An example that should be building, and working using
|
||||
cross-compilation is the `examples/hello` system. Read its README for more
|
||||
information.
|
||||
|
||||
$ nix-build examples/hello --argstr device ${final_device} -A build.default
|
||||
|
||||
*************************************************************************
|
||||
* Please also read your device's documentation for further usage notes. *
|
||||
*************************************************************************
|
||||
|
23
examples/hello/README.md
Normal file
23
examples/hello/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
> **NOTE**: This example system can be used to make a minimal system that can
|
||||
> be built using cross-compilation, to validate that the device goes to stage-2.
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
$ cd .../mobile-nixos
|
||||
$ nix-build examples/hello --argstr device DEVICE-NAME -A build.default
|
||||
```
|
||||
|
||||
## Installing
|
||||
|
||||
Follow the installation instructions for your device.
|
||||
|
||||
## Running
|
||||
|
||||
This system should boot using the usual stage-1 boot process, followed by a
|
||||
specialized stage-2 configuration that runs a single-purpose application to
|
||||
provide a tangible proof that the boot process has completed successfully.
|
||||
|
||||
Note that there is no expected way to use this system other than to see the
|
||||
specialized application starting. This is not intended to be a starting point
|
||||
to configure a "normal" system on your device.
|
28
examples/hello/app/default.nix
Normal file
28
examples/hello/app/default.nix
Normal file
@ -0,0 +1,28 @@
|
||||
{ stdenv
|
||||
, lib
|
||||
, mruby
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "hello-gui.mrb";
|
||||
|
||||
src = lib.cleanSource ./.;
|
||||
|
||||
nativeBuildInputs = [
|
||||
mruby
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
mrbc -g -o app.mrb \
|
||||
$(find ${../../../boot/gui/lib} -type f -name '*.rb' | sort) \
|
||||
$(find ./lib -type f -name '*.rb' | sort) \
|
||||
main.rb
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/libexec/
|
||||
mv -v app.mrb $out/libexec/
|
||||
|
||||
mkdir -p $out/share/hello-gui
|
||||
'';
|
||||
}
|
256
examples/hello/app/lib/gui.rb
Normal file
256
examples/hello/app/lib/gui.rb
Normal file
@ -0,0 +1,256 @@
|
||||
module GUI
|
||||
# Refreshing at 120 times per second *really* helps with the drag operations
|
||||
# responsiveness. At 60 it feels a bit sluggish.
|
||||
# This likely comes from the naïve implementation that we are not refreshing at
|
||||
# 60 times per seconds, but rather, refresh and wait 1/60th of a second. This
|
||||
# makes the refresh rate a tad slower.
|
||||
# Boosting to 120 doesn't seem to have ill effects. It's simply refreshed more.
|
||||
REFRESH_RATE = 120
|
||||
|
||||
# UI constants
|
||||
NIXOS_LIGHT_HUE = 205
|
||||
NIXOS_DARK_HUE = 220
|
||||
|
||||
DIR = File.dirname($0)
|
||||
ASSETS_DIR = File.join(File.dirname($0), "../share/hello-gui")
|
||||
|
||||
# Sets things up; back box for some ugly hacks.
|
||||
def self.init()
|
||||
return if @initialized
|
||||
@initialized = true
|
||||
|
||||
# Get exclusive control of the framebuffer
|
||||
# By design we will not restore the console at exit.
|
||||
# We are assuming the target does not necessarily have a console attached to
|
||||
# the framebuffer, so this program has to be enough by itself.
|
||||
VTConsole.map_console(0)
|
||||
|
||||
# Used by the "simulator"
|
||||
LVGL::Hacks.monitor_width = 720
|
||||
LVGL::Hacks.monitor_height = 1280
|
||||
|
||||
# Prepare LVGL
|
||||
LVGL::Hacks.init()
|
||||
|
||||
# And switch to the desired theme
|
||||
LVGL::Hacks.theme_night(NIXOS_LIGHT_HUE)
|
||||
end
|
||||
|
||||
# Runs the app, black boxes LVGL things.
|
||||
def self.main_loop()
|
||||
# Main loop
|
||||
while true
|
||||
LVGL::Hacks::LVTask.handle_tasks
|
||||
sleep(1.0/REFRESH_RATE)
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps an LVGL widget.
|
||||
class Widget
|
||||
def initialize(widget)
|
||||
@widget = widget
|
||||
end
|
||||
def method_missing(*args)
|
||||
@widget.send(*args)
|
||||
end
|
||||
end
|
||||
|
||||
class Button < Widget
|
||||
def initialize(parent)
|
||||
super(LVGL::LVButton.new(parent))
|
||||
set_layout(LVGL::LAYOUT::COL_M)
|
||||
set_ink_in_time(200)
|
||||
set_ink_wait_time(100)
|
||||
set_ink_out_time(500)
|
||||
set_fit2(LVGL::FIT::FILL, LVGL::FIT::TIGHT)
|
||||
@label = LVGL::LVLabel.new(self)
|
||||
end
|
||||
|
||||
def set_label(label)
|
||||
@label.set_text(label)
|
||||
end
|
||||
end
|
||||
|
||||
# Common pattern for a "back button".
|
||||
# Handles its presentation, and handles its behaviour.
|
||||
class BackButton < Button
|
||||
# +parent+: Parent object
|
||||
# +location+: An instance on which `present` can be called.
|
||||
def initialize(parent, location)
|
||||
@holder = LVGL::LVContainer.new(parent)
|
||||
@holder.set_fit2(LVGL::FIT::FILL, LVGL::FIT::TIGHT)
|
||||
@holder.set_style(LVGL::CONT_STYLE::MAIN, LVGL::LVStyle::STYLE_TRANSP.dup)
|
||||
style = @holder.get_style(LVGL::CONT_STYLE::MAIN)
|
||||
style.body_padding_top = 0
|
||||
style.body_padding_left = 0
|
||||
style.body_padding_right = 0
|
||||
style.body_padding_bottom = 0
|
||||
|
||||
super(@holder)
|
||||
|
||||
@location = location
|
||||
set_label("Back")
|
||||
set_fit2(LVGL::FIT::NONE, LVGL::FIT::TIGHT)
|
||||
set_width(@holder.get_width / 2)
|
||||
set_x(0)
|
||||
|
||||
self.event_handler = ->(event) do
|
||||
case event
|
||||
when LVGL::EVENT::CLICKED
|
||||
location.present()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Implements a clock as a wrapped LVLabel.
|
||||
class Clock < Widget
|
||||
def initialize(parent)
|
||||
super(LVGL::LVLabel.new(parent))
|
||||
set_align(LVGL::LABEL_ALIGN::LEFT)
|
||||
set_long_mode(LVGL::LABEL_LONG::CROP)
|
||||
|
||||
# Update the text once
|
||||
update_clock
|
||||
|
||||
# Then register a task to update regularly.
|
||||
@task = LVGL::Hacks::LVTask.create_task(250, LVGL::TASK_PRIO::MID, ->() do
|
||||
update_clock
|
||||
end)
|
||||
end
|
||||
|
||||
def update_clock()
|
||||
now = Time.now
|
||||
set_text([
|
||||
:hour,
|
||||
:min,
|
||||
:sec,
|
||||
].map{|fn| now.send(fn).to_s.rjust(2, "0") }.join(":"))
|
||||
end
|
||||
end
|
||||
|
||||
# Implements a battery widget as a wrapped LVLabel.
|
||||
class Battery < Widget
|
||||
def initialize(parent)
|
||||
super(LVGL::LVLabel.new(parent))
|
||||
set_align(LVGL::LABEL_ALIGN::RIGHT)
|
||||
set_long_mode(LVGL::LABEL_LONG::CROP)
|
||||
|
||||
@battery = HAL::Battery.main_battery
|
||||
|
||||
# Update the text once
|
||||
update_text
|
||||
|
||||
# Then register a task to update regularly.
|
||||
@task = LVGL::Hacks::LVTask.create_task(1000 * 15, LVGL::TASK_PRIO::LOW, ->() do
|
||||
update_text
|
||||
end)
|
||||
end
|
||||
|
||||
def update_text()
|
||||
if @battery
|
||||
set_text("(#{@battery.status}) #{@battery.percent}%")
|
||||
else
|
||||
set_text("[no known battery]")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Empty invisible widget
|
||||
class Screen < Widget
|
||||
def initialize()
|
||||
super(LVGL::LVContainer.new())
|
||||
set_layout(LVGL::LAYOUT::COL_M)
|
||||
|
||||
style = get_style(LVGL::CONT_STYLE::MAIN).dup
|
||||
set_style(LVGL::CONT_STYLE::MAIN, style)
|
||||
style.body_padding_top = 0
|
||||
style.body_padding_left = 0
|
||||
style.body_padding_right = 0
|
||||
style.body_padding_bottom = 0
|
||||
style.body_padding_inner = 0
|
||||
end
|
||||
end
|
||||
|
||||
# Scrolling page.
|
||||
class Page < Widget
|
||||
def initialize(parent)
|
||||
# A "holder" widget to work around idiosyncracies of pages.
|
||||
@holder = LVGL::LVContainer.new(parent)
|
||||
@holder.set_fit2(LVGL::FIT::FILL, LVGL::FIT::NONE)
|
||||
@holder.set_style(LVGL::CONT_STYLE::MAIN, LVGL::LVStyle::STYLE_TRANSP.dup)
|
||||
@holder.set_height(parent.get_height_fit - @holder.get_y)
|
||||
|
||||
# The actual widget we interact with
|
||||
super(LVGL::LVPage.new(@holder))
|
||||
style = LVGL::LVStyle::STYLE_TRANSP.dup
|
||||
# Padding to zero in the actual scrolling widget makes the scrollbar visible
|
||||
style.body_padding_top = style.body_padding_top / 2
|
||||
style.body_padding_left = 0
|
||||
style.body_padding_right = 0
|
||||
|
||||
set_style(LVGL::PAGE_STYLE::BG, style)
|
||||
set_style(LVGL::PAGE_STYLE::SCRL, style)
|
||||
set_fit2(LVGL::FIT::FILL, LVGL::FIT::NONE)
|
||||
|
||||
# Filling the parent that is at the root of the screen is apparently broken :/.
|
||||
set_height(@holder.get_height - get_y)
|
||||
|
||||
# Make this scroll
|
||||
set_scrl_layout(LVGL::LAYOUT::COL_M)
|
||||
end
|
||||
end
|
||||
|
||||
# Widget implementing the whole header
|
||||
class Header < Widget
|
||||
def initialize(parent)
|
||||
super(LVGL::LVContainer.new(parent))
|
||||
|
||||
header_style = get_style(LVGL::CONT_STYLE::MAIN).dup
|
||||
set_style(LVGL::CONT_STYLE::MAIN, header_style)
|
||||
header_style.glass = 1
|
||||
header_style.body_radius = 0
|
||||
header_style.body_opa = 255 * 0.6
|
||||
|
||||
set_fit2(LVGL::FIT::FILL, LVGL::FIT::TIGHT)
|
||||
set_layout(LVGL::LAYOUT::PRETTY)
|
||||
|
||||
# Split 50/50
|
||||
child_width = (
|
||||
get_width -
|
||||
header_style.body_padding_left -
|
||||
header_style.body_padding_right -
|
||||
header_style.body_padding_inner*2
|
||||
) / 2
|
||||
|
||||
# [00:00 ]
|
||||
@clock = Clock.new(self)
|
||||
@clock.set_width(child_width)
|
||||
|
||||
# [ 69%]
|
||||
@battery = Battery.new(self)
|
||||
@battery.set_width(child_width)
|
||||
end
|
||||
end
|
||||
|
||||
# Extend this to make a "window"
|
||||
class BaseWindow
|
||||
include Singleton
|
||||
|
||||
def initialize()
|
||||
super()
|
||||
# Initializes GUI things if required...
|
||||
GUI.init
|
||||
|
||||
# Preps a basic display
|
||||
@screen = Screen.new()
|
||||
@header = Header.new(@screen)
|
||||
@container = Page.new(@screen)
|
||||
end
|
||||
|
||||
# Switch to this window
|
||||
def present()
|
||||
LVGL::FFI.lv_disp_load_scr(@screen.lv_obj_pointer)
|
||||
end
|
||||
end
|
||||
end
|
76
examples/hello/app/lib/hal.rb
Normal file
76
examples/hello/app/lib/hal.rb
Normal file
@ -0,0 +1,76 @@
|
||||
module HAL
|
||||
class Battery
|
||||
NODE_BASE = "/sys/class/power_supply"
|
||||
# Guesstimates the main battery for a list of likely candidates
|
||||
def self.main_battery()
|
||||
node = %w{
|
||||
battery
|
||||
bms
|
||||
BAT0
|
||||
}.map { |name| File.join(NODE_BASE, name) }
|
||||
.find { |path| File.exist?(path) }
|
||||
|
||||
if node
|
||||
Battery.new(node)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(node)
|
||||
@node = node
|
||||
end
|
||||
|
||||
# google-walleye
|
||||
# [nixos@nixos:/sys/class/power_supply]$ cat battery/uevent
|
||||
# POWER_SUPPLY_NAME=battery
|
||||
# POWER_SUPPLY_INPUT_SUSPEND=0
|
||||
# POWER_SUPPLY_STATUS=Charging
|
||||
# POWER_SUPPLY_HEALTH=Good
|
||||
# POWER_SUPPLY_PRESENT=1
|
||||
# POWER_SUPPLY_CHARGE_TYPE=Fast
|
||||
# POWER_SUPPLY_CAPACITY=56
|
||||
# POWER_SUPPLY_SYSTEM_TEMP_LEVEL=0
|
||||
# POWER_SUPPLY_CHARGER_TEMP=364
|
||||
# POWER_SUPPLY_CHARGER_TEMP_MAX=803
|
||||
# POWER_SUPPLY_INPUT_CURRENT_LIMITED=1
|
||||
# POWER_SUPPLY_VOLTAGE_NOW=3780507
|
||||
# POWER_SUPPLY_VOLTAGE_MAX=4400000
|
||||
# POWER_SUPPLY_VOLTAGE_QNOVO=-22
|
||||
# POWER_SUPPLY_CURRENT_NOW=183105
|
||||
# POWER_SUPPLY_CURRENT_QNOVO=-22
|
||||
# POWER_SUPPLY_CONSTANT_CHARGE_CURRENT_MAX=2700000
|
||||
# POWER_SUPPLY_TEMP=320
|
||||
# POWER_SUPPLY_TECHNOLOGY=Li-ion
|
||||
# POWER_SUPPLY_STEP_CHARGING_ENABLED=0
|
||||
# POWER_SUPPLY_STEP_CHARGING_STEP=-1
|
||||
# POWER_SUPPLY_CHARGE_DISABLE=0
|
||||
# POWER_SUPPLY_CHARGE_DONE=0
|
||||
# POWER_SUPPLY_PARALLEL_DISABLE=0
|
||||
# POWER_SUPPLY_SET_SHIP_MODE=0
|
||||
# POWER_SUPPLY_CHARGE_FULL=2805000
|
||||
# POWER_SUPPLY_DIE_HEALTH=Cool
|
||||
# POWER_SUPPLY_RERUN_AICL=0
|
||||
# POWER_SUPPLY_DP_DM=0
|
||||
# POWER_SUPPLY_CHARGE_COUNTER=1455951
|
||||
# POWER_SUPPLY_CYCLE_COUNT=849
|
||||
def uevent()
|
||||
File.read(File.join(@node, "uevent")).split("\n").map do |line|
|
||||
key, value = line.split("=", 2)
|
||||
[key.downcase.to_sym, value]
|
||||
end.to_h
|
||||
end
|
||||
|
||||
def name()
|
||||
uevent[:power_supply_name] || "unknown"
|
||||
end
|
||||
|
||||
def status()
|
||||
uevent[:power_supply_status] || "unknown"
|
||||
end
|
||||
|
||||
def percent()
|
||||
uevent[:power_supply_capacity] || "unknown"
|
||||
end
|
||||
end
|
||||
end
|
91
examples/hello/app/lib/windows.rb
Normal file
91
examples/hello/app/lib/windows.rb
Normal file
@ -0,0 +1,91 @@
|
||||
module GUI
|
||||
# Helper methods to help creating a "button palette" kind of window.
|
||||
module ButtonPalette
|
||||
def add_button(label)
|
||||
Button.new(@container).tap do |btn|
|
||||
btn.glue_obj(true)
|
||||
btn.set_label(label)
|
||||
btn.event_handler = ->(event) do
|
||||
case event
|
||||
when LVGL::EVENT::CLICKED
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_buttons(list)
|
||||
list.each do |pair|
|
||||
label, action = pair
|
||||
add_button(label, &action)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class MainWindow < BaseWindow
|
||||
include ButtonPalette
|
||||
def initialize()
|
||||
super()
|
||||
|
||||
LVGL::LVLabel.new(@container).tap do |label|
|
||||
label.set_long_mode(LVGL::LABEL_LONG::BREAK)
|
||||
label.set_text(%Q{\nSelect from the following options})
|
||||
label.set_align(LVGL::LABEL_ALIGN::CENTER)
|
||||
label.set_width(@container.get_width_fit)
|
||||
end
|
||||
|
||||
add_buttons([
|
||||
["About", ->() { AboutWindow.instance.present }],
|
||||
["Quit", ->() { QuitWindow.instance.present }],
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
class QuitWindow < BaseWindow
|
||||
include ButtonPalette
|
||||
|
||||
def run(*cmd)
|
||||
$stderr.puts " $ " + cmd.join(" ")
|
||||
system(*cmd) unless LVGL::Introspection.simulator?
|
||||
end
|
||||
|
||||
def initialize()
|
||||
super()
|
||||
BackButton.new(@container, MainWindow.instance)
|
||||
|
||||
add_buttons([
|
||||
["Reboot", ->() { run("reboot") }],
|
||||
["Reboot to recovery", ->() { run("reboot recovery") }],
|
||||
["Reboot to bootloader", ->() { run("reboot bootloader") }],
|
||||
["Power off", ->() { run("poweroff") }],
|
||||
])
|
||||
|
||||
if LVGL::Introspection.simulator?
|
||||
add_button("Quit") { exit(0) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AboutWindow < BaseWindow
|
||||
include ButtonPalette
|
||||
def initialize()
|
||||
super()
|
||||
BackButton.new(@container, MainWindow.instance)
|
||||
|
||||
LVGL::LVLabel.new(@container).tap do |label|
|
||||
text = <<EOF
|
||||
Mobile NixOS "Hello GUI"
|
||||
|
||||
This application is intended to provide a minimum viable known working framebuffer application to test different components of your mobile device.
|
||||
|
||||
This is NOT a complete useful system.
|
||||
EOF
|
||||
label.set_long_mode(LVGL::LABEL_LONG::BREAK)
|
||||
label.set_text(%Q{\n#{text}})
|
||||
label.set_align(LVGL::LABEL_ALIGN::CENTER)
|
||||
label.set_width(@container.get_width_fit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
3
examples/hello/app/main.rb
Normal file
3
examples/hello/app/main.rb
Normal file
@ -0,0 +1,3 @@
|
||||
GUI::MainWindow.instance.present
|
||||
|
||||
GUI.main_loop
|
26
examples/hello/app/simulator.nix
Normal file
26
examples/hello/app/simulator.nix
Normal file
@ -0,0 +1,26 @@
|
||||
{ stdenv
|
||||
, lib
|
||||
, callPackage
|
||||
, mrbgems
|
||||
, mruby
|
||||
, mobile-nixos
|
||||
}:
|
||||
|
||||
let
|
||||
script-loader = mobile-nixos.stage-1.script-loader.override({
|
||||
mrbgems = mrbgems // {
|
||||
mruby-lvgui = callPackage ../../../overlay/mruby-builder/mrbgems/mruby-lvgui {
|
||||
withSimulator = true;
|
||||
};
|
||||
};
|
||||
});
|
||||
applet = callPackage ./. {};
|
||||
in
|
||||
(script-loader.wrap {
|
||||
name = "simulator";
|
||||
applet = "${applet}/libexec/app.mrb";
|
||||
}).overrideAttrs(old: rec {
|
||||
pname = "hello-gui-simulator";
|
||||
version = "0.0.1";
|
||||
name = "${pname}-${version}";
|
||||
})
|
106
examples/hello/configuration.nix
Normal file
106
examples/hello/configuration.nix
Normal file
@ -0,0 +1,106 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib.strings) makeBinPath;
|
||||
|
||||
hello-gui = pkgs.mobile-nixos.stage-1.script-loader.wrap {
|
||||
name = "hello-gui";
|
||||
applet = "${pkgs.callPackage ./app {}}/libexec/app.mrb";
|
||||
env = {
|
||||
PATH = "${makeBinPath (with pkgs;[
|
||||
systemd # journalctl
|
||||
glibc # iconv
|
||||
utillinux # lsblk
|
||||
input-utils # lsinput
|
||||
])}:$PATH";
|
||||
};
|
||||
};
|
||||
|
||||
tmpfsConf = {
|
||||
device = "tmpfs";
|
||||
fsType = "tmpfs";
|
||||
neededForBoot = true;
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./workaround-v4l_id-hang.nix
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
hello-gui
|
||||
input-utils
|
||||
];
|
||||
|
||||
# Make the system rootfs different enough that mixing stage-1 and stage-2
|
||||
# will fail and not have weird unexpected behaviours.
|
||||
mobile.generatedFilesystems = {
|
||||
rootfs = lib.mkDefault {
|
||||
label = lib.mkForce "MOBILE_HELLO";
|
||||
id = lib.mkForce "12345678-1324-1234-0000-D00D00000001";
|
||||
};
|
||||
};
|
||||
|
||||
fileSystems = {
|
||||
"/" = lib.mkDefault {
|
||||
autoResize = lib.mkForce false;
|
||||
};
|
||||
# Nothing is saved, except for the nix store being rehydrated.
|
||||
"/tmp" = tmpfsConf;
|
||||
"/var/log" = tmpfsConf;
|
||||
"/home" = tmpfsConf;
|
||||
};
|
||||
|
||||
mobile.boot.stage-1.bootConfig = {
|
||||
# This will be useful for debugging boot issues over serial in the default
|
||||
# configuration.
|
||||
log.level = "DEBUG";
|
||||
};
|
||||
|
||||
systemd.services.hello-gui = {
|
||||
description = "GUI for the hello example of Mobile NixOS";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Restart = "always";
|
||||
SyslogIdentifier = "hello-gui";
|
||||
ExecStart = ''
|
||||
${hello-gui}/bin/hello-gui
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# Only enable `adb` if we know how to.
|
||||
# FIXME: relies on implementation details. Poor separation of concerns.
|
||||
mobile.adbd.enable = (config.mobile.system.type == "android") &&
|
||||
(config.mobile.usb.mode != "gadgetfs" || config.mobile.usb.gadgetfs.functions ? ffs)
|
||||
;
|
||||
|
||||
boot.postBootCommands = lib.mkOrder (-1) ''
|
||||
brightness=10
|
||||
echo "Setting brightness to $brightness"
|
||||
if [ -e /sys/class/backlight/backlight/brightness ]; then
|
||||
echo $(($(cat /sys/class/backlight/backlight/max_brightness) * brightness / 100)) > /sys/class/backlight/backlight/brightness
|
||||
elif [ -e /sys/class/leds/lcd-backlight/max_brightness ]; then
|
||||
echo $(($(cat /sys/class/leds/lcd-backlight/max_brightness) * brightness / 100)) > /sys/class/leds/lcd-backlight/brightness
|
||||
elif [ -e /sys/class/leds/lcd-backlight/brightness ]; then
|
||||
# Assumes max brightness is 100... probably wrong, but good enough, eh.
|
||||
echo $brightness > /sys/class/leds/lcd-backlight/brightness
|
||||
fi
|
||||
'';
|
||||
|
||||
users.users.nixos = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" "networkmanager" "video" ];
|
||||
};
|
||||
|
||||
security.sudo = {
|
||||
enable = true;
|
||||
wheelNeedsPassword = lib.mkForce false;
|
||||
};
|
||||
|
||||
services.mingetty.autologinUser = "nixos";
|
||||
|
||||
system.build = {
|
||||
app-simulator = pkgs.callPackage ./app/simulator.nix {};
|
||||
};
|
||||
}
|
22
examples/hello/default.nix
Normal file
22
examples/hello/default.nix
Normal file
@ -0,0 +1,22 @@
|
||||
{ device ? null }:
|
||||
|
||||
let
|
||||
system-build = import ../../. {
|
||||
inherit device;
|
||||
configuration = [ { imports = [ ./configuration.nix ]; } ];
|
||||
};
|
||||
in
|
||||
system-build // {
|
||||
___readme-default = throw ''
|
||||
Cannot directly build for ${device}...
|
||||
|
||||
You can build the `-A build.default` attribute to build the default output
|
||||
for your device.
|
||||
|
||||
$ nix-build examples/hello --argstr device ${device} -A build.default
|
||||
|
||||
*************************************************************************
|
||||
* Please also read your device's documentation for further usage notes. *
|
||||
*************************************************************************
|
||||
'';
|
||||
}
|
17
examples/hello/workaround-v4l_id-hang.nix
Normal file
17
examples/hello/workaround-v4l_id-hang.nix
Normal file
@ -0,0 +1,17 @@
|
||||
# This works around an issue on at least one device (motorola-addison) where
|
||||
# the v4l_id tool from udev hangs for more than a minute on boot.
|
||||
#
|
||||
# This replaces the file from udev with an empty one.
|
||||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
emptyV4lRules = pkgs.runCommandNoCC "empty-v4l-rules" {} ''
|
||||
mkdir -p $out/lib/udev/rules.d
|
||||
touch $out/lib/udev/rules.d/60-persistent-v4l.rules
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.udev.packages = lib.mkOrder 10000 [
|
||||
emptyV4lRules
|
||||
];
|
||||
}
|
Loading…
Reference in New Issue
Block a user