diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 514e68a3..00000000 --- a/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright 2019 Dividat AG - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 120000 index 00000000..e7aa3976 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +controller/licenses/PLAYOS \ No newline at end of file diff --git a/Readme.md b/Readme.md index 20c8fc0b..7a3403b1 100644 --- a/Readme.md +++ b/Readme.md @@ -157,57 +157,13 @@ The following [dev tools](dev-tools/Readme.md) are available: ## Attribution and Licensing -Most code in this repository is authored by the Dividat AG and the project contributors. This code is licensed under an MIT license. +Most code in this repository is authored by the Dividat AG and the project +contributors. This code is licensed under [the MIT license](./LICENSE). -Some source files in this project are portions of other open-source project and may be released under different licenses. The applicable licenses are stated here as well as in the relevant subdirectories. +Some source files in this project are portions of other open-source project and +may be released under different licenses. The applicable licenses are stated +here as well as in the relevant subdirectories. -### [nixpkgs](https://github.com/NixOS/nixpkgs) - -``` -Copyright (c) 2003-2018 Eelco Dolstra and the Nixpkgs/NixOS contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -``` - -### [Feather](https://feathericons.com/) - -``` -The MIT License (MIT) - -Copyright (c) 2013-2017 Cole Bemis - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` +- [nixpkgs](https://github.com/NixOS/nixpkgs) ([MIT License](./controller/licenses/NIXPKGS)) +- [Feather](https://feathericons.com/) ([MIT License](./controller/licenses/FEATHER)) – used in the [controller](./controller/Readme.md) +- [Qt6](https://www.qt.io/product/qt6) ([LGPLv3 License](./controller/licenses/QT6)) – used in the [kiosk](./kiosk/Readme.md) diff --git a/application.nix b/application.nix index ae0ae7a6..3bfbd065 100644 --- a/application.nix +++ b/application.nix @@ -83,7 +83,7 @@ rec { ;; esac - # Enable Qt WebEngine Developer Tools (https://doc.qt.io/qt-5/qtwebengine-debugging.html) + # Enable Qt WebEngine Developer Tools (https://doc.qt.io/qt-6/qtwebengine-debugging.html) export QTWEBENGINE_REMOTE_DEBUGGING="127.0.0.1:3355" ${pkgs.playos-kiosk-browser}/bin/kiosk-browser \ diff --git a/controller/Changelog.md b/controller/Changelog.md index f07b9fbd..a1e9521b 100644 --- a/controller/Changelog.md +++ b/controller/Changelog.md @@ -4,6 +4,7 @@ - kiosk: Add a key combination to perform hard refresh (Ctrl-Shift-R) - os: Added localization options for Polish and Czech +- controller: Add licensing page in System Settings ## Changed @@ -11,6 +12,7 @@ - os: Set noexec for volatile root and persistent storage mounts - os: Restrict remote maintenance to the ZeroTier network - os: Limit permitted SSH modes and forwarding options +- kiosk: Migrate to Qt6 ## Removed diff --git a/controller/bindings/util/dune b/controller/bindings/util/dune index c56da1f2..48205593 100644 --- a/controller/bindings/util/dune +++ b/controller/bindings/util/dune @@ -1,6 +1,6 @@ (library (name util) (modules util) - (libraries logs logs.lwt cohttp-lwt-unix sexplib) + (libraries logs logs.lwt cohttp-lwt-unix sexplib fpath) (preprocess (pps lwt_ppx ppx_sexp_conv))) diff --git a/controller/bindings/util/util.ml b/controller/bindings/util/util.ml index 6dc07119..51e8dd41 100644 --- a/controller/bindings/util/util.ml +++ b/controller/bindings/util/util.ml @@ -1,5 +1,12 @@ open Lwt +(* Require the resource directory to be at a directory fixed to the binary + * location. This is not optimal, but works for the moment. *) +let resource_path end_path = + let open Fpath in + (Sys.argv.(0) |> v |> parent) / ".." / "share" // end_path + |> to_string + let read_from_file log_src path = let%lwt exists = Lwt_unix.file_exists path in if exists then diff --git a/controller/dune b/controller/dune index 8f496cf2..d41bcc98 100644 --- a/controller/dune +++ b/controller/dune @@ -2,6 +2,10 @@ (section share_root) (files (Changelog.html as Changelog.html) + (licenses/PLAYOS as licenses/PLAYOS) + (licenses/NIXPKGS as licenses/NIXPKGS) + (licenses/FEATHER as licenses/FEATHER) + (licenses/QT6 as licenses/QT6) (gui/reset.css as static/reset.css) (gui/style.css as static/style.css) (gui/client.js as static/client.js))) diff --git a/controller/gui/style.css b/controller/gui/style.css index 335574ee..bb9d373e 100644 --- a/controller/gui/style.css +++ b/controller/gui/style.css @@ -318,6 +318,16 @@ html { margin-top: 2rem; } +/* Licensing */ + +.d-Licensing__Link { + text-decoration: underline; +} + +.d-Licensing__Details { + margin-bottom: var(--spacing-dog); +} + /* Form */ :root { diff --git a/controller/licenses/FEATHER b/controller/licenses/FEATHER new file mode 100644 index 00000000..1f4f4336 --- /dev/null +++ b/controller/licenses/FEATHER @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2023 Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/controller/licenses/NIXPKGS b/controller/licenses/NIXPKGS new file mode 100644 index 00000000..40eae096 --- /dev/null +++ b/controller/licenses/NIXPKGS @@ -0,0 +1,20 @@ +Copyright (c) 2003-2024 Eelco Dolstra and the Nixpkgs/NixOS contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/controller/licenses/PLAYOS b/controller/licenses/PLAYOS new file mode 100644 index 00000000..d35cf97c --- /dev/null +++ b/controller/licenses/PLAYOS @@ -0,0 +1,19 @@ +Copyright © Dividat AG + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/controller/licenses/QT6 b/controller/licenses/QT6 new file mode 100644 index 00000000..22f0f6fc --- /dev/null +++ b/controller/licenses/QT6 @@ -0,0 +1,167 @@ +The Qt Toolkit is Copyright (C) 2018 The Qt Company Ltd. and other contributors. +Contact: https://www.qt.io/licensing/ + +GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/controller/server/dune b/controller/server/dune index 4f9b3d5e..e7fbc9d3 100644 --- a/controller/server/dune +++ b/controller/server/dune @@ -6,7 +6,7 @@ (modules server update info gui health info_page localization_page status_page error_page label_page network_list_page network_details_page changelog_page - page definition icon) + licensing_page page definition icon) (libraries lwt logs logs.fmt logs.lwt fpath cohttp-lwt-unix logging opium tyxml rauc zerotier connman locale network timedate systemd label_printer semver2 fieldslib screen_settings) diff --git a/controller/server/gui.ml b/controller/server/gui.ml index 17bc7934..3853b3ed 100644 --- a/controller/server/gui.ml +++ b/controller/server/gui.ml @@ -6,17 +6,9 @@ open Sys let log_src = Logs.Src.create "gui" -(* Require the resource directory to be at a directory fixed to the binary - * location. This is not optimal, but works for the moment. *) -let resource_path end_path = - let open Fpath in - (Sys.argv.(0) |> v |> parent) / ".." / "share" // end_path - |> to_string - - (* Middleware that makes static content available *) let static () = - let static_dir = resource_path (Fpath.v "static") in + let static_dir = Util.resource_path (Fpath.v "static") in Logs.debug (fun m -> m "static content dir: %s" static_dir); Opium.Middleware.static ~local_path:static_dir ~uri_prefix:"/static" () @@ -220,7 +212,7 @@ module NetworkGui = struct let pp_proxy p = let uri = p |> Service.Proxy.to_uri ~include_userinfo:false |> Uri.to_string in match p.credentials with - | Some({ user; password }) -> + | Some({ user; password }) -> let password_indication = if password = "" then "" else ", password: *****" in uri ^ " (user: " ^ user ^ password_indication ^ ")" | None -> uri @@ -235,7 +227,7 @@ module NetworkGui = struct })) (** Internet status **) - let internet_status ~connman _ = + let internet_status ~connman _ = let%lwt proxy = Manager.get_default_proxy connman in match%lwt Curl.request ?proxy:(Option.map (Service.Proxy.to_uri ~include_userinfo:true) proxy) (Uri.of_string "http://captive.dividat.com/") with | RequestSuccess (code, response) -> @@ -281,10 +273,10 @@ module NetworkGui = struct let password_input = form_data |> List.assoc "proxy_password" |> List.hd |> non_empty in - let keep_password = + let keep_password = form_data |> List.assoc_opt "keep_password" |> Option.is_some in - let password = + let password = match (keep_password, current_proxy_opt) with | (true, Some ({ host; port; credentials = Some { user; password } })) -> if host_input = Some host && port_input = Some port && user_input = Some user then @@ -492,10 +484,18 @@ module ChangelogGui = struct let build app = app |> get "/changelog" (fun _ -> - let%lwt changelog = Util.read_from_file log_src (resource_path (Fpath.v "Changelog.html")) in + let%lwt changelog = Util.read_from_file log_src (Util.resource_path (Fpath.v "Changelog.html")) in Lwt.return (page (Changelog_page.html changelog))) end +module LicensingGui = struct + let build app = + app + |> get "/licensing" (fun _ -> + let%lwt p = Licensing_page.html in + Lwt.return (page p)) +end + module RemoteMaintenanceGui = struct let rec wait_until_zerotier_is_on () = @@ -544,6 +544,7 @@ let routes ~systemd ~shutdown ~health_s ~update_s ~rauc ~connman app = |> LabelGui.build |> StatusGui.build ~health_s ~update_s ~rauc |> ChangelogGui.build + |> LicensingGui.build |> RemoteMaintenanceGui.build ~systemd (* NOTE: probably easier to create a record with all the inputs instead of passing in x arguments. *) diff --git a/controller/server/view/common/icon.ml b/controller/server/view/common/icon.ml index c231a75b..ac39d98b 100644 --- a/controller/server/view/common/icon.ml +++ b/controller/server/view/common/icon.ml @@ -1,10 +1,10 @@ open Tyxml.Svg -(* Helpers *) +(* Helpers *) let svg ?a ?stroke_width content = - Tyxml.Html.svg - ~a:([ a_viewBox (0., 0., 24., 24.) + Tyxml.Html.svg + ~a:([ a_viewBox (0., 0., 24., 24.) ; a_width (24., None) ; a_height (24., None) ; a_fill `None @@ -16,31 +16,31 @@ let svg ?a ?stroke_width content = content let line (x1, y1) (x2, y2) = - Tyxml.Svg.line - ~a:[ a_x1 (x1, None) - ; a_y1 (y1, None) - ; a_x2 (x2, None) - ; a_y2 (y2, None) - ] + Tyxml.Svg.line + ~a:[ a_x1 (x1, None) + ; a_y1 (y1, None) + ; a_x2 (x2, None) + ; a_y2 (y2, None) + ] [] let circle (x, y) r = - Tyxml.Svg.circle - ~a:[ a_cx (x, None) - ; a_cy (y, None) - ; a_r (r, None) + Tyxml.Svg.circle + ~a:[ a_cx (x, None) + ; a_cy (y, None) + ; a_r (r, None) ] [] let rect ?rx ?fill (x1, y1) (x2, y2) = - Tyxml.Svg.rect - ~a:[ a_x (x1, None) - ; a_y (y1, None) - ; a_width (x2 -. x1, None) - ; a_height (y2 -. y1, None) - ; a_rx (Option.value ~default:0. rx, None) + Tyxml.Svg.rect + ~a:[ a_x (x1, None) + ; a_y (y1, None) + ; a_width (x2 -. x1, None) + ; a_height (y2 -. y1, None) + ; a_rx (Option.value ~default:0. rx, None) ; a_fill (`Color (Option.value ~default:"transparent" fill, None)) - ] + ] [] (* Icons *) @@ -60,7 +60,7 @@ let wifi ?strength () = else if strength < 75 then "Medium" else "Strong" in - svg + svg ~a:[ a_class [ "d-WifiSignal--" ^ modifier ] ] [ path ~a:[ a_class [ "d-WifiSignal__Wave--Outer" ] ; a_d "M1.42 9a16 16 0 0 1 21.16 0" ] [] ; path ~a:[ a_class [ "d-WifiSignal__Wave--Middle" ] ; a_d "M5 12.55a11 11 0 0 1 14.08 0" ] [] @@ -68,7 +68,7 @@ let wifi ?strength () = ; line (12., 20.) (12., 20.) ] -let ethernet = +let ethernet = svg [ path ~a: [ a_d "M2 2 H22 V18 H18 V22 H6 V18 H2 Z" ] [] ; line (6., 6.) (6., 10.) @@ -90,14 +90,14 @@ let power = ; line (12., 2.) (12., 12.) ] -let screen = +let screen = svg [ rect ~rx:2. (2.5, 2.) (21.5, 16.) ; line (12., 16.) (12., 22.) ; line (8., 22.) (16., 22.) ] -let document = +let document = svg [ rect ~rx:1. (4., 2.) (20., 22.) ; line (8., 8.) (16., 8.) @@ -112,17 +112,30 @@ let arrow_left = ; line (2., 12.) (12., 2.) ] -let letter = +let letter = svg ~stroke_width:1. [ rect ~rx:1. ~fill:"black" (2., 2.) (22., 22.) - ; text - ~a:[ a_fill (`Color ("white", None)) - ; a_stroke (`Color ("white", None)) + ; text + ~a:[ a_fill (`Color ("white", None)) + ; a_stroke (`Color ("white", None)) ; a_font_size "16" ; Unsafe.string_attrib "x" "50%" ; Unsafe.string_attrib "y" "55%" ; a_dominant_baseline `Middle ; a_text_anchor `Middle - ] - [ txt "A" ] + ] + [ txt "A" ] + ] + +let copyright = + svg + [ circle (12., 12.) 10. + ; text + ~a:[ a_font_size "10" + ; Unsafe.string_attrib "x" "50%" + ; Unsafe.string_attrib "y" "55%" + ; a_dominant_baseline `Middle + ; a_text_anchor `Middle + ] + [ txt "C" ] ] diff --git a/controller/server/view/common/icon.mli b/controller/server/view/common/icon.mli index d039a04e..8099ff09 100644 --- a/controller/server/view/common/icon.mli +++ b/controller/server/view/common/icon.mli @@ -7,3 +7,4 @@ val screen : [> Html_types.svg ] Tyxml.Html.elt val document : [> Html_types.svg ] Tyxml.Html.elt val arrow_left : [> Html_types.svg ] Tyxml.Html.elt val letter : [> Html_types.svg ] Tyxml.Html.elt +val copyright : [> Html_types.svg ] Tyxml.Html.elt diff --git a/controller/server/view/common/page.ml b/controller/server/view/common/page.ml index bfa3a107..f7d64dd7 100644 --- a/controller/server/view/common/page.ml +++ b/controller/server/view/common/page.ml @@ -6,6 +6,7 @@ type page = | Localization | SystemStatus | Changelog + | Licensing | Shutdown let menu_link page = @@ -15,6 +16,7 @@ let menu_link page = | Localization -> "/localization" | SystemStatus -> "/status" | Changelog -> "/changelog" + | Licensing -> "/licensing" | Shutdown -> "/shutdown" let menu_icon page = @@ -24,6 +26,7 @@ let menu_icon page = | Localization -> Icon.letter | SystemStatus -> Icon.screen | Changelog -> Icon.document + | Licensing -> Icon.copyright | Shutdown -> Icon.power let menu_label page = @@ -33,6 +36,7 @@ let menu_label page = | Localization -> "Localization & Display" | SystemStatus -> "System Status" | Changelog -> "Changelog" + | Licensing -> "Licensing" | Shutdown -> "Shutdown" let menu_item current_page page = @@ -41,15 +45,15 @@ let menu_item current_page page = (if current_page = Some page then [ "d-Menu__Item--Active" ] else []) in a - ~a:[ a_href (menu_link page) - ; a_class class_ + ~a:[ a_href (menu_link page) + ; a_class class_ ] [ menu_icon page ; txt (menu_label page) ] let html ?current_page ?header content = - let header = + let header = match header with | Some header -> [ Tyxml.Html.header ~a:[ a_class [ "d-Layout__Header" ] ] [ header ] ] | None -> [] @@ -68,13 +72,13 @@ let html ?current_page ?header content = (( aside ~a:[ a_class [ "d-Layout__Aside" ] ] [ nav - ([ Info; Network; Localization; SystemStatus; Changelog ] + ([ Info; Network; Localization; SystemStatus; Changelog; Licensing ] |> List.concat_map (fun page -> [ menu_item current_page page; txt " " ])) ; div ~a: [ a_class [ "d-Layout__Shutdown" ] ] [ menu_item current_page Shutdown ] - ]) - :: header + ]) + :: header @ [ main ~a:[ a_class [ "d-Layout__Main" ] ] [ content ] @@ -93,7 +97,7 @@ let header_title ?back_url ?icon ?right_action content = | Some icon -> [ span ~a: [ a_class [ "d-Header__Icon" ] ] [ icon ] ] | None -> [] in - let right_action = + let right_action = match right_action with | Some right_action -> [ right_action ] | None -> [] diff --git a/controller/server/view/common/page.mli b/controller/server/view/common/page.mli index d57fe75f..3403b8da 100644 --- a/controller/server/view/common/page.mli +++ b/controller/server/view/common/page.mli @@ -4,6 +4,7 @@ type page = | Localization | SystemStatus | Changelog + | Licensing | Shutdown val html : diff --git a/controller/server/view/licensing_page.ml b/controller/server/view/licensing_page.ml new file mode 100644 index 00000000..1b08a190 --- /dev/null +++ b/controller/server/view/licensing_page.ml @@ -0,0 +1,58 @@ +open Tyxml.Html +open Lwt + +let log_src = Logs.Src.create "licensing_page" + +let tool ~name ~license_name ~license_content content = + div + [ h2 ~a:[ a_class [ "d-Title" ] ] [ txt name ] + ; div content + ; details + ~a:[ a_class [ "d-Licensing__Details" ] ] + (summary [ txt license_name ]) + [ pre ~a: [ a_class [ "d-Preformatted" ] ] [ txt license_content ] ] + ] + +let read_license key = + Util.read_from_file log_src (Util.resource_path (Fpath.v ("licenses/" ^ key))) + +let html = + let%lwt playos_license = read_license "PLAYOS" in + let%lwt nixpkgs_license = read_license "NIXPKGS" in + let%lwt feather_license = read_license "FEATHER" in + let%lwt qt6_license = read_license "QT6" in + Lwt.return (Page.html + ~current_page:Page.Licensing + ~header:(Page.header_title + ~icon:Icon.copyright + [ txt "Licensing" ]) + (div + [ tool + ~name:"PlayOS" + ~license_name:"MIT License" + ~license_content:playos_license + [ p + ~a:[ a_class [ "d-Paragraph" ] ] + [ txt "Source code is available at " + ; span (* Using span as we don’t intend the user to leave the current page *) + ~a:[ a_class [ "d-Licensing__Link" ] ] + [ txt "https://github.com/dividat/playos" ] + ; txt ", with instructions to build and modify the software." + ] + ] + ; tool + ~name:"Nixpkgs" + ~license_name:"MIT License" + ~license_content:nixpkgs_license + [] + ; tool + ~name:"Feather" + ~license_name:"MIT License" + ~license_content:feather_license + [] + ; tool + ~name:"Qt6" + ~license_name:"GNU Lesser General Public License v3.0" + ~license_content:qt6_license + [] + ])) diff --git a/controller/server/view/licensing_page.mli b/controller/server/view/licensing_page.mli new file mode 100644 index 00000000..8044a962 --- /dev/null +++ b/controller/server/view/licensing_page.mli @@ -0,0 +1,2 @@ +val html : + [> Html_types.html ] Tyxml.Html.elt Lwt.t diff --git a/controller/server/view/status_page.ml b/controller/server/view/status_page.ml index c3e963dc..78238f14 100644 --- a/controller/server/view/status_page.ml +++ b/controller/server/view/status_page.ml @@ -12,10 +12,10 @@ type params = } let html { health; update; rauc } = - Page.html - ~current_page:Page.SystemStatus - ~header:(Page.header_title - ~icon:Icon.screen + Page.html + ~current_page:Page.SystemStatus + ~header:(Page.header_title + ~icon:Icon.screen [ txt "System Statu" ; a ~a:[ a_class [ "d-HiddenLink" ] diff --git a/docs/arch/Readme.org b/docs/arch/Readme.org index a5bfb7f1..8b34117c 100644 --- a/docs/arch/Readme.org +++ b/docs/arch/Readme.org @@ -210,13 +210,13 @@ The [[https://github.com/dividat/driver][Dividat Driver]], which handles connect ** Kiosk -The system automatically logs in the user ~play~, starts an X session and launches a custom Kiosk Application based on [[http://doc.qt.io/qt-5/qtwebengine-index.html][QtWebEngine]]. The Kiosk Application loads Dividat Play in a restricted environment. +The system automatically logs in the user ~play~, starts an X session and launches a custom Kiosk Application based on [[https://doc.qt.io/qt-6/qtwebengine-index.html][QtWebEngine]]. The Kiosk Application loads Dividat Play in a restricted environment. The [[*User interface][user interface for system configuration]] can be accessed with the key-combination ~Ctrl-Shift-F12~. If a captive portal is detected, which requires user interaction before granting Internet access, a prompt appears to open it. -For debugging the [[https://doc.qt.io/qt-5/qtwebengine-debugging.html][Qt WebEngine Developer Tools]] are enabled and accessible at http://localhost:3355 and chrome://inspect/#devices. The Dev Tools can be used to inspect and interact with the running page (e.g. load a new page with ~location.replace("https://nixos.org")~). +For debugging the [[https://doc.qt.io/qt-6/qtwebengine-debugging.html][Qt WebEngine Developer Tools]] are enabled and accessible at http://localhost:3355 and chrome://inspect/#devices. The Dev Tools can be used to inspect and interact with the running page (e.g. load a new page with ~location.replace("https://nixos.org")~). ** Audio diff --git a/kiosk/Readme.md b/kiosk/Readme.md index f3de69af..b02467e4 100644 --- a/kiosk/Readme.md +++ b/kiosk/Readme.md @@ -1,6 +1,6 @@ # PlayOS Kiosk Browser -Cycle between two urls in a full screen, locked down browser based on [QtWebEngine](http://doc.qt.io/qt-5/qtwebengine-index.html). Allow login to captive portals. +Cycle between two urls in a full screen, locked down browser based on [QtWebEngine](https://doc.qt.io/qt-6/qtwebengine-index.html). Allow login to captive portals. ## Development @@ -27,4 +27,4 @@ QTWEBENGINE_REMOTE_DEBUGGING=3355 bin/kiosk-browser … Then, point a Chromium-based browser to `http://127.0.0.1:3355`. Additional documentation is available at: -https://doc.qt.io/qt-5/qtwebengine-debugging.html +https://doc.qt.io/qt-6/qtwebengine-debugging.html diff --git a/kiosk/bin/kiosk-browser b/kiosk/bin/kiosk-browser index fa3fdbc0..8cb828c9 100755 --- a/kiosk/bin/kiosk-browser +++ b/kiosk/bin/kiosk-browser @@ -10,7 +10,7 @@ default_toggle_settings_key = 'CTRL+SHIFT+F12' parser = argparse.ArgumentParser( prog='kiosk-browser', description='Cycle through two urls in kiosk mode. Allow login to captive portals.', - epilog='Additional browser debugging environment variables can be found under https://doc.qt.io/qt-5/qtwebengine-debugging.html' + epilog='Additional browser debugging environment variables can be found under https://doc.qt.io/qt-6/qtwebengine-debugging.html' ) parser.add_argument( diff --git a/kiosk/default.nix b/kiosk/default.nix index 837d0956..3c0e373f 100644 --- a/kiosk/default.nix +++ b/kiosk/default.nix @@ -15,15 +15,23 @@ python3Packages.buildPythonApplication rec { --replace "@system_version@" "${system_version}" ''; - doCheck = false; + buildInputs = [ + bashInteractive + makeWrapper + ]; - nativeBuildInputs = [ qt5.wrapQtAppsHook mypy ]; + nativeBuildInputs = [ + mypy + qt6.wrapQtAppsHook + ]; propagatedBuildInputs = with python3Packages; [ dbus-python pygobject3 - pyqtwebengine + pyqt6-webengine pytest + qt6.full + qt6.qtbase requests types-requests ]; @@ -32,15 +40,13 @@ python3Packages.buildPythonApplication rec { cp -r images/ $out/images ''; - dontWrapQtApps = true; - makeWrapperArgs = [ "\${qtWrapperArgs[@]}" ]; - shellHook = '' # Give access to kiosk_browser module export PYTHONPATH=./:$PYTHONPATH - # Give access to Qt platform plugin "xcb" in nix-shell - export QT_QPA_PLATFORM_PLUGIN_PATH="${qt5.qtbase.bin}/lib/qt-${qt5.qtbase.version}/plugins"; + # Setup Qt environment + bashdir=$(mktemp -d) + makeWrapper "$(type -p bash)" "$bashdir/bash" "''${qtWrapperArgs[@]}" + exec "$bashdir/bash" ''; - } diff --git a/kiosk/kiosk_browser/__init__.py b/kiosk/kiosk_browser/__init__.py index 2a9a0fa8..51389cad 100644 --- a/kiosk/kiosk_browser/__init__.py +++ b/kiosk/kiosk_browser/__init__.py @@ -1,8 +1,8 @@ import sys import logging -from PyQt5.QtCore import Qt, QUrl -from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QApplication +from PyQt6.QtCore import Qt, QUrl +from PyQt6.QtGui import QKeySequence +from PyQt6.QtWidgets import QApplication from kiosk_browser import main_widget @@ -18,12 +18,12 @@ def start(kiosk_url, settings_url, toggle_settings_key, fullscreen = True): toggle_settings_key = QKeySequence(toggle_settings_key) ) - mainWidget.setContextMenuPolicy(Qt.NoContextMenu) + mainWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) if fullscreen: set_fullscreen(app, mainWidget) - app.exec_() + app.exec() def parseUrl(url): parsed_url = QUrl(url) diff --git a/kiosk/kiosk_browser/browser_widget.py b/kiosk/kiosk_browser/browser_widget.py index de06dae8..57a6e72c 100644 --- a/kiosk/kiosk_browser/browser_widget.py +++ b/kiosk/kiosk_browser/browser_widget.py @@ -1,4 +1,4 @@ -from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets, QtGui, QtSvg +from PyQt6 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebEngineCore, QtGui, QtSvgWidgets from enum import Enum, auto import logging import re @@ -52,7 +52,7 @@ def __init__(self, url, get_current_proxy, parent): )) # Allow sound playback without user gesture - self._webview.page().settings().setAttribute(QtWebEngineWidgets.QWebEngineSettings.PlaybackRequiresUserGesture, False) + self._webview.page().settings().setAttribute(QtWebEngineCore.QWebEngineSettings.WebAttribute.PlaybackRequiresUserGesture, False) # Load url self._webview.setUrl(url) @@ -60,10 +60,10 @@ def __init__(self, url, get_current_proxy, parent): self._webview.loadFinished.connect(self._load_finished) # Shortcut to manually reload - self._reload_shortcut = QtWidgets.QShortcut('CTRL+R', self) + self._reload_shortcut = QtGui.QShortcut('CTRL+R', self) self._reload_shortcut.activated.connect(self.reload) # Shortcut to perform a hard refresh - self._hard_refresh_shortcut = QtWidgets.QShortcut('CTRL+SHIFT+R', self) + self._hard_refresh_shortcut = QtGui.QShortcut('CTRL+SHIFT+R', self) self._hard_refresh_shortcut.activated.connect(self._hard_refresh) # Prepare reload timer @@ -181,8 +181,8 @@ def network_error_page(parent): paragraph_1 = paragraph("Please ensure the Internet connection to this device is active.", parent) paragraph_2 = paragraph("If the problem persists, contact Senso Service.", parent) - logo = QtSvg.QSvgWidget("images/dividat-logo.svg", parent) - logo.renderer().setAspectRatioMode(QtCore.Qt.KeepAspectRatio) + logo = QtSvgWidgets.QSvgWidget("images/dividat-logo.svg", parent) + logo.renderer().setAspectRatioMode(QtCore.Qt.AspectRatioMode.KeepAspectRatio) logo.setFixedHeight(30) layout = QtWidgets.QVBoxLayout() diff --git a/kiosk/kiosk_browser/captive_portal.py b/kiosk/kiosk_browser/captive_portal.py index 5f78fcc0..8e2eec7a 100644 --- a/kiosk/kiosk_browser/captive_portal.py +++ b/kiosk/kiosk_browser/captive_portal.py @@ -10,7 +10,7 @@ import logging from enum import Enum, auto from http import HTTPStatus -from PyQt5 import QtWidgets +from PyQt6 import QtWidgets from typing import Callable check_connection_url = 'http://captive.dividat.com/' diff --git a/kiosk/kiosk_browser/dialogable_widget.py b/kiosk/kiosk_browser/dialogable_widget.py index 227b907b..14e6200d 100644 --- a/kiosk/kiosk_browser/dialogable_widget.py +++ b/kiosk/kiosk_browser/dialogable_widget.py @@ -1,4 +1,4 @@ -from PyQt5 import QtWidgets, QtCore, QtGui +from PyQt6 import QtWidgets, QtCore, QtGui from typing import Callable overlay_color: str = '#888888' @@ -30,8 +30,8 @@ def __init__( policy = QtWidgets.QSizePolicy() policy.setVerticalStretch(1) policy.setHorizontalStretch(1) - policy.setVerticalPolicy(QtWidgets.QSizePolicy.Preferred) - policy.setHorizontalPolicy(QtWidgets.QSizePolicy.Preferred) + policy.setVerticalPolicy(QtWidgets.QSizePolicy.Policy.Preferred) + policy.setHorizontalPolicy(QtWidgets.QSizePolicy.Policy.Preferred) self.setSizePolicy(policy) # Layout @@ -41,7 +41,7 @@ def __init__( self.setLayout(self._layout) # Shortcuts - QtWidgets.QShortcut('ESC', self).activated.connect(self._on_escape) + QtGui.QShortcut('ESC', self).activated.connect(self._on_escape) def inner_widget(self): return self._inner_widget @@ -118,7 +118,7 @@ def title_line( """) button = QtWidgets.QPushButton("❌", dialog) - button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) + button.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) button.setStyleSheet(""" QPushButton { background-color: rgba(255, 255, 255, 0.2); diff --git a/kiosk/kiosk_browser/main_widget.py b/kiosk/kiosk_browser/main_widget.py index c3bb8f75..686edb6d 100644 --- a/kiosk/kiosk_browser/main_widget.py +++ b/kiosk/kiosk_browser/main_widget.py @@ -1,4 +1,4 @@ -from PyQt5 import QtWidgets, QtCore +from PyQt6 import QtWidgets, QtCore, QtGui from kiosk_browser import browser_widget, captive_portal, dialogable_widget, proxy as proxy_module @@ -23,8 +23,8 @@ def __init__(self, kiosk_url: str, settings_url: str, toggle_settings_key: str): self._dialogable_browser = dialogable_widget.DialogableWidget( parent = self, inner_widget = browser_widget.BrowserWidget( - url = kiosk_url, - get_current_proxy = proxy.get_current, + url = kiosk_url, + get_current_proxy = proxy.get_current, parent = self), on_close = self._close_dialog) @@ -44,7 +44,7 @@ def __init__(self, kiosk_url: str, settings_url: str, toggle_settings_key: str): self.setLayout(self._layout) # Shortcuts - QtWidgets.QShortcut(toggle_settings_key, self).activated.connect(self._toggle_settings) + QtGui.QShortcut(toggle_settings_key, self).activated.connect(self._toggle_settings) # Private diff --git a/kiosk/kiosk_browser/proxy.py b/kiosk/kiosk_browser/proxy.py index d5c26900..5c5c3dd6 100644 --- a/kiosk/kiosk_browser/proxy.py +++ b/kiosk/kiosk_browser/proxy.py @@ -6,7 +6,7 @@ import logging import threading import urllib -from PyQt5.QtNetwork import QNetworkProxy +from PyQt6.QtNetwork import QNetworkProxy from dataclasses import dataclass from dbus.mainloop.glib import DBusGMainLoop from gi.repository import GLib diff --git a/kiosk/mypy.ini b/kiosk/mypy.ini index f7e489a3..ff82608e 100644 --- a/kiosk/mypy.ini +++ b/kiosk/mypy.ini @@ -1,18 +1,18 @@ [mypy] -[mypy-PyQt5] +[mypy-PyQt6] ignore_missing_imports = True -[mypy-PyQt5.QtNetwork] +[mypy-PyQt6.QtNetwork] ignore_missing_imports = True -[mypy-PyQt5.QtCore] +[mypy-PyQt6.QtCore] ignore_missing_imports = True -[mypy-PyQt5.QtGui] +[mypy-PyQt6.QtGui] ignore_missing_imports = True -[mypy-PyQt5.QtWidgets] +[mypy-PyQt6.QtWidgets] ignore_missing_imports = True [mypy-dbus]