2023-10-19 17:49:54 +02:00
;; tinmop: a multiprotocol client
2023-10-19 17:46:22 +02:00
;; Copyright © cage
2020-05-08 15:45:43 +02:00
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program.
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
( in-package :main )
2020-09-06 14:42:16 +02:00
( defparameter *time* 0.0 )
( defparameter *ticks* 0 )
2020-05-08 15:45:43 +02:00
( define-constant +dt+ ( / 1 +fps+ ) :test #' = )
( defun incf-dt ( )
2020-09-06 14:42:16 +02:00
( incf *time* +dt+ ) )
( defun incf-ticks ( )
( incf *ticks* ) )
2020-05-08 15:45:43 +02:00
( defun setup-bindings ( )
" This is where an UI event is bound to a function the event nil is
2021-01-13 17:28:23 +01:00
the event that is fired when no input from user ( key pressed mouse
2020-05-08 15:45:43 +02:00
etc. ) happened "
( windows:with-croatoan-window ( croatoan-window specials:*main-window* )
2022-07-06 14:37:56 +02:00
( let ( ( skip-event nil ) )
( flet ( ( manage-keyboard-event ( event )
( incf-dt )
( handler-bind ( ( conditions:command-not-found
( lambda ( e )
( invoke-restart 'command-window:print-error e ) ) ) )
( command-window:manage-event event ) ) ) )
( c:bind croatoan-window
:resize
( lambda ( w event )
( declare ( ignore w event ) )
( windows:refresh-config-all )
( windows:draw-all ) ) )
;; this is an ugly hack for the bug reported here:
;; https://lists.gnu.org/archive/html/help-ncurses/2022-07/msg00000.html
;; If someone have an idea how to address the issue drop me a message!
( c:bind croatoan-window
#\Esc
( lambda ( w e )
( declare ( ignore w e ) )
( setf skip-event t ) ) )
( c:bind croatoan-window
t
( lambda ( w event )
( declare ( ignore w ) )
( let ( ( event-key ( c:event-key event ) ) )
( when ( not ( and skip-event
( characterp event-key )
( <= ( char-code event-key ) 255 ) ) )
( setf skip-event nil )
( manage-keyboard-event event ) ) ) ) )
;; this is the main thread
( c:bind croatoan-window
nil
( lambda ( w e )
( declare ( ignore w e ) )
( incf-dt )
( incf-ticks )
( scheduled-events:run-scheduled-events *ticks* )
( when ( not ( program-events:stop-event-dispatching-p ) )
( program-events:dispatch-program-events ) )
( windows:calculate-all +dt+ ) ) ) ) ) ) )
2020-05-08 15:45:43 +02:00
( defun init-i18n ( )
"Initialize i18n machinery"
( handler-bind ( ( error
( lambda ( e )
( declare ( ignore e ) )
( invoke-restart 'cl-i18n:return-empty-translation-table ) ) ) )
( setf cl-i18n:*translation-file-root* +catalog-dir+ )
( cl-i18n:load-language +text-domain+ :locale ( cl-i18n:find-locale ) ) ) )
( defun init-db ( )
"Initialize the database"
2021-07-31 11:04:18 +02:00
( db-utils:with-ready-database ( :connect t ) ) )
2020-05-08 15:45:43 +02:00
( defun change-folder ( )
"Change folder, used in requests of a command line switch"
( let ( ( refresh-event ( make-instance 'program-events:refresh-thread-windows-event
:new-folder command-line:*start-folder* ) )
( folder-exists-p ( db:folder-exists-p command-line:*start-folder* ) ) )
2020-05-30 09:53:12 +02:00
( if folder-exists-p
( program-events:push-event refresh-event )
( ui:error-message ( format nil
( _ "Folder ~s does not exists" )
command-line:*start-folder* ) ) ) ) )
2020-05-08 15:45:43 +02:00
( defun change-timeline ( )
"Change timeline, used in requests of a command line switch"
( let* ( ( refresh-event ( make-instance 'program-events:refresh-thread-windows-event
:new-timeline command-line:*start-timeline* ) ) )
( program-events:push-event refresh-event ) ) )
2020-05-18 19:16:52 +02:00
( defun reset-timeline-pagination ( )
( ui:reset-timeline-pagination ) )
2022-12-27 13:19:59 +01:00
( defun load-configuration-files ( &key ( verbose t ) )
( when ( and verbose
( not command-line:*script-file* ) )
2022-02-17 18:14:04 +01:00
( format t
( _ "Loading configuration file ~a~%" )
swconf:+shared-conf-filename+ ) )
2021-01-13 17:28:23 +01:00
( handler-case
2021-01-16 13:17:15 +01:00
( multiple-value-bind ( x configuration-file-path )
( swconf:load-config-file swconf:+shared-conf-filename+ )
( declare ( ignore x ) )
2024-03-17 14:11:42 +01:00
( swconf:perform-trivial-configuration-checks configuration-file-path ) )
2021-01-13 17:28:23 +01:00
( error ( e )
( format *error-output* "~a~%" e )
( os-utils:exit-program 1 ) ) )
2024-03-17 14:11:42 +01:00
( handler-case
2024-03-29 15:06:36 +01:00
( multiple-value-bind ( command-line-username command-line-server-name )
( command-line:fediverse-account-parameters )
2024-03-17 14:11:42 +01:00
( swconf:load-config-file swconf:+conf-filename+ t )
2024-03-29 15:06:36 +01:00
( swconf:set-current-username-and-server command-line-username
command-line-server-name ) )
2024-03-17 14:11:42 +01:00
( error ( e )
( format *error-output*
2024-03-30 20:34:16 +01:00
( _ "Configuration error~%~a~%Tinmop will create an empty file for you in ~a (if such file does not exists). This file will be enough to use the program as a gemini client but, to connect to fediverse, the file must be properly filled.~2%Consult the manpage ~a(1) for more details.~2%If you already wrote a configuration file, check the error printed below, try to fix the configuration file and restart ~a.~%" )
2024-03-17 14:11:42 +01:00
e
( res:home-confdir )
+program-name+
+program-name+ )
( res:create-empty-file-in-home swconf:+conf-filename+ )
( os-utils:exit-program 1 ) ) ) )
2020-05-16 13:45:07 +02:00
2024-03-29 15:06:36 +01:00
( defun shared-init ( &key ( verbose t ) ( initialize-database t ) )
2021-10-10 12:38:37 +02:00
( num:lcg-set-seed )
2022-12-27 13:19:59 +01:00
( load-configuration-files :verbose verbose )
2024-03-29 15:06:36 +01:00
( when initialize-database
( init-db ) ) )
2021-05-09 16:32:32 +02:00
2022-12-27 13:19:59 +01:00
( defun rpc-server-init ( )
"Initialize the program"
( shared-init :verbose nil )
( db-utils:with-ready-database ( :connect nil )
( when command-line:*module-file*
( handler-case
( modules:load-module command-line:*module-file* )
( error ( )
2023-02-09 16:28:53 +01:00
( format *error-output* ( _ "Unable to load module ~a" )
command-line:*module-file* ) ) ) )
( json-rpc-communication:start-server ) ) )
2022-12-27 13:19:59 +01:00
2024-03-29 15:06:36 +01:00
( defun tui-init ( draw-welcome-window )
2020-05-08 15:45:43 +02:00
"Initialize the program"
( db-utils:with-ready-database ( :connect nil )
2020-12-29 19:14:18 +01:00
( complete:initialize-complete-username-cache )
2021-10-07 21:52:36 +02:00
( let ( ( system-config-file-found-p ( modules:load-sys-module +starting-init-file+
:not-found-signal-error nil ) ) )
( multiple-value-bind ( home-config-file-found-p error-message )
( modules:load-module +starting-init-file+ :not-found-signal-error nil )
( when ( not ( or system-config-file-found-p
home-config-file-found-p ) )
2021-04-10 21:14:20 +02:00
( croatoan:end-screen )
2021-10-07 21:52:36 +02:00
( format *error-output* "~a~%" error-message )
( os-utils:exit-program 1 ) ) ) )
2020-05-08 15:45:43 +02:00
;; init main window for first...
( main-window:init )
( keybindings-window:init )
( command-window:init )
2024-03-29 15:06:36 +01:00
( when draw-welcome-window
( ui:show-welcome-window ) )
2021-11-12 15:00:34 +01:00
( when ( not command-line:*gemini-full-screen-mode* )
( thread-window:init ) )
2020-05-08 15:45:43 +02:00
;; the size of message and tag window depends from the sizes of
;; thread-window and command window, so the first two must be
;; initialized after the latter
( message-window:init )
2021-11-12 15:00:34 +01:00
( when ( not command-line:*gemini-full-screen-mode* )
( tags-window:init )
( conversations-window:init ) )
2021-04-03 15:03:24 +02:00
( gemini-client:init-default-gemini-theme specials:*message-window* )
2020-05-08 15:45:43 +02:00
( setup-bindings )
;; ... and init-keyboard-mapping-for last
( keybindings:init-keyboard-mapping )
2021-11-12 15:00:34 +01:00
( if command-line:*gemini-full-screen-mode*
( progn
( ui:display-latest-visited-urls )
( ui:focus-to-message-window ) )
( ui:focus-to-thread-window ) )
2024-03-30 15:47:39 +01:00
;; now init the client; we do not check for empty server name
;; list, as the length of server-names and usernames must be the
;; same (this rule is enforced by
;; 'swconf:trivial-configuration-checks')
2024-03-30 14:49:41 +01:00
( when ( not ( or *gemini-full-screen-mode*
( null ( swconf::config-username ) ) ) )
2023-02-17 18:31:49 +01:00
( client:init )
( client:authorize ) )
2021-04-10 21:14:20 +02:00
( when command-line:*module-file*
( handler-case
( modules:load-module command-line:*module-file* )
( error ( )
( ui:notify ( format nil
( _ "Unable to load module ~a" )
command-line:*module-file* )
:as-error t ) ) ) )
2022-02-05 16:28:40 +01:00
( if command-line:*net-address*
2022-02-17 19:01:48 +01:00
( progn
( gemini-page-toc:open-toc-window specials:*message-window* )
( ui:open-net-address command-line:*net-address* ) )
2020-06-23 15:51:43 +02:00
( progn
( let ( ( program-events:*process-events-immediately* t ) )
( when command-line:*start-timeline*
( change-timeline ) )
( when command-line:*start-folder*
( change-folder ) ) )
( when command-line:*reset-timeline-pagination*
( reset-timeline-pagination ) )
( when command-line:*update-timeline*
( ui:update-current-timeline ) )
( when command-line:*check-follow-requests*
( ui:start-follow-request-processing ) ) ) ) ) )
2020-05-08 15:45:43 +02:00
2024-03-29 15:06:36 +01:00
( defun run ( )
2020-05-08 15:45:43 +02:00
( windows:with-croatoan-window ( croatoan-window specials:*main-window* )
2022-03-21 21:42:50 +01:00
( setf ( c:frame-rate croatoan-window ) +fps+ )
2020-05-08 15:45:43 +02:00
( db-utils:with-ready-database ( :connect nil )
( unwind-protect
( progn
( hooks:run-hooks 'hooks:*before-main-loop* )
2022-03-21 21:42:50 +01:00
( c:run-event-loop croatoan-window ) )
( c:end-screen ) ) ) ) )
2020-05-08 15:45:43 +02:00
( defun load-script-file ( )
2020-09-05 17:02:00 +02:00
"Load (execute) a lisp file used in requests of a command line switch"
2020-05-08 15:45:43 +02:00
( setf program-events:*process-events-immediately* t )
2021-05-09 16:32:32 +02:00
( shared-init )
2020-05-08 15:45:43 +02:00
( db-utils:with-ready-database ( :connect nil )
2023-02-17 18:16:34 +01:00
( when ( not *gemini-full-screen-mode* )
( client:init )
( client:authorize ) )
2020-05-08 15:45:43 +02:00
( load command-line:*script-file* :verbose nil :print nil ) ) )
2023-01-11 19:10:51 +01:00
( defun rpc-client-load-configuration ( )
2023-07-29 12:23:02 +02:00
( gui-conf:load-config-file gui-conf:+client-sys-conf-filename+ )
2023-01-11 19:10:51 +01:00
( handler-bind ( ( error
( lambda ( e )
( format *error-output*
( _ "Fatal error~%~a~%Tinmop is unable to find a configuration for the graphical user interface (GUI). Maybe the software need to be updated or reinstalled. Please, fill the empty config file this program just created for you under ~a, or contact your system administrator" )
e
( res:home-confdir ) )
( invoke-restart 'res:create-empty-in-home ) ) ) )
2023-02-21 20:20:19 +01:00
( gui-conf:load-config-file ) ) )
2023-01-11 19:10:51 +01:00
( defun rpc-client-init ( )
( shared-init )
( rpc-client-load-configuration ) )
2020-05-08 15:45:43 +02:00
( defun main ( )
"The entry point function of the program"
2024-03-17 17:29:16 +01:00
( init-i18n )
( res:init )
( command-line:manage-opts )
( cond
( command-line:*start-dummy-server*
( gemini-dummy-server:start ) )
( command-line:*rpc-server-mode*
2024-03-24 14:14:19 +01:00
( rpc-server-init ) )
2024-03-17 17:29:16 +01:00
( command-line:*rpc-client-mode*
( rpc-client-init )
( json-rpc-communication::start-client )
( client-main-window:init-main-window command-line:*net-address* ) )
( command-line:*print-lisp-dependencies*
( misc:all-program-dependencies t ) )
( command-line:*script-file*
( load-script-file ) )
( t
( let ( ( croatoan::*debugger-hook* #' ( lambda ( c h )
( declare ( ignore h ) )
( c:end-screen )
( print c ) ) ) )
2024-03-29 15:06:36 +01:00
( shared-init :initialize-database nil )
( let* ( ( config-usernames ( swconf::config-username ) )
( config-server-names ( swconf::config-server-name ) )
( first-time-starting ( not ( db-utils:a-database-file-exists-p config-usernames
config-server-names ) ) ) )
( init-db )
( tui-init first-time-starting )
( run ) ) ) ) ) )