Pinephone第一步

2020-05-12 01:37:06

在Anki销毁了我的历史记录,将废弃的历史记录上传到同步服务器,然后每次我试图从备份中还原时,我都会反复重新同步这些废弃的历史记录,然后我编写了自己的间隔重复应用程序。它从标记文件中读取输入,在终端中询问问题,并将状态存储在json文件中。我在一个下午写了几百行代码,从那以后我就一直在用它。

我也想在我的Android手机上运行它,但经过几天的挣扎和几个千兆字节的IDE下载,我失去了兴趣。

这是我的典型经验。扩展和定制我的笔记本电脑是一件例行公事。扩展和定制我的手机是如此痛苦,以至于我每次都放弃。我笔记本电脑上的软件感觉就像一只舒适的旧靴子,经过多年的调整和脚本编写,穿上了。我手机上的软件感觉就像一台老虎机,每月移动一次所有的按钮,在广告播放时让我睁开眼睛。它又亮又亮,但它不在我这边。

Pinephone是一款售价150美元的手机,旨在能够运行主线Linux。我买了一台,希望创造一种更像使用笔记本电脑的体验。

显然,编写一套完整的手机套装是一项大工程,因此,我会坚持不懈地尽可能偷工减料,并采用简洁美观的功能。

我装了移动电话。当时的医生非常简陋,但Samueldr非常友好,通过IRC向我介绍了这一过程,我在这里写下了这个过程。

我还没有开始编写系统配置,但是一旦我这样做了,它将是使用nixops ploy-d Focus的一次单击部署-这比LineageOS更新过程有了很大的改进。默认配置如下所示。

因为手机运行的是普通的Linux用户,所以可以直接在手机上编译。但是我的笔记本电脑的编译速度大约快了10倍,所以似乎值得投资于交叉编译。

第一步是获得我所有依赖项的arm64版本。这是在shell.nix中完成的。

我固定nixpkgs的版本,以确保本地和远程开发使用每个依赖项的完全相同的版本。

NIX最近获得了对交叉编译的支持,但是很多包还没有成功地交叉编译。所以诀窍是设置交叉编译,但是从本地的arm64repo获取一些包。这些不会在非arm64机器上从源代码构建,但是nixpkgs二进制缓存中有预构建的版本。

armPkgs=import nixpkgs{system=";aarch64-linux";;};CrossPkgs=import nixpkgs{CroSystem=hostPkgs。利布。系统。举例说明。aarch64-多平台;overays=[(Self:Super:{Inherit(ArmPkgs)GCC台面libGLSDL2;})];};

大多数配置在本地构建和跨构建之间共享,因此shell.nix接受一个布尔参数来告诉它我们正在尝试什么。

我们需要可在主机上运行的pkgconfig和patchelf,因为它们在编译期间使用。我们需要在目标机器上可运行的libGL和SDL2进行链接。

现在,我们可以执行nix-shell以放入shell设置中进行本地编译,或者执行nix-shell--arg cross true以放入shell设置中进行交叉编译。

我把每件事都写得有条不紊,因为我想让整个系统保持小巧、简单和快速。Zig是一种小型、简单、快速的语言,它也非常关心交叉编译。

唯一的问题是它在交叉编译时很难使用pkgconfig找到头文件。我还没有试着调试这个--只是直接传递了它们。

..。try include deNix(exe,";NIX_LIBGL_DEV";);try include deNix(exe,";NIX_SDL2_DEV";);.fn include deNix(exe:*std.build.LibExeObjStep,env_var:[]const U8)!void{var buf=std.ArrayList(U8).init(allocator。exe.addIncludeDir(buf.item);}。

Zig也没有在生成的二进制文件中正确设置路径,但这是合理的-它没有办法知道我正在交叉编译到相同的NIX系统,所以它默认使用一些合理的启发式方法。我只是在编译后修补二进制文件。

然后,构建过程要么是本地ZIG构建,要么是跨&;&;./sync的ZIG构建。

因为笔记本电脑和手机都运行相同的操作系统,具有相同的固定依赖关系,所以我不会费心使用虚拟机进行本地开发。我只需用代码模拟设备传感器,其他一切都会一样运行。

我正在编写一个即时模式的GUI库,因为我发现它们在代码行和智力开销方面都比大多数保留的系统简单得多。我们的机械公司最近在GDC上发表了一次演讲,阐述了这一理论基础。

电池使用率经常受到关注,但在我的笔记本电脑上,这个应用程序一直徘徊在1%的CPU左右,而每当我按下按钮时,GNOME计算器就会达到20%-30%。如果这成为更复杂的UI的问题,我可以在绘图命令层添加一些缓存。

肯定有一些事情很难在即时模式下做好,比如复杂的反应性布局,但幸运的是,这些都不是我关心在手机上做的事情。

我从Microui复制了我的渲染器和字体图集,我已经构建了足够的UI库来运行。以下是这款间隔重复应用的整个用户界面:

if(ui.key orelse0==';Q';){尝试保存日志(self.logs.item);std.os.exit(0);}常量白色=UI.color{.R=255,.g=255,.B=255,.a=255};var TEXT_RECT=RECT;var BUTTON_RECT=text_rect.splitBottom(atlas.text_height);switch(self.state){.Prepare=>;{try ui.text(text_rect,White,try format(allocator,";{}PENDING";,.{self.queue e.len}));if(try ui.button(button_rect,White,";Go";)){self.state=.Prompt;},.Prompt=>;{const next=self.queue[0];const text=try format(allocator,";){self.state=.Prompt;},.Prompt=>;{const next=self.queue[0];const text=try format(allocator,";)。{}\n\n(紧急性={},间隔={})";,.{next.cloze.renders[next.state.ender_ix],next.state.urgency,next.state.interval_ns});尝试ui.text(text_rect,White,text);if(try ui.button(button_rect,White,";show";){self.state=.Reval;}},.Reval=&Get。尝试ui.text(RECT,白色,Try Format(allocator,";{}";,.{next.cloze.text}));var event_o:?Log.Event=null;var misse_rect=button_rect;var Hit_rect=misse_rect.plitRight(@divTrunc(misse_rect.w,2));if(try ui.button(misse_rect,White,";misse&。}if(try ui.button(Hit_rect,White,";Hit";)){event_o=.hit;}if(Event_O)|event|{try self.logs.append(.{.at_ns=std.time.milliTimestamp()*1_000_000,.cloze_text=next.cloze.text,.ender_ix=next.state.ender_ix,.event=event,});self.queue=。if(self.queue e.len==0){self.queue=try sortByUrgency(&;self.frame_arena,self.cloze,self.logs.item);self.state=.Prepare;}Else{self.state=.Prompt;}},}。

我真的很喜欢它是直接的代码,而不是分散在多个文件、类或回调中。你可以从上到下看。如果我想对某个组件或布局进行抽象,我只需将其放入一个函数中即可。

显然,要改进抗锯齿、字体、布局等方面需要做很多工作,但我认为可以在不使上面的界面复杂化的情况下完成这些工作。